1
0
mirror of https://github.com/muety/wakapi.git synced 2023-08-10 21:12:56 +03:00

chore: implement diagnostics endpoint (resolve #225)

This commit is contained in:
Ferdinand Mütsch 2021-08-07 10:16:50 +02:00
parent 9e3203ac41
commit 2088987a0c
13 changed files with 843 additions and 536 deletions

View File

@ -33,6 +33,7 @@ const (
SimpleDateTimeFormat = "2006-01-02 15:04:05" SimpleDateTimeFormat = "2006-01-02 15:04:05"
ErrUnauthorized = "401 unauthorized" ErrUnauthorized = "401 unauthorized"
ErrBadRequest = "400 bad request"
ErrInternalServerError = "500 internal server error" ErrInternalServerError = "500 internal server error"
) )
@ -203,6 +204,9 @@ func (c *Config) GetMigrationFunc(dbDialect string) models.MigrationFunc {
if err := db.AutoMigrate(&models.ProjectLabel{}); err != nil && !c.Db.AutoMigrateFailSilently { if err := db.AutoMigrate(&models.ProjectLabel{}); err != nil && !c.Db.AutoMigrateFailSilently {
return err return err
} }
if err := db.AutoMigrate(&models.Diagnostics{}); err != nil && !c.Db.AutoMigrateFailSilently {
return err
}
return nil return nil
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -54,6 +54,7 @@ var (
projectLabelRepository repositories.IProjectLabelRepository projectLabelRepository repositories.IProjectLabelRepository
summaryRepository repositories.ISummaryRepository summaryRepository repositories.ISummaryRepository
keyValueRepository repositories.IKeyValueRepository keyValueRepository repositories.IKeyValueRepository
diagnosticsRepository repositories.IDiagnosticsRepository
) )
var ( var (
@ -67,6 +68,7 @@ var (
mailService services.IMailService mailService services.IMailService
keyValueService services.IKeyValueService keyValueService services.IKeyValueService
reportService services.IReportService reportService services.IReportService
diagnosticsService services.IDiagnosticsService
miscService services.IMiscService miscService services.IMiscService
) )
@ -143,6 +145,7 @@ func main() {
projectLabelRepository = repositories.NewProjectLabelRepository(db) projectLabelRepository = repositories.NewProjectLabelRepository(db)
summaryRepository = repositories.NewSummaryRepository(db) summaryRepository = repositories.NewSummaryRepository(db)
keyValueRepository = repositories.NewKeyValueRepository(db) keyValueRepository = repositories.NewKeyValueRepository(db)
diagnosticsRepository = repositories.NewDiagnosticsRepository(db)
// Services // Services
mailService = mail.NewMailService() mailService = mail.NewMailService()
@ -155,6 +158,7 @@ func main() {
aggregationService = services.NewAggregationService(userService, summaryService, heartbeatService) aggregationService = services.NewAggregationService(userService, summaryService, heartbeatService)
keyValueService = services.NewKeyValueService(keyValueRepository) keyValueService = services.NewKeyValueService(keyValueRepository)
reportService = services.NewReportService(summaryService, userService, mailService) reportService = services.NewReportService(summaryService, userService, mailService)
diagnosticsService = services.NewDiagnosticsService(diagnosticsRepository)
miscService = services.NewMiscService(userService, summaryService, keyValueService) miscService = services.NewMiscService(userService, summaryService, keyValueService)
// Schedule background tasks // Schedule background tasks
@ -169,6 +173,7 @@ func main() {
heartbeatApiHandler := api.NewHeartbeatApiHandler(userService, heartbeatService, languageMappingService) heartbeatApiHandler := api.NewHeartbeatApiHandler(userService, heartbeatService, languageMappingService)
summaryApiHandler := api.NewSummaryApiHandler(userService, summaryService) summaryApiHandler := api.NewSummaryApiHandler(userService, summaryService)
metricsHandler := api.NewMetricsHandler(userService, summaryService, heartbeatService, keyValueService) metricsHandler := api.NewMetricsHandler(userService, summaryService, heartbeatService, keyValueService)
diagnosticsHandler := api.NewDiagnosticsApiHandler(userService, diagnosticsService)
// Compat Handlers // Compat Handlers
wakatimeV1AllHandler := wtV1Routes.NewAllTimeHandler(userService, summaryService) wakatimeV1AllHandler := wtV1Routes.NewAllTimeHandler(userService, summaryService)
@ -219,6 +224,7 @@ func main() {
healthApiHandler.RegisterRoutes(apiRouter) healthApiHandler.RegisterRoutes(apiRouter)
heartbeatApiHandler.RegisterRoutes(apiRouter) heartbeatApiHandler.RegisterRoutes(apiRouter)
metricsHandler.RegisterRoutes(apiRouter) metricsHandler.RegisterRoutes(apiRouter)
diagnosticsHandler.RegisterRoutes(apiRouter)
wakatimeV1AllHandler.RegisterRoutes(apiRouter) wakatimeV1AllHandler.RegisterRoutes(apiRouter)
wakatimeV1SummariesHandler.RegisterRoutes(apiRouter) wakatimeV1SummariesHandler.RegisterRoutes(apiRouter)
wakatimeV1StatsHandler.RegisterRoutes(apiRouter) wakatimeV1StatsHandler.RegisterRoutes(apiRouter)

13
models/diagnostics.go Normal file
View File

@ -0,0 +1,13 @@
package models
type Diagnostics struct {
ID uint `gorm:"primary_key"`
User *User `json:"-" gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
UserID string `json:"-" gorm:"not null; index:idx_diagnostics_user"`
Platform string `json:"platform"`
Architecture string `json:"architecture"`
Plugin string `json:"plugin"`
CliVersion string `json:"cli_version"`
Logs string `json:"logs" gorm:"type:text"`
StackTrace string `json:"stacktrace" gorm:"type:text"`
}

View File

@ -0,0 +1,18 @@
package repositories
import (
"github.com/muety/wakapi/models"
"gorm.io/gorm"
)
type DiagnosticsRepository struct {
db *gorm.DB
}
func NewDiagnosticsRepository(db *gorm.DB) *DiagnosticsRepository {
return &DiagnosticsRepository{db: db}
}
func (r *DiagnosticsRepository) Insert(diagnostics *models.Diagnostics) (*models.Diagnostics, error) {
return diagnostics, r.db.Create(diagnostics).Error
}

View File

@ -31,6 +31,10 @@ type IHeartbeatRepository interface {
DeleteBefore(time.Time) error DeleteBefore(time.Time) error
} }
type IDiagnosticsRepository interface {
Insert(diagnostics *models.Diagnostics) (*models.Diagnostics, error)
}
type IKeyValueRepository interface { type IKeyValueRepository interface {
GetAll() ([]*models.KeyStringValue, error) GetAll() ([]*models.KeyStringValue, error)
GetString(string) (*models.KeyStringValue, error) GetString(string) (*models.KeyStringValue, error)

71
routes/api/diagnostics.go Normal file
View File

@ -0,0 +1,71 @@
package api
import (
"encoding/json"
"github.com/gorilla/mux"
conf "github.com/muety/wakapi/config"
"github.com/muety/wakapi/middlewares"
"github.com/muety/wakapi/services"
"github.com/muety/wakapi/utils"
"net/http"
"github.com/muety/wakapi/models"
)
type DiagnosticsApiHandler struct {
config *conf.Config
userSrvc services.IUserService
diagnosticsSrvc services.IDiagnosticsService
}
func NewDiagnosticsApiHandler(userService services.IUserService, diagnosticsService services.IDiagnosticsService) *DiagnosticsApiHandler {
return &DiagnosticsApiHandler{
config: conf.Get(),
userSrvc: userService,
diagnosticsSrvc: diagnosticsService,
}
}
func (h *DiagnosticsApiHandler) RegisterRoutes(router *mux.Router) {
r := router.PathPrefix("/plugins/errors").Subrouter()
r.Use(
middlewares.NewAuthenticateMiddleware(h.userSrvc).Handler,
)
r.Path("").Methods(http.MethodPost).HandlerFunc(h.Post)
}
// @Summary Push a new diagnostics object
// @ID post-diagnostics
// @Tags diagnostics
// @Accept json
// @Param diagnostics body models.Diagnostics true "A single diagnostics object sent by WakaTime CLI"
// @Security ApiKeyAuth
// @Success 201
// @Router /plugins/errors [post]
func (h *DiagnosticsApiHandler) Post(w http.ResponseWriter, r *http.Request) {
var diagnostics models.Diagnostics
user := middlewares.GetPrincipal(r)
if user == nil {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(conf.ErrUnauthorized))
return
}
if err := json.NewDecoder(r.Body).Decode(&diagnostics); err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(conf.ErrBadRequest))
conf.Log().Request(r).Error("failed to parse diagnostics for user %s - %v", err)
return
}
diagnostics.UserID = user.ID
if _, err := h.diagnosticsSrvc.Create(&diagnostics); err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(conf.ErrInternalServerError))
conf.Log().Request(r).Error("failed to insert diagnostics for user %s - %v", err)
return
}
utils.RespondJSON(w, r, http.StatusCreated, struct{}{})
}

View File

@ -72,6 +72,7 @@ func (h *HeartbeatApiHandler) Post(w http.ResponseWriter, r *http.Request) {
if err != nil { if err != nil {
heartbeats, err = h.tryParseSingle(r) heartbeats, err = h.tryParseSingle(r)
if err != nil { if err != nil {
conf.Log().Request(r).Error(err.Error())
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error())) w.Write([]byte(err.Error()))
return return

23
services/diagnostics.go Normal file
View File

@ -0,0 +1,23 @@
package services
import (
"github.com/muety/wakapi/config"
"github.com/muety/wakapi/models"
"github.com/muety/wakapi/repositories"
)
type DiagnosticsService struct {
config *config.Config
repository repositories.IDiagnosticsRepository
}
func NewDiagnosticsService(diagnosticsRepo repositories.IDiagnosticsRepository) *DiagnosticsService {
return &DiagnosticsService{
config: config.Get(),
repository: diagnosticsRepo,
}
}
func (srv *DiagnosticsService) Create(diagnostics *models.Diagnostics) (*models.Diagnostics, error) {
return srv.repository.Insert(diagnostics)
}

View File

@ -39,6 +39,10 @@ type IHeartbeatService interface {
DeleteBefore(time.Time) error DeleteBefore(time.Time) error
} }
type IDiagnosticsService interface {
Create(*models.Diagnostics) (*models.Diagnostics, error)
}
type IKeyValueService interface { type IKeyValueService interface {
GetString(string) (*models.KeyStringValue, error) GetString(string) (*models.KeyStringValue, error)
MustGetString(string) *models.KeyStringValue MustGetString(string) *models.KeyStringValue

View File

@ -482,6 +482,39 @@ var doc = `{
} }
} }
}, },
"/plugins/errors": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"consumes": [
"application/json"
],
"tags": [
"diagnostics"
],
"summary": "Push a new diagnostics object",
"operationId": "post-diagnostics",
"parameters": [
{
"description": "A single diagnostics object sent by WakaTime CLI",
"name": "diagnostics",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.Diagnostics"
}
}
],
"responses": {
"201": {
"description": ""
}
}
}
},
"/summary": { "/summary": {
"get": { "get": {
"security": [ "security": [
@ -687,6 +720,32 @@ var doc = `{
} }
}, },
"definitions": { "definitions": {
"models.Diagnostics": {
"type": "object",
"properties": {
"architecture": {
"type": "string"
},
"cli_version": {
"type": "string"
},
"id": {
"type": "integer"
},
"logs": {
"type": "string"
},
"platform": {
"type": "string"
},
"plugin": {
"type": "string"
},
"stacktrace": {
"type": "string"
}
}
},
"models.Heartbeat": { "models.Heartbeat": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -750,6 +809,7 @@ var doc = `{
"example": "2006-01-02 15:04:05.000" "example": "2006-01-02 15:04:05.000"
}, },
"labels": { "labels": {
"description": "labels are not persisted, but calculated at runtime, i.e. when summary is retrieved",
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/models.SummaryItem" "$ref": "#/definitions/models.SummaryItem"

View File

@ -466,6 +466,39 @@
} }
} }
}, },
"/plugins/errors": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"consumes": [
"application/json"
],
"tags": [
"diagnostics"
],
"summary": "Push a new diagnostics object",
"operationId": "post-diagnostics",
"parameters": [
{
"description": "A single diagnostics object sent by WakaTime CLI",
"name": "diagnostics",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.Diagnostics"
}
}
],
"responses": {
"201": {
"description": ""
}
}
}
},
"/summary": { "/summary": {
"get": { "get": {
"security": [ "security": [
@ -671,6 +704,32 @@
} }
}, },
"definitions": { "definitions": {
"models.Diagnostics": {
"type": "object",
"properties": {
"architecture": {
"type": "string"
},
"cli_version": {
"type": "string"
},
"id": {
"type": "integer"
},
"logs": {
"type": "string"
},
"platform": {
"type": "string"
},
"plugin": {
"type": "string"
},
"stacktrace": {
"type": "string"
}
}
},
"models.Heartbeat": { "models.Heartbeat": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -734,6 +793,7 @@
"example": "2006-01-02 15:04:05.000" "example": "2006-01-02 15:04:05.000"
}, },
"labels": { "labels": {
"description": "labels are not persisted, but calculated at runtime, i.e. when summary is retrieved",
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/models.SummaryItem" "$ref": "#/definitions/models.SummaryItem"

View File

@ -1,5 +1,22 @@
basePath: /api basePath: /api
definitions: definitions:
models.Diagnostics:
properties:
architecture:
type: string
cli_version:
type: string
id:
type: integer
logs:
type: string
platform:
type: string
plugin:
type: string
stacktrace:
type: string
type: object
models.Heartbeat: models.Heartbeat:
properties: properties:
branch: branch:
@ -44,6 +61,8 @@ definitions:
format: date format: date
type: string type: string
labels: labels:
description: labels are not persisted, but calculated at runtime, i.e. when
summary is retrieved
items: items:
$ref: '#/definitions/models.SummaryItem' $ref: '#/definitions/models.SummaryItem'
type: array type: array
@ -622,6 +641,26 @@ paths:
summary: Push new heartbeats summary: Push new heartbeats
tags: tags:
- heartbeat - heartbeat
/plugins/errors:
post:
consumes:
- application/json
operationId: post-diagnostics
parameters:
- description: A single diagnostics object sent by WakaTime CLI
in: body
name: diagnostics
required: true
schema:
$ref: '#/definitions/models.Diagnostics'
responses:
"201":
description: ""
security:
- ApiKeyAuth: []
summary: Push a new diagnostics object
tags:
- diagnostics
/summary: /summary:
get: get:
operationId: get-summary operationId: get-summary