mirror of
https://github.com/wahyd4/badger.git
synced 2026-08-09 05:15:58 +10:00
add stats
This commit is contained in:
+24
-10
@@ -2,31 +2,45 @@ package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/wahyd4/badger/models"
|
||||
)
|
||||
|
||||
type StatsHandler struct {
|
||||
DB *gorm.DB
|
||||
}
|
||||
|
||||
type StatsResult struct {
|
||||
Date time.Time `json:"label"`
|
||||
Count int `json:"data"`
|
||||
}
|
||||
|
||||
func (handler *StatsHandler) GetStats(c *gin.Context) {
|
||||
if err := handler.badgeStats(c); err != nil {
|
||||
logrus.Error(err)
|
||||
badge := c.MustGet("badge").(*models.Badge)
|
||||
result, err := handler.badgeStats(badge)
|
||||
if err != nil {
|
||||
logrus.Errorf("failed to generate stats information for %s, %v", badge.ULID, err)
|
||||
sentry.CaptureException(err)
|
||||
if err == ErrNoSuchBadgeFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": ErrorMessageInternalError})
|
||||
return
|
||||
}
|
||||
c.JSON(200, gin.H{"msg": "hello"})
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
func (handler *StatsHandler) badgeStats(c *gin.Context) error {
|
||||
return nil
|
||||
func (handler *StatsHandler) badgeStats(badge *models.Badge) ([]*StatsResult, error) {
|
||||
var result = make([]*StatsResult, 0)
|
||||
err := handler.DB.Raw(`select
|
||||
date_trunc('day', created_at AT TIME ZONE 'Australia/Melbourne') as date, -- or hour, day, week, month, year
|
||||
count(1)
|
||||
from view_records
|
||||
where ulid = ?
|
||||
group by 1
|
||||
order by date ASC`, badge.ULID).Scan(&result).Error
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
@@ -93,12 +93,14 @@ func main() {
|
||||
|
||||
api := r.Group("/api")
|
||||
api.Use(userHandler.AuthRequired)
|
||||
api.Group("/badges/:name", handler.ValidateBadge)
|
||||
|
||||
api.POST("/auth", userHandler.Authenticate)
|
||||
api.POST("/badges", handler.CreateBadge)
|
||||
api.GET("/badges", handler.MyBadges)
|
||||
api.GET("/badges/:name/stats", statsHandler.GetStats)
|
||||
|
||||
statsAPI := r.Group("/api/stats")
|
||||
statsAPI.Use(handler.ValidateBadge)
|
||||
statsAPI.GET(":name", statsHandler.GetStats)
|
||||
|
||||
go func() {
|
||||
http.Handle("/metrics", promhttp.Handler())
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<script>
|
||||
import { Line } from 'vue-chartjs'
|
||||
|
||||
export default {
|
||||
extends: Line,
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.renderChart(this.data, this.options)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Generated
+2999
-1384
File diff suppressed because it is too large
Load Diff
+5
-3
@@ -12,18 +12,20 @@
|
||||
"dependencies": {
|
||||
"@nuxt/typescript-runtime": "^1.0.0",
|
||||
"@nuxtjs/auth-next": "5.0.0-1602966760.822bc05",
|
||||
"@nuxtjs/axios": "^5.12.2",
|
||||
"@nuxtjs/axios": "^5.12.5",
|
||||
"@nuxtjs/pwa": "^3.0.0-beta.20",
|
||||
"bootstrap": "^4.5.0",
|
||||
"bootstrap-vue": "^2.15.0",
|
||||
"chart.js": "^2.9.4",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"express-jwt": "^6.0.0",
|
||||
"lodash": "^4.17.20",
|
||||
"lodash.get": "^4.4.2",
|
||||
"moment": "^2.27.0",
|
||||
"nanoid": "^2.1.11",
|
||||
"nuxt": "^2.14.4",
|
||||
"nuxt-i18n": "^6.13.12"
|
||||
"nuxt": "^2.14.12",
|
||||
"nuxt-i18n": "^6.13.12",
|
||||
"vue-chartjs": "^3.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/types": "^2.14.4",
|
||||
|
||||
+2
-1
@@ -205,7 +205,8 @@ export default Vue.extend({
|
||||
},
|
||||
updateBadgeText: function(color) {
|
||||
var url = `${this.$config.backendURL}/badges/${this.badge.id}/${color}.svg`;
|
||||
this.text = `[](${url} "Get your own page views count badge on badges.toozhao.com")`;
|
||||
var statsUrl = `${this.$config.backendURL}/stats/${this.badge.id}`;
|
||||
this.text = `[](${statsUrl} "Get your own page views count badge on badges.toozhao.com")`;
|
||||
this.activeBadgeColor = color;
|
||||
},
|
||||
myBadge: function(color) {
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<div class="stats-container container">
|
||||
<b-container fluid="lg">
|
||||
<b-row>
|
||||
<b-col lg="12">
|
||||
<h3>Badge view count statistics</h3>
|
||||
<img :src="badgeUrl(id)" class="my-badge" />
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row class="stats">
|
||||
<b-col lg="12">
|
||||
<line-chart :data="chartsData" :options="options" v-if="loaded" />
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row class="">
|
||||
<b-col lg="4" offset-lg="4" class="text-center generate-badge">
|
||||
<b-button
|
||||
pill
|
||||
size="lg"
|
||||
class="generate-button"
|
||||
variant="outline-primary"
|
||||
href="/"
|
||||
>Create another Badge
|
||||
</b-button>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapMutations, mapActions } from "vuex";
|
||||
import moment from "moment";
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
loaded: false,
|
||||
chartData: null
|
||||
}),
|
||||
props: {
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'bottom',
|
||||
align: 'end'
|
||||
},
|
||||
scales: {
|
||||
yAxes: [{
|
||||
stacked: true,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.9)',
|
||||
}]
|
||||
},
|
||||
responsive: true,
|
||||
maintainAspectRatio: false
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
id() {
|
||||
return this.$route.params.id;
|
||||
},
|
||||
stats() {
|
||||
return this.$store.state.stats;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loaded = false;
|
||||
this.$store.commit("loading/updateLoading");
|
||||
|
||||
this.$store.dispatch("stats/getBadgeStats", {
|
||||
badgeId: this.$route.params.id,
|
||||
callback: result => {
|
||||
const labels = result.map(item => moment(item.label).format("MM-DD"));
|
||||
const data = result.map(item => item.data);
|
||||
this.chartsData = {
|
||||
labels: labels,
|
||||
datasets: [
|
||||
{
|
||||
label: "View Count",
|
||||
data: data,
|
||||
borderWidth: 2,
|
||||
borderColor: 'rgb(49, 197, 83)',
|
||||
fill: false,
|
||||
lineTension: 1
|
||||
}
|
||||
]
|
||||
};
|
||||
this.$store.commit("loading/updateLoading");
|
||||
this.loaded = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
badgeUrl(id) {
|
||||
return `${this.$config.backendURL}/badges/${id}/green.svg?i=1`;
|
||||
},
|
||||
badgeCreated(createdAt) {
|
||||
return `Created at: ${moment(createdAt).format("MM-DD ")}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.stats-container {
|
||||
margin-top: 5rem;
|
||||
.stats {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,18 @@
|
||||
export const state = () => ({
|
||||
stats: []
|
||||
});
|
||||
|
||||
export const mutations = {
|
||||
setStats(state, stats) {
|
||||
state.stats = stats;
|
||||
}
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
async getBadgeStats(context, {badgeId, callback}) {
|
||||
const result = await this.$axios.$get(`/api/stats/${badgeId}`);
|
||||
context.commit("setStats", result);
|
||||
callback(result);
|
||||
}
|
||||
|
||||
};
|
||||
Reference in New Issue
Block a user