diff --git a/backend/cmd/main.go b/backend/cmd/main.go
index 612a022..45fd6ac 100644
--- a/backend/cmd/main.go
+++ b/backend/cmd/main.go
@@ -15,17 +15,17 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-// @title Easywish client API
-// @version 1.0
-// @description Easy and feature-rich wishlist.
-// @license.name GPL-3.0
+// @title Easywish client API
+// @version 1.0
+// @description Easy and feature-rich wishlist.
+// @license.name GPL-3.0
-// @BasePath /api/
-// @Schemes http
+// @BasePath /api/
+// @Schemes http
// @securityDefinitions.apikey JWT
-// @in header
-// @name Authorization
+// @in header
+// @name Authorization
package main
@@ -46,7 +46,6 @@ import (
"easywish/internal/database"
"easywish/internal/logger"
redisclient "easywish/internal/redisClient"
- "easywish/internal/routes"
"easywish/internal/services"
"easywish/internal/validation"
@@ -74,7 +73,6 @@ func main() {
validation.Module,
controllers.Module,
- routes.Module,
fx.Invoke(func(lc fx.Lifecycle, router *gin.Engine, syncLogger *logger.SyncLogger) {
diff --git a/backend/docs/docs.go b/backend/docs/docs.go
index e1df226..9da7489 100644
--- a/backend/docs/docs.go
+++ b/backend/docs/docs.go
@@ -38,257 +38,6 @@ const docTemplate = `{
"responses": {}
}
},
- "/auth/changePassword": {
- "post": {
- "security": [
- {
- "JWT": []
- }
- ],
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "Auth"
- ],
- "summary": "Set new password using the old password",
- "parameters": [
- {
- "description": " ",
- "name": "request",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/models.ChangePasswordRequest"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "Password successfully changed"
- },
- "403": {
- "description": "Invalid old password"
- }
- }
- }
- },
- "/auth/login": {
- "post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "Auth"
- ],
- "summary": "Acquire tokens via login credentials (and 2FA code if needed)",
- "parameters": [
- {
- "description": " ",
- "name": "request",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/models.LoginRequest"
- }
- }
- ],
- "responses": {
- "200": {
- "description": " ",
- "schema": {
- "$ref": "#/definitions/models.LoginResponse"
- }
- },
- "403": {
- "description": "Invalid login credentials"
- }
- }
- }
- },
- "/auth/passwordResetBegin": {
- "post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "Auth"
- ],
- "summary": "Request password reset email",
- "parameters": [
- {
- "description": " ",
- "name": "request",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/models.PasswordResetBeginRequest"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "Reset code sent to the email if it is attached to an account"
- },
- "429": {
- "description": "Too many recent requests for this email"
- }
- }
- }
- },
- "/auth/passwordResetComplete": {
- "post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "Auth"
- ],
- "summary": "Complete password reset via email code",
- "parameters": [
- {
- "description": " ",
- "name": "request",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/models.PasswordResetCompleteRequest"
- }
- }
- ],
- "responses": {
- "200": {
- "description": " ",
- "schema": {
- "$ref": "#/definitions/models.PasswordResetCompleteResponse"
- }
- },
- "403": {
- "description": "Wrong verification code or username"
- }
- }
- }
- },
- "/auth/refresh": {
- "post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "Auth"
- ],
- "summary": "Receive new tokens via refresh token",
- "parameters": [
- {
- "description": " ",
- "name": "request",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/models.RefreshRequest"
- }
- }
- ],
- "responses": {
- "200": {
- "description": " ",
- "schema": {
- "$ref": "#/definitions/models.RefreshResponse"
- }
- },
- "401": {
- "description": "Invalid refresh token"
- }
- }
- }
- },
- "/auth/registrationBegin": {
- "post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "Auth"
- ],
- "summary": "Register an account",
- "parameters": [
- {
- "description": " ",
- "name": "request",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/models.RegistrationBeginRequest"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "Account is created and awaiting verification"
- },
- "409": {
- "description": "Username or email is already taken"
- },
- "429": {
- "description": "Too many recent registration attempts for this email"
- }
- }
- }
- },
- "/auth/registrationComplete": {
- "post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "Auth"
- ],
- "summary": "Confirm with code, finish creating the account",
- "parameters": [
- {
- "description": " ",
- "name": "request",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/models.RegistrationCompleteRequest"
- }
- }
- ],
- "responses": {
- "200": {
- "description": " ",
- "schema": {
- "$ref": "#/definitions/models.RegistrationCompleteResponse"
- }
- },
- "403": {
- "description": "Invalid email or verification code"
- }
- }
- }
- },
"/profile": {
"patch": {
"security": [
@@ -395,207 +144,6 @@ const docTemplate = `{
],
"responses": {}
}
- },
- "/service/health": {
- "get": {
- "description": "Used internally for checking service health",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "Service"
- ],
- "summary": "Get health status",
- "responses": {
- "200": {
- "description": "Says whether it's healthy or not",
- "schema": {
- "$ref": "#/definitions/controllers.HealthStatus"
- }
- }
- }
- }
- }
- },
- "definitions": {
- "controllers.HealthStatus": {
- "type": "object",
- "properties": {
- "healthy": {
- "type": "boolean"
- }
- }
- },
- "models.ChangePasswordRequest": {
- "type": "object",
- "required": [
- "old_password",
- "password"
- ],
- "properties": {
- "old_password": {
- "type": "string"
- },
- "password": {
- "type": "string"
- },
- "totp": {
- "type": "string"
- }
- }
- },
- "models.LoginRequest": {
- "type": "object",
- "required": [
- "password",
- "username"
- ],
- "properties": {
- "password": {
- "type": "string",
- "maxLength": 100
- },
- "totp": {
- "type": "string"
- },
- "username": {
- "type": "string",
- "maxLength": 20,
- "minLength": 3
- }
- }
- },
- "models.LoginResponse": {
- "type": "object",
- "properties": {
- "access_token": {
- "type": "string"
- },
- "refresh_token": {
- "type": "string"
- }
- }
- },
- "models.PasswordResetBeginRequest": {
- "type": "object",
- "required": [
- "email"
- ],
- "properties": {
- "email": {
- "type": "string"
- }
- }
- },
- "models.PasswordResetCompleteRequest": {
- "type": "object",
- "required": [
- "email",
- "password",
- "verification_code"
- ],
- "properties": {
- "email": {
- "type": "string"
- },
- "log_out_sessions": {
- "type": "boolean"
- },
- "password": {
- "type": "string"
- },
- "verification_code": {
- "type": "string"
- }
- }
- },
- "models.PasswordResetCompleteResponse": {
- "type": "object",
- "properties": {
- "access_token": {
- "type": "string"
- },
- "refresh_token": {
- "type": "string"
- }
- }
- },
- "models.RefreshRequest": {
- "type": "object",
- "required": [
- "refresh_token"
- ],
- "properties": {
- "refresh_token": {
- "type": "string"
- }
- }
- },
- "models.RefreshResponse": {
- "type": "object",
- "properties": {
- "access_token": {
- "type": "string"
- },
- "refresh_token": {
- "type": "string"
- }
- }
- },
- "models.RegistrationBeginRequest": {
- "type": "object",
- "required": [
- "email",
- "password",
- "username"
- ],
- "properties": {
- "email": {
- "type": "string"
- },
- "password": {
- "type": "string"
- },
- "username": {
- "type": "string"
- }
- }
- },
- "models.RegistrationCompleteRequest": {
- "type": "object",
- "required": [
- "name",
- "username",
- "verification_code"
- ],
- "properties": {
- "birthday": {
- "type": "string"
- },
- "name": {
- "type": "string"
- },
- "username": {
- "type": "string"
- },
- "verification_code": {
- "type": "string"
- }
- }
- },
- "models.RegistrationCompleteResponse": {
- "type": "object",
- "properties": {
- "access_token": {
- "type": "string"
- },
- "refresh_token": {
- "type": "string"
- }
- }
}
},
"securityDefinitions": {
diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json
index af5b4dd..c2c6d2a 100644
--- a/backend/docs/swagger.json
+++ b/backend/docs/swagger.json
@@ -34,257 +34,6 @@
"responses": {}
}
},
- "/auth/changePassword": {
- "post": {
- "security": [
- {
- "JWT": []
- }
- ],
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "Auth"
- ],
- "summary": "Set new password using the old password",
- "parameters": [
- {
- "description": " ",
- "name": "request",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/models.ChangePasswordRequest"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "Password successfully changed"
- },
- "403": {
- "description": "Invalid old password"
- }
- }
- }
- },
- "/auth/login": {
- "post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "Auth"
- ],
- "summary": "Acquire tokens via login credentials (and 2FA code if needed)",
- "parameters": [
- {
- "description": " ",
- "name": "request",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/models.LoginRequest"
- }
- }
- ],
- "responses": {
- "200": {
- "description": " ",
- "schema": {
- "$ref": "#/definitions/models.LoginResponse"
- }
- },
- "403": {
- "description": "Invalid login credentials"
- }
- }
- }
- },
- "/auth/passwordResetBegin": {
- "post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "Auth"
- ],
- "summary": "Request password reset email",
- "parameters": [
- {
- "description": " ",
- "name": "request",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/models.PasswordResetBeginRequest"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "Reset code sent to the email if it is attached to an account"
- },
- "429": {
- "description": "Too many recent requests for this email"
- }
- }
- }
- },
- "/auth/passwordResetComplete": {
- "post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "Auth"
- ],
- "summary": "Complete password reset via email code",
- "parameters": [
- {
- "description": " ",
- "name": "request",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/models.PasswordResetCompleteRequest"
- }
- }
- ],
- "responses": {
- "200": {
- "description": " ",
- "schema": {
- "$ref": "#/definitions/models.PasswordResetCompleteResponse"
- }
- },
- "403": {
- "description": "Wrong verification code or username"
- }
- }
- }
- },
- "/auth/refresh": {
- "post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "Auth"
- ],
- "summary": "Receive new tokens via refresh token",
- "parameters": [
- {
- "description": " ",
- "name": "request",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/models.RefreshRequest"
- }
- }
- ],
- "responses": {
- "200": {
- "description": " ",
- "schema": {
- "$ref": "#/definitions/models.RefreshResponse"
- }
- },
- "401": {
- "description": "Invalid refresh token"
- }
- }
- }
- },
- "/auth/registrationBegin": {
- "post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "Auth"
- ],
- "summary": "Register an account",
- "parameters": [
- {
- "description": " ",
- "name": "request",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/models.RegistrationBeginRequest"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "Account is created and awaiting verification"
- },
- "409": {
- "description": "Username or email is already taken"
- },
- "429": {
- "description": "Too many recent registration attempts for this email"
- }
- }
- }
- },
- "/auth/registrationComplete": {
- "post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "Auth"
- ],
- "summary": "Confirm with code, finish creating the account",
- "parameters": [
- {
- "description": " ",
- "name": "request",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/models.RegistrationCompleteRequest"
- }
- }
- ],
- "responses": {
- "200": {
- "description": " ",
- "schema": {
- "$ref": "#/definitions/models.RegistrationCompleteResponse"
- }
- },
- "403": {
- "description": "Invalid email or verification code"
- }
- }
- }
- },
"/profile": {
"patch": {
"security": [
@@ -391,207 +140,6 @@
],
"responses": {}
}
- },
- "/service/health": {
- "get": {
- "description": "Used internally for checking service health",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "tags": [
- "Service"
- ],
- "summary": "Get health status",
- "responses": {
- "200": {
- "description": "Says whether it's healthy or not",
- "schema": {
- "$ref": "#/definitions/controllers.HealthStatus"
- }
- }
- }
- }
- }
- },
- "definitions": {
- "controllers.HealthStatus": {
- "type": "object",
- "properties": {
- "healthy": {
- "type": "boolean"
- }
- }
- },
- "models.ChangePasswordRequest": {
- "type": "object",
- "required": [
- "old_password",
- "password"
- ],
- "properties": {
- "old_password": {
- "type": "string"
- },
- "password": {
- "type": "string"
- },
- "totp": {
- "type": "string"
- }
- }
- },
- "models.LoginRequest": {
- "type": "object",
- "required": [
- "password",
- "username"
- ],
- "properties": {
- "password": {
- "type": "string",
- "maxLength": 100
- },
- "totp": {
- "type": "string"
- },
- "username": {
- "type": "string",
- "maxLength": 20,
- "minLength": 3
- }
- }
- },
- "models.LoginResponse": {
- "type": "object",
- "properties": {
- "access_token": {
- "type": "string"
- },
- "refresh_token": {
- "type": "string"
- }
- }
- },
- "models.PasswordResetBeginRequest": {
- "type": "object",
- "required": [
- "email"
- ],
- "properties": {
- "email": {
- "type": "string"
- }
- }
- },
- "models.PasswordResetCompleteRequest": {
- "type": "object",
- "required": [
- "email",
- "password",
- "verification_code"
- ],
- "properties": {
- "email": {
- "type": "string"
- },
- "log_out_sessions": {
- "type": "boolean"
- },
- "password": {
- "type": "string"
- },
- "verification_code": {
- "type": "string"
- }
- }
- },
- "models.PasswordResetCompleteResponse": {
- "type": "object",
- "properties": {
- "access_token": {
- "type": "string"
- },
- "refresh_token": {
- "type": "string"
- }
- }
- },
- "models.RefreshRequest": {
- "type": "object",
- "required": [
- "refresh_token"
- ],
- "properties": {
- "refresh_token": {
- "type": "string"
- }
- }
- },
- "models.RefreshResponse": {
- "type": "object",
- "properties": {
- "access_token": {
- "type": "string"
- },
- "refresh_token": {
- "type": "string"
- }
- }
- },
- "models.RegistrationBeginRequest": {
- "type": "object",
- "required": [
- "email",
- "password",
- "username"
- ],
- "properties": {
- "email": {
- "type": "string"
- },
- "password": {
- "type": "string"
- },
- "username": {
- "type": "string"
- }
- }
- },
- "models.RegistrationCompleteRequest": {
- "type": "object",
- "required": [
- "name",
- "username",
- "verification_code"
- ],
- "properties": {
- "birthday": {
- "type": "string"
- },
- "name": {
- "type": "string"
- },
- "username": {
- "type": "string"
- },
- "verification_code": {
- "type": "string"
- }
- }
- },
- "models.RegistrationCompleteResponse": {
- "type": "object",
- "properties": {
- "access_token": {
- "type": "string"
- },
- "refresh_token": {
- "type": "string"
- }
- }
}
},
"securityDefinitions": {
diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml
index 4454738..ba28971 100644
--- a/backend/docs/swagger.yaml
+++ b/backend/docs/swagger.yaml
@@ -1,122 +1,4 @@
basePath: /api/
-definitions:
- controllers.HealthStatus:
- properties:
- healthy:
- type: boolean
- type: object
- models.ChangePasswordRequest:
- properties:
- old_password:
- type: string
- password:
- type: string
- totp:
- type: string
- required:
- - old_password
- - password
- type: object
- models.LoginRequest:
- properties:
- password:
- maxLength: 100
- type: string
- totp:
- type: string
- username:
- maxLength: 20
- minLength: 3
- type: string
- required:
- - password
- - username
- type: object
- models.LoginResponse:
- properties:
- access_token:
- type: string
- refresh_token:
- type: string
- type: object
- models.PasswordResetBeginRequest:
- properties:
- email:
- type: string
- required:
- - email
- type: object
- models.PasswordResetCompleteRequest:
- properties:
- email:
- type: string
- log_out_sessions:
- type: boolean
- password:
- type: string
- verification_code:
- type: string
- required:
- - email
- - password
- - verification_code
- type: object
- models.PasswordResetCompleteResponse:
- properties:
- access_token:
- type: string
- refresh_token:
- type: string
- type: object
- models.RefreshRequest:
- properties:
- refresh_token:
- type: string
- required:
- - refresh_token
- type: object
- models.RefreshResponse:
- properties:
- access_token:
- type: string
- refresh_token:
- type: string
- type: object
- models.RegistrationBeginRequest:
- properties:
- email:
- type: string
- password:
- type: string
- username:
- type: string
- required:
- - email
- - password
- - username
- type: object
- models.RegistrationCompleteRequest:
- properties:
- birthday:
- type: string
- name:
- type: string
- username:
- type: string
- verification_code:
- type: string
- required:
- - name
- - username
- - verification_code
- type: object
- models.RegistrationCompleteResponse:
- properties:
- access_token:
- type: string
- refresh_token:
- type: string
- type: object
info:
contact: {}
description: Easy and feature-rich wishlist.
@@ -137,165 +19,6 @@ paths:
summary: Change account password
tags:
- Account
- /auth/changePassword:
- post:
- consumes:
- - application/json
- parameters:
- - description: ' '
- in: body
- name: request
- required: true
- schema:
- $ref: '#/definitions/models.ChangePasswordRequest'
- produces:
- - application/json
- responses:
- "200":
- description: Password successfully changed
- "403":
- description: Invalid old password
- security:
- - JWT: []
- summary: Set new password using the old password
- tags:
- - Auth
- /auth/login:
- post:
- consumes:
- - application/json
- parameters:
- - description: ' '
- in: body
- name: request
- required: true
- schema:
- $ref: '#/definitions/models.LoginRequest'
- produces:
- - application/json
- responses:
- "200":
- description: ' '
- schema:
- $ref: '#/definitions/models.LoginResponse'
- "403":
- description: Invalid login credentials
- summary: Acquire tokens via login credentials (and 2FA code if needed)
- tags:
- - Auth
- /auth/passwordResetBegin:
- post:
- consumes:
- - application/json
- parameters:
- - description: ' '
- in: body
- name: request
- required: true
- schema:
- $ref: '#/definitions/models.PasswordResetBeginRequest'
- produces:
- - application/json
- responses:
- "200":
- description: Reset code sent to the email if it is attached to an account
- "429":
- description: Too many recent requests for this email
- summary: Request password reset email
- tags:
- - Auth
- /auth/passwordResetComplete:
- post:
- consumes:
- - application/json
- parameters:
- - description: ' '
- in: body
- name: request
- required: true
- schema:
- $ref: '#/definitions/models.PasswordResetCompleteRequest'
- produces:
- - application/json
- responses:
- "200":
- description: ' '
- schema:
- $ref: '#/definitions/models.PasswordResetCompleteResponse'
- "403":
- description: Wrong verification code or username
- summary: Complete password reset via email code
- tags:
- - Auth
- /auth/refresh:
- post:
- consumes:
- - application/json
- parameters:
- - description: ' '
- in: body
- name: request
- required: true
- schema:
- $ref: '#/definitions/models.RefreshRequest'
- produces:
- - application/json
- responses:
- "200":
- description: ' '
- schema:
- $ref: '#/definitions/models.RefreshResponse'
- "401":
- description: Invalid refresh token
- summary: Receive new tokens via refresh token
- tags:
- - Auth
- /auth/registrationBegin:
- post:
- consumes:
- - application/json
- parameters:
- - description: ' '
- in: body
- name: request
- required: true
- schema:
- $ref: '#/definitions/models.RegistrationBeginRequest'
- produces:
- - application/json
- responses:
- "200":
- description: Account is created and awaiting verification
- "409":
- description: Username or email is already taken
- "429":
- description: Too many recent registration attempts for this email
- summary: Register an account
- tags:
- - Auth
- /auth/registrationComplete:
- post:
- consumes:
- - application/json
- parameters:
- - description: ' '
- in: body
- name: request
- required: true
- schema:
- $ref: '#/definitions/models.RegistrationCompleteRequest'
- produces:
- - application/json
- responses:
- "200":
- description: ' '
- schema:
- $ref: '#/definitions/models.RegistrationCompleteResponse'
- "403":
- description: Invalid email or verification code
- summary: Confirm with code, finish creating the account
- tags:
- - Auth
/profile:
patch:
consumes:
@@ -361,21 +84,6 @@ paths:
summary: Update profile privacy settings
tags:
- Profile
- /service/health:
- get:
- consumes:
- - application/json
- description: Used internally for checking service health
- produces:
- - application/json
- responses:
- "200":
- description: Says whether it's healthy or not
- schema:
- $ref: '#/definitions/controllers.HealthStatus'
- summary: Get health status
- tags:
- - Service
schemes:
- http
securityDefinitions:
diff --git a/backend/internal/controllers/account.go b/backend/internal/controllers/account.go
index 3f2e1d3..22e3862 100644
--- a/backend/internal/controllers/account.go
+++ b/backend/internal/controllers/account.go
@@ -23,12 +23,12 @@ import (
"github.com/gin-gonic/gin"
)
-// @Summary Change account password
-// @Tags Account
-// @Accept json
-// @Produce json
-// @Security JWT
-// @Router /account/changePassword [put]
+// @Summary Change account password
+// @Tags Account
+// @Accept json
+// @Produce json
+// @Security JWT
+// @Router /account/changePassword [put]
func ChangePassword(c *gin.Context) {
c.Status(http.StatusNotImplemented)
}
diff --git a/backend/internal/controllers/auth.go b/backend/internal/controllers/auth.go
index 8e82813..d21cc37 100644
--- a/backend/internal/controllers/auth.go
+++ b/backend/internal/controllers/auth.go
@@ -19,7 +19,6 @@ package controllers
import (
errs "easywish/internal/errors"
- "easywish/internal/middleware"
"easywish/internal/models"
"easywish/internal/services"
"easywish/internal/utils"
@@ -31,249 +30,258 @@ import (
"go.uber.org/zap"
)
-type AuthController interface {
- RegistrationBegin(c *gin.Context)
- RegistrationComplete(c *gin.Context)
- Login(c *gin.Context)
- Refresh(c *gin.Context)
- PasswordResetBegin(c *gin.Context)
- PasswordResetComplete(c *gin.Context)
- ChangePassword(c *gin.Context)
- Router
-}
-
-type authControllerImpl struct {
- log *zap.Logger
- auth services.AuthService
-}
-
-func NewAuthController(_log *zap.Logger, _auth services.AuthService) AuthController {
- return &authControllerImpl{log: _log, auth: _auth}
-}
-
-// @Summary Acquire tokens via login credentials (and 2FA code if needed)
-// @Tags Auth
-// @Accept json
-// @Produce json
-// @Param request body models.LoginRequest true " "
-// @Success 200 {object} models.LoginResponse " "
-// @Failure 403 "Invalid login credentials"
-// @Router /auth/login [post]
-func (a *authControllerImpl) Login(c *gin.Context) {
- request, ok := utils.GetRequest[models.LoginRequest](c)
- if !ok {
- c.Status(http.StatusBadRequest)
- return
- }
-
- response, err := a.auth.Login(request.User, request.Body)
-
- if err != nil {
- if errors.Is(err, errs.ErrForbidden) {
- c.Status(http.StatusForbidden)
- } else {
- c.Status(http.StatusInternalServerError)
- }
- return
- }
-
- c.JSON(http.StatusOK, response)
-}
-
-// @Summary Request password reset email
-// @Tags Auth
-// @Accept json
-// @Produce json
-// @Param request body models.PasswordResetBeginRequest true " "
-// @Router /auth/passwordResetBegin [post]
-// @Success 200 "Reset code sent to the email if it is attached to an account"
-// @Failure 429 "Too many recent requests for this email"
-func (a *authControllerImpl) PasswordResetBegin(c *gin.Context) {
- request, ok := utils.GetRequest[models.PasswordResetBeginRequest](c)
- if !ok {
- c.Status(http.StatusBadRequest)
- return
- }
-
- response, err := a.auth.PasswordResetBegin(request.Body)
- if err != nil {
- if errors.Is(err, errs.ErrTooManyRequests) {
- c.Status(http.StatusTooManyRequests)
- } else {
- c.Status(http.StatusInternalServerError)
- }
- return
- }
-
- c.JSON(http.StatusOK, response)
-}
-
-// @Summary Complete password reset via email code
-// @Tags Auth
-// @Accept json
-// @Produce json
-// @Param request body models.PasswordResetCompleteRequest true " "
-// @Router /auth/passwordResetComplete [post]
-// @Success 200 {object} models.PasswordResetCompleteResponse " "
-// @Success 403 "Wrong verification code or username"
-func (a *authControllerImpl) PasswordResetComplete(c *gin.Context) {
-
- request, ok := utils.GetRequest[models.PasswordResetCompleteRequest](c)
- if !ok {
- c.Status(http.StatusBadRequest)
- return
- }
-
- response, err := a.auth.PasswordResetComplete(request.Body)
- if err != nil {
- if errors.Is(err, errs.ErrForbidden) {
- c.Status(http.StatusForbidden)
- } else {
- c.Status(http.StatusInternalServerError)
- }
- return
- }
-
- c.JSON(http.StatusOK, response)
-}
-
-
-// @Summary Receive new tokens via refresh token
-// @Tags Auth
-// @Accept json
-// @Produce json
-// @Param request body models.RefreshRequest true " "
-// @Router /auth/refresh [post]
-// @Success 200 {object} models.RefreshResponse " "
-// @Failure 401 "Invalid refresh token"
-func (a *authControllerImpl) Refresh(c *gin.Context) {
-
- request, ok := utils.GetRequest[models.RefreshRequest](c)
- if !ok {
- c.Status(http.StatusBadRequest)
- return
- }
-
- response, err := a.auth.Refresh(request.Body)
- if err != nil {
- if utils.ErrorIsOneOf(
- err,
- errs.ErrTokenExpired,
- errs.ErrTokenInvalid,
- errs.ErrInvalidToken,
- errs.ErrWrongTokenType,
- errs.ErrSessionNotFound,
- errs.ErrSessionTerminated,
- ) {
- c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
- } else {
- c.JSON(http.StatusInternalServerError, err.Error())
- }
- return
- }
-
- c.JSON(http.StatusOK, response)
-}
-
-// @Summary Register an account
-// @Tags Auth
-// @Accept json
-// @Produce json
-// @Param request body models.RegistrationBeginRequest true " "
-// @Success 200 "Account is created and awaiting verification"
-// @Failure 409 "Username or email is already taken"
-// @Failure 429 "Too many recent registration attempts for this email"
-// @Router /auth/registrationBegin [post]
-func (a *authControllerImpl) RegistrationBegin(c *gin.Context) {
-
- request, ok := utils.GetRequest[models.RegistrationBeginRequest](c)
- if !ok {
- c.Status(http.StatusBadRequest)
- return
- }
-
- _, err := a.auth.RegistrationBegin(request.Body)
-
- if err != nil {
- if errors.Is(err, errs.ErrUsernameTaken) || errors.Is(err, errs.ErrEmailTaken) {
- c.Status(http.StatusConflict)
- } else {
- c.Status(http.StatusInternalServerError)
- }
- return
- }
-
- c.Status(http.StatusOK)
- return
-}
-
-// @Summary Confirm with code, finish creating the account
-// @Tags Auth
-// @Accept json
-// @Produce json
-// @Param request body models.RegistrationCompleteRequest true " "
-// @Success 200 {object} models.RegistrationCompleteResponse " "
-// @Failure 403 "Invalid email or verification code"
-// @Router /auth/registrationComplete [post]
-func (a *authControllerImpl) RegistrationComplete(c *gin.Context) {
- request, ok := utils.GetRequest[models.RegistrationCompleteRequest](c)
- if !ok {
- c.Status(http.StatusBadRequest)
- return
- }
-
- response, err := a.auth.RegistrationComplete(request.User, request.Body)
-
- if err != nil {
- if errors.Is(err, errs.ErrForbidden) {
- c.Status(http.StatusForbidden)
- } else if errors.Is(err, errs.ErrUnauthorized) {
- c.Status(http.StatusUnauthorized)
- } else {
- c.Status(http.StatusInternalServerError)
- }
- return
- }
-
- c.JSON(http.StatusOK, response)
-}
-
-// @Summary Set new password using the old password
-// @Tags Auth
-// @Accept json
-// @Produce json
-// @Security JWT
-// @Param request body models.ChangePasswordRequest true " "
-// @Success 200 "Password successfully changed"
-// @Failure 403 "Invalid old password"
-// @Router /auth/changePassword [post]
-func (a *authControllerImpl) ChangePassword(c *gin.Context) {
- request, ok := utils.GetRequest[models.ChangePasswordRequest](c)
- if !ok {
- c.Status(http.StatusBadRequest)
- return
- }
-
- response, err := a.auth.ChangePassword(request.Body, request.User)
-
- if err != nil {
- if errors.Is(err, errs.ErrForbidden) {
- c.Status(http.StatusForbidden)
- } else {
- c.Status(http.StatusInternalServerError)
- }
- return
- }
-
- c.JSON(http.StatusOK, response)
-}
-
-func (a *authControllerImpl) RegisterRoutes(group *gin.RouterGroup) {
- group.POST("/registrationBegin", middleware.RequestMiddleware[models.RegistrationBeginRequest](enums.GuestRole), a.RegistrationBegin)
- group.POST("/registrationComplete", middleware.RequestMiddleware[models.RegistrationCompleteRequest](enums.GuestRole), a.RegistrationComplete)
- group.POST("/login", middleware.RequestMiddleware[models.LoginRequest](enums.GuestRole), a.Login)
- group.POST("/refresh", middleware.RequestMiddleware[models.RefreshRequest](enums.GuestRole), a.Refresh)
- group.POST("/passwordResetBegin", middleware.RequestMiddleware[models.PasswordResetBeginRequest](enums.GuestRole), a.PasswordResetBegin)
- group.POST("/passwordResetComplete", middleware.RequestMiddleware[models.PasswordResetCompleteRequest](enums.GuestRole), a.PasswordResetComplete)
- group.POST("/changePassword", middleware.RequestMiddleware[models.ChangePasswordRequest](enums.UserRole), a.ChangePassword)
+func NewAuthController(log *zap.Logger, auth services.AuthService) Controller {
+ return &controllerImpl{
+ Path: "/auth",
+ Middleware: []gin.HandlerFunc{},
+ Methods: []ControllerMethod{
+
+ // @Summary Register an account
+ // @Tags Auth
+ // @Accept json
+ // @Produce json
+ // @Param request body models.RegistrationBeginRequest true " "
+ // @Success 200 "Account is created and awaiting verification"
+ // @Failure 409 "Username or email is already taken"
+ // @Failure 429 "Too many recent registration attempts for this email"
+ // @Router /auth/registrationBegin [post]
+ {
+ HttpMethod: POST,
+ Path: "/registrationBegin",
+ Authorization: enums.GuestRole,
+ Middleware: []gin.HandlerFunc{},
+ Function: func(c *gin.Context) {
+
+ request, err := GetRequest[models.RegistrationBeginRequest](c); if err != nil {
+ return
+ }
+
+ _, err = auth.RegistrationBegin(request.Body); if err != nil {
+ if errors.Is(err, errs.ErrUsernameTaken) || errors.Is(err, errs.ErrEmailTaken) {
+ c.Status(http.StatusConflict)
+ } else {
+ c.Status(http.StatusInternalServerError)
+ }
+ return
+ }
+
+ c.Status(http.StatusOK)
+ return
+ },
+ },
+
+ // @Summary Confirm with code, finish creating the account
+ // @Tags Auth
+ // @Accept json
+ // @Produce json
+ // @Param request body models.RegistrationCompleteRequest true " "
+ // @Success 200 {object} models.RegistrationCompleteResponse " "
+ // @Failure 403 "Invalid email or verification code"
+ // @Router /auth/registrationComplete [post]
+ {
+ HttpMethod: POST,
+ Path: "/registrationComplete",
+ Authorization: enums.GuestRole,
+ Middleware: []gin.HandlerFunc{},
+ Function: func(c *gin.Context) {
+
+ request, err := GetRequest[models.RegistrationCompleteRequest](c); if err != nil {
+ return
+ }
+
+ response, err := auth.RegistrationComplete(request.User, request.Body)
+
+ if err != nil {
+ if errors.Is(err, errs.ErrForbidden) {
+ c.Status(http.StatusForbidden)
+ } else if errors.Is(err, errs.ErrUnauthorized) {
+ c.Status(http.StatusUnauthorized)
+ } else {
+ c.Status(http.StatusInternalServerError)
+ }
+ return
+ }
+
+ c.JSON(http.StatusOK, response)
+ },
+ },
+
+ // @Summary Acquire tokens via login credentials (and 2FA code if needed)
+ // @Tags Auth
+ // @Accept json
+ // @Produce json
+ // @Param request body models.LoginRequest true " "
+ // @Success 200 {object} models.LoginResponse " "
+ // @Failure 403 "Invalid login credentials"
+ // @Router /auth/login [post]
+ {
+ HttpMethod: POST,
+ Path: "/login",
+ Authorization: enums.GuestRole,
+ Middleware: []gin.HandlerFunc{},
+ Function: func(c *gin.Context) {
+ request, err := GetRequest[models.LoginRequest](c); if err != nil {
+ return
+ }
+
+ response, err := auth.Login(request.User, request.Body)
+
+ if err != nil {
+ if errors.Is(err, errs.ErrForbidden) {
+ c.Status(http.StatusForbidden)
+ } else {
+ c.Status(http.StatusInternalServerError)
+ }
+ return
+ }
+
+ c.JSON(http.StatusOK, response)
+ },
+ },
+
+ // @Summary Receive new tokens via refresh token
+ // @Tags Auth
+ // @Accept json
+ // @Produce json
+ // @Param request body models.RefreshRequest true " "
+ // @Router /auth/refresh [post]
+ // @Success 200 {object} models.RefreshResponse " "
+ // @Failure 401 "Invalid refresh token"
+ {
+ HttpMethod: POST,
+ Path: "/refresh",
+ Authorization: enums.GuestRole,
+ Middleware: []gin.HandlerFunc{},
+ Function: func(c *gin.Context) {
+
+ request, err := GetRequest[models.RefreshRequest](c); if err != nil {
+ return
+ }
+
+ response, err := auth.Refresh(request.Body)
+ if err != nil {
+ if utils.ErrorIsOneOf(
+ err,
+ errs.ErrTokenExpired,
+ errs.ErrTokenInvalid,
+ errs.ErrInvalidToken,
+ errs.ErrWrongTokenType,
+ errs.ErrSessionNotFound,
+ errs.ErrSessionTerminated,
+ ) {
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
+ } else {
+ c.JSON(http.StatusInternalServerError, err.Error())
+ }
+ return
+ }
+
+ c.JSON(http.StatusOK, response)
+ },
+ },
+
+ // @Summary Request password reset email
+ // @Tags Auth
+ // @Accept json
+ // @Produce json
+ // @Param request body models.PasswordResetBeginRequest true " "
+ // @Router /auth/passwordResetBegin [post]
+ // @Success 200 "Reset code sent to the email if it is attached to an account"
+ // @Failure 429 "Too many recent requests for this email"
+ {
+ HttpMethod: POST,
+ Path: "/passwordResetBegin",
+ Authorization: enums.GuestRole,
+ Middleware: []gin.HandlerFunc{},
+ Function: func(c *gin.Context) {
+
+ request, err := GetRequest[models.PasswordResetBeginRequest](c); if err != nil {
+ return
+ }
+
+ response, err := auth.PasswordResetBegin(request.Body)
+ if err != nil {
+ if errors.Is(err, errs.ErrTooManyRequests) {
+ c.Status(http.StatusTooManyRequests)
+ } else {
+ c.Status(http.StatusInternalServerError)
+ }
+ return
+ }
+
+ c.JSON(http.StatusOK, response)
+ },
+ },
+
+
+ // @Summary Complete password reset via email code
+ // @Tags Auth
+ // @Accept json
+ // @Produce json
+ // @Param request body models.PasswordResetCompleteRequest true " "
+ // @Router /auth/passwordResetComplete [post]
+ // @Success 200 {object} models.PasswordResetCompleteResponse " "
+ // @Success 403 "Wrong verification code or username"
+ {
+ HttpMethod: POST,
+ Path: "/passwordResetComplete",
+ Authorization: enums.GuestRole,
+ Middleware: []gin.HandlerFunc{},
+ Function: func(c *gin.Context) {
+
+ request, err := GetRequest[models.PasswordResetCompleteRequest](c); if err != nil {
+ return
+ }
+
+ response, err := auth.PasswordResetComplete(request.Body)
+ if err != nil {
+ if errors.Is(err, errs.ErrForbidden) {
+ c.Status(http.StatusForbidden)
+ } else {
+ c.Status(http.StatusInternalServerError)
+ }
+ return
+ }
+
+ c.JSON(http.StatusOK, response)
+ },
+ },
+
+ // @Summary Set new password using the old password
+ // @Tags Auth
+ // @Accept json
+ // @Produce json
+ // @Security JWT
+ // @Param request body models.ChangePasswordRequest true " "
+ // @Success 200 "Password successfully changed"
+ // @Failure 403 "Invalid old password"
+ // @Router /auth/changePassword [post]
+ {
+ HttpMethod: POST,
+ Path: "/changePassword",
+ Authorization: enums.UserRole,
+ Middleware: []gin.HandlerFunc{},
+ Function: func(c *gin.Context) {
+
+ request, err := GetRequest[models.ChangePasswordRequest](c); if err != nil {
+ return
+ }
+
+ response, err := auth.ChangePassword(request.Body, request.User)
+
+ if err != nil {
+ if errors.Is(err, errs.ErrForbidden) {
+ c.Status(http.StatusForbidden)
+ } else {
+ c.Status(http.StatusInternalServerError)
+ }
+ return
+ }
+
+ c.JSON(http.StatusOK, response)
+ },
+ },
+
+ },
+ }
}
diff --git a/backend/internal/controllers/controller.go b/backend/internal/controllers/controller.go
index 15e9195..63de177 100644
--- a/backend/internal/controllers/controller.go
+++ b/backend/internal/controllers/controller.go
@@ -19,7 +19,6 @@ package controllers
import (
"easywish/internal/dto"
- "easywish/internal/middleware"
"easywish/internal/services"
"easywish/internal/utils/enums"
"easywish/internal/validation"
@@ -48,39 +47,16 @@ type ControllerMethod struct {
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 {
+type Controller interface {
+ Setup(group *gin.RouterGroup, log *zap.Logger, auth services.AuthService)
+}
+
+func (ctrl *controllerImpl) Setup(group *gin.RouterGroup, log *zap.Logger, auth services.AuthService) {
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 {
@@ -100,11 +76,6 @@ func (ctrl *controllerImpl) Setup(group *gin.RouterGroup, log *zap.Logger, auth
}),
method.Function)...,
)}
- return ctrlGroup
-}
-
-type Controller interface {
- Setup(group *gin.RouterGroup, log *zap.Logger, auth services.AuthService)
}
func GetRequest[ModelT any](c *gin.Context) (*dto.Request[ModelT], error) {
diff --git a/backend/internal/controllers/profile.go b/backend/internal/controllers/profile.go
index 5a4fbd8..9d3d1cc 100644
--- a/backend/internal/controllers/profile.go
+++ b/backend/internal/controllers/profile.go
@@ -39,53 +39,53 @@ func NewProfileController() ProfileController {
return &profileControllerImpl{}
}
-// @Summary Get someone's profile details
-// @Tags Profile
-// @Accept json
-// @Produce json
-// @Param username path string true "Username"
-// @Security JWT
-// @Router /profile/{username} [get]
+// @Summary Get someone's profile details
+// @Tags Profile
+// @Accept json
+// @Produce json
+// @Param username path string true "Username"
+// @Security JWT
+// @Router /profile/{username} [get]
func (p *profileControllerImpl) GetProfile(c *gin.Context) {
c.Status(http.StatusNotImplemented)
}
-// @Summary Get own profile when authorized
-// @Tags Profile
-// @Accept json
-// @Produce json
-// @Security JWT
-// @Router /profile/me [get]
+// @Summary Get own profile when authorized
+// @Tags Profile
+// @Accept json
+// @Produce json
+// @Security JWT
+// @Router /profile/me [get]
func (p *profileControllerImpl) GetOwnProfile(c *gin.Context) {
c.Status(http.StatusNotImplemented)
}
-// @Summary Update profile
-// @Tags Profile
-// @Accept json
-// @Produce json
-// @Security JWT
-// @Router /profile [patch]
+// @Summary Update profile
+// @Tags Profile
+// @Accept json
+// @Produce json
+// @Security JWT
+// @Router /profile [patch]
func (p *profileControllerImpl) UpdateProfile(c *gin.Context) {
c.Status(http.StatusNotImplemented)
}
-// @Summary Get profile privacy settings
-// @Tags Profile
-// @Accept json
-// @Produce json
-// @Security JWT
-// @Router /profile/privacy [get]
+// @Summary Get profile privacy settings
+// @Tags Profile
+// @Accept json
+// @Produce json
+// @Security JWT
+// @Router /profile/privacy [get]
func (p *profileControllerImpl) GetPrivacySettings(c *gin.Context) {
c.Status(http.StatusNotImplemented)
}
-// @Summary Update profile privacy settings
-// @Tags Profile
-// @Accept json
-// @Produce json
-// @Security JWT
-// @Router /profile/privacy [patch]
+// @Summary Update profile privacy settings
+// @Tags Profile
+// @Accept json
+// @Produce json
+// @Security JWT
+// @Router /profile/privacy [patch]
func (p *profileControllerImpl) UpdatePrivacySettings(c *gin.Context) {
c.Status(http.StatusNotImplemented)
}
diff --git a/backend/internal/controllers/service.go b/backend/internal/controllers/service.go
index d0012a4..c19be38 100644
--- a/backend/internal/controllers/service.go
+++ b/backend/internal/controllers/service.go
@@ -1,56 +1,56 @@
// 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/models"
+ "easywish/internal/utils/enums"
"net/http"
"github.com/gin-gonic/gin"
)
-type ServiceController interface {
- HealthCheck(c *gin.Context)
- Router
-}
+func NewServiceController() Controller {
+ return &controllerImpl{
+ Path: "/service",
+ Middleware: []gin.HandlerFunc{},
+ Methods: []ControllerMethod{
-type serviceControllerImpl struct{}
+ // Health godoc
+ // @Summary Get health status
+ // @Description Used internally for checking service health
+ // @Tags Service
+ // @Accept json
+ // @Produce json
+ // @Success 200 {object} HealthStatus "Says whether it's healthy or not"
+ // @Router /service/health [get]
+ {
+ HttpMethod: GET,
+ Path: "/health",
+ Authorization: enums.GuestRole,
+ Middleware: []gin.HandlerFunc{},
+ Function: func(c *gin.Context) {
-func NewServiceController() ServiceController {
- return &serviceControllerImpl{}
-}
+ c.JSON(http.StatusOK, models.HealthStatusResponse{Healthy: true,})
-// HealthCheck implements ServiceController.
-// @Summary Get health status
-// @Description Used internally for checking service health
-// @Tags Service
-// @Accept json
-// @Produce json
-// @Success 200 {object} HealthStatus "Says whether it's healthy or not"
-// @Router /service/health [get]
-func (s *serviceControllerImpl) HealthCheck(c *gin.Context) {
- 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"`
+ },
+ }
}
diff --git a/backend/internal/controllers/setup.go b/backend/internal/controllers/setup.go
index 5f4ba0a..9816a9f 100644
--- a/backend/internal/controllers/setup.go
+++ b/backend/internal/controllers/setup.go
@@ -1,30 +1,72 @@
// 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"
+ "easywish/internal/middleware"
+ "easywish/internal/services"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
"go.uber.org/fx"
+ "go.uber.org/zap"
)
+type SetupControllersParams struct {
+ fx.In
+ Controllers []Controller `group:"controllers"`
+ Log *zap.Logger
+ Auth services.AuthService
+ Group *gin.Engine
+}
+
+func setupControllers(p SetupControllersParams) {
+
+ apiGroup := p.Group.Group("/api")
+ apiGroup.Use(middleware.AuthMiddleware(p.Log, p.Auth))
+ apiGroup.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)
+
+ c.Set("client_info", dto.ClientInfo{
+ SessionInfo: sessionInfo,
+ IP: ip,
+ UserAgent: userAgent,
+ })
+
+ c.Next()
+ }))
+ for _, ctrl := range p.Controllers {
+ ctrl.Setup(apiGroup, p.Log, p.Auth)
+ }
+}
+
var Module = fx.Module("controllers",
fx.Provide(
- NewServiceController,
- NewAuthController,
- NewProfileController,
- ),
+ fx.Annotate(NewAuthController, fx.ResultTags(`group:"controllers"`)),
+ fx.Annotate(NewServiceController, fx.ResultTags(`group:"controllers"`)),
+ ),
+ fx.Invoke(setupControllers),
)
diff --git a/backend/internal/middleware/request.go b/backend/internal/middleware/request.go
index a729644..7ebfa2a 100644
--- a/backend/internal/middleware/request.go
+++ b/backend/internal/middleware/request.go
@@ -30,6 +30,7 @@ import (
const requestKey = "request"
+// Deprecated: no longer used, embedded into controllers.GetRequest instead
func ClientInfoFromContext(c *gin.Context) (*dto.ClientInfo, bool) {
var ok bool
@@ -58,10 +59,12 @@ func ClientInfoFromContext(c *gin.Context) (*dto.ClientInfo, bool) {
}, true
}
+// Deprecated: no longer used, see controllers.GetRequest
func RequestFromContext[T any](c *gin.Context) dto.Request[T] {
return c.Value(requestKey).(dto.Request[T])
}
+// Deprecated: no longer used, see controllers.GetRequest
func RequestMiddleware[T any](role enums.Role) gin.HandlerFunc {
return gin.HandlerFunc(func(c *gin.Context) {
diff --git a/backend/internal/models/service.go b/backend/internal/models/service.go
new file mode 100644
index 0000000..373b050
--- /dev/null
+++ b/backend/internal/models/service.go
@@ -0,0 +1,22 @@
+// 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 models
+
+type HealthStatusResponse struct {
+ Healthy bool `json:"healthy"`
+}
diff --git a/backend/internal/routes/router.go b/backend/internal/routes/router.go
index 7a263f7..e211a14 100644
--- a/backend/internal/routes/router.go
+++ b/backend/internal/routes/router.go
@@ -26,6 +26,7 @@ import (
"go.uber.org/zap"
)
+// Deprecated: no longer used, see controllers
func NewRouter(engine *gin.Engine, log *zap.Logger, auth services.AuthService, groups []RouteGroup) *gin.Engine {
apiGroup := engine.Group("/api")
apiGroup.Use(middleware.AuthMiddleware(log, auth))
@@ -37,29 +38,30 @@ func NewRouter(engine *gin.Engine, log *zap.Logger, auth services.AuthService, g
return engine
}
+// Deprecated: no longer used, see controllers
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,
- },
- }
-}
+// 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,
+// },
+// }
+// }
diff --git a/backend/internal/routes/setup.go b/backend/internal/routes/setup.go
index 5a1e8f7..03a8c37 100644
--- a/backend/internal/routes/setup.go
+++ b/backend/internal/routes/setup.go
@@ -21,9 +21,10 @@ import (
"go.uber.org/fx"
)
+// Deprecated: no longer used, see controllers
var Module = fx.Module("routes",
- fx.Provide(
- NewRouteGroups,
- ),
- fx.Invoke(NewRouter),
+ // fx.Provide(
+ // NewRouteGroups,
+ // ),
+ // fx.Invoke(NewRouter),
)
diff --git a/backend/internal/utils/request.go b/backend/internal/utils/request.go
index 81e3195..dcec554 100644
--- a/backend/internal/utils/request.go
+++ b/backend/internal/utils/request.go
@@ -22,6 +22,7 @@ import (
"github.com/gin-gonic/gin"
)
+// Deprecated: use controllers.GetRequest method for the new controllers
func GetRequest[T any](c *gin.Context) (*dto.Request[T], bool) {
req, ok := c.Get("request")