feat: service/controller prototype
This commit is contained in:
@@ -50,7 +50,25 @@ const docTemplate = `{
|
|||||||
"Auth"
|
"Auth"
|
||||||
],
|
],
|
||||||
"summary": "Acquire tokens via login credentials (and 2FA code if needed)",
|
"summary": "Acquire tokens via login credentials (and 2FA code if needed)",
|
||||||
"responses": {}
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "desc",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/models.LoginRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "desc",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/models.LoginResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/auth/passwordResetBegin": {
|
"/auth/passwordResetBegin": {
|
||||||
@@ -230,7 +248,7 @@ const docTemplate = `{
|
|||||||
"summary": "Get health status",
|
"summary": "Get health status",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "desc",
|
"description": "Says whether it's healthy or not",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/controllers.HealthStatus"
|
"$ref": "#/definitions/controllers.HealthStatus"
|
||||||
}
|
}
|
||||||
@@ -247,6 +265,31 @@ const docTemplate = `{
|
|||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"models.LoginRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"password": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"totp": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"models.LoginResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"access_token": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"refresh_token": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securityDefinitions": {
|
"securityDefinitions": {
|
||||||
|
|||||||
@@ -46,7 +46,25 @@
|
|||||||
"Auth"
|
"Auth"
|
||||||
],
|
],
|
||||||
"summary": "Acquire tokens via login credentials (and 2FA code if needed)",
|
"summary": "Acquire tokens via login credentials (and 2FA code if needed)",
|
||||||
"responses": {}
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "desc",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/models.LoginRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "desc",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/models.LoginResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/auth/passwordResetBegin": {
|
"/auth/passwordResetBegin": {
|
||||||
@@ -226,7 +244,7 @@
|
|||||||
"summary": "Get health status",
|
"summary": "Get health status",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "desc",
|
"description": "Says whether it's healthy or not",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/controllers.HealthStatus"
|
"$ref": "#/definitions/controllers.HealthStatus"
|
||||||
}
|
}
|
||||||
@@ -243,6 +261,31 @@
|
|||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"models.LoginRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"password": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"totp": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"models.LoginResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"access_token": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"refresh_token": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securityDefinitions": {
|
"securityDefinitions": {
|
||||||
|
|||||||
@@ -5,6 +5,22 @@ definitions:
|
|||||||
healthy:
|
healthy:
|
||||||
type: boolean
|
type: boolean
|
||||||
type: object
|
type: object
|
||||||
|
models.LoginRequest:
|
||||||
|
properties:
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
totp:
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
models.LoginResponse:
|
||||||
|
properties:
|
||||||
|
access_token:
|
||||||
|
type: string
|
||||||
|
refresh_token:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
info:
|
info:
|
||||||
contact: {}
|
contact: {}
|
||||||
description: Easy and feature-rich wishlist.
|
description: Easy and feature-rich wishlist.
|
||||||
@@ -29,9 +45,20 @@ paths:
|
|||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- description: desc
|
||||||
|
in: body
|
||||||
|
name: request
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/models.LoginRequest'
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses: {}
|
responses:
|
||||||
|
"200":
|
||||||
|
description: desc
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/models.LoginResponse'
|
||||||
summary: Acquire tokens via login credentials (and 2FA code if needed)
|
summary: Acquire tokens via login credentials (and 2FA code if needed)
|
||||||
tags:
|
tags:
|
||||||
- Auth
|
- Auth
|
||||||
@@ -148,7 +175,7 @@ paths:
|
|||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: desc
|
description: Says whether it's healthy or not
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/controllers.HealthStatus'
|
$ref: '#/definitions/controllers.HealthStatus'
|
||||||
summary: Get health status
|
summary: Get health status
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"easywish/internal/models"
|
||||||
|
"easywish/internal/services"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -28,9 +30,28 @@ func RegistrationComplete(c *gin.Context) {
|
|||||||
// @Tags Auth
|
// @Tags Auth
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
|
// @Param request body models.LoginRequest true "desc"
|
||||||
|
// @Success 200 {object} models.LoginResponse "desc"
|
||||||
// @Router /auth/login [post]
|
// @Router /auth/login [post]
|
||||||
func Login(c *gin.Context) {
|
func Login(c *gin.Context) {
|
||||||
c.Status(http.StatusNotImplemented)
|
|
||||||
|
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
|
// @Summary Receive new tokens via refresh token
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type HealthStatus struct {
|
|||||||
// @Tags Service
|
// @Tags Service
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} HealthStatus "desc"
|
// @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 HealthCheck(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, gin.H{"healthy": true})
|
c.JSON(http.StatusOK, gin.H{"healthy": true})
|
||||||
|
|||||||
@@ -546,12 +546,12 @@ WHERE
|
|||||||
users.verified IS TRUE AND -- Verified
|
users.verified IS TRUE AND -- Verified
|
||||||
users.deleted IS FALSE AND -- Not deleted
|
users.deleted IS FALSE AND -- Not deleted
|
||||||
banned.user_id IS NULL AND -- Not banned
|
banned.user_id IS NULL AND -- Not banned
|
||||||
linfo.password_hash = crypt($2, linfo.password_hash)
|
linfo.password_hash = crypt($2::text, linfo.password_hash)
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetUserByLoginCredentialsParams struct {
|
type GetUserByLoginCredentialsParams struct {
|
||||||
Username string
|
Username string
|
||||||
Crypt string
|
Password string
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetUserByLoginCredentialsRow struct {
|
type GetUserByLoginCredentialsRow struct {
|
||||||
@@ -562,7 +562,7 @@ type GetUserByLoginCredentialsRow struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetUserByLoginCredentials(ctx context.Context, arg GetUserByLoginCredentialsParams) (GetUserByLoginCredentialsRow, error) {
|
func (q *Queries) GetUserByLoginCredentials(ctx context.Context, arg GetUserByLoginCredentialsParams) (GetUserByLoginCredentialsRow, error) {
|
||||||
row := q.db.QueryRow(ctx, getUserByLoginCredentials, arg.Username, arg.Crypt)
|
row := q.db.QueryRow(ctx, getUserByLoginCredentials, arg.Username, arg.Password)
|
||||||
var i GetUserByLoginCredentialsRow
|
var i GetUserByLoginCredentialsRow
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
|
|||||||
9
backend/internal/errors/auth.go
Normal file
9
backend/internal/errors/auth.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrUnauthorized = errors.New("User is not authorized")
|
||||||
|
)
|
||||||
11
backend/internal/errors/general.go
Normal file
11
backend/internal/errors/general.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotImplemented = errors.New("Feature is not implemented")
|
||||||
|
ErrBadRequest = errors.New("Bad request")
|
||||||
|
)
|
||||||
|
|
||||||
24
backend/internal/models/auth.go
Normal file
24
backend/internal/models/auth.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type Tokens struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoginRequest struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
TOTP *string `json:"totp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoginResponse struct {
|
||||||
|
Tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
type RefreshRequest struct {
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RefreshResponse struct {
|
||||||
|
Tokens
|
||||||
|
}
|
||||||
50
backend/internal/services/auth.go
Normal file
50
backend/internal/services/auth.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"easywish/internal/database"
|
||||||
|
"easywish/internal/errors"
|
||||||
|
errs "easywish/internal/errors"
|
||||||
|
"easywish/internal/models"
|
||||||
|
"easywish/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuthService interface {
|
||||||
|
Login(request models.LoginRequest) (*models.LoginResponse, error)
|
||||||
|
Refresh(request models.RefreshRequest) (*models.RefreshResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type authServiceImpl struct {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuthService() AuthService {
|
||||||
|
return &authServiceImpl{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *authServiceImpl) Login(request models.LoginRequest) (*models.LoginResponse, error) {
|
||||||
|
conn, ctx, err := utils.GetDbConn()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer conn.Close(ctx)
|
||||||
|
|
||||||
|
queries := database.New(conn)
|
||||||
|
|
||||||
|
user, err := queries.GetUserByLoginCredentials(ctx, database.GetUserByLoginCredentialsParams{
|
||||||
|
Username: request.Username,
|
||||||
|
Password: request.Password,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.ErrUnauthorized
|
||||||
|
}
|
||||||
|
|
||||||
|
accessToken, refreshToken, err := utils.GenerateTokens(user.Username)
|
||||||
|
|
||||||
|
return &models.LoginResponse{Tokens: models.Tokens{AccessToken: accessToken, RefreshToken: refreshToken}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (a *authServiceImpl) Refresh(request models.RefreshRequest) (*models.RefreshResponse, error) {
|
||||||
|
return nil, errors.ErrNotImplemented
|
||||||
|
}
|
||||||
18
backend/internal/utils/db.go
Normal file
18
backend/internal/utils/db.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"easywish/config"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetDbConn() (*pgx.Conn, context.Context, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
conn, err := pgx.Connect(ctx, config.GetConfig().DatabaseUrl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, ctx, nil
|
||||||
|
}
|
||||||
@@ -46,7 +46,7 @@ WHERE
|
|||||||
users.verified IS TRUE AND -- Verified
|
users.verified IS TRUE AND -- Verified
|
||||||
users.deleted IS FALSE AND -- Not deleted
|
users.deleted IS FALSE AND -- Not deleted
|
||||||
banned.user_id IS NULL AND -- Not banned
|
banned.user_id IS NULL AND -- Not banned
|
||||||
linfo.password_hash = crypt($2, linfo.password_hash); -- Password hash matches
|
linfo.password_hash = crypt(@password::text, linfo.password_hash); -- Password hash matches
|
||||||
|
|
||||||
--: }}}
|
--: }}}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user