experiment-service_controller_pattern #1
@@ -1,25 +1,33 @@
|
|||||||
// @title Easywish client API
|
// @title Easywish client API
|
||||||
// @version 1.0
|
// @version 1.0
|
||||||
// @description Easy and feature-rich wishlist.
|
// @description Easy and feature-rich wishlist.
|
||||||
// @license.name GPL 3.0
|
// @license.name GPL 3.0
|
||||||
|
|
||||||
// @BasePath /api/
|
// @BasePath /api/
|
||||||
// @Schemes http
|
// @Schemes http
|
||||||
|
|
||||||
// @securityDefinitions.apikey JWT
|
// @securityDefinitions.apikey JWT
|
||||||
// @in header
|
// @in header
|
||||||
// @name Authorization
|
// @name Authorization
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/fx"
|
||||||
|
|
||||||
"easywish/config"
|
"easywish/config"
|
||||||
|
docs "easywish/docs"
|
||||||
|
"easywish/internal/controllers"
|
||||||
"easywish/internal/logger"
|
"easywish/internal/logger"
|
||||||
"easywish/internal/routes"
|
"easywish/internal/routes"
|
||||||
|
"easywish/internal/services"
|
||||||
|
|
||||||
docs "easywish/docs"
|
|
||||||
swaggerfiles "github.com/swaggo/files"
|
swaggerfiles "github.com/swaggo/files"
|
||||||
ginSwagger "github.com/swaggo/gin-swagger"
|
ginSwagger "github.com/swaggo/gin-swagger"
|
||||||
)
|
)
|
||||||
@@ -30,14 +38,43 @@ func main() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer logger.Sync()
|
fx.New(
|
||||||
|
fx.Provide(
|
||||||
|
logger.NewLogger,
|
||||||
|
gin.Default,
|
||||||
|
),
|
||||||
|
services.Module,
|
||||||
|
controllers.Module,
|
||||||
|
routes.Module,
|
||||||
|
|
||||||
r := gin.Default()
|
fx.Invoke(func(lc fx.Lifecycle, router *gin.Engine) {
|
||||||
r = routes.SetupRoutes(r)
|
|
||||||
|
|
||||||
|
// Swagger
|
||||||
docs.SwaggerInfo.Schemes = []string{"http"}
|
docs.SwaggerInfo.Schemes = []string{"http"}
|
||||||
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
|
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
|
||||||
|
|
||||||
r.Run(":8080")
|
// Gin
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: fmt.Sprintf(":%s", config.GetConfig().Port),
|
||||||
|
Handler: router,
|
||||||
|
}
|
||||||
|
|
||||||
|
lc.Append(fx.Hook{
|
||||||
|
OnStart: func(ctx context.Context) error {
|
||||||
|
go func() {
|
||||||
|
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
OnStop: func(ctx context.Context) error {
|
||||||
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
return server.Shutdown(shutdownCtx)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
|
||||||
|
).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -146,6 +146,26 @@ const docTemplate = `{
|
|||||||
"responses": {}
|
"responses": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/profile": {
|
||||||
|
"patch": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"JWT": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Profile"
|
||||||
|
],
|
||||||
|
"summary": "Update profile",
|
||||||
|
"responses": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/profile/me": {
|
"/profile/me": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|||||||
@@ -142,6 +142,26 @@
|
|||||||
"responses": {}
|
"responses": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/profile": {
|
||||||
|
"patch": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"JWT": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Profile"
|
||||||
|
],
|
||||||
|
"summary": "Update profile",
|
||||||
|
"responses": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/profile/me": {
|
"/profile/me": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|||||||
@@ -113,6 +113,18 @@ paths:
|
|||||||
summary: Confirm with code, finish creating the account
|
summary: Confirm with code, finish creating the account
|
||||||
tags:
|
tags:
|
||||||
- Auth
|
- Auth
|
||||||
|
/profile:
|
||||||
|
patch:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses: {}
|
||||||
|
security:
|
||||||
|
- JWT: []
|
||||||
|
summary: Update profile
|
||||||
|
tags:
|
||||||
|
- Profile
|
||||||
/profile/{username}:
|
/profile/{username}:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ require (
|
|||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||||
|
go.uber.org/dig v1.19.0 // indirect
|
||||||
|
go.uber.org/fx v1.24.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/arch v0.18.0 // indirect
|
golang.org/x/arch v0.18.0 // indirect
|
||||||
golang.org/x/crypto v0.39.0 // indirect
|
golang.org/x/crypto v0.39.0 // indirect
|
||||||
|
|||||||
@@ -120,6 +120,10 @@ github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2
|
|||||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4=
|
||||||
|
go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
|
||||||
|
go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg=
|
||||||
|
go.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
|
|||||||
@@ -1,31 +1,31 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"easywish/internal/models"
|
|
||||||
"easywish/internal/services"
|
"easywish/internal/services"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// @Summary Register an account
|
type AuthController interface {
|
||||||
// @Tags Auth
|
RegistrationBegin(c *gin.Context)
|
||||||
// @Accept json
|
RegistrationComplete(c *gin.Context)
|
||||||
// @Produce json
|
Login(c *gin.Context)
|
||||||
// @Router /auth/registrationBegin [post]
|
Refresh(c *gin.Context)
|
||||||
func RegistrationBegin(c *gin.Context) {
|
PasswordResetBegin(c *gin.Context)
|
||||||
c.Status(http.StatusNotImplemented)
|
PasswordResetComplete(c *gin.Context)
|
||||||
|
Router
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Confirm with code, finish creating the account
|
type authControllerImpl struct {
|
||||||
// @Tags Auth
|
authService services.AuthService
|
||||||
// @Accept json
|
|
||||||
// @Produce json
|
|
||||||
// @Router /auth/registrationComplete [post]
|
|
||||||
func RegistrationComplete(c *gin.Context) {
|
|
||||||
c.Status(http.StatusNotImplemented)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewAuthController(as services.AuthService) AuthController {
|
||||||
|
return &authControllerImpl{authService: as}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login implements AuthController.
|
||||||
// @Summary Acquire tokens via login credentials (and 2FA code if needed)
|
// @Summary Acquire tokens via login credentials (and 2FA code if needed)
|
||||||
// @Tags Auth
|
// @Tags Auth
|
||||||
// @Accept json
|
// @Accept json
|
||||||
@@ -33,50 +33,65 @@ func RegistrationComplete(c *gin.Context) {
|
|||||||
// @Param request body models.LoginRequest true "desc"
|
// @Param request body models.LoginRequest true "desc"
|
||||||
// @Success 200 {object} models.LoginResponse "desc"
|
// @Success 200 {object} models.LoginResponse "desc"
|
||||||
// @Router /auth/login [post]
|
// @Router /auth/login [post]
|
||||||
func Login(c *gin.Context) {
|
func (a *authControllerImpl) Login(c *gin.Context) {
|
||||||
|
|
||||||
var request models.LoginRequest
|
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&request); err != nil {
|
|
||||||
c.Status(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
auths := services.NewAuthService()
|
|
||||||
|
|
||||||
result, err := auths.Login(request)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
c.Status(http.StatusTeapot)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusFound, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Summary Receive new tokens via refresh token
|
|
||||||
// @Tags Auth
|
|
||||||
// @Accept json
|
|
||||||
// @Produce json
|
|
||||||
// @Router /auth/refresh [post]
|
|
||||||
func Refresh(c *gin.Context) {
|
|
||||||
c.Status(http.StatusNotImplemented)
|
c.Status(http.StatusNotImplemented)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PasswordResetBegin implements AuthController.
|
||||||
// @Summary Request password reset email
|
// @Summary Request password reset email
|
||||||
// @Tags Auth
|
// @Tags Auth
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Router /auth/passwordResetBegin [post]
|
// @Router /auth/passwordResetBegin [post]
|
||||||
func PasswordResetBegin(c *gin.Context) {
|
func (a *authControllerImpl) PasswordResetBegin(c *gin.Context) {
|
||||||
c.Status(http.StatusNotImplemented)
|
c.Status(http.StatusNotImplemented)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PasswordResetComplete implements AuthController.
|
||||||
// @Summary Complete password reset with email code and provide 2FA code or backup code if needed
|
// @Summary Complete password reset with email code and provide 2FA code or backup code if needed
|
||||||
// @Tags Auth
|
// @Tags Auth
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Router /auth/passwordResetComplete [post]
|
// @Router /auth/passwordResetComplete [post]
|
||||||
func PasswordResetComplete(c *gin.Context) {
|
func (a *authControllerImpl) PasswordResetComplete(c *gin.Context) {
|
||||||
c.Status(http.StatusNotImplemented)
|
c.Status(http.StatusNotImplemented)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresh implements AuthController.
|
||||||
|
// @Summary Receive new tokens via refresh token
|
||||||
|
// @Tags Auth
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Router /auth/refresh [post]
|
||||||
|
func (a *authControllerImpl) Refresh(c *gin.Context) {
|
||||||
|
c.Status(http.StatusNotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegistrationComplete implements AuthController.
|
||||||
|
// @Summary Register an account
|
||||||
|
// @Tags Auth
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Router /auth/registrationBegin [post]
|
||||||
|
func (a *authControllerImpl) RegistrationBegin(c *gin.Context) {
|
||||||
|
c.Status(http.StatusNotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegistrationBegin implements AuthController.
|
||||||
|
// @Summary Confirm with code, finish creating the account
|
||||||
|
// @Tags Auth
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Router /auth/registrationComplete [post]
|
||||||
|
func (a *authControllerImpl) RegistrationComplete(c *gin.Context) {
|
||||||
|
c.Status(http.StatusNotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authControllerImpl) RegisterRoutes(group *gin.RouterGroup) {
|
||||||
|
group.POST("/registrationBegin", a.RegistrationBegin)
|
||||||
|
group.POST("/registrationComplete", a.RegistrationComplete)
|
||||||
|
group.POST("/login", a.Login)
|
||||||
|
group.POST("/refresh", a.Refresh)
|
||||||
|
group.POST("/passwordResetBegin", a.PasswordResetBegin)
|
||||||
|
group.POST("/passwordResetComplete", a.PasswordResetComplete)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,28 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"easywish/internal/middleware"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ProfileController interface {
|
||||||
|
GetProfile(c *gin.Context)
|
||||||
|
GetOwnProfile(c *gin.Context)
|
||||||
|
UpdateProfile(c *gin.Context)
|
||||||
|
GetPrivacySettings(c *gin.Context)
|
||||||
|
UpdatePrivacySettings(c *gin.Context)
|
||||||
|
Router
|
||||||
|
}
|
||||||
|
|
||||||
|
type profileControllerImpl struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProfileController() ProfileController {
|
||||||
|
return &profileControllerImpl{}
|
||||||
|
}
|
||||||
|
|
||||||
// @Summary Get someone's profile details
|
// @Summary Get someone's profile details
|
||||||
// @Tags Profile
|
// @Tags Profile
|
||||||
// @Accept json
|
// @Accept json
|
||||||
@@ -13,20 +30,8 @@ import (
|
|||||||
// @Param username path string true "Username"
|
// @Param username path string true "Username"
|
||||||
// @Security JWT
|
// @Security JWT
|
||||||
// @Router /profile/{username} [get]
|
// @Router /profile/{username} [get]
|
||||||
func GetProfile(c *gin.Context) {
|
func (p *profileControllerImpl) GetProfile(c *gin.Context) {
|
||||||
|
c.Status(http.StatusNotImplemented)
|
||||||
username := c.Param("username")
|
|
||||||
|
|
||||||
if username == "" {
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
|
||||||
"error": "Username cannot be empty",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusNotImplemented, gin.H{
|
|
||||||
"username": username,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Get own profile when authorized
|
// @Summary Get own profile when authorized
|
||||||
@@ -35,13 +40,18 @@ func GetProfile(c *gin.Context) {
|
|||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security JWT
|
// @Security JWT
|
||||||
// @Router /profile/me [get]
|
// @Router /profile/me [get]
|
||||||
func GetOwnProfile(c *gin.Context) {
|
func (p *profileControllerImpl) GetOwnProfile(c *gin.Context) {
|
||||||
|
c.Status(http.StatusNotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
username := "Gregory House"
|
// @Summary Update profile
|
||||||
|
// @Tags Profile
|
||||||
c.JSON(http.StatusNotImplemented, gin.H{
|
// @Accept json
|
||||||
"username": username,
|
// @Produce json
|
||||||
})
|
// @Security JWT
|
||||||
|
// @Router /profile [patch]
|
||||||
|
func (p *profileControllerImpl) UpdateProfile(c *gin.Context) {
|
||||||
|
c.Status(http.StatusNotImplemented)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Get profile privacy settings
|
// @Summary Get profile privacy settings
|
||||||
@@ -50,7 +60,7 @@ func GetOwnProfile(c *gin.Context) {
|
|||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security JWT
|
// @Security JWT
|
||||||
// @Router /profile/privacy [get]
|
// @Router /profile/privacy [get]
|
||||||
func GetPrivacySettings(c *gin.Context) {
|
func (p *profileControllerImpl) GetPrivacySettings(c *gin.Context) {
|
||||||
c.Status(http.StatusNotImplemented)
|
c.Status(http.StatusNotImplemented)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +70,16 @@ func GetPrivacySettings(c *gin.Context) {
|
|||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security JWT
|
// @Security JWT
|
||||||
// @Router /profile/privacy [patch]
|
// @Router /profile/privacy [patch]
|
||||||
func UpdatePrivacySettings(c *gin.Context) {
|
func (p *profileControllerImpl) UpdatePrivacySettings(c *gin.Context) {
|
||||||
c.Status(http.StatusNotImplemented)
|
c.Status(http.StatusNotImplemented)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *profileControllerImpl) RegisterRoutes(group *gin.RouterGroup) {
|
||||||
|
protected := group.Group("")
|
||||||
|
protected.Use(middleware.JWTAuthMiddleware())
|
||||||
|
{
|
||||||
|
protected.GET("/me", p.GetOwnProfile)
|
||||||
|
protected.GET("/:username", p.GetProfile)
|
||||||
|
protected.GET("/privacy", p.GetPrivacySettings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
9
backend/internal/controllers/router.go
Normal file
9
backend/internal/controllers/router.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Router interface {
|
||||||
|
RegisterRoutes(group *gin.RouterGroup)
|
||||||
|
}
|
||||||
@@ -6,10 +6,18 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HealthStatus struct {
|
type ServiceController interface {
|
||||||
Healthy bool `json:"healthy"`
|
HealthCheck(c *gin.Context)
|
||||||
|
Router
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type serviceControllerImpl struct{}
|
||||||
|
|
||||||
|
func NewServiceController() ServiceController {
|
||||||
|
return &serviceControllerImpl{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HealthCheck implements ServiceController.
|
||||||
// @Summary Get health status
|
// @Summary Get health status
|
||||||
// @Description Used internally for checking service health
|
// @Description Used internally for checking service health
|
||||||
// @Tags Service
|
// @Tags Service
|
||||||
@@ -17,6 +25,15 @@ type HealthStatus struct {
|
|||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} HealthStatus "Says whether it's healthy or not"
|
// @Success 200 {object} HealthStatus "Says whether it's healthy or not"
|
||||||
// @Router /service/health [get]
|
// @Router /service/health [get]
|
||||||
func HealthCheck(c *gin.Context) {
|
func (s *serviceControllerImpl) HealthCheck(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, gin.H{"healthy": true})
|
c.JSON(http.StatusOK, gin.H{"healthy": true})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterRoutes implements ServiceController.
|
||||||
|
func (s *serviceControllerImpl) RegisterRoutes(group *gin.RouterGroup) {
|
||||||
|
group.GET("/health", s.HealthCheck)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HealthStatus struct {
|
||||||
|
Healthy bool `json:"healthy"`
|
||||||
|
}
|
||||||
|
|||||||
13
backend/internal/controllers/setup.go
Normal file
13
backend/internal/controllers/setup.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.uber.org/fx"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Module = fx.Module("controllers",
|
||||||
|
fx.Provide(
|
||||||
|
NewServiceController,
|
||||||
|
NewAuthController,
|
||||||
|
NewProfileController,
|
||||||
|
),
|
||||||
|
)
|
||||||
@@ -6,4 +6,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
ErrUnauthorized = errors.New("User is not authorized")
|
ErrUnauthorized = errors.New("User is not authorized")
|
||||||
|
ErrUsernameTaken = errors.New("Provided username is already in use")
|
||||||
|
ErrInvalidCredentials = errors.New("Invalid username, password or TOTP code")
|
||||||
|
ErrInvalidToken = errors.New("Token is invalid or expired")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,17 +1,31 @@
|
|||||||
package logger
|
package logger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"easywish/config"
|
"easywish/config"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Logger interface {
|
||||||
|
Get() *zap.Logger
|
||||||
|
Sync() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type loggerImpl struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLogger() Logger {
|
||||||
|
return &loggerImpl{}
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
once sync.Once
|
once sync.Once
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetLogger() *zap.Logger {
|
func (l *loggerImpl) Get() *zap.Logger {
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
var err error
|
var err error
|
||||||
cfg := config.GetConfig()
|
cfg := config.GetConfig()
|
||||||
@@ -28,6 +42,6 @@ func GetLogger() *zap.Logger {
|
|||||||
return logger
|
return logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func Sync() error {
|
func (l *loggerImpl) Sync() error {
|
||||||
return logger.Sync()
|
return logger.Sync()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,24 @@ type Tokens struct {
|
|||||||
RefreshToken string `json:"refresh_token"`
|
RefreshToken string `json:"refresh_token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RegistrationBeginRequest struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Email *string `json:"email"`
|
||||||
|
Password string `json:"password"` // TODO: password checking
|
||||||
|
}
|
||||||
|
|
||||||
|
type RegistrationCompleteRequest struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
VerificationCode string `json:"verification_code"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Birthday *string `json:"birthday"`
|
||||||
|
AvatarUrl *string `json:"avatar_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RegistrationCompleteResponse struct {
|
||||||
|
Tokens
|
||||||
|
}
|
||||||
|
|
||||||
type LoginRequest struct {
|
type LoginRequest struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
|
|||||||
44
backend/internal/routes/router.go
Normal file
44
backend/internal/routes/router.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"easywish/internal/controllers"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewRouter(engine *gin.Engine, groups []RouteGroup) *gin.Engine {
|
||||||
|
apiGroup := engine.Group("/api")
|
||||||
|
for _, group := range groups {
|
||||||
|
subgroup := apiGroup.Group(group.BasePath)
|
||||||
|
subgroup.Use(group.Middleware...)
|
||||||
|
group.Router.RegisterRoutes(subgroup)
|
||||||
|
}
|
||||||
|
return engine
|
||||||
|
}
|
||||||
|
|
||||||
|
type RouteGroup struct {
|
||||||
|
BasePath string
|
||||||
|
Middleware []gin.HandlerFunc
|
||||||
|
Router controllers.Router
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRouteGroups(
|
||||||
|
authController controllers.AuthController,
|
||||||
|
serviceController controllers.ServiceController,
|
||||||
|
profileController controllers.ProfileController,
|
||||||
|
) []RouteGroup {
|
||||||
|
return []RouteGroup{
|
||||||
|
{
|
||||||
|
BasePath: "/auth",
|
||||||
|
Router: authController,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BasePath: "/service",
|
||||||
|
Router: serviceController,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BasePath: "/profile",
|
||||||
|
Router: profileController,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,52 +1,12 @@
|
|||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"easywish/internal/controllers"
|
"go.uber.org/fx"
|
||||||
"easywish/internal/middleware"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func SetupRoutes(r *gin.Engine) *gin.Engine {
|
var Module = fx.Module("routes",
|
||||||
apiGroup := r.Group("/api")
|
fx.Provide(
|
||||||
{
|
NewRouteGroups,
|
||||||
serviceGroup := apiGroup.Group("/service")
|
),
|
||||||
{
|
fx.Invoke(NewRouter),
|
||||||
serviceGroup.GET("/health", controllers.HealthCheck)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
authGroup := apiGroup.Group("/auth")
|
|
||||||
{
|
|
||||||
authGroup.POST("/registrationBegin", controllers.RegistrationBegin)
|
|
||||||
authGroup.POST("/registrationComplete", controllers.RegistrationComplete)
|
|
||||||
authGroup.POST("/login", controllers.Login)
|
|
||||||
authGroup.POST("/refresh", controllers.Refresh)
|
|
||||||
authGroup.POST("/passwordResetBegin", controllers.PasswordResetBegin)
|
|
||||||
authGroup.POST("/passwordResetComplete", controllers.PasswordResetComplete)
|
|
||||||
}
|
|
||||||
|
|
||||||
profileGroup := apiGroup.Group("/profile")
|
|
||||||
{
|
|
||||||
profileGroup.GET("/:username", controllers.GetProfile)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected := apiGroup.Group("")
|
|
||||||
protected.Use(middleware.JWTAuthMiddleware())
|
|
||||||
{
|
|
||||||
accountGroup := protected.Group("/account")
|
|
||||||
{
|
|
||||||
accountGroup.PUT("/changePassword", controllers.ChangePassword)
|
|
||||||
}
|
|
||||||
|
|
||||||
profileGroup := protected.Group("/profile")
|
|
||||||
{
|
|
||||||
profileGroup.GET("/me", controllers.GetOwnProfile)
|
|
||||||
profileGroup.GET("/privacy", controllers.GetPrivacySettings)
|
|
||||||
profileGroup.PATCH("/privacy", controllers.UpdatePrivacySettings)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,25 +2,33 @@ package services
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"easywish/internal/database"
|
"easywish/internal/database"
|
||||||
"easywish/internal/errors"
|
|
||||||
errs "easywish/internal/errors"
|
errs "easywish/internal/errors"
|
||||||
"easywish/internal/models"
|
"easywish/internal/models"
|
||||||
"easywish/internal/utils"
|
"easywish/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuthService interface {
|
type AuthService interface {
|
||||||
|
RegistrationBegin(request models.RegistrationBeginRequest) (bool, error)
|
||||||
|
RegistrationComplete(request models.RegistrationBeginRequest) (*models.RegistrationCompleteResponse, error)
|
||||||
Login(request models.LoginRequest) (*models.LoginResponse, error)
|
Login(request models.LoginRequest) (*models.LoginResponse, error)
|
||||||
Refresh(request models.RefreshRequest) (*models.RefreshResponse, error)
|
Refresh(request models.RefreshRequest) (*models.RefreshResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type authServiceImpl struct {
|
type authServiceImpl struct {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthService() AuthService {
|
func NewAuthService() AuthService {
|
||||||
return &authServiceImpl{}
|
return &authServiceImpl{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *authServiceImpl) RegistrationBegin(request models.RegistrationBeginRequest) (bool, error) {
|
||||||
|
return false, errs.ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authServiceImpl) RegistrationComplete(request models.RegistrationBeginRequest) (*models.RegistrationCompleteResponse, error) {
|
||||||
|
return nil, errs.ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
func (a *authServiceImpl) Login(request models.LoginRequest) (*models.LoginResponse, error) {
|
func (a *authServiceImpl) Login(request models.LoginRequest) (*models.LoginResponse, error) {
|
||||||
conn, ctx, err := utils.GetDbConn()
|
conn, ctx, err := utils.GetDbConn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -44,7 +52,6 @@ func (a *authServiceImpl) Login(request models.LoginRequest) (*models.LoginRespo
|
|||||||
return &models.LoginResponse{Tokens: models.Tokens{AccessToken: accessToken, RefreshToken: refreshToken}}, nil
|
return &models.LoginResponse{Tokens: models.Tokens{AccessToken: accessToken, RefreshToken: refreshToken}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (a *authServiceImpl) Refresh(request models.RefreshRequest) (*models.RefreshResponse, error) {
|
func (a *authServiceImpl) Refresh(request models.RefreshRequest) (*models.RefreshResponse, error) {
|
||||||
return nil, errors.ErrNotImplemented
|
return nil, errs.ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|||||||
12
backend/internal/services/setup.go
Normal file
12
backend/internal/services/setup.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.uber.org/fx"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Module = fx.Module("services",
|
||||||
|
fx.Provide(
|
||||||
|
NewAuthService,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
Reference in New Issue
Block a user