init base struct
This commit is contained in:
commit
210c4159e7
1
.env
Normal file
1
.env
Normal file
@ -0,0 +1 @@
|
|||||||
|
APP_NAME=codeword
|
161
.gitignore
vendored
Normal file
161
.gitignore
vendored
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,goland,go
|
||||||
|
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,goland,go
|
||||||
|
|
||||||
|
### Go ###
|
||||||
|
# If you prefer the allow list template instead of the deny list, see community template:
|
||||||
|
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||||
|
#
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
|
||||||
|
### GoLand ###
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# AWS User-specific
|
||||||
|
.idea/**/aws.xml
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/artifacts
|
||||||
|
# .idea/compiler.xml
|
||||||
|
# .idea/jarRepositories.xml
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# SonarLint plugin
|
||||||
|
.idea/sonarlint/
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
### GoLand Patch ###
|
||||||
|
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
||||||
|
|
||||||
|
# *.iml
|
||||||
|
# modules.xml
|
||||||
|
# .idea/misc.xml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# Sonarlint plugin
|
||||||
|
# https://plugins.jetbrains.com/plugin/7973-sonarlint
|
||||||
|
.idea/**/sonarlint/
|
||||||
|
|
||||||
|
# SonarQube Plugin
|
||||||
|
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
|
||||||
|
.idea/**/sonarIssues.xml
|
||||||
|
|
||||||
|
# Markdown Navigator plugin
|
||||||
|
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
|
||||||
|
.idea/**/markdown-navigator.xml
|
||||||
|
.idea/**/markdown-navigator-enh.xml
|
||||||
|
.idea/**/markdown-navigator/
|
||||||
|
|
||||||
|
# Cache file creation bug
|
||||||
|
# See https://youtrack.jetbrains.com/issue/JBR-2257
|
||||||
|
.idea/$CACHE_FILE$
|
||||||
|
|
||||||
|
# CodeStream plugin
|
||||||
|
# https://plugins.jetbrains.com/plugin/12206-codestream
|
||||||
|
.idea/codestream.xml
|
||||||
|
|
||||||
|
# Azure Toolkit for IntelliJ plugin
|
||||||
|
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
|
||||||
|
.idea/**/azureSettings.xml
|
||||||
|
|
||||||
|
### VisualStudioCode ###
|
||||||
|
.vscode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
!.vscode/*.code-snippets
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
|
||||||
|
# Built Visual Studio Code Extensions
|
||||||
|
*.vsix
|
||||||
|
|
||||||
|
### VisualStudioCode Patch ###
|
||||||
|
# Ignore all local history of files
|
||||||
|
.history
|
||||||
|
.ionide
|
||||||
|
|
||||||
|
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,goland,go
|
14
blueprint.yaml
Normal file
14
blueprint.yaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
ProjectName: codeword
|
||||||
|
Description: Service for exchanging codewords
|
||||||
|
|
||||||
|
Template:
|
||||||
|
path: "./"
|
||||||
|
|
||||||
|
Modules:
|
||||||
|
logger:
|
||||||
|
name: zap
|
||||||
|
env:
|
||||||
|
vars:
|
||||||
|
- name: APP_NAME
|
||||||
|
type: string
|
||||||
|
default: "{{.ProjectName}}"
|
32
cmd/codeword/main.go
Normal file
32
cmd/codeword/main.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"codeword/internal/app"
|
||||||
|
"codeword/internal/initialize"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
logger, err := zap.NewProduction()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to initialize logger: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := initialize.LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal("Failed to load config", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
if err = app.Run(ctx, *config, logger); err != nil {
|
||||||
|
logger.Fatal("App exited with error", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
0
deployment/local/docker-compose.yaml
Normal file
0
deployment/local/docker-compose.yaml
Normal file
0
deployment/staging/docker-compose.yaml
Normal file
0
deployment/staging/docker-compose.yaml
Normal file
0
deployment/test/docker-compose.yaml
Normal file
0
deployment/test/docker-compose.yaml
Normal file
0
docs/openapi.yaml
Normal file
0
docs/openapi.yaml
Normal file
21
go.mod
Normal file
21
go.mod
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
module codeword
|
||||||
|
|
||||||
|
go 1.21
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||||
|
github.com/caarlos0/env/v8 v8.0.0 // indirect
|
||||||
|
github.com/gofiber/fiber/v2 v2.51.0 // indirect
|
||||||
|
github.com/google/uuid v1.4.0 // indirect
|
||||||
|
github.com/klauspost/compress v1.16.7 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/fasthttp v1.50.0 // indirect
|
||||||
|
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||||
|
go.uber.org/multierr v1.10.0 // indirect
|
||||||
|
go.uber.org/zap v1.26.0 // indirect
|
||||||
|
golang.org/x/sys v0.14.0 // indirect
|
||||||
|
)
|
33
go.sum
Normal file
33
go.sum
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||||
|
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
|
github.com/caarlos0/env/v8 v8.0.0 h1:POhxHhSpuxrLMIdvTGARuZqR4Jjm8AYmoi/JKlcScs0=
|
||||||
|
github.com/caarlos0/env/v8 v8.0.0/go.mod h1:7K4wMY9bH0esiXSSHlfHLX5xKGQMnkH5Fk4TDSSSzfo=
|
||||||
|
github.com/gofiber/fiber/v2 v2.51.0 h1:JNACcZy5e2tGApWB2QrRpenTWn0fq0hkFm6k0C86gKQ=
|
||||||
|
github.com/gofiber/fiber/v2 v2.51.0/go.mod h1:xaQRZQJGqnKOQnbQw+ltvku3/h8QxvNi8o6JiJ7Ll0U=
|
||||||
|
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||||
|
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
||||||
|
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M=
|
||||||
|
github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
|
||||||
|
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||||
|
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||||
|
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||||
|
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
||||||
|
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||||
|
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
13
internal/adapters/client/mail.go
Normal file
13
internal/adapters/client/mail.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type RecoveryEmailSender struct{}
|
||||||
|
|
||||||
|
// SendRecoveryEmail отправляет email с подписью для восстановления доступа
|
||||||
|
func (r *RecoveryEmailSender) SendRecoveryEmail(email, signature string) error {
|
||||||
|
|
||||||
|
fmt.Printf("Отправляем письмо для восстановления доступа на: %s с подписью: %s\n", email, signature)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
52
internal/app/app.go
Normal file
52
internal/app/app.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"codeword/internal/adapters/client"
|
||||||
|
controller "codeword/internal/controller/recovery"
|
||||||
|
"codeword/internal/initialize"
|
||||||
|
"codeword/internal/repository"
|
||||||
|
httpserver "codeword/internal/server/http"
|
||||||
|
"codeword/internal/services"
|
||||||
|
"context"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error {
|
||||||
|
logger.Info("Запуск приложения", zap.String("AppName", cfg.AppName))
|
||||||
|
|
||||||
|
userRepo := repository.NewUserRepository()
|
||||||
|
recoveryEmailSender := &client.RecoveryEmailSender{}
|
||||||
|
recoveryService := services.NewRecoveryService(logger, userRepo, recoveryEmailSender)
|
||||||
|
recoveryController := controller.NewRecoveryController(logger, recoveryService)
|
||||||
|
server := httpserver.NewServer(httpserver.ServerConfig{
|
||||||
|
Logger: logger,
|
||||||
|
Repo: userRepo,
|
||||||
|
RecoveryController: recoveryController,
|
||||||
|
})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := server.Start(":3000"); err != nil {
|
||||||
|
logger.Error("Ошибка запуска сервера", zap.Error(err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-ctx.Done()
|
||||||
|
|
||||||
|
if err := shutdownApp(server, logger); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Info("Приложение остановлено")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func shutdownApp(server *httpserver.Server, logger *zap.Logger) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := server.Shutdown(ctx); err != nil {
|
||||||
|
logger.Error("Ошибка при остановке сервера Fiber", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
81
internal/controller/recovery/recovery_controller.go
Normal file
81
internal/controller/recovery/recovery_controller.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"codeword/internal/services"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RecoveryController struct {
|
||||||
|
Logger *zap.Logger
|
||||||
|
Service *services.RecoveryService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRecoveryController(logger *zap.Logger, service *services.RecoveryService) *RecoveryController {
|
||||||
|
return &RecoveryController{
|
||||||
|
Logger: logger,
|
||||||
|
Service: service,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleRecoveryRequest обрабатывает запрос на восстановление пароля
|
||||||
|
func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error {
|
||||||
|
email := c.FormValue("email")
|
||||||
|
|
||||||
|
key, err := r.Service.GenerateKey()
|
||||||
|
if err != nil {
|
||||||
|
r.Logger.Error("Failed to generate key", zap.Error(err))
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
|
||||||
|
}
|
||||||
|
fmt.Println(key)
|
||||||
|
|
||||||
|
user, err := r.Service.FindUserByEmail(email)
|
||||||
|
if err != nil {
|
||||||
|
r.Logger.Error("Failed to find user by email", zap.Error(err))
|
||||||
|
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
|
||||||
|
}
|
||||||
|
fmt.Println(user)
|
||||||
|
// сохраняем в бд
|
||||||
|
signature, err := r.Service.StoreRecoveryRecord("user")
|
||||||
|
if err != nil {
|
||||||
|
r.Logger.Error("Failed to store recovery record", zap.Error(err))
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
|
||||||
|
}
|
||||||
|
// тут что-то на подобии канала или что-то подобное так как отправка выполнятеся в воркере,
|
||||||
|
//это пока временное решение для написания структуры кода и проверки отправки, далее перепишу
|
||||||
|
// под горутины
|
||||||
|
err = r.Service.SendRecoveryEmail(email, signature)
|
||||||
|
if err != nil {
|
||||||
|
r.Logger.Error("Failed to send recovery email", zap.Error(err))
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Recovery email sent successfully"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleRecoveryLink обрабатывает ссылку восстановления и обменивает ее на токены
|
||||||
|
func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error {
|
||||||
|
signature := c.Params("sign")
|
||||||
|
// тут получается
|
||||||
|
record, err := r.Service.GetRecoveryRecord(signature)
|
||||||
|
if err != nil {
|
||||||
|
r.Logger.Error("Failed to get recovery record", zap.Error(err))
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// проверка на более чем 15 минут
|
||||||
|
if time.Since(record.CreatedAt) > 15*time.Minute {
|
||||||
|
r.Logger.Error("Recovery link expired", zap.String("signature", signature))
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Recovery link expired"})
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens, err := r.Service.ExchangeForTokens(record.UserID)
|
||||||
|
if err != nil {
|
||||||
|
r.Logger.Error("Failed to exchange recovery link for tokens", zap.Error(err))
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(tokens)
|
||||||
|
}
|
1
internal/errors/errors.go
Normal file
1
internal/errors/errors.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package errors
|
17
internal/initialize/initialize.go
Normal file
17
internal/initialize/initialize.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package initialize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caarlos0/env/v8"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
AppName string `env:"APP_NAME" envDefault:"codeword"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadConfig() (*Config, error) {
|
||||||
|
var config Config
|
||||||
|
if err := env.Parse(&config); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &config, nil
|
||||||
|
}
|
17
internal/models/user.go
Normal file
17
internal/models/user.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
// получаем данные из другого сервиса
|
||||||
|
}
|
||||||
|
|
||||||
|
type RestoreRequest struct {
|
||||||
|
ID string // xid или ObjectID
|
||||||
|
CreatedAt time.Time
|
||||||
|
Sign string // подпись
|
||||||
|
Email string // email из запроса
|
||||||
|
UserID string // айдишник юзера, которого нашли по email
|
||||||
|
Sent bool
|
||||||
|
SentAt time.Time
|
||||||
|
}
|
36
internal/repository/user_repository.go
Normal file
36
internal/repository/user_repository.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"codeword/internal/models"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserRepository struct {
|
||||||
|
//todo
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserRepository() *UserRepository {
|
||||||
|
//todo
|
||||||
|
return &UserRepository{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *UserRepository) FindByEmail(email string) (*models.User, error) {
|
||||||
|
//todo
|
||||||
|
return &models.User{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *UserRepository) StoreRecoveryRecord(userID, signature string, createdAt time.Time) error {
|
||||||
|
//todo
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *UserRepository) GetRecoveryRecord(signature string) (*models.RestoreRequest, error) {
|
||||||
|
//todo
|
||||||
|
|
||||||
|
return &models.RestoreRequest{UserID: "123", Sign: signature, CreatedAt: time.Now()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *UserRepository) Ping() error {
|
||||||
|
return nil
|
||||||
|
}
|
66
internal/server/http/http_server.go
Normal file
66
internal/server/http/http_server.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
controller "codeword/internal/controller/recovery"
|
||||||
|
"codeword/internal/repository"
|
||||||
|
"context"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerConfig struct {
|
||||||
|
Logger *zap.Logger
|
||||||
|
Repo *repository.UserRepository
|
||||||
|
RecoveryController *controller.RecoveryController
|
||||||
|
}
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
Logger *zap.Logger
|
||||||
|
Repo *repository.UserRepository
|
||||||
|
RecoveryController *controller.RecoveryController
|
||||||
|
app *fiber.App
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(config ServerConfig) *Server {
|
||||||
|
app := fiber.New()
|
||||||
|
|
||||||
|
s := &Server{
|
||||||
|
Logger: config.Logger,
|
||||||
|
Repo: config.Repo,
|
||||||
|
RecoveryController: config.RecoveryController,
|
||||||
|
app: app,
|
||||||
|
}
|
||||||
|
|
||||||
|
s.registerRoutes()
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) registerRoutes() {
|
||||||
|
s.app.Get("/liveness", s.handleLiveness)
|
||||||
|
s.app.Get("/readiness", s.handleReadiness)
|
||||||
|
|
||||||
|
s.app.Post("/recover", s.RecoveryController.HandleRecoveryRequest)
|
||||||
|
s.app.Get("/recover/:sign", s.RecoveryController.HandleRecoveryLink)
|
||||||
|
//... other
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) handleLiveness(c *fiber.Ctx) error {
|
||||||
|
return c.SendStatus(fiber.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) handleReadiness(c *fiber.Ctx) error {
|
||||||
|
if err := s.Repo.Ping(); err != nil {
|
||||||
|
s.Logger.Error("Failed to ping the database", zap.Error(err))
|
||||||
|
return c.Status(fiber.StatusServiceUnavailable).SendString("DB ping failed")
|
||||||
|
}
|
||||||
|
return c.Status(fiber.StatusOK).SendString("42")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Start(addr string) error {
|
||||||
|
return s.app.Listen(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Shutdown(ctx context.Context) error {
|
||||||
|
return s.app.Shutdown()
|
||||||
|
}
|
66
internal/services/recovery_service.go
Normal file
66
internal/services/recovery_service.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"codeword/internal/models"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserRepository interface {
|
||||||
|
FindByEmail(email string) (*models.User, error)
|
||||||
|
StoreRecoveryRecord(userID string, signature string, createdAt time.Time) error
|
||||||
|
GetRecoveryRecord(signature string) (*models.RestoreRequest, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmailSender interface {
|
||||||
|
SendRecoveryEmail(email, signature string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecoveryService struct {
|
||||||
|
Logger *zap.Logger
|
||||||
|
Repository UserRepository
|
||||||
|
Email EmailSender
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRecoveryService(logger *zap.Logger, repository UserRepository, email EmailSender) *RecoveryService {
|
||||||
|
return &RecoveryService{
|
||||||
|
Logger: logger,
|
||||||
|
Repository: repository,
|
||||||
|
Email: email,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateKey генерирует ключ, используя шифрование на основе эллиптической кривой
|
||||||
|
func (s *RecoveryService) GenerateKey() (string, error) {
|
||||||
|
// TODO
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindUserByEmail ищет пользователя по электронной почте
|
||||||
|
func (s *RecoveryService) FindUserByEmail(email string) (*models.User, error) {
|
||||||
|
return s.Repository.FindByEmail(email)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreRecoveryRecord сохраняет запись восстановления в базе данных
|
||||||
|
func (s *RecoveryService) StoreRecoveryRecord(userID string) (string, error) {
|
||||||
|
signature := ""
|
||||||
|
createdAt := time.Now()
|
||||||
|
err := s.Repository.StoreRecoveryRecord(userID, signature, createdAt)
|
||||||
|
return signature, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRecoveryRecord получает запись восстановления из базы данных
|
||||||
|
func (s *RecoveryService) GetRecoveryRecord(signature string) (*models.RestoreRequest, error) {
|
||||||
|
return s.Repository.GetRecoveryRecord(signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendRecoveryEmail посылает письмо для восстановления доступа пользователю
|
||||||
|
func (s *RecoveryService) SendRecoveryEmail(email string, signature string) error {
|
||||||
|
return s.Email.SendRecoveryEmail(email, signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExchangeForTokens обменивает ссылку восстановления на токены используя сервис аутентификации.
|
||||||
|
func (s *RecoveryService) ExchangeForTokens(userID string) (map[string]string, error) {
|
||||||
|
// TODO
|
||||||
|
return nil, nil
|
||||||
|
}
|
1
internal/worker/recovery_worker/recovery_worker.go
Normal file
1
internal/worker/recovery_worker/recovery_worker.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package recovery_worker
|
161
template/.gitignore.tmpl
Normal file
161
template/.gitignore.tmpl
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,goland,go
|
||||||
|
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,goland,go
|
||||||
|
|
||||||
|
### Go ###
|
||||||
|
# If you prefer the allow list template instead of the deny list, see community template:
|
||||||
|
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||||
|
#
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
|
||||||
|
### GoLand ###
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# AWS User-specific
|
||||||
|
.idea/**/aws.xml
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/artifacts
|
||||||
|
# .idea/compiler.xml
|
||||||
|
# .idea/jarRepositories.xml
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# SonarLint plugin
|
||||||
|
.idea/sonarlint/
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
### GoLand Patch ###
|
||||||
|
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
||||||
|
|
||||||
|
# *.iml
|
||||||
|
# modules.xml
|
||||||
|
# .idea/misc.xml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# Sonarlint plugin
|
||||||
|
# https://plugins.jetbrains.com/plugin/7973-sonarlint
|
||||||
|
.idea/**/sonarlint/
|
||||||
|
|
||||||
|
# SonarQube Plugin
|
||||||
|
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
|
||||||
|
.idea/**/sonarIssues.xml
|
||||||
|
|
||||||
|
# Markdown Navigator plugin
|
||||||
|
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
|
||||||
|
.idea/**/markdown-navigator.xml
|
||||||
|
.idea/**/markdown-navigator-enh.xml
|
||||||
|
.idea/**/markdown-navigator/
|
||||||
|
|
||||||
|
# Cache file creation bug
|
||||||
|
# See https://youtrack.jetbrains.com/issue/JBR-2257
|
||||||
|
.idea/$CACHE_FILE$
|
||||||
|
|
||||||
|
# CodeStream plugin
|
||||||
|
# https://plugins.jetbrains.com/plugin/12206-codestream
|
||||||
|
.idea/codestream.xml
|
||||||
|
|
||||||
|
# Azure Toolkit for IntelliJ plugin
|
||||||
|
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
|
||||||
|
.idea/**/azureSettings.xml
|
||||||
|
|
||||||
|
### VisualStudioCode ###
|
||||||
|
.vscode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
!.vscode/*.code-snippets
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
|
||||||
|
# Built Visual Studio Code Extensions
|
||||||
|
*.vsix
|
||||||
|
|
||||||
|
### VisualStudioCode Patch ###
|
||||||
|
# Ignore all local history of files
|
||||||
|
.history
|
||||||
|
.ionide
|
||||||
|
|
||||||
|
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,goland,go
|
29
template/cmd/{{.ProjectName}}/main.go.tmpl
Normal file
29
template/cmd/{{.ProjectName}}/main.go.tmpl
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"{{.Vars.ProjectName}}/internal/app"
|
||||||
|
|
||||||
|
{{.Modules.logger.Import}}
|
||||||
|
{{.Modules.logger.ImportCore}}
|
||||||
|
|
||||||
|
{{.Modules.env.Import}}
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
{{.Modules.logger.Init}}
|
||||||
|
|
||||||
|
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var config app.Config
|
||||||
|
|
||||||
|
{{.Modules.env.Declaration "config"}}
|
||||||
|
|
||||||
|
if err := app.Run(ctx, config, logger); err != nil {
|
||||||
|
{{.Modules.logger.Message "Fatal" "Failed to run app" "Error" "err"}}
|
||||||
|
}
|
||||||
|
}
|
19
template/internal/app/app.go.tmpl
Normal file
19
template/internal/app/app.go.tmpl
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
{{.Modules.logger.Import}}
|
||||||
|
)
|
||||||
|
|
||||||
|
{{.Modules.env.Struct}}
|
||||||
|
|
||||||
|
func Run(ctx context.Context, config Config, logger {{.Modules.logger.Type}}) error {
|
||||||
|
{{.Modules.logger.Message "info" "App started" "config" "config"}}
|
||||||
|
|
||||||
|
<-ctx.Done()
|
||||||
|
|
||||||
|
logger.Info("App shutting down gracefully")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user