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
|
||||
// @version 1.0
|
||||
// @description Easy and feature-rich wishlist.
|
||||
// @license.name GPL 3.0
|
||||
// @license.name GPL-3.0
|
||||
|
||||
// @BasePath /api/
|
||||
// @Schemes http
|
||||
@@ -20,6 +37,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/fx"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"easywish/config"
|
||||
docs "easywish/docs"
|
||||
@@ -42,6 +60,7 @@ func main() {
|
||||
fx.New(
|
||||
fx.Provide(
|
||||
logger.NewLogger,
|
||||
logger.NewSyncLogger,
|
||||
gin.Default,
|
||||
),
|
||||
database.Module,
|
||||
@@ -49,7 +68,7 @@ func main() {
|
||||
controllers.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
|
||||
docs.SwaggerInfo.Schemes = []string{"http"}
|
||||
@@ -65,6 +84,7 @@ func main() {
|
||||
OnStart: func(ctx context.Context) error {
|
||||
go func() {
|
||||
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
syncLogger.Fatal("Server failed", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
@@ -72,11 +92,18 @@ func main() {
|
||||
OnStop: func(ctx context.Context) error {
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
import (
|
||||
@@ -8,30 +25,47 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Hostname string `mapstructure:"HOSTNAME"`
|
||||
Port string `mapstructure:"PORT"`
|
||||
DatabaseUrl string `mapstructure:"POSTGRES_URL"`
|
||||
RedisUrl string `mapstructure:"REDIS_URL"`
|
||||
MinioUrl string `mapstructure:"MINIO_URL"`
|
||||
JwtAlgorithm string `mapstructure:"JWT_ALGORITHM"`
|
||||
JwtSecret string `mapstructure:"JWT_SECRET"`
|
||||
JwtIssuer string `mapstructure:"JWT_ISSUER"`
|
||||
JwtAudience string `mapstructure:"JWT_AUDIENCE"`
|
||||
JwtExpAccess int `mapstructure:"JWT_EXP_ACCESS"`
|
||||
JwtExpRefresh int `mapstructure:"JWT_EXP_REFRESH"`
|
||||
Environment string `mapstructure:"ENVIRONMENT"`
|
||||
Hostname string `mapstructure:"HOSTNAME"`
|
||||
Port string `mapstructure:"PORT"`
|
||||
|
||||
DatabaseUrl string `mapstructure:"POSTGRES_URL"`
|
||||
RedisUrl string `mapstructure:"REDIS_URL"`
|
||||
MinioUrl string `mapstructure:"MINIO_URL"`
|
||||
|
||||
JwtAlgorithm string `mapstructure:"JWT_ALGORITHM"`
|
||||
JwtSecret string `mapstructure:"JWT_SECRET"`
|
||||
JwtIssuer string `mapstructure:"JWT_ISSUER"`
|
||||
JwtAudience string `mapstructure:"JWT_AUDIENCE"`
|
||||
JwtExpAccess int `mapstructure:"JWT_EXP_ACCESS"`
|
||||
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"`
|
||||
}
|
||||
|
||||
func Load() (*Config, error) {
|
||||
|
||||
viper.SetDefault("HOSTNAME", "localhost")
|
||||
viper.SetDefault("PORT", "8080")
|
||||
|
||||
viper.SetDefault("JWT_ALGORITHM", "HS256")
|
||||
viper.SetDefault("JWT_SECRET", "default_jwt_secret_please_change")
|
||||
viper.SetDefault("JWT_EXP_ACCESS", 5)
|
||||
viper.SetDefault("JWT_EXP_REFRESH", 10080)
|
||||
viper.SetDefault("JWT_AUDIENCE", "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.AutomaticEnv()
|
||||
@@ -39,18 +73,28 @@ func Load() (*Config, error) {
|
||||
// Viper's AutomaticEnv() expects lowercase keys for unmarshalling into structs by default,
|
||||
// while the environment variables and struct tags are in uppercase.
|
||||
// Here's the stupidity we have to do to fix it:
|
||||
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
viper.BindEnv("HOSTNAME")
|
||||
viper.BindEnv("PORT")
|
||||
|
||||
viper.BindEnv("POSTGRES_URL")
|
||||
viper.BindEnv("REDIS_URL")
|
||||
viper.BindEnv("MINIO_URL")
|
||||
|
||||
viper.BindEnv("JWT_ALGORITHM")
|
||||
viper.BindEnv("JWT_SECRET")
|
||||
viper.BindEnv("JWT_ISSUER")
|
||||
viper.BindEnv("JWT_AUDIENCE")
|
||||
viper.BindEnv("JWT_EXP_ACCESS")
|
||||
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")
|
||||
|
||||
required := []string{
|
||||
|
||||
@@ -299,6 +299,10 @@ const docTemplate = `{
|
||||
},
|
||||
"models.LoginRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"password",
|
||||
"username"
|
||||
],
|
||||
"properties": {
|
||||
"password": {
|
||||
"type": "string"
|
||||
@@ -324,6 +328,10 @@ const docTemplate = `{
|
||||
},
|
||||
"models.RegistrationBeginRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"password",
|
||||
"username"
|
||||
],
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string"
|
||||
@@ -333,7 +341,9 @@ const docTemplate = `{
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 20,
|
||||
"minLength": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,6 +295,10 @@
|
||||
},
|
||||
"models.LoginRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"password",
|
||||
"username"
|
||||
],
|
||||
"properties": {
|
||||
"password": {
|
||||
"type": "string"
|
||||
@@ -320,6 +324,10 @@
|
||||
},
|
||||
"models.RegistrationBeginRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"password",
|
||||
"username"
|
||||
],
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string"
|
||||
@@ -329,7 +337,9 @@
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"maxLength": 20,
|
||||
"minLength": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@ definitions:
|
||||
type: string
|
||||
username:
|
||||
type: string
|
||||
required:
|
||||
- password
|
||||
- username
|
||||
type: object
|
||||
models.LoginResponse:
|
||||
properties:
|
||||
@@ -29,7 +32,12 @@ definitions:
|
||||
description: 'TODO: password checking'
|
||||
type: string
|
||||
username:
|
||||
maxLength: 20
|
||||
minLength: 3
|
||||
type: string
|
||||
required:
|
||||
- password
|
||||
- username
|
||||
type: object
|
||||
info:
|
||||
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
|
||||
|
||||
import (
|
||||
@@ -15,4 +32,3 @@ import (
|
||||
func ChangePassword(c *gin.Context) {
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
UserID int64
|
||||
Date pgtype.Timestamp
|
||||
Reason pgtype.Text
|
||||
Reason *string
|
||||
ExpiresAt pgtype.Timestamp
|
||||
BannedBy pgtype.Text
|
||||
Pardoned pgtype.Bool
|
||||
PardonedBy pgtype.Text
|
||||
BannedBy *string
|
||||
Pardoned *bool
|
||||
PardonedBy *string
|
||||
}
|
||||
|
||||
type ConfirmationCode struct {
|
||||
@@ -25,17 +25,17 @@ type ConfirmationCode struct {
|
||||
CodeType int32
|
||||
CodeHash string
|
||||
ExpiresAt pgtype.Timestamp
|
||||
Used pgtype.Bool
|
||||
Deleted pgtype.Bool
|
||||
Used *bool
|
||||
Deleted *bool
|
||||
}
|
||||
|
||||
type LoginInformation struct {
|
||||
ID int64
|
||||
UserID int64
|
||||
Email pgtype.Text
|
||||
Email *string
|
||||
PasswordHash string
|
||||
TotpEncrypted pgtype.Text
|
||||
Email2faEnabled pgtype.Bool
|
||||
TotpEncrypted *string
|
||||
Email2faEnabled *bool
|
||||
PasswordChangeDate pgtype.Timestamp
|
||||
}
|
||||
|
||||
@@ -43,41 +43,41 @@ type Profile struct {
|
||||
ID int64
|
||||
UserID int64
|
||||
Name string
|
||||
Bio pgtype.Text
|
||||
AvatarUrl pgtype.Text
|
||||
Bio *string
|
||||
AvatarUrl *string
|
||||
Birthday pgtype.Timestamp
|
||||
Color pgtype.Text
|
||||
ColorGrad pgtype.Text
|
||||
Color *string
|
||||
ColorGrad *string
|
||||
}
|
||||
|
||||
type ProfileSetting struct {
|
||||
ID int64
|
||||
ProfileID int64
|
||||
HideFulfilled pgtype.Bool
|
||||
HideProfileDetails pgtype.Bool
|
||||
HideForUnauthenticated pgtype.Bool
|
||||
HideBirthday pgtype.Bool
|
||||
HideDates pgtype.Bool
|
||||
Captcha pgtype.Bool
|
||||
FollowersOnlyInteraction pgtype.Bool
|
||||
HideFulfilled *bool
|
||||
HideProfileDetails *bool
|
||||
HideForUnauthenticated *bool
|
||||
HideBirthday *bool
|
||||
HideDates *bool
|
||||
Captcha *bool
|
||||
FollowersOnlyInteraction *bool
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
ID int64
|
||||
UserID int64
|
||||
Guid pgtype.UUID
|
||||
Name pgtype.Text
|
||||
Platform pgtype.Text
|
||||
LatestIp pgtype.Text
|
||||
Name *string
|
||||
Platform *string
|
||||
LatestIp *string
|
||||
LoginTime pgtype.Timestamp
|
||||
LastSeenDate pgtype.Timestamp
|
||||
Terminated pgtype.Bool
|
||||
Terminated *bool
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID int64
|
||||
Username string
|
||||
Verified pgtype.Bool
|
||||
Verified *bool
|
||||
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 {
|
||||
UserID int64
|
||||
ExpiresAt pgtype.Timestamp
|
||||
Reason pgtype.Text
|
||||
BannedBy pgtype.Text
|
||||
Reason *string
|
||||
BannedBy *string
|
||||
}
|
||||
|
||||
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
|
||||
INSERT INTO confirmation_codes(user_id, code_type, code_hash, expires_at)
|
||||
VALUES ($1, $2, crypt($3, gen_salt('bf')), $4) RETURNING id, user_id, code_type, code_hash, expires_at, used, deleted
|
||||
INSERT INTO confirmation_codes(user_id, code_type, code_hash)
|
||||
VALUES ($1, $2, crypt($3::text, gen_salt('bf'))) RETURNING id, user_id, code_type, code_hash, expires_at, used, deleted
|
||||
`
|
||||
|
||||
type CreateConfirmationCodeParams struct {
|
||||
UserID int64
|
||||
CodeType int32
|
||||
Crypt string
|
||||
ExpiresAt pgtype.Timestamp
|
||||
UserID int64
|
||||
CodeType int32
|
||||
Code string
|
||||
}
|
||||
|
||||
func (q *Queries) CreateConfirmationCode(ctx context.Context, arg CreateConfirmationCodeParams) (ConfirmationCode, error) {
|
||||
row := q.db.QueryRow(ctx, createConfirmationCode,
|
||||
arg.UserID,
|
||||
arg.CodeType,
|
||||
arg.Crypt,
|
||||
arg.ExpiresAt,
|
||||
)
|
||||
row := q.db.QueryRow(ctx, createConfirmationCode, arg.UserID, arg.CodeType, arg.Code)
|
||||
var i ConfirmationCode
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
@@ -83,7 +77,7 @@ VALUES ( $1, $2, crypt($3::text, gen_salt('bf')) ) RETURNING id, user_id, email,
|
||||
|
||||
type CreateLoginInformationParams struct {
|
||||
UserID int64
|
||||
Email pgtype.Text
|
||||
Email *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 {
|
||||
UserID int64
|
||||
Name string
|
||||
Bio pgtype.Text
|
||||
Bio *string
|
||||
Birthday pgtype.Timestamp
|
||||
AvatarUrl pgtype.Text
|
||||
Color pgtype.Text
|
||||
ColorGrad pgtype.Text
|
||||
AvatarUrl *string
|
||||
Color *string
|
||||
ColorGrad *string
|
||||
}
|
||||
|
||||
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 {
|
||||
UserID int64
|
||||
Name pgtype.Text
|
||||
Platform pgtype.Text
|
||||
LatestIp pgtype.Text
|
||||
Name *string
|
||||
Platform *string
|
||||
LatestIp *string
|
||||
}
|
||||
|
||||
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 {
|
||||
Username string
|
||||
Column2 pgtype.Bool
|
||||
Column2 *bool
|
||||
}
|
||||
|
||||
type GetProfileByUsernameRestrictedRow struct {
|
||||
Username string
|
||||
Name string
|
||||
Birthday pgtype.Timestamp
|
||||
Bio pgtype.Text
|
||||
AvatarUrl pgtype.Text
|
||||
Color pgtype.Text
|
||||
ColorGrad pgtype.Text
|
||||
HideProfileDetails pgtype.Bool
|
||||
Bio *string
|
||||
AvatarUrl *string
|
||||
Color *string
|
||||
ColorGrad *string
|
||||
HideProfileDetails *bool
|
||||
}
|
||||
|
||||
func (q *Queries) GetProfileByUsernameRestricted(ctx context.Context, arg GetProfileByUsernameRestrictedParams) (GetProfileByUsernameRestrictedRow, error) {
|
||||
@@ -405,17 +399,17 @@ LIMIT 20 OFFSET 20 * $1
|
||||
`
|
||||
|
||||
type GetProfilesRestrictedParams struct {
|
||||
Column1 pgtype.Int4
|
||||
Column2 pgtype.Bool
|
||||
Column1 *int32
|
||||
Column2 *bool
|
||||
}
|
||||
|
||||
type GetProfilesRestrictedRow struct {
|
||||
Username string
|
||||
Name string
|
||||
AvatarUrl pgtype.Text
|
||||
Color pgtype.Text
|
||||
ColorGrad pgtype.Text
|
||||
HideProfileDetails pgtype.Bool
|
||||
AvatarUrl *string
|
||||
Color *string
|
||||
ColorGrad *string
|
||||
HideProfileDetails *bool
|
||||
}
|
||||
|
||||
func (q *Queries) GetProfilesRestricted(ctx context.Context, arg GetProfilesRestrictedParams) ([]GetProfilesRestrictedRow, error) {
|
||||
@@ -558,7 +552,7 @@ type GetUserByLoginCredentialsRow struct {
|
||||
ID int64
|
||||
Username string
|
||||
PasswordHash string
|
||||
TotpEncrypted pgtype.Text
|
||||
TotpEncrypted *string
|
||||
}
|
||||
|
||||
func (q *Queries) GetUserByLoginCredentials(ctx context.Context, arg GetUserByLoginCredentialsParams) (GetUserByLoginCredentialsRow, error) {
|
||||
@@ -654,11 +648,11 @@ WHERE id = $1
|
||||
|
||||
type UpdateBannedUserParams struct {
|
||||
ID int64
|
||||
Reason pgtype.Text
|
||||
Reason *string
|
||||
ExpiresAt pgtype.Timestamp
|
||||
BannedBy pgtype.Text
|
||||
Pardoned pgtype.Bool
|
||||
PardonedBy pgtype.Text
|
||||
BannedBy *string
|
||||
Pardoned *bool
|
||||
PardonedBy *string
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateBannedUser(ctx context.Context, arg UpdateBannedUserParams) error {
|
||||
@@ -681,8 +675,8 @@ WHERE id = $1
|
||||
|
||||
type UpdateConfirmationCodeParams struct {
|
||||
ID int64
|
||||
Used pgtype.Bool
|
||||
Deleted pgtype.Bool
|
||||
Used *bool
|
||||
Deleted *bool
|
||||
}
|
||||
|
||||
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 {
|
||||
Username string
|
||||
Email pgtype.Text
|
||||
Email *string
|
||||
Password string
|
||||
TotpEncrypted pgtype.Text
|
||||
Email2faEnabled pgtype.Bool
|
||||
TotpEncrypted *string
|
||||
Email2faEnabled *bool
|
||||
PasswordChangeDate pgtype.Timestamp
|
||||
}
|
||||
|
||||
@@ -728,11 +722,11 @@ WHERE username = $1
|
||||
type UpdateProfileByUsernameParams struct {
|
||||
Username string
|
||||
Name string
|
||||
Bio pgtype.Text
|
||||
Bio *string
|
||||
Birthday pgtype.Timestamp
|
||||
AvatarUrl pgtype.Text
|
||||
Color pgtype.Text
|
||||
ColorGrad pgtype.Text
|
||||
AvatarUrl *string
|
||||
Color *string
|
||||
ColorGrad *string
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateProfileByUsername(ctx context.Context, arg UpdateProfileByUsernameParams) error {
|
||||
@@ -763,13 +757,13 @@ WHERE id = $1
|
||||
|
||||
type UpdateProfileSettingsParams struct {
|
||||
ID int64
|
||||
HideFulfilled pgtype.Bool
|
||||
HideProfileDetails pgtype.Bool
|
||||
HideForUnauthenticated pgtype.Bool
|
||||
HideBirthday pgtype.Bool
|
||||
HideDates pgtype.Bool
|
||||
Captcha pgtype.Bool
|
||||
FollowersOnlyInteraction pgtype.Bool
|
||||
HideFulfilled *bool
|
||||
HideProfileDetails *bool
|
||||
HideForUnauthenticated *bool
|
||||
HideBirthday *bool
|
||||
HideDates *bool
|
||||
Captcha *bool
|
||||
FollowersOnlyInteraction *bool
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateProfileSettings(ctx context.Context, arg UpdateProfileSettingsParams) error {
|
||||
@@ -794,12 +788,12 @@ WHERE id = $1
|
||||
|
||||
type UpdateSessionParams struct {
|
||||
ID int64
|
||||
Name pgtype.Text
|
||||
Platform pgtype.Text
|
||||
LatestIp pgtype.Text
|
||||
Name *string
|
||||
Platform *string
|
||||
LatestIp *string
|
||||
LoginTime pgtype.Timestamp
|
||||
LastSeenDate pgtype.Timestamp
|
||||
Terminated pgtype.Bool
|
||||
Terminated *bool
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateSession(ctx context.Context, arg UpdateSessionParams) error {
|
||||
@@ -823,8 +817,8 @@ WHERE id = $1
|
||||
|
||||
type UpdateUserParams struct {
|
||||
ID int64
|
||||
Verified pgtype.Bool
|
||||
Deleted pgtype.Bool
|
||||
Verified *bool
|
||||
Deleted *bool
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) error {
|
||||
@@ -840,8 +834,8 @@ WHERE username = $1
|
||||
|
||||
type UpdateUserByUsernameParams struct {
|
||||
Username string
|
||||
Verified pgtype.Bool
|
||||
Deleted pgtype.Bool
|
||||
Verified *bool
|
||||
Deleted *bool
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
@@ -9,5 +26,3 @@ var Module = fx.Module("database",
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
@@ -8,4 +25,3 @@ var (
|
||||
ErrNotImplemented = errors.New("Feature is not implemented")
|
||||
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
|
||||
|
||||
import (
|
||||
@@ -8,40 +25,36 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type Logger interface {
|
||||
Get() *zap.Logger
|
||||
Sync() error
|
||||
}
|
||||
|
||||
type loggerImpl struct {
|
||||
}
|
||||
|
||||
func NewLogger() Logger {
|
||||
return &loggerImpl{}
|
||||
}
|
||||
|
||||
var (
|
||||
logger *zap.Logger
|
||||
once sync.Once
|
||||
instance *zap.Logger
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
func (l *loggerImpl) Get() *zap.Logger {
|
||||
func NewLogger() *zap.Logger {
|
||||
once.Do(func() {
|
||||
var err error
|
||||
cfg := config.GetConfig()
|
||||
var err error
|
||||
|
||||
if cfg.Environment == "production" {
|
||||
logger, err = zap.NewProduction()
|
||||
instance, err = zap.NewProduction()
|
||||
} else {
|
||||
logger, err = zap.NewDevelopment()
|
||||
instance, err = zap.NewDevelopment()
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
panic("failed to initialize logger: " + err.Error())
|
||||
}
|
||||
})
|
||||
return logger
|
||||
return instance
|
||||
}
|
||||
|
||||
func (l *loggerImpl) Sync() error {
|
||||
return logger.Sync()
|
||||
type SyncLogger struct {
|
||||
*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
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,31 +1,50 @@
|
||||
// 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
|
||||
|
||||
type Tokens struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
type RegistrationBeginRequest struct {
|
||||
Username string `json:"username"`
|
||||
Email *string `json:"email"`
|
||||
Password string `json:"password"` // TODO: password checking
|
||||
Username string `json:"username" binding:"required,min=3,max=20"`
|
||||
Email *string `json:"email" binding:"email"`
|
||||
Password string `json:"password" binding:"required,password"` // TODO: password checking
|
||||
}
|
||||
|
||||
// TODO: length check
|
||||
type RegistrationCompleteRequest struct {
|
||||
Username string `json:"username"`
|
||||
VerificationCode string `json:"verification_code"`
|
||||
Name string `json:"name"`
|
||||
Username string `json:"username" binding:"required"`
|
||||
VerificationCode string `json:"verification_code" binding:"required"`
|
||||
Name string `json:"name" binding:"required,max=75"`
|
||||
Birthday *string `json:"birthday"`
|
||||
AvatarUrl *string `json:"avatar_url"`
|
||||
AvatarUrl *string `json:"avatar_url" binding:"http_url,max=255"`
|
||||
}
|
||||
|
||||
type RegistrationCompleteResponse struct {
|
||||
Tokens
|
||||
}
|
||||
|
||||
// TODO: length check
|
||||
type LoginRequest struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
TOTP *string `json:"totp"`
|
||||
}
|
||||
|
||||
@@ -33,8 +52,9 @@ type LoginResponse struct {
|
||||
Tokens
|
||||
}
|
||||
|
||||
// TODO: length check
|
||||
type RefreshRequest struct {
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
RefreshToken string `json:"refresh_token" binding:"required"`
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"easywish/internal/database"
|
||||
errs "easywish/internal/errors"
|
||||
"easywish/internal/logger"
|
||||
"easywish/internal/models"
|
||||
"easywish/internal/utils"
|
||||
"easywish/internal/utils/enums"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -19,25 +35,59 @@ type AuthService interface {
|
||||
}
|
||||
|
||||
type authServiceImpl struct {
|
||||
log logger.Logger
|
||||
log *zap.Logger
|
||||
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}
|
||||
}
|
||||
|
||||
func (a *authServiceImpl) RegistrationBegin(request models.RegistrationBeginRequest) (bool, error) {
|
||||
|
||||
ctx := context.Background()
|
||||
queries := database.New(a.dbctx)
|
||||
user, err := queries.CreateUser(ctx, request.Username) // TODO: validation
|
||||
var user database.User
|
||||
var generatedCode string
|
||||
|
||||
if err != nil {
|
||||
a.log.Get().Error("Failed to add user to database", zap.Error(err))
|
||||
helper, db, _ := database.NewDbHelperTransaction(a.dbctx)
|
||||
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
|
||||
}
|
||||
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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
import (
|
||||
@@ -9,4 +26,3 @@ var Module = fx.Module("services",
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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 {{{
|
||||
|
||||
;-- name: CreateConfirmationCode :one
|
||||
INSERT INTO confirmation_codes(user_id, code_type, code_hash, expires_at)
|
||||
VALUES ($1, $2, crypt($3, gen_salt('bf')), $4) RETURNING *;
|
||||
INSERT INTO confirmation_codes(user_id, code_type, code_hash)
|
||||
VALUES ($1, $2, crypt(@code::text, gen_salt('bf'))) RETURNING *;
|
||||
|
||||
;-- name: GetConfirmationCodeByCode :one
|
||||
SELECT * FROM confirmation_codes
|
||||
|
||||
@@ -8,7 +8,7 @@ sql:
|
||||
out: "../backend/internal/database"
|
||||
sql_package: "pgx/v5"
|
||||
emit_prepared_queries: true
|
||||
emit_interface: false
|
||||
emit_pointers_for_null_types: true
|
||||
database:
|
||||
# managed: true
|
||||
uri: "postgresql://postgres:postgres@localhost:5432/postgres?sslmode=disable"
|
||||
|
||||
Reference in New Issue
Block a user