Compare commits
7 Commits
e1df58b434
...
be9aee7145
| Author | SHA1 | Date | |
|---|---|---|---|
| be9aee7145 | |||
| cfe60cfb8e | |||
| e5d245519a | |||
| 0a00a5ee2b | |||
| 1b55498b00 | |||
| ea3743cb04 | |||
| 613deae8e2 |
@@ -1,7 +1,24 @@
|
|||||||
|
// Copyright (c) 2025 Nikolai Papin
|
||||||
|
//
|
||||||
|
// This file is part of Easywish
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||||
|
// the GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// @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
|
||||||
@@ -20,6 +37,7 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"go.uber.org/fx"
|
"go.uber.org/fx"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"easywish/config"
|
"easywish/config"
|
||||||
docs "easywish/docs"
|
docs "easywish/docs"
|
||||||
@@ -42,6 +60,7 @@ func main() {
|
|||||||
fx.New(
|
fx.New(
|
||||||
fx.Provide(
|
fx.Provide(
|
||||||
logger.NewLogger,
|
logger.NewLogger,
|
||||||
|
logger.NewSyncLogger,
|
||||||
gin.Default,
|
gin.Default,
|
||||||
),
|
),
|
||||||
database.Module,
|
database.Module,
|
||||||
@@ -49,7 +68,7 @@ func main() {
|
|||||||
controllers.Module,
|
controllers.Module,
|
||||||
routes.Module,
|
routes.Module,
|
||||||
|
|
||||||
fx.Invoke(func(lc fx.Lifecycle, router *gin.Engine) {
|
fx.Invoke(func(lc fx.Lifecycle, router *gin.Engine, syncLogger *logger.SyncLogger) {
|
||||||
|
|
||||||
// Swagger
|
// Swagger
|
||||||
docs.SwaggerInfo.Schemes = []string{"http"}
|
docs.SwaggerInfo.Schemes = []string{"http"}
|
||||||
@@ -65,6 +84,7 @@ func main() {
|
|||||||
OnStart: func(ctx context.Context) error {
|
OnStart: func(ctx context.Context) error {
|
||||||
go func() {
|
go func() {
|
||||||
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
|
syncLogger.Fatal("Server failed", zap.Error(err))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return nil
|
return nil
|
||||||
@@ -72,11 +92,18 @@ func main() {
|
|||||||
OnStop: func(ctx context.Context) error {
|
OnStop: func(ctx context.Context) error {
|
||||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
return server.Shutdown(shutdownCtx)
|
|
||||||
|
if err := server.Shutdown(shutdownCtx); err != nil {
|
||||||
|
syncLogger.Error("Server shutdown error", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := syncLogger.Close(); err != nil {
|
||||||
|
syncLogger.Error("Logger sync error", zap.Error(err))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
|
||||||
).Run()
|
).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -10,15 +27,24 @@ import (
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
Hostname string `mapstructure:"HOSTNAME"`
|
Hostname string `mapstructure:"HOSTNAME"`
|
||||||
Port string `mapstructure:"PORT"`
|
Port string `mapstructure:"PORT"`
|
||||||
|
|
||||||
DatabaseUrl string `mapstructure:"POSTGRES_URL"`
|
DatabaseUrl string `mapstructure:"POSTGRES_URL"`
|
||||||
RedisUrl string `mapstructure:"REDIS_URL"`
|
RedisUrl string `mapstructure:"REDIS_URL"`
|
||||||
MinioUrl string `mapstructure:"MINIO_URL"`
|
MinioUrl string `mapstructure:"MINIO_URL"`
|
||||||
|
|
||||||
JwtAlgorithm string `mapstructure:"JWT_ALGORITHM"`
|
JwtAlgorithm string `mapstructure:"JWT_ALGORITHM"`
|
||||||
JwtSecret string `mapstructure:"JWT_SECRET"`
|
JwtSecret string `mapstructure:"JWT_SECRET"`
|
||||||
JwtIssuer string `mapstructure:"JWT_ISSUER"`
|
JwtIssuer string `mapstructure:"JWT_ISSUER"`
|
||||||
JwtAudience string `mapstructure:"JWT_AUDIENCE"`
|
JwtAudience string `mapstructure:"JWT_AUDIENCE"`
|
||||||
JwtExpAccess int `mapstructure:"JWT_EXP_ACCESS"`
|
JwtExpAccess int `mapstructure:"JWT_EXP_ACCESS"`
|
||||||
JwtExpRefresh int `mapstructure:"JWT_EXP_REFRESH"`
|
JwtExpRefresh int `mapstructure:"JWT_EXP_REFRESH"`
|
||||||
|
|
||||||
|
PasswordCheckLength bool `mapstructure:"PASSWORD_CHECK_LENGTH"`
|
||||||
|
PasswordCheckNumbers bool `mapstructure:"PASSWORD_CHECK_NUMBERS"`
|
||||||
|
PasswordCheckCases bool `mapstructure:"PASSWORD_CHECK_CASES"`
|
||||||
|
PasswordCheckSymbols bool `mapstructure:"PASSWORD_CHECK_SYMBOLS"`
|
||||||
|
PasswordCheckLeaked bool `mapstructure:"PASSWORD_CHECK_LEAKED"`
|
||||||
|
|
||||||
Environment string `mapstructure:"ENVIRONMENT"`
|
Environment string `mapstructure:"ENVIRONMENT"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,12 +52,20 @@ func Load() (*Config, error) {
|
|||||||
|
|
||||||
viper.SetDefault("HOSTNAME", "localhost")
|
viper.SetDefault("HOSTNAME", "localhost")
|
||||||
viper.SetDefault("PORT", "8080")
|
viper.SetDefault("PORT", "8080")
|
||||||
|
|
||||||
viper.SetDefault("JWT_ALGORITHM", "HS256")
|
viper.SetDefault("JWT_ALGORITHM", "HS256")
|
||||||
viper.SetDefault("JWT_SECRET", "default_jwt_secret_please_change")
|
viper.SetDefault("JWT_SECRET", "default_jwt_secret_please_change")
|
||||||
viper.SetDefault("JWT_EXP_ACCESS", 5)
|
viper.SetDefault("JWT_EXP_ACCESS", 5)
|
||||||
viper.SetDefault("JWT_EXP_REFRESH", 10080)
|
viper.SetDefault("JWT_EXP_REFRESH", 10080)
|
||||||
viper.SetDefault("JWT_AUDIENCE", "easywish")
|
viper.SetDefault("JWT_AUDIENCE", "easywish")
|
||||||
viper.SetDefault("JWT_ISSUER", "easywish")
|
viper.SetDefault("JWT_ISSUER", "easywish")
|
||||||
|
|
||||||
|
viper.SetDefault("PASSWORD_CHECK_LENGTH", true)
|
||||||
|
viper.SetDefault("PASSWORD_CHECK_NUMBERS", false)
|
||||||
|
viper.SetDefault("PASSWORD_CHECK_CASES", false)
|
||||||
|
viper.SetDefault("PASSWORD_CHECK_SYMBOLS", false)
|
||||||
|
viper.SetDefault("PASSWORD_CHECK_LEAKED", false)
|
||||||
|
|
||||||
viper.SetDefault("ENVIRONMENT", "production")
|
viper.SetDefault("ENVIRONMENT", "production")
|
||||||
|
|
||||||
viper.AutomaticEnv()
|
viper.AutomaticEnv()
|
||||||
@@ -39,18 +73,28 @@ func Load() (*Config, error) {
|
|||||||
// Viper's AutomaticEnv() expects lowercase keys for unmarshalling into structs by default,
|
// Viper's AutomaticEnv() expects lowercase keys for unmarshalling into structs by default,
|
||||||
// while the environment variables and struct tags are in uppercase.
|
// while the environment variables and struct tags are in uppercase.
|
||||||
// Here's the stupidity we have to do to fix it:
|
// Here's the stupidity we have to do to fix it:
|
||||||
|
|
||||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||||
viper.BindEnv("HOSTNAME")
|
viper.BindEnv("HOSTNAME")
|
||||||
viper.BindEnv("PORT")
|
viper.BindEnv("PORT")
|
||||||
|
|
||||||
viper.BindEnv("POSTGRES_URL")
|
viper.BindEnv("POSTGRES_URL")
|
||||||
viper.BindEnv("REDIS_URL")
|
viper.BindEnv("REDIS_URL")
|
||||||
viper.BindEnv("MINIO_URL")
|
viper.BindEnv("MINIO_URL")
|
||||||
|
|
||||||
viper.BindEnv("JWT_ALGORITHM")
|
viper.BindEnv("JWT_ALGORITHM")
|
||||||
viper.BindEnv("JWT_SECRET")
|
viper.BindEnv("JWT_SECRET")
|
||||||
viper.BindEnv("JWT_ISSUER")
|
viper.BindEnv("JWT_ISSUER")
|
||||||
viper.BindEnv("JWT_AUDIENCE")
|
viper.BindEnv("JWT_AUDIENCE")
|
||||||
viper.BindEnv("JWT_EXP_ACCESS")
|
viper.BindEnv("JWT_EXP_ACCESS")
|
||||||
viper.BindEnv("JWT_EXP_REFRESH")
|
viper.BindEnv("JWT_EXP_REFRESH")
|
||||||
|
|
||||||
|
viper.BindEnv("PASSWORD_CHECK_LENGTH")
|
||||||
|
viper.BindEnv("PASSWORD_CHECK_NUMBERS")
|
||||||
|
viper.BindEnv("PASSWORD_CHECK_CASES")
|
||||||
|
viper.BindEnv("PASSWORD_CHECK_SYMBOLS")
|
||||||
|
viper.BindEnv("PASSWORD_CHECK_LEAKED")
|
||||||
|
|
||||||
viper.BindEnv("ENVIRONMENT")
|
viper.BindEnv("ENVIRONMENT")
|
||||||
|
|
||||||
required := []string{
|
required := []string{
|
||||||
|
|||||||
@@ -299,6 +299,10 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"models.LoginRequest": {
|
"models.LoginRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"password",
|
||||||
|
"username"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"password": {
|
"password": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -324,6 +328,10 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"models.RegistrationBeginRequest": {
|
"models.RegistrationBeginRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"password",
|
||||||
|
"username"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -333,7 +341,9 @@ const docTemplate = `{
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"username": {
|
"username": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"maxLength": 20,
|
||||||
|
"minLength": 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -295,6 +295,10 @@
|
|||||||
},
|
},
|
||||||
"models.LoginRequest": {
|
"models.LoginRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"password",
|
||||||
|
"username"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"password": {
|
"password": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -320,6 +324,10 @@
|
|||||||
},
|
},
|
||||||
"models.RegistrationBeginRequest": {
|
"models.RegistrationBeginRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"password",
|
||||||
|
"username"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -329,7 +337,9 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"username": {
|
"username": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"maxLength": 20,
|
||||||
|
"minLength": 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
username:
|
username:
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- password
|
||||||
|
- username
|
||||||
type: object
|
type: object
|
||||||
models.LoginResponse:
|
models.LoginResponse:
|
||||||
properties:
|
properties:
|
||||||
@@ -29,7 +32,12 @@ definitions:
|
|||||||
description: 'TODO: password checking'
|
description: 'TODO: password checking'
|
||||||
type: string
|
type: string
|
||||||
username:
|
username:
|
||||||
|
maxLength: 20
|
||||||
|
minLength: 3
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- password
|
||||||
|
- username
|
||||||
type: object
|
type: object
|
||||||
info:
|
info:
|
||||||
contact: {}
|
contact: {}
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -15,4 +32,3 @@ import (
|
|||||||
func ChangePassword(c *gin.Context) {
|
func ChangePassword(c *gin.Context) {
|
||||||
c.Status(http.StatusNotImplemented)
|
c.Status(http.StatusNotImplemented)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
100
backend/internal/database/helper.go
Normal file
100
backend/internal/database/helper.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DbHelperTransaction interface {
|
||||||
|
Commit() error
|
||||||
|
Rollback() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type DbHelper struct {
|
||||||
|
CTX context.Context
|
||||||
|
Queries Queries
|
||||||
|
}
|
||||||
|
|
||||||
|
type dbHelperTransactionImpl struct {
|
||||||
|
CTX context.Context
|
||||||
|
TXlessQueries Queries
|
||||||
|
TX pgx.Tx
|
||||||
|
TXQueries Queries
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDbHelper(dbContext DbContext) DbHelper {
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
queries := New(dbContext)
|
||||||
|
|
||||||
|
return DbHelper{
|
||||||
|
CTX: ctx,
|
||||||
|
Queries: *queries,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDbHelperTransaction(dbContext DbContext) (DbHelperTransaction, *dbHelperTransactionImpl, error) {
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
queries := New(dbContext)
|
||||||
|
tx, err := dbContext.BeginTx(ctx)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
txQueries := queries.WithTx(tx)
|
||||||
|
|
||||||
|
obj := &dbHelperTransactionImpl{
|
||||||
|
CTX: ctx,
|
||||||
|
TXlessQueries: *queries,
|
||||||
|
TX: tx,
|
||||||
|
TXQueries: *txQueries,
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj, obj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit implements DbHelperTransaction.
|
||||||
|
func (d *dbHelperTransactionImpl) Commit() error {
|
||||||
|
errCommit := d.TX.Commit(d.CTX)
|
||||||
|
|
||||||
|
if errCommit != nil {
|
||||||
|
errRollback := d.TX.Rollback(d.CTX)
|
||||||
|
|
||||||
|
if errRollback != nil {
|
||||||
|
return errRollback
|
||||||
|
}
|
||||||
|
|
||||||
|
return errCommit
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rollback implements DbHelperTransaction.
|
||||||
|
func (d *dbHelperTransactionImpl) Rollback() error {
|
||||||
|
err := d.TX.Rollback(d.CTX)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -12,11 +12,11 @@ type BannedUser struct {
|
|||||||
ID int64
|
ID int64
|
||||||
UserID int64
|
UserID int64
|
||||||
Date pgtype.Timestamp
|
Date pgtype.Timestamp
|
||||||
Reason pgtype.Text
|
Reason *string
|
||||||
ExpiresAt pgtype.Timestamp
|
ExpiresAt pgtype.Timestamp
|
||||||
BannedBy pgtype.Text
|
BannedBy *string
|
||||||
Pardoned pgtype.Bool
|
Pardoned *bool
|
||||||
PardonedBy pgtype.Text
|
PardonedBy *string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConfirmationCode struct {
|
type ConfirmationCode struct {
|
||||||
@@ -25,17 +25,17 @@ type ConfirmationCode struct {
|
|||||||
CodeType int32
|
CodeType int32
|
||||||
CodeHash string
|
CodeHash string
|
||||||
ExpiresAt pgtype.Timestamp
|
ExpiresAt pgtype.Timestamp
|
||||||
Used pgtype.Bool
|
Used *bool
|
||||||
Deleted pgtype.Bool
|
Deleted *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoginInformation struct {
|
type LoginInformation struct {
|
||||||
ID int64
|
ID int64
|
||||||
UserID int64
|
UserID int64
|
||||||
Email pgtype.Text
|
Email *string
|
||||||
PasswordHash string
|
PasswordHash string
|
||||||
TotpEncrypted pgtype.Text
|
TotpEncrypted *string
|
||||||
Email2faEnabled pgtype.Bool
|
Email2faEnabled *bool
|
||||||
PasswordChangeDate pgtype.Timestamp
|
PasswordChangeDate pgtype.Timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,41 +43,41 @@ type Profile struct {
|
|||||||
ID int64
|
ID int64
|
||||||
UserID int64
|
UserID int64
|
||||||
Name string
|
Name string
|
||||||
Bio pgtype.Text
|
Bio *string
|
||||||
AvatarUrl pgtype.Text
|
AvatarUrl *string
|
||||||
Birthday pgtype.Timestamp
|
Birthday pgtype.Timestamp
|
||||||
Color pgtype.Text
|
Color *string
|
||||||
ColorGrad pgtype.Text
|
ColorGrad *string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProfileSetting struct {
|
type ProfileSetting struct {
|
||||||
ID int64
|
ID int64
|
||||||
ProfileID int64
|
ProfileID int64
|
||||||
HideFulfilled pgtype.Bool
|
HideFulfilled *bool
|
||||||
HideProfileDetails pgtype.Bool
|
HideProfileDetails *bool
|
||||||
HideForUnauthenticated pgtype.Bool
|
HideForUnauthenticated *bool
|
||||||
HideBirthday pgtype.Bool
|
HideBirthday *bool
|
||||||
HideDates pgtype.Bool
|
HideDates *bool
|
||||||
Captcha pgtype.Bool
|
Captcha *bool
|
||||||
FollowersOnlyInteraction pgtype.Bool
|
FollowersOnlyInteraction *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Session struct {
|
type Session struct {
|
||||||
ID int64
|
ID int64
|
||||||
UserID int64
|
UserID int64
|
||||||
Guid pgtype.UUID
|
Guid pgtype.UUID
|
||||||
Name pgtype.Text
|
Name *string
|
||||||
Platform pgtype.Text
|
Platform *string
|
||||||
LatestIp pgtype.Text
|
LatestIp *string
|
||||||
LoginTime pgtype.Timestamp
|
LoginTime pgtype.Timestamp
|
||||||
LastSeenDate pgtype.Timestamp
|
LastSeenDate pgtype.Timestamp
|
||||||
Terminated pgtype.Bool
|
Terminated *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID int64
|
ID int64
|
||||||
Username string
|
Username string
|
||||||
Verified pgtype.Bool
|
Verified *bool
|
||||||
RegistrationDate pgtype.Timestamp
|
RegistrationDate pgtype.Timestamp
|
||||||
Deleted pgtype.Bool
|
Deleted *bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ VALUES ( $1, $2, $3, $4) RETURNING id, user_id, date, reason, expires_at, banned
|
|||||||
type CreateBannedUserParams struct {
|
type CreateBannedUserParams struct {
|
||||||
UserID int64
|
UserID int64
|
||||||
ExpiresAt pgtype.Timestamp
|
ExpiresAt pgtype.Timestamp
|
||||||
Reason pgtype.Text
|
Reason *string
|
||||||
BannedBy pgtype.Text
|
BannedBy *string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateBannedUser(ctx context.Context, arg CreateBannedUserParams) (BannedUser, error) {
|
func (q *Queries) CreateBannedUser(ctx context.Context, arg CreateBannedUserParams) (BannedUser, error) {
|
||||||
@@ -45,24 +45,18 @@ func (q *Queries) CreateBannedUser(ctx context.Context, arg CreateBannedUserPara
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createConfirmationCode = `-- name: CreateConfirmationCode :one
|
const createConfirmationCode = `-- name: CreateConfirmationCode :one
|
||||||
INSERT INTO confirmation_codes(user_id, code_type, code_hash, expires_at)
|
INSERT INTO confirmation_codes(user_id, code_type, code_hash)
|
||||||
VALUES ($1, $2, crypt($3, gen_salt('bf')), $4) RETURNING id, user_id, code_type, code_hash, expires_at, used, deleted
|
VALUES ($1, $2, crypt($3::text, gen_salt('bf'))) RETURNING id, user_id, code_type, code_hash, expires_at, used, deleted
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateConfirmationCodeParams struct {
|
type CreateConfirmationCodeParams struct {
|
||||||
UserID int64
|
UserID int64
|
||||||
CodeType int32
|
CodeType int32
|
||||||
Crypt string
|
Code string
|
||||||
ExpiresAt pgtype.Timestamp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateConfirmationCode(ctx context.Context, arg CreateConfirmationCodeParams) (ConfirmationCode, error) {
|
func (q *Queries) CreateConfirmationCode(ctx context.Context, arg CreateConfirmationCodeParams) (ConfirmationCode, error) {
|
||||||
row := q.db.QueryRow(ctx, createConfirmationCode,
|
row := q.db.QueryRow(ctx, createConfirmationCode, arg.UserID, arg.CodeType, arg.Code)
|
||||||
arg.UserID,
|
|
||||||
arg.CodeType,
|
|
||||||
arg.Crypt,
|
|
||||||
arg.ExpiresAt,
|
|
||||||
)
|
|
||||||
var i ConfirmationCode
|
var i ConfirmationCode
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
@@ -83,7 +77,7 @@ VALUES ( $1, $2, crypt($3::text, gen_salt('bf')) ) RETURNING id, user_id, email,
|
|||||||
|
|
||||||
type CreateLoginInformationParams struct {
|
type CreateLoginInformationParams struct {
|
||||||
UserID int64
|
UserID int64
|
||||||
Email pgtype.Text
|
Email *string
|
||||||
Password string
|
Password string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,11 +104,11 @@ VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id, user_id, name, bio, avatar_url
|
|||||||
type CreateProfileParams struct {
|
type CreateProfileParams struct {
|
||||||
UserID int64
|
UserID int64
|
||||||
Name string
|
Name string
|
||||||
Bio pgtype.Text
|
Bio *string
|
||||||
Birthday pgtype.Timestamp
|
Birthday pgtype.Timestamp
|
||||||
AvatarUrl pgtype.Text
|
AvatarUrl *string
|
||||||
Color pgtype.Text
|
Color *string
|
||||||
ColorGrad pgtype.Text
|
ColorGrad *string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateProfile(ctx context.Context, arg CreateProfileParams) (Profile, error) {
|
func (q *Queries) CreateProfile(ctx context.Context, arg CreateProfileParams) (Profile, error) {
|
||||||
@@ -170,9 +164,9 @@ VALUES ($1, $2, $3, $4) RETURNING id, user_id, guid, name, platform, latest_ip,
|
|||||||
|
|
||||||
type CreateSessionParams struct {
|
type CreateSessionParams struct {
|
||||||
UserID int64
|
UserID int64
|
||||||
Name pgtype.Text
|
Name *string
|
||||||
Platform pgtype.Text
|
Platform *string
|
||||||
LatestIp pgtype.Text
|
LatestIp *string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateSession(ctx context.Context, arg CreateSessionParams) (Session, error) {
|
func (q *Queries) CreateSession(ctx context.Context, arg CreateSessionParams) (Session, error) {
|
||||||
@@ -331,18 +325,18 @@ WHERE users.username = $1 AND ($2 IS FALSE OR profile_settings.hide_for_unauthen
|
|||||||
|
|
||||||
type GetProfileByUsernameRestrictedParams struct {
|
type GetProfileByUsernameRestrictedParams struct {
|
||||||
Username string
|
Username string
|
||||||
Column2 pgtype.Bool
|
Column2 *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetProfileByUsernameRestrictedRow struct {
|
type GetProfileByUsernameRestrictedRow struct {
|
||||||
Username string
|
Username string
|
||||||
Name string
|
Name string
|
||||||
Birthday pgtype.Timestamp
|
Birthday pgtype.Timestamp
|
||||||
Bio pgtype.Text
|
Bio *string
|
||||||
AvatarUrl pgtype.Text
|
AvatarUrl *string
|
||||||
Color pgtype.Text
|
Color *string
|
||||||
ColorGrad pgtype.Text
|
ColorGrad *string
|
||||||
HideProfileDetails pgtype.Bool
|
HideProfileDetails *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetProfileByUsernameRestricted(ctx context.Context, arg GetProfileByUsernameRestrictedParams) (GetProfileByUsernameRestrictedRow, error) {
|
func (q *Queries) GetProfileByUsernameRestricted(ctx context.Context, arg GetProfileByUsernameRestrictedParams) (GetProfileByUsernameRestrictedRow, error) {
|
||||||
@@ -405,17 +399,17 @@ LIMIT 20 OFFSET 20 * $1
|
|||||||
`
|
`
|
||||||
|
|
||||||
type GetProfilesRestrictedParams struct {
|
type GetProfilesRestrictedParams struct {
|
||||||
Column1 pgtype.Int4
|
Column1 *int32
|
||||||
Column2 pgtype.Bool
|
Column2 *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetProfilesRestrictedRow struct {
|
type GetProfilesRestrictedRow struct {
|
||||||
Username string
|
Username string
|
||||||
Name string
|
Name string
|
||||||
AvatarUrl pgtype.Text
|
AvatarUrl *string
|
||||||
Color pgtype.Text
|
Color *string
|
||||||
ColorGrad pgtype.Text
|
ColorGrad *string
|
||||||
HideProfileDetails pgtype.Bool
|
HideProfileDetails *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetProfilesRestricted(ctx context.Context, arg GetProfilesRestrictedParams) ([]GetProfilesRestrictedRow, error) {
|
func (q *Queries) GetProfilesRestricted(ctx context.Context, arg GetProfilesRestrictedParams) ([]GetProfilesRestrictedRow, error) {
|
||||||
@@ -558,7 +552,7 @@ type GetUserByLoginCredentialsRow struct {
|
|||||||
ID int64
|
ID int64
|
||||||
Username string
|
Username string
|
||||||
PasswordHash string
|
PasswordHash string
|
||||||
TotpEncrypted pgtype.Text
|
TotpEncrypted *string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetUserByLoginCredentials(ctx context.Context, arg GetUserByLoginCredentialsParams) (GetUserByLoginCredentialsRow, error) {
|
func (q *Queries) GetUserByLoginCredentials(ctx context.Context, arg GetUserByLoginCredentialsParams) (GetUserByLoginCredentialsRow, error) {
|
||||||
@@ -654,11 +648,11 @@ WHERE id = $1
|
|||||||
|
|
||||||
type UpdateBannedUserParams struct {
|
type UpdateBannedUserParams struct {
|
||||||
ID int64
|
ID int64
|
||||||
Reason pgtype.Text
|
Reason *string
|
||||||
ExpiresAt pgtype.Timestamp
|
ExpiresAt pgtype.Timestamp
|
||||||
BannedBy pgtype.Text
|
BannedBy *string
|
||||||
Pardoned pgtype.Bool
|
Pardoned *bool
|
||||||
PardonedBy pgtype.Text
|
PardonedBy *string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdateBannedUser(ctx context.Context, arg UpdateBannedUserParams) error {
|
func (q *Queries) UpdateBannedUser(ctx context.Context, arg UpdateBannedUserParams) error {
|
||||||
@@ -681,8 +675,8 @@ WHERE id = $1
|
|||||||
|
|
||||||
type UpdateConfirmationCodeParams struct {
|
type UpdateConfirmationCodeParams struct {
|
||||||
ID int64
|
ID int64
|
||||||
Used pgtype.Bool
|
Used *bool
|
||||||
Deleted pgtype.Bool
|
Deleted *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdateConfirmationCode(ctx context.Context, arg UpdateConfirmationCodeParams) error {
|
func (q *Queries) UpdateConfirmationCode(ctx context.Context, arg UpdateConfirmationCodeParams) error {
|
||||||
@@ -699,10 +693,10 @@ WHERE users.username = $1 AND login_informations.user_id = users.id
|
|||||||
|
|
||||||
type UpdateLoginInformationByUsernameParams struct {
|
type UpdateLoginInformationByUsernameParams struct {
|
||||||
Username string
|
Username string
|
||||||
Email pgtype.Text
|
Email *string
|
||||||
Password string
|
Password string
|
||||||
TotpEncrypted pgtype.Text
|
TotpEncrypted *string
|
||||||
Email2faEnabled pgtype.Bool
|
Email2faEnabled *bool
|
||||||
PasswordChangeDate pgtype.Timestamp
|
PasswordChangeDate pgtype.Timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -728,11 +722,11 @@ WHERE username = $1
|
|||||||
type UpdateProfileByUsernameParams struct {
|
type UpdateProfileByUsernameParams struct {
|
||||||
Username string
|
Username string
|
||||||
Name string
|
Name string
|
||||||
Bio pgtype.Text
|
Bio *string
|
||||||
Birthday pgtype.Timestamp
|
Birthday pgtype.Timestamp
|
||||||
AvatarUrl pgtype.Text
|
AvatarUrl *string
|
||||||
Color pgtype.Text
|
Color *string
|
||||||
ColorGrad pgtype.Text
|
ColorGrad *string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdateProfileByUsername(ctx context.Context, arg UpdateProfileByUsernameParams) error {
|
func (q *Queries) UpdateProfileByUsername(ctx context.Context, arg UpdateProfileByUsernameParams) error {
|
||||||
@@ -763,13 +757,13 @@ WHERE id = $1
|
|||||||
|
|
||||||
type UpdateProfileSettingsParams struct {
|
type UpdateProfileSettingsParams struct {
|
||||||
ID int64
|
ID int64
|
||||||
HideFulfilled pgtype.Bool
|
HideFulfilled *bool
|
||||||
HideProfileDetails pgtype.Bool
|
HideProfileDetails *bool
|
||||||
HideForUnauthenticated pgtype.Bool
|
HideForUnauthenticated *bool
|
||||||
HideBirthday pgtype.Bool
|
HideBirthday *bool
|
||||||
HideDates pgtype.Bool
|
HideDates *bool
|
||||||
Captcha pgtype.Bool
|
Captcha *bool
|
||||||
FollowersOnlyInteraction pgtype.Bool
|
FollowersOnlyInteraction *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdateProfileSettings(ctx context.Context, arg UpdateProfileSettingsParams) error {
|
func (q *Queries) UpdateProfileSettings(ctx context.Context, arg UpdateProfileSettingsParams) error {
|
||||||
@@ -794,12 +788,12 @@ WHERE id = $1
|
|||||||
|
|
||||||
type UpdateSessionParams struct {
|
type UpdateSessionParams struct {
|
||||||
ID int64
|
ID int64
|
||||||
Name pgtype.Text
|
Name *string
|
||||||
Platform pgtype.Text
|
Platform *string
|
||||||
LatestIp pgtype.Text
|
LatestIp *string
|
||||||
LoginTime pgtype.Timestamp
|
LoginTime pgtype.Timestamp
|
||||||
LastSeenDate pgtype.Timestamp
|
LastSeenDate pgtype.Timestamp
|
||||||
Terminated pgtype.Bool
|
Terminated *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdateSession(ctx context.Context, arg UpdateSessionParams) error {
|
func (q *Queries) UpdateSession(ctx context.Context, arg UpdateSessionParams) error {
|
||||||
@@ -823,8 +817,8 @@ WHERE id = $1
|
|||||||
|
|
||||||
type UpdateUserParams struct {
|
type UpdateUserParams struct {
|
||||||
ID int64
|
ID int64
|
||||||
Verified pgtype.Bool
|
Verified *bool
|
||||||
Deleted pgtype.Bool
|
Deleted *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) error {
|
func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) error {
|
||||||
@@ -840,8 +834,8 @@ WHERE username = $1
|
|||||||
|
|
||||||
type UpdateUserByUsernameParams struct {
|
type UpdateUserByUsernameParams struct {
|
||||||
Username string
|
Username string
|
||||||
Verified pgtype.Bool
|
Verified *bool
|
||||||
Deleted pgtype.Bool
|
Deleted *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdateUserByUsername(ctx context.Context, arg UpdateUserByUsernameParams) error {
|
func (q *Queries) UpdateUserByUsername(ctx context.Context, arg UpdateUserByUsernameParams) error {
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -9,5 +26,3 @@ var Module = fx.Module("database",
|
|||||||
NewDbContext,
|
NewDbContext,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package errors
|
package errors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package errors
|
package errors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -8,4 +25,3 @@ var (
|
|||||||
ErrNotImplemented = errors.New("Feature is not implemented")
|
ErrNotImplemented = errors.New("Feature is not implemented")
|
||||||
ErrBadRequest = errors.New("Bad request")
|
ErrBadRequest = errors.New("Bad request")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package logger
|
package logger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -8,40 +25,36 @@ import (
|
|||||||
"go.uber.org/zap"
|
"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
|
instance *zap.Logger
|
||||||
once sync.Once
|
once sync.Once
|
||||||
)
|
)
|
||||||
|
|
||||||
func (l *loggerImpl) Get() *zap.Logger {
|
func NewLogger() *zap.Logger {
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
var err error
|
|
||||||
cfg := config.GetConfig()
|
cfg := config.GetConfig()
|
||||||
|
var err error
|
||||||
|
|
||||||
if cfg.Environment == "production" {
|
if cfg.Environment == "production" {
|
||||||
logger, err = zap.NewProduction()
|
instance, err = zap.NewProduction()
|
||||||
} else {
|
} else {
|
||||||
logger, err = zap.NewDevelopment()
|
instance, err = zap.NewDevelopment()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic("failed to initialize logger: " + err.Error())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return logger
|
return instance
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *loggerImpl) Sync() error {
|
type SyncLogger struct {
|
||||||
return logger.Sync()
|
*zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSyncLogger(logger *zap.Logger) *SyncLogger {
|
||||||
|
return &SyncLogger{logger}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SyncLogger) Close() error {
|
||||||
|
return s.Sync()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package models
|
package models
|
||||||
|
|
||||||
type Tokens struct {
|
type Tokens struct {
|
||||||
@@ -6,26 +23,28 @@ type Tokens struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RegistrationBeginRequest struct {
|
type RegistrationBeginRequest struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username" binding:"required,min=3,max=20"`
|
||||||
Email *string `json:"email"`
|
Email *string `json:"email" binding:"email"`
|
||||||
Password string `json:"password"` // TODO: password checking
|
Password string `json:"password" binding:"required,password"` // TODO: password checking
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: length check
|
||||||
type RegistrationCompleteRequest struct {
|
type RegistrationCompleteRequest struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username" binding:"required"`
|
||||||
VerificationCode string `json:"verification_code"`
|
VerificationCode string `json:"verification_code" binding:"required"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name" binding:"required,max=75"`
|
||||||
Birthday *string `json:"birthday"`
|
Birthday *string `json:"birthday"`
|
||||||
AvatarUrl *string `json:"avatar_url"`
|
AvatarUrl *string `json:"avatar_url" binding:"http_url,max=255"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RegistrationCompleteResponse struct {
|
type RegistrationCompleteResponse struct {
|
||||||
Tokens
|
Tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: length check
|
||||||
type LoginRequest struct {
|
type LoginRequest struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username" binding:"required"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password" binding:"required"`
|
||||||
TOTP *string `json:"totp"`
|
TOTP *string `json:"totp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,8 +52,9 @@ type LoginResponse struct {
|
|||||||
Tokens
|
Tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: length check
|
||||||
type RefreshRequest struct {
|
type RefreshRequest struct {
|
||||||
RefreshToken string `json:"refresh_token"`
|
RefreshToken string `json:"refresh_token" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RefreshResponse struct {
|
type RefreshResponse struct {
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,12 +1,28 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"easywish/internal/database"
|
"easywish/internal/database"
|
||||||
errs "easywish/internal/errors"
|
errs "easywish/internal/errors"
|
||||||
"easywish/internal/logger"
|
|
||||||
"easywish/internal/models"
|
"easywish/internal/models"
|
||||||
"easywish/internal/utils"
|
"easywish/internal/utils"
|
||||||
|
"easywish/internal/utils/enums"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
@@ -19,25 +35,59 @@ type AuthService interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type authServiceImpl struct {
|
type authServiceImpl struct {
|
||||||
log logger.Logger
|
log *zap.Logger
|
||||||
dbctx database.DbContext
|
dbctx database.DbContext
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthService(_log logger.Logger, _dbctx database.DbContext) AuthService {
|
func NewAuthService(_log *zap.Logger, _dbctx database.DbContext) AuthService {
|
||||||
return &authServiceImpl{log: _log, dbctx: _dbctx}
|
return &authServiceImpl{log: _log, dbctx: _dbctx}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *authServiceImpl) RegistrationBegin(request models.RegistrationBeginRequest) (bool, error) {
|
func (a *authServiceImpl) RegistrationBegin(request models.RegistrationBeginRequest) (bool, error) {
|
||||||
|
|
||||||
ctx := context.Background()
|
var user database.User
|
||||||
queries := database.New(a.dbctx)
|
var generatedCode string
|
||||||
user, err := queries.CreateUser(ctx, request.Username) // TODO: validation
|
|
||||||
|
|
||||||
if err != nil {
|
helper, db, _ := database.NewDbHelperTransaction(a.dbctx)
|
||||||
a.log.Get().Error("Failed to add user to database", zap.Error(err))
|
defer helper.Rollback()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if user, err = db.TXQueries.CreateUser(db.CTX, request.Username); err != nil { // TODO: validation
|
||||||
|
a.log.Error("Failed to add user to database", zap.Error(err))
|
||||||
return false, errs.ErrServerError
|
return false, errs.ErrServerError
|
||||||
}
|
}
|
||||||
a.log.Get().Info("Registered a new user", zap.String("username", user.Username))
|
|
||||||
|
a.log.Info("Registraion of a new user", zap.String("username", user.Username), zap.Int64("id", user.ID))
|
||||||
|
|
||||||
|
if _, err = db.TXQueries.CreateLoginInformation(db.CTX, database.CreateLoginInformationParams{
|
||||||
|
UserID: user.ID,
|
||||||
|
Email: request.Email,
|
||||||
|
Password: request.Password, // Hashed in database
|
||||||
|
}); err != nil {
|
||||||
|
a.log.Error("Failed to add login information for user to database", zap.Error(err))
|
||||||
|
return false, errs.ErrServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
if generatedCode, err = utils.GenerateSecure6DigitNumber(); err != nil {
|
||||||
|
a.log.Error("Failed to generate a registration code", zap.Error(err))
|
||||||
|
return false, errs.ErrServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = db.TXQueries.CreateConfirmationCode(db.CTX, database.CreateConfirmationCodeParams{
|
||||||
|
UserID: user.ID,
|
||||||
|
CodeType: int32(enums.RegistrationCodeType),
|
||||||
|
Code: generatedCode, // Hashed in database
|
||||||
|
}); err != nil {
|
||||||
|
a.log.Error("Failed to add registration code to database", zap.Error(err))
|
||||||
|
return false, errs.ErrServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
a.log.Info("Registered a new user", zap.String("username", user.Username))
|
||||||
|
|
||||||
|
helper.Commit()
|
||||||
|
|
||||||
|
a.log.Debug("Declated registration code for a new user", zap.String("username", user.Username), zap.String("code", generatedCode))
|
||||||
|
|
||||||
// TODO: Send verification email
|
// TODO: Send verification email
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -9,4 +26,3 @@ var Module = fx.Module("services",
|
|||||||
NewAuthService,
|
NewAuthService,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
25
backend/internal/utils/enums/enums.go
Normal file
25
backend/internal/utils/enums/enums.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package enums
|
||||||
|
|
||||||
|
type ConfirmationCodeType int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
RegistrationCodeType ConfirmationCodeType = iota
|
||||||
|
PasswordResetCodeType
|
||||||
|
)
|
||||||
@@ -1,3 +1,20 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
80
backend/internal/utils/security.go
Normal file
80
backend/internal/utils/security.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"easywish/config"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateSecure6DigitNumber() (string, error) {
|
||||||
|
// Generate a random number between 0 and 999999 (inclusive)
|
||||||
|
// This ensures we get a 6-digit number, including those starting with 0
|
||||||
|
max := 1000000 // Upper bound (exclusive)
|
||||||
|
b := make([]byte, 4) // A 4-byte slice is sufficient for a 32-bit integer
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to read random bytes: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert bytes to an integer
|
||||||
|
// We use a simple modulo operation to get a number within our desired range.
|
||||||
|
// While this introduces a slight bias for very large ranges, for 1,000,000
|
||||||
|
// it's negligible and simpler than more complex methods like rejection sampling.
|
||||||
|
num := int(uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3])) % max
|
||||||
|
|
||||||
|
return fmt.Sprintf("%06d", num), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidatePassword(password string) error {
|
||||||
|
cfg := config.GetConfig()
|
||||||
|
|
||||||
|
if cfg.PasswordCheckLength {
|
||||||
|
passwordLength := len(password); if passwordLength < 8 || passwordLength > 100 {
|
||||||
|
return errors.New("Password must be between 8 and 100 characters")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.PasswordCheckNumbers {
|
||||||
|
numbersPresent := regexp.MustCompile(`[0-9]`).MatchString(password); if !numbersPresent {
|
||||||
|
return errors.New("Password must contain at least 1 number")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.PasswordCheckCases {
|
||||||
|
differentCasesPresent := regexp.MustCompile(`(?=.*[a-z])(?=.*[A-Z])`).MatchString(password); if !differentCasesPresent {
|
||||||
|
return errors.New("Password must have uppercase and lowercase characters")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.PasswordCheckSymbols {
|
||||||
|
symbolsPresent := regexp.MustCompile(`[.,/;'[\]\-=_+{}:"<>?\/|!@#$%^&*()~]`).MatchString(password); if !symbolsPresent {
|
||||||
|
return errors.New("Password must contain at least one special symbol")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.PasswordCheckLeaked {
|
||||||
|
// TODO: implement checking leaked passwords via rockme.txt
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -94,8 +94,8 @@ WHERE users.username = $1;
|
|||||||
--: Confirmation Code Object {{{
|
--: Confirmation Code Object {{{
|
||||||
|
|
||||||
;-- name: CreateConfirmationCode :one
|
;-- name: CreateConfirmationCode :one
|
||||||
INSERT INTO confirmation_codes(user_id, code_type, code_hash, expires_at)
|
INSERT INTO confirmation_codes(user_id, code_type, code_hash)
|
||||||
VALUES ($1, $2, crypt($3, gen_salt('bf')), $4) RETURNING *;
|
VALUES ($1, $2, crypt(@code::text, gen_salt('bf'))) RETURNING *;
|
||||||
|
|
||||||
;-- name: GetConfirmationCodeByCode :one
|
;-- name: GetConfirmationCodeByCode :one
|
||||||
SELECT * FROM confirmation_codes
|
SELECT * FROM confirmation_codes
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ sql:
|
|||||||
out: "../backend/internal/database"
|
out: "../backend/internal/database"
|
||||||
sql_package: "pgx/v5"
|
sql_package: "pgx/v5"
|
||||||
emit_prepared_queries: true
|
emit_prepared_queries: true
|
||||||
emit_interface: false
|
emit_pointers_for_null_types: true
|
||||||
database:
|
database:
|
||||||
# managed: true
|
# managed: true
|
||||||
uri: "postgresql://postgres:postgres@localhost:5432/postgres?sslmode=disable"
|
uri: "postgresql://postgres:postgres@localhost:5432/postgres?sslmode=disable"
|
||||||
|
|||||||
Reference in New Issue
Block a user