diff --git a/backend/internal/controllers/controller.go b/backend/internal/controllers/controller.go new file mode 100644 index 0000000..9686bec --- /dev/null +++ b/backend/internal/controllers/controller.go @@ -0,0 +1,141 @@ +// Copyright (c) 2025 Nikolai Papin +// +// This file is part of Easywish +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See +// the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package controllers + +import ( + "easywish/internal/dto" + errs "easywish/internal/errors" + "easywish/internal/middleware" + "easywish/internal/services" + "easywish/internal/utils/enums" + "easywish/internal/validation" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + "go.uber.org/zap" +) + +var ( + GET = "GET" + POST = "POST" + PUT = "PUT" + PATCH = "PATCH" + DELETE = "DELETE" +) + +type ControllerMethod struct { + HttpMethod string + Path string + Authorization enums.Role + Middleware []gin.HandlerFunc + Function func (c *gin.Context) +} + +type controllerImpl struct { + Path string + Authorization enums.Role + Middleware []gin.HandlerFunc + Methods []ControllerMethod +} + +func (ctrl *controllerImpl) Setup(group *gin.RouterGroup, log *zap.Logger, auth services.AuthService) *gin.RouterGroup { + ctrlGroup := group.Group(ctrl.Path) + ctrlGroup.Use(middleware.AuthMiddleware(log, auth)) + ctrlGroup.Use(gin.HandlerFunc(func(c *gin.Context) { + ip := c.ClientIP() + userAgent := c.Request.UserAgent() + sessionInfoFromCtx, ok := c.Get("session_info"); if !ok { + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid or missing session data"}) + return + } + + sessionInfo := sessionInfoFromCtx.(dto.SessionInfo) + + if sessionInfo.Role < ctrl.Authorization { + c.AbortWithStatusJSON( + http.StatusForbidden, + gin.H{"error": "Insufficient authorization for this controller"}) + return + } + + c.Set("client_info", dto.ClientInfo{ + SessionInfo: sessionInfo, + IP: ip, + UserAgent: userAgent, + }) + + c.Next() + })) + ctrlGroup.Use(ctrl.Middleware...) + + for _, method := range ctrl.Methods { + ctrlGroup.Handle( + method.HttpMethod, + method.Path, + append( + method.Middleware, + gin.HandlerFunc(func(c *gin.Context) { + clientInfo, _ := c.Get("client_info") + if clientInfo.(dto.ClientInfo).Role < method.Authorization { + c.AbortWithStatusJSON( + http.StatusForbidden, + gin.H{"error": "Insufficient authorization for this method"}) + return + } + }), + method.Function)..., + )} + return ctrlGroup +} + +type Controller interface { + Setup() +} + +func GetRequest[ModelT any](c *gin.Context) (*dto.Request[ModelT], error) { + + var body ModelT + if err := c.ShouldBindJSON(&body); err != nil { + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return nil, err + } + + validate := validation.NewValidator() + + if err := validate.Struct(body); err != nil { + errorList := err.(validator.ValidationErrors) + c.AbortWithStatusJSON( + http.StatusBadRequest, + gin.H{"error": errorList}) + return nil, err + } + + cinfoFromCtx, ok := c.Get("client_info"); if !ok { + c.AbortWithStatusJSON( + http.StatusInternalServerError, + gin.H{"error": "Client info was not found"}) + return nil, errs.ErrClientInfoNotProvided + } + cinfo := cinfoFromCtx.(*dto.ClientInfo) + + return &dto.Request[ModelT]{ + Body: body, + User: *cinfo, + }, nil +} diff --git a/backend/internal/errors/controller.go b/backend/internal/errors/controller.go new file mode 100644 index 0000000..4489316 --- /dev/null +++ b/backend/internal/errors/controller.go @@ -0,0 +1,24 @@ +// Copyright (c) 2025 Nikolai Papin +// +// This file is part of Easywish +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See +// the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package errors + +import "errors" + +var ( + ErrClientInfoNotProvided = errors.New("No client info provded") +)