Skip to content
This repository has been archived by the owner on May 16, 2023. It is now read-only.

Commit

Permalink
added report statistics, extended api
Browse files Browse the repository at this point in the history
  • Loading branch information
dreske committed Dec 15, 2021
1 parent d9aadf6 commit be8e129
Show file tree
Hide file tree
Showing 12 changed files with 189 additions and 33 deletions.
6 changes: 6 additions & 0 deletions resources/db/migration/V1.2.5.2__add_report_statistics.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
create table report_statistics
(
subject varchar(128) not null,
count integer not null,
constraint report_statistics_pk primary key (subject)
)
42 changes: 38 additions & 4 deletions src/api/centers.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,17 @@ type Centers struct {
func NewCentersAPI(centersService services.Centers, centersRepository repositories.Centers,
bugReportsService services.BugReports,
operatorsService services.Operators, geocoder geocoding.Geocoder, auth *jwtauth.JWTAuth) *Centers {
validate := validator.New()
validate.RegisterTagNameFunc(util.JsonTagNameFunc)

centers := &Centers{
Router: chi.NewRouter(),
centersService: centersService,
centersRepository: centersRepository,
operatorsService: operatorsService,
geocoder: geocoder,
bugReportsService: bugReportsService,
validate: validator.New(),
validate: validate,
}

// public endpoints
Expand All @@ -84,6 +87,7 @@ func NewCentersAPI(centersService services.Centers, centersRepository repositori
r.Get("/all", api.Handle(centers.getAllCenters))
r.Post("/csv", api.Handle(centers.prepareCSVImport))
r.Post("/", api.Handle(centers.importCenters))
r.Put("/{uuid}", api.Handle(centers.updateCenter))

// get centers
r.Get("/reference/{reference}", api.Handle(centers.getCenterByReferenceLegacy))
Expand Down Expand Up @@ -245,7 +249,7 @@ func (c *Centers) importCenters(_ http.ResponseWriter, r *http.Request) (interfa

centers := make([]domain.Center, len(importData.Centers))
for i, center := range importData.Centers {
centers[i] = center.MapToDomain()
centers[i] = *center.MapToDomain()
}

result, err := c.centersService.ImportCenters(r.Context(), centers, importData.DeleteAll)
Expand All @@ -255,16 +259,46 @@ func (c *Centers) importCenters(_ http.ResponseWriter, r *http.Request) (interfa
return model.MapToCenterDTOs(result), nil
}

func (c *Centers) updateCenter(_ http.ResponseWriter, r *http.Request) (interface{}, error) {
centerUUID := chi.URLParam(r, "uuid")
logrus.WithField("uuid", centerUUID).Trace("updateCenter")

operator, err := c.operatorsService.GetCurrentOperator(r.Context())
if err != nil {
return nil, err
}

center, err := c.centersRepository.FindByUUID(r.Context(), centerUUID)
if err != nil {
return nil, err
}

if center.OperatorUUID != operator.UUID && !security.HasRole(r.Context(), security.RoleAdmin) {
return nil, gorm.ErrRecordNotFound
}

var editCenterDTO model.EditCenterDTO
if err := api.ParseRequestBody(r, c.validate, &editCenterDTO); err != nil {
return nil, err
}

editCenterDTO.CopyToDomain(&center)
if err = c.centersService.Save(r.Context(), &center, true); err != nil {
return nil, err
}
return model.CenterDTO{}.MapFromDomain(&center), nil
}

// getCenterByUUID returns the center with the given uuid.
// If the center does not belong to the currently authenticated operator, this method will return an error
func (c *Centers) getCenterByUUID(_ http.ResponseWriter, r *http.Request) (interface{}, error) {
uuid := chi.URLParam(r, "uuid")
centerUUID := chi.URLParam(r, "uuid")
operator, err := c.operatorsService.GetCurrentOperator(r.Context())
if err != nil {
return nil, err
}

center, err := c.centersRepository.FindByUUID(r.Context(), uuid)
center, err := c.centersRepository.FindByUUID(r.Context(), centerUUID)
if err != nil {
return nil, err
}
Expand Down
38 changes: 19 additions & 19 deletions src/api/model/centers.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,35 +136,35 @@ type EditCenterDTO struct {
Note *string `json:"note"`
}

func (c EditCenterDTO) MapToDomain() domain.Center {
var enterDate *time.Time
func (c EditCenterDTO) CopyToDomain(dst *domain.Center) *domain.Center {
if c.EnterDate != nil {
if date, err := time.Parse("02.01.2006", *c.EnterDate); err == nil {
enterDate = &date
dst.EnterDate = &date
}
}

var leaveDate *time.Time
if c.LeaveDate != nil {
if date, err := time.Parse("02.01.2006", *c.LeaveDate); err == nil {
leaveDate = &date
dst.LeaveDate = &date
}
}

return domain.Center{
UserReference: c.UserReference,
Name: c.Name,
Website: c.Website,
Address: c.Address,
AddressNote: c.AddressNote,
OpeningHours: c.OpeningHours,
Appointment: (*domain.AppointmentType)(c.Appointment),
TestKinds: c.TestKinds,
DCC: c.DCC,
EnterDate: enterDate,
LeaveDate: leaveDate,
Email: c.Email,
}
dst.UserReference = c.UserReference
dst.Name = c.Name
dst.Website = c.Website
dst.Address = c.Address
dst.AddressNote = c.AddressNote
dst.OpeningHours = c.OpeningHours
dst.Appointment = (*domain.AppointmentType)(c.Appointment)
dst.TestKinds = c.TestKinds
dst.DCC = c.DCC
dst.Email = c.Email

return dst
}

func (c EditCenterDTO) MapToDomain() *domain.Center {
return c.CopyToDomain(&domain.Center{})
}

func (EditCenterDTO) MapFromDomain(center domain.Center) EditCenterDTO {
Expand Down
6 changes: 4 additions & 2 deletions src/api/model/operators.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import "com.t-systems-mms.cwa/domain"
type OperatorDTO struct {
UUID string `json:"uuid"`
OperatorNumber *string `json:"operatorNumber"`
Name string `json:"name"`
Email *string `json:"email"`
Name string `json:"name" validate:"required"`
Email *string `json:"email" validate:"email"`
Logo *string `json:"logo"`
MarkerIcon *string `json:"markerIcon"`
ReportReceiver *string `json:"reportReceiver" validate:"oneof=operator center"`
}

func MapToOperatorDTO(operator *domain.Operator) *OperatorDTO {
Expand All @@ -35,5 +36,6 @@ func MapToOperatorDTO(operator *domain.Operator) *OperatorDTO {
Logo: logo,
MarkerIcon: markerIcon,
Email: operator.Email,
ReportReceiver: operator.BugReportsReceiver,
}
}
15 changes: 15 additions & 0 deletions src/api/model/statistics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package model

import "com.t-systems-mms.cwa/repositories"

type ReportStatisticsDTO struct {
Subject string `json:"subject"`
ReportCount uint `json:"report_count"`
}

func (dto ReportStatisticsDTO) FromModel(model repositories.ReportStatistics) *ReportStatisticsDTO {
return &ReportStatisticsDTO{
Subject: model.Subject,
ReportCount: model.Count,
}
}
18 changes: 11 additions & 7 deletions src/api/operators.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,34 @@ import (
"bytes"
"com.t-systems-mms.cwa/api/model"
"com.t-systems-mms.cwa/core/api"
"com.t-systems-mms.cwa/core/util"
"com.t-systems-mms.cwa/repositories"
"com.t-systems-mms.cwa/services"
"encoding/json"
"github.com/go-chi/chi"
"github.com/go-chi/jwtauth"
"github.com/go-playground/validator"
"github.com/sirupsen/logrus"
"github.com/vincent-petithory/dataurl"
"image"
"io"
"net/http"
)

type Operators struct {
chi.Router
operatorsRepository repositories.Operators
operatorsService services.Operators
validate *validator.Validate
}

func NewOperatorsAPI(operatorsRepository repositories.Operators, operatorsService services.Operators, auth *jwtauth.JWTAuth) *Operators {
validate := validator.New()
validate.RegisterTagNameFunc(util.JsonTagNameFunc)

operators := &Operators{
Router: chi.NewRouter(),
operatorsService: operatorsService,
operatorsRepository: operatorsRepository,
validate: validate,
}

operators.Get("/{operator}/logo", operators.GetOperatorLogo)
Expand All @@ -48,16 +53,15 @@ func (c *Operators) SaveCurrentOperator(w http.ResponseWriter, r *http.Request)
return nil, err
}

body, err := io.ReadAll(r.Body)
if err != nil {
return nil, err
}
request := model.OperatorDTO{}
if err := json.Unmarshal(body, &request); err != nil {
if err := api.ParseRequestBody(r, c.validate, &request); err != nil {
return nil, err
}

operator.Name = request.Name
operator.Email = request.Email
operator.BugReportsReceiver = request.ReportReceiver

if request.Logo != nil {
data, err := dataurl.DecodeString(*request.Logo)
if err != nil {
Expand Down
45 changes: 45 additions & 0 deletions src/api/statistics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package api

import (
"com.t-systems-mms.cwa/api/model"
"com.t-systems-mms.cwa/core/api"
"com.t-systems-mms.cwa/core/security"
"com.t-systems-mms.cwa/repositories"
"github.com/go-chi/chi"
"github.com/go-chi/jwtauth"
"net/http"
)

type Statistics struct {
chi.Router
reportsRepository repositories.BugReports
}

func NewStatisticsAPI(reportsRepository repositories.BugReports, auth *jwtauth.JWTAuth) *Statistics {
statistics := &Statistics{
Router: chi.NewRouter(),
reportsRepository: reportsRepository,
}

statistics.Group(func(r chi.Router) {
r.Use(jwtauth.Verifier(auth))
r.Use(jwtauth.Authenticator)
r.Use(api.RequireRole(security.RoleAdmin))

r.Get("/reports", api.Handle(statistics.getReportStatistics))
})
return statistics
}

func (c *Statistics) getReportStatistics(_ http.ResponseWriter, r *http.Request) (interface{}, error) {
stats, err := c.reportsRepository.GetStatistics(r.Context())
if err != nil {
return nil, err
}

result := make([]model.ReportStatisticsDTO, len(stats))
for i, stat := range stats {
result[i] = *model.ReportStatisticsDTO{}.FromModel(stat)
}
return result, nil
}
1 change: 1 addition & 0 deletions src/cmd/backend/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ func main() {
router := chi.NewRouter()
router.Use(middleware.DefaultLogger)
router.Handle("/metrics", initMetricsHandler(centersRepository, operatorsRepository))
router.Mount("/api/statistics", api.NewStatisticsAPI(bugReportsRepository, tokenAuth))
router.Mount("/api/centers", api.NewCentersAPI(centersService, centersRepository, bugReportsService, operatorsService, geocoder, tokenAuth))
router.Mount("/api/operators", api.NewOperatorsAPI(operatorsRepository, operatorsService, tokenAuth))

Expand Down
14 changes: 14 additions & 0 deletions src/core/util/validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package util

import (
"reflect"
"strings"
)

func JsonTagNameFunc(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "-" {
return ""
}
return name
}
24 changes: 24 additions & 0 deletions src/repositories/bugreports.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import (
"time"
)

type ReportStatistics struct {
Subject string
Count uint
}

type BugReports interface {
Repository
Save(ctx context.Context, center *domain.BugReport) error
Expand All @@ -18,6 +23,9 @@ type BugReports interface {
UpdateLeaderForAll(ctx context.Context, leader string) error
FindAllByLeader(ctx context.Context, leader string) ([]domain.BugReport, error)
ResetLeader(ctx context.Context, leader string) error

IncrementReportCount(ctx context.Context, subject string) error
GetStatistics(ctx context.Context) ([]ReportStatistics, error)
}

type bugReportsRepository struct {
Expand Down Expand Up @@ -72,3 +80,19 @@ func (b *bugReportsRepository) FindAll(ctx context.Context) ([]domain.BugReport,
err := b.GetTX(ctx).Find(&reports).Error
return reports, err
}

func (b *bugReportsRepository) IncrementReportCount(ctx context.Context, subject string) error {
return b.GetTX(ctx).Exec("insert into report_statistics (subject, count)"+
"VALUES (?, 1)"+
"on conflict (subject) "+
"do update set count = report_statistics.count + 1", subject).Error
}

func (b *bugReportsRepository) GetStatistics(ctx context.Context) ([]ReportStatistics, error) {
var statistics []ReportStatistics
err := b.GetTX(ctx).
Raw("select * from report_statistics").
Scan(&statistics).Error

return statistics, err
}
8 changes: 8 additions & 0 deletions src/services/bugreports.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@ func (s *bugReportsService) CreateBugReport(ctx context.Context, centerUUID, sub
}

err = s.bugReportsRepository.Save(ctx, &report)
if err != nil {
logrus.WithError(err).Error("Error creating bug report")
}

if err := s.bugReportsRepository.IncrementReportCount(ctx, report.Subject); err != nil {
logrus.WithError(err).Error("Error updating report statistics")
}

createdBugReportsCount.Inc()
return report, err
}
Expand Down
5 changes: 4 additions & 1 deletion src/services/centers.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,15 @@ type centersService struct {
}

func NewCentersService(centersRepository repositories.Centers, operators repositories.Operators, operatorsService Operators, geocoder geocoding.Geocoder) Centers {
validate := validator.New()
validate.RegisterTagNameFunc(util.JsonTagNameFunc)

return &centersService{
centersRepository: centersRepository,
operators: operators,
operatorsService: operatorsService,
geocoder: geocoder,
validate: validator.New(),
validate: validate,
}
}

Expand Down

0 comments on commit be8e129

Please sign in to comment.