diff --git a/handlers/toggles.go b/handlers/toggles.go new file mode 100644 index 0000000..9782631 --- /dev/null +++ b/handlers/toggles.go @@ -0,0 +1,25 @@ +package handlers + +import ( + "net/http" + + "github.com/getsentry/sentry-go" + "github.com/gin-gonic/gin" + "github.com/jinzhu/gorm" + "github.com/wahyd4/badger/models" +) + +type FeatureTogglesHandler struct { + DB *gorm.DB +} + +func (handler *FeatureTogglesHandler) Toggles(c *gin.Context) { + toggles := make([]*models.FeatureToggle, 0) + + if err := handler.DB.Find(&toggles).Error; err != nil { + sentry.CaptureException(err) + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"msg": ErrorMessageInternalError}) + } + + c.JSON(http.StatusOK, toggles) +} diff --git a/handlers/users.go b/handlers/users.go index f935cc8..64635aa 100644 --- a/handlers/users.go +++ b/handlers/users.go @@ -20,7 +20,7 @@ const ( ) var ( - NoAuthPaths = []string{"/api/badges", "/api/auth"} + NoAuthPaths = []string{"/api/badges", "/api/auth", "/api/toggles"} ) type UsersHandler struct { diff --git a/main.go b/main.go index 465a981..2e1749f 100644 --- a/main.go +++ b/main.go @@ -60,6 +60,7 @@ func main() { &models.Token{}, &models.User{}, &models.ViewRecord{}, + &models.FeatureToggle{}, &models.BadgeViewCount{}) client, err := api.NewClient(api.Config{ @@ -74,8 +75,8 @@ func main() { handler := &handlers.BadgesHandler{DB: db, PrometheusAPIClient: client} userHandler := &handlers.UsersHandler{DB: db} - statsHandler := &handlers.StatsHandler{DB: db} + toggleHandler := &handlers.FeatureTogglesHandler{DB: db} r := gin.Default() @@ -97,6 +98,7 @@ func main() { api.POST("/auth", userHandler.Authenticate) api.POST("/badges", handler.CreateBadge) api.GET("/badges", handler.MyBadges) + api.GET("/toggles", toggleHandler.Toggles) statsAPI := r.Group("/api/stats") statsAPI.Use(handler.ValidateBadge) diff --git a/manifest.yaml b/manifest.yaml index 00cb4ad..48e0048 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -158,6 +158,11 @@ spec: backend: serviceName: badger-ui servicePort: 80 + - path: /badges + pathType: Exact + backend: + serviceName: badger-ui + servicePort: 80 - path: /stats pathType: Prefix backend: diff --git a/models/feature_toggle.go b/models/feature_toggle.go new file mode 100644 index 0000000..bb8dded --- /dev/null +++ b/models/feature_toggle.go @@ -0,0 +1,9 @@ +package models + +import "github.com/jinzhu/gorm" + +type FeatureToggle struct { + gorm.Model + Name string `json:"name"` + Enabled bool `json:"enabled"` +} diff --git a/www/layouts/default.vue b/www/layouts/default.vue index 4cd6ec6..188fe3a 100644 --- a/www/layouts/default.vue +++ b/www/layouts/default.vue @@ -1,10 +1,10 @@ @@ -76,7 +74,7 @@ export default Vue.extend({ return; } - this.$store.dispatch("users/loginFromLocal") + this.$store.dispatch("users/loginFromLocal"); if (this.$store.state.users.token === "") { this.$store.dispatch("users/authenticate", { @@ -98,7 +96,7 @@ export default Vue.extend({ hasError() { return this.$store.state.error.error !== ""; }, - error(){ + error() { return this.$store.state.error.error; }, isProduction() { @@ -142,7 +140,8 @@ html { font-size: 13px; background: #eee; padding: 5vh 0; - .copyright, .contact-me { + .copyright, + .contact-me { display: flex; align-items: center; justify-content: center; diff --git a/www/nuxt.config.js b/www/nuxt.config.js index e294ada..adce9cf 100644 --- a/www/nuxt.config.js +++ b/www/nuxt.config.js @@ -105,9 +105,9 @@ export default { ** See https://nuxtjs.org/api/configuration-build/ */ build: { - babel: { - compact: true - } + // babel: { + // compact: true + // } }, auth: { redirect: { @@ -145,6 +145,13 @@ export default { port: 3000, host: '0.0.0.0' }, + bootstrapVue: { + // Install the `IconsPlugin` plugin (in addition to `BootstrapVue` plugin) + icons: false, + components: ['BContainer', 'BRow', 'BCol', 'BFormInput', 'BButton', 'BTable', 'BModal', 'BCard', + 'BLink', 'BCardText', 'BFormTextarea', 'BImg', 'BDropdownItem', 'BOverlay', 'BNavbar', 'BNavbarBrand', + 'BNavbarToggle', 'BCollapse', 'BNavbarNav', 'BNavItem', 'BNavItemDropdown', 'BButtonGroup', 'BAlert'] + } // router: { // middleware: ['auth'] // } diff --git a/www/package-lock.json b/www/package-lock.json index 00d70bf..a054cdc 100644 --- a/www/package-lock.json +++ b/www/package-lock.json @@ -3491,13 +3491,58 @@ } }, "@nuxt/opencollective": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@nuxt/opencollective/-/opencollective-0.3.0.tgz", - "integrity": "sha512-Vf09BxCdj1iT2IRqVwX5snaY2WCTkvM0O4cWWSO1ThCFuc4if0Q/nNwAgCxRU0FeYHJ7DdyMUNSdswCLKlVqeg==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@nuxt/opencollective/-/opencollective-0.3.2.tgz", + "integrity": "sha512-XG7rUdXG9fcafu9KTDIYjJSkRO38EwjlKYIb5TQ/0WDbiTUTtUtgncMscKOYzfsY86kGs05pAuMOR+3Fi0aN3A==", "requires": { - "chalk": "^2.4.2", - "consola": "^2.10.1", - "node-fetch": "^2.6.0" + "chalk": "^4.1.0", + "consola": "^2.15.0", + "node-fetch": "^2.6.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } } }, "@nuxt/server": { @@ -6032,17 +6077,17 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, "bootstrap": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.5.2.tgz", - "integrity": "sha512-vlGn0bcySYl/iV+BGA544JkkZP5LB3jsmkeKLFQakCOwCM3AOk7VkldBz4jrzSe+Z0Ezn99NVXa1o45cQY4R6A==" + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz", + "integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==" }, "bootstrap-vue": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/bootstrap-vue/-/bootstrap-vue-2.16.0.tgz", - "integrity": "sha512-gLETwPmeRHCe5WHmhGxzb5PtTEuKqQPGl0TFvZ2Odbkg/7UuIHdqIexrJRerpnomP4ZzDQ+qYGL91Ls9lcQsJQ==", + "version": "2.21.2", + "resolved": "https://registry.npmjs.org/bootstrap-vue/-/bootstrap-vue-2.21.2.tgz", + "integrity": "sha512-0Exe+4MZysqhZNXIKf4TzkvXaupxh9EHsoCRez0o5Dc0J7rlafayOEwql63qXv74CgZO8E4U8ugRNJko1vMvNw==", "requires": { - "@nuxt/opencollective": "^0.3.0", - "bootstrap": ">=4.5.0 <5.0.0", + "@nuxt/opencollective": "^0.3.2", + "bootstrap": ">=4.5.3 <5.0.0", "popper.js": "^1.16.1", "portal-vue": "^2.1.7", "vue-functional-data-merge": "^3.1.0" diff --git a/www/package.json b/www/package.json index 12efec7..64060c0 100644 --- a/www/package.json +++ b/www/package.json @@ -14,8 +14,8 @@ "@nuxtjs/auth-next": "5.0.0-1611574754.9020f2a", "@nuxtjs/axios": "^5.12.5", "@nuxtjs/pwa": "^3.0.0-beta.20", - "bootstrap": "^4.5.0", - "bootstrap-vue": "^2.15.0", + "bootstrap": "^4.6.0", + "bootstrap-vue": "^2.21.2", "chart.js": "^2.9.4", "cookie-parser": "^1.4.5", "express-jwt": "^6.0.0", diff --git a/www/pages/badges/index.vue b/www/pages/badges/index.vue index 365b186..1a1c315 100644 --- a/www/pages/badges/index.vue +++ b/www/pages/badges/index.vue @@ -18,7 +18,7 @@ - + @@ -62,6 +62,9 @@ export default { }, badgeUrl(badge) { return `${this.$config.backendURL}/badges/${badge.ulid}/green.svg?i=1`; + }, + statsUrl(badge) { + return `/stats/${badge.ulid}`; } } }; diff --git a/www/pages/login.vue b/www/pages/login.vue index 49e8851..d2d46c1 100644 --- a/www/pages/login.vue +++ b/www/pages/login.vue @@ -8,14 +8,14 @@
- +
diff --git a/www/store/toggles.js b/www/store/toggles.js new file mode 100644 index 0000000..da61f6a --- /dev/null +++ b/www/store/toggles.js @@ -0,0 +1,17 @@ +export const state = () => ({ + toggles: [] +}); + +export const mutations = { + setToggles(state, toggles) { + state.toggles = toggles; + } +}; + +export const actions = { + async getToggles(context, callback) { + const result = await this.$axios.$get("/api/toggles"); + context.commit("setToggles", result); + callback(); + } +};