From 0eba18e367fc828b6c5b6fc6ac35611afae0e9ed Mon Sep 17 00:00:00 2001 From: Floriansylvain Date: Sun, 21 Dec 2025 21:34:07 +0100 Subject: [PATCH] fix: removed useless file --- all_go_files.txt | 1632 ---------------------------------------------- 1 file changed, 1632 deletions(-) delete mode 100644 all_go_files.txt diff --git a/all_go_files.txt b/all_go_files.txt deleted file mode 100644 index c8fdb59..0000000 --- a/all_go_files.txt +++ /dev/null @@ -1,1632 +0,0 @@ -===== FILE: ./api/dependecyInjection.go ===== -package api - -import ( - domainArticle "RenewCMS/internal/domain/article" - domainImage "RenewCMS/internal/domain/image" - domainMail "RenewCMS/internal/domain/mail" - domainUser "RenewCMS/internal/domain/user" - "RenewCMS/internal/infrastructure/mailer" - "RenewCMS/internal/infrastructure/persistence" - "RenewCMS/internal/infrastructure/persistence/models" - "RenewCMS/internal/infrastructure/useCases" - "os" - "path/filepath" - - "github.com/glebarez/sqlite" - "github.com/go-chi/jwtauth/v5" - "gorm.io/gorm" -) - -type UseCases struct { - Token *jwtauth.JWTAuth - CreateArticleUseCase *useCases.CreateArticleUseCase - GetArticleUseCase *useCases.GetArticleUseCase - ListArticlesUseCase *useCases.ListArticlesUseCase - UpdateArticleUseCase *useCases.UpdateArticleUseCase - DeleteArticleUseCase *useCases.DeleteArticleUseCase - GetUserUseCase *useCases.GetUserUseCase - CreateUserUseCase *useCases.CreateUserUseCase - UpdateUserUseCase *useCases.UpdateUserUseCase - DeleteUserUseCase *useCases.DeleteUserUseCase - ListUsersUseCase *useCases.ListUsersUseCase - SendMailUseCase *useCases.SendMailUseCase - CreateImageUseCase *useCases.CreateImageUseCase - DeleteImageUseCase *useCases.DeleteImageUseCase -} - -type Repositories struct { - ArticleRepo domainArticle.Repository - UserRepo domainUser.Repository - ImageRepo domainImage.Repository - MailRepo domainMail.Repository -} - -func getDb() *gorm.DB { - dbName := os.Getenv("DB_FILE") - if err := os.MkdirAll(filepath.Dir(dbName), os.ModePerm); err != nil { - panic("Unable to create necessary subdirectories: " + err.Error()) - } - db, err := gorm.Open(sqlite.Open(dbName), &gorm.Config{}) - if err != nil { - panic("Unable to open the database: " + err.Error()) - } - - if err := db.AutoMigrate(&models.Article{}, &models.User{}); err != nil { - panic("Failed to migrate database: " + err.Error()) - } - - return db -} - -func getRepositories(db *gorm.DB) *Repositories { - return &Repositories{ - ArticleRepo: persistence.NewArticleRepository(db), - UserRepo: persistence.NewUserRepository(db), - ImageRepo: persistence.NewImageRepository(db), - MailRepo: mailer.NewMailRepository(), - } -} - -func getUseCases(repos *Repositories) *UseCases { - return &UseCases{ - Token: jwtauth.New("HS256", []byte(os.Getenv("JWT_SECRET")), nil), - CreateArticleUseCase: useCases.NewCreateArticleUseCase(repos.ArticleRepo), - GetArticleUseCase: useCases.NewGetArticleUseCase(repos.ArticleRepo), - ListArticlesUseCase: useCases.NewListArticlesUseCase(repos.ArticleRepo), - UpdateArticleUseCase: useCases.NewUpdateArticleUseCase(repos.ArticleRepo), - DeleteArticleUseCase: useCases.NewDeleteArticleUseCase(repos.ArticleRepo), - GetUserUseCase: useCases.NewGetUserUseCase(repos.UserRepo), - CreateUserUseCase: useCases.NewCreateUserUseCase(repos.UserRepo), - UpdateUserUseCase: useCases.NewUpdateUserUseCase(repos.UserRepo), - DeleteUserUseCase: useCases.NewDeleteUserUseCase(repos.UserRepo), - ListUsersUseCase: useCases.NewListUsersUseCase(repos.UserRepo), - SendMailUseCase: useCases.NewSendMailUseCase(repos.MailRepo), - CreateImageUseCase: useCases.NewCreateImageUseCase(repos.ImageRepo), - DeleteImageUseCase: useCases.NewDeleteImageUseCase(repos.ImageRepo), - } -} - -func InitContainer() *UseCases { - db := getDb() - repos := getRepositories(db) - return getUseCases(repos) -} - -===== FILE: ./api/handlers/article/article.go ===== -package article - -import ( - "RenewCMS/api/pkg/apperr" - validator "RenewCMS/api/pkg/validator" - domainArticle "RenewCMS/internal/domain/article" - "RenewCMS/internal/infrastructure/useCases" - "encoding/json" - "net/http" - "strconv" - - "github.com/go-chi/chi/v5" -) - -type Handler struct { - CreateUseCase *useCases.CreateArticleUseCase - GetUseCase *useCases.GetArticleUseCase - ListUseCase *useCases.ListArticlesUseCase - DeleteUseCase *useCases.DeleteArticleUseCase -} - -type PostArticle struct { - Title string `json:"title" validate:"required,min=3,max=50"` - Body string `json:"body" validate:"required,max=10000"` -} - -func NewHandler(create *useCases.CreateArticleUseCase, get *useCases.GetArticleUseCase, list *useCases.ListArticlesUseCase, delete *useCases.DeleteArticleUseCase) *Handler { - return &Handler{ - CreateUseCase: create, - GetUseCase: get, - ListUseCase: list, - DeleteUseCase: delete, - } -} - -func (h *Handler) GetArticle(w http.ResponseWriter, r *http.Request) { - id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32) - if err != nil { - http.Error(w, apperr.IdUint32ErrorMessage, http.StatusBadRequest) - return - } - - localArticle, err := h.GetUseCase.GetArticle(uint32(id)) - if err != nil || !localArticle.IsOnline { - http.Error(w, "The requested resource, identified by its unique ID, could not be found on the server.", http.StatusNotFound) - return - } - - articleJson, _ := json.Marshal(localArticle) - _, _ = w.Write(articleJson) -} - -func (h *Handler) PostArticle(w http.ResponseWriter, r *http.Request) { - var localArticle PostArticle - err := json.NewDecoder(r.Body).Decode(&localArticle) - if err != nil { - http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest) - return - } - - err = validator.Validate.Struct(localArticle) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - createdArticle, err := h.CreateUseCase.CreateArticle(useCases.CreateArticleCommand{ - Title: localArticle.Title, - Body: localArticle.Body, - }) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - articleJson, _ := json.Marshal(createdArticle) - - _, _ = w.Write(articleJson) -} - -func (h *Handler) ListArticles(w http.ResponseWriter, _ *http.Request) { - articles := h.ListUseCase.ListArticles() - onlineArticles := make([]domainArticle.Article, 0) - for _, localArticle := range articles { - if localArticle.IsOnline { - onlineArticles = append(onlineArticles, localArticle) - } - } - articlesJson, _ := json.Marshal(onlineArticles) - _, _ = w.Write(articlesJson) -} - -func (h *Handler) DeleteArticle(w http.ResponseWriter, r *http.Request) { - id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32) - if err != nil { - http.Error(w, apperr.IdUint32ErrorMessage, http.StatusBadRequest) - return - } - - err = h.DeleteUseCase.DeleteArticle(uint32(id)) - if err != nil { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - - _, _ = w.Write([]byte("article deleted")) -} - -===== FILE: ./api/handlers/auth/auth.go ===== -package auth - -import ( - "RenewCMS/api/pkg/apperr" - validator "RenewCMS/api/pkg/validator" - "RenewCMS/internal/domain/user" - "RenewCMS/internal/infrastructure/useCases" - "encoding/json" - "net/http" - "os" - "time" - - "github.com/go-chi/jwtauth/v5" - "golang.org/x/crypto/bcrypt" -) - -type Handler struct { - Token *jwtauth.JWTAuth - GetUseCase *useCases.GetUserUseCase - ListUseCase *useCases.ListUsersUseCase - CreateUseCase *useCases.CreateUserUseCase -} - -type RegisterCredentials struct { - Username string `json:"username" validate:"required,min=3,max=20"` - Password string `json:"password" validate:"required,min=8"` - Email string `json:"email" validate:"required,email"` -} - -type LoginCredentials struct { - Username string `json:"username" validate:"required,min=3,max=20"` - Password string `json:"password" validate:"required,min=8"` -} - -var shouldCookieBeSecure = os.Getenv("ENVIRONMENT") == "production" - -func removeJwtCookie(w http.ResponseWriter) { - http.SetCookie(w, &http.Cookie{ - Name: "jwt", - Value: "", - Expires: time.Now(), - MaxAge: -1, - Secure: shouldCookieBeSecure, - HttpOnly: true, - Path: "/", - }) -} - -func NewHandler(token *jwtauth.JWTAuth, get *useCases.GetUserUseCase, list *useCases.ListUsersUseCase, create *useCases.CreateUserUseCase) *Handler { - return &Handler{ - Token: token, - GetUseCase: get, - ListUseCase: list, - CreateUseCase: create, - } -} - -func (h *Handler) SetJwtCookie(w *http.ResponseWriter, userId uint32) error { - _, tokenString, err := h.Token.Encode(map[string]any{"user_id": userId}) - if err != nil { - return err - } - http.SetCookie(*w, &http.Cookie{ - Name: "jwt", - Value: tokenString, - Expires: time.Now().Add(24 * time.Hour), - Secure: shouldCookieBeSecure, - HttpOnly: true, - Path: "/", - }) - return nil -} - -func (h *Handler) SomeUsersVerified() bool { - users := h.ListUseCase.ListUsers() - for _, localUser := range users { - if localUser.IsVerified { - return true - } - } - return false -} - -func (h *Handler) IsUserTableEmpty() bool { - users := h.ListUseCase.ListUsers() - return len(users) == 0 -} - -func (h *Handler) IsLoggedIn(r *http.Request) bool { - token, err := jwtauth.VerifyRequest( - h.Token, - r, - jwtauth.TokenFromCookie, - jwtauth.TokenFromHeader, - jwtauth.TokenFromQuery) - return token != nil && err == nil -} - -func (h *Handler) IsVerified(r *http.Request) bool { - token, err := jwtauth.VerifyRequest( - h.Token, - r, - jwtauth.TokenFromCookie, - jwtauth.TokenFromHeader, - jwtauth.TokenFromQuery) - if err != nil { - return false - } - userId := token.PrivateClaims()["user_id"].(float64) - currentUser, _ := h.GetUseCase.GetUser(uint32(userId)) - return currentUser.IsVerified -} - -func (h *Handler) GetUserFromCredentials(credentials LoginCredentials) (user.User, error) { - dbUser, err := h.GetUseCase.GetUserByUsername(credentials.Username) - if err != nil { - return user.User{}, err - } - - err = bcrypt.CompareHashAndPassword([]byte(dbUser.Password), []byte(credentials.Password)) - if err != nil { - return user.User{}, err - } - - return dbUser, nil -} - -func (h *Handler) Login(w http.ResponseWriter, r *http.Request) { - var credentials LoginCredentials - - err := json.NewDecoder(r.Body).Decode(&credentials) - if err != nil { - http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest) - return - } - - err = validator.Validate.Struct(credentials) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - dbUser, err := h.GetUserFromCredentials(credentials) - if err != nil { - http.Error(w, apperr.LogsErrorMessage, http.StatusForbidden) - return - } - - _ = h.SetJwtCookie(&w, dbUser.ID) - - message, _ := json.Marshal(map[string]any{"message": "User logged in! HTTPonly jwt cookie created"}) - _, _ = w.Write(message) -} - -func (h *Handler) Register(w http.ResponseWriter, r *http.Request) { - if !h.IsLoggedIn(r) && !h.SomeUsersVerified() { - http.Error(w, "You are not allowed to create a user. Log in or reset database.", http.StatusForbidden) - return - } - - var credentials RegisterCredentials - err := json.NewDecoder(r.Body).Decode(&credentials) - if err != nil { - http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest) - return - } - - err = validator.Validate.Struct(credentials) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - createdUser, err := h.CreateUseCase.CreateUser(useCases.CreateUserCommand{ - Username: credentials.Username, - Password: credentials.Password, - Email: credentials.Email, - }) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - _ = h.SetJwtCookie(&w, createdUser.ID) - - message, _ := json.Marshal(map[string]any{"message": "User registered! HTTPonly jwt cookie created"}) - _, _ = w.Write(message) -} - -func (h *Handler) Logout(w http.ResponseWriter, _ *http.Request) { - removeJwtCookie(w) - message, _ := json.Marshal(map[string]any{"message": "User logged out! HTTPonly jwt cookie deleted"}) - _, _ = w.Write(message) -} - -===== FILE: ./api/handlers/image/image.go ===== -package image - -import ( - "RenewCMS/internal/infrastructure/useCases" - "encoding/json" - "net/http" - "strconv" - - "github.com/go-chi/chi/v5" -) - -type Handler struct { - CreateUseCase *useCases.CreateImageUseCase - UpdateUseCase *useCases.UpdateArticleUseCase -} - -func NewHandler(create *useCases.CreateImageUseCase, update *useCases.UpdateArticleUseCase) *Handler { - return &Handler{ - CreateUseCase: create, - UpdateUseCase: update, - } -} - -func (h *Handler) PostImage(w http.ResponseWriter, r *http.Request) { - _ = r.ParseForm() - - id := chi.URLParam(r, "id") - idInt, _ := strconv.Atoi(id) - - file, fileHeader, err := r.FormFile("file") - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - newImage, err := h.CreateUseCase.CreateImage(file, *fileHeader) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - err = h.UpdateUseCase.AddImage(uint32(idInt), newImage.ID) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - newJson := map[string]any{"location": newImage.Path} - newJsonBytes, _ := json.Marshal(newJson) - - _, _ = w.Write(newJsonBytes) -} - -===== FILE: ./api/middleware/auth.go ===== -package middleware - -import "net/http" - -const KeyContentType = "Content-Type" - -func JsonContentTypeMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set(KeyContentType, "application/json") - next.ServeHTTP(w, r) - }) -} - -func HtmlContentTypeMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set(KeyContentType, "text/html") - next.ServeHTTP(w, r) - }) -} - -===== FILE: ./api/router.go ===== -package api - -import ( - "RenewCMS/api/handlers/article" - "RenewCMS/api/handlers/auth" - "RenewCMS/api/middleware" - "encoding/json" - "net/http" - "os" - "strings" - - "github.com/MadAppGang/httplog" - "github.com/go-chi/chi/v5" - "github.com/go-chi/cors" - "github.com/go-chi/jwtauth/v5" -) - -func GetHelloWorld(w http.ResponseWriter, _ *http.Request) { - msg, _ := json.Marshal(map[string]string{"message": "Hello World"}) - _, _ = w.Write(msg) -} - -func NewArticleRouter(container *UseCases) http.Handler { - r := chi.NewRouter() - - articleHandler := article.NewHandler(container.CreateArticleUseCase, container.GetArticleUseCase, container.ListArticlesUseCase, container.DeleteArticleUseCase) - - r.Get("/{id}", articleHandler.GetArticle) - r.Post("/", articleHandler.PostArticle) - r.Get("/", articleHandler.ListArticles) - r.Delete("/{id}", articleHandler.DeleteArticle) - return r -} - -func NewAuthRouter(container *UseCases) http.Handler { - r := chi.NewRouter() - - authHandler := auth.NewHandler(container.Token, container.GetUserUseCase, container.ListUsersUseCase, container.CreateUserUseCase) - - r.Post("/register", authHandler.Register) - r.Post("/login", authHandler.Login) - r.Post("/logout", authHandler.Logout) - return r -} - -func InitBackendRoutes(container *UseCases) *chi.Mux { - r := chi.NewRouter() - - r.Use(httplog.LoggerWithName("backend")) - r.Use(middleware.JsonContentTypeMiddleware) - r.Get("/", GetHelloWorld) - r.Group(func(r chi.Router) { - r.Use(jwtauth.Verifier(container.Token)) - r.Use(jwtauth.Authenticator(container.Token)) - r.Mount("/article", NewArticleRouter(container)) - }) - r.Mount("/auth", NewAuthRouter(container)) - - return r -} - -func InitRoutes(container *UseCases) *chi.Mux { - backend := InitBackendRoutes(container) - - apiRouter := chi.NewRouter() - apiRouter.Use(cors.Handler(cors.Options{ - AllowedOrigins: strings.Split(os.Getenv("CORS_ALLOWED_ORIGINS"), ";"), - AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, - AllowedHeaders: []string{"Accept", "Authorization", middleware.KeyContentType, "X-CSRF-Token"}, - ExposedHeaders: []string{"Link"}, - AllowCredentials: false, - MaxAge: 300, - })) - apiRouter.Mount("/v1", backend) - - return apiRouter -} - -===== FILE: ./api/pkg/validator/validator.go ===== -package api - -import "github.com/go-playground/validator/v10" - -var Validate *validator.Validate - -func InitValidator() { - Validate = validator.New() -} - -===== FILE: ./api/pkg/apperr/messages.go ===== -package apperr - -const LogsErrorMessage = "Access to the requested resource is forbidden due to incorrect password and/or username." -const BodyErrorMessage = "The request cannot be processed due to a mismatch in the format of the body." -const IdUint32ErrorMessage = "The server expects the ID to be in the format of an unsigned 32-bit integer (uint32)." - -===== FILE: ./cmd/server/main.go ===== -package main - -import ( - "RenewCMS/api" - validator "RenewCMS/api/pkg/validator" - "fmt" - "net/http" - "os" - - "github.com/go-chi/chi/v5" - "github.com/joho/godotenv" -) - -var possibleEnvFileLocations = []string{".env", "../.env"} -var envVarsToLoad = []string{ - "HOST", - "PORT", - "JWT_SECRET", - "ENVIRONMENT", - "CORS_ALLOWED_ORIGINS", - "DB_FILE", - "SMTP_EMAIL", - "SMTP_PASSWORD", - "SMTP_HOST", - "SMTP_PORT", -} - -func initEnvVariables() { - var err error - for _, envLocation := range possibleEnvFileLocations { - err = godotenv.Load(envLocation) - if err == nil { - break - } - } - if err != nil { - fmt.Println("WARNING: Could not load any .env file") - } - - for _, envVar := range envVarsToLoad { - if _, ok := os.LookupEnv(envVar); !ok { - panic(fmt.Sprintf("Environment variable %s is not set", envVar)) - } - } -} - -func initServer() *chi.Mux { - initEnvVariables() - - container := api.InitContainer() - validator.InitValidator() - - return api.InitRoutes(container) -} - -func startServer(router *chi.Mux) error { - fmt.Println("Server starting on http://" + os.Getenv("HOST")) - err := http.ListenAndServe(":"+os.Getenv("PORT"), router) - return err -} - -func main() { - router := initServer() - err := startServer(router) - if err != nil { - panic(err) - } -} - -===== FILE: ./internal/domain/article/article.go ===== -package article - -import ( - domain "RenewCMS/internal/domain/image" - entity "RenewCMS/internal/infrastructure/persistence/models" - "time" -) - -type Article struct { - ID uint32 `json:"id"` - Title string `json:"title"` - Body string `json:"body"` - Images []*domain.Image `json:"images"` - IsOnline bool `json:"is_online"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} - -func FromApi( - title string, - body string, -) Article { - return Article{ - Title: title, - Body: body, - } -} - -func FromDb( - id uint32, - title string, - body string, - images []*entity.Image, - isOnline bool, - createdAt time.Time, - updatedAt time.Time, -) Article { - domainImages := make([]*domain.Image, len(images)) - for i, img := range images { - domainImage := domain.FromDB( - img.ID, - img.Path, - img.ArticleID, - img.CreatedAt, - img.UpdatedAt, - ) - domainImages[i] = &domainImage - } - return Article{ - ID: id, - Title: title, - Body: body, - Images: domainImages, - IsOnline: isOnline, - CreatedAt: createdAt, - UpdatedAt: updatedAt, - } -} - -===== FILE: ./internal/domain/article/repository.go ===== -package article - -type Repository interface { - Get(id uint32) (Article, error) - GetByName(name string) (Article, error) - GetAll() []Article - Create(post Article) (Article, error) - UpdateBody(id uint32, body string) (Article, error) - UpdateIsOnline(id uint32, isOnline bool) (Article, error) - Delete(id uint32) error - AddImage(postId uint32, imageId uint32) error -} - -===== FILE: ./internal/domain/image/image.go ===== -package image - -import ( - "time" -) - -type Image struct { - ID uint32 `json:"id"` - Path string `json:"path"` - ArticleID uint32 `json:"article_id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} - -func FromDB(id uint32, path string, articleId uint32, createdAt time.Time, updatedAt time.Time) Image { - return Image{ - ID: id, - Path: path, - ArticleID: articleId, - CreatedAt: createdAt, - UpdatedAt: updatedAt, - } -} - -===== FILE: ./internal/domain/image/repository.go ===== -package image - -import ( - "mime/multipart" -) - -type Repository interface { - Create(file multipart.File, fileHeader multipart.FileHeader) (Image, error) - Delete(id uint32) error -} - -===== FILE: ./internal/domain/user/user.go ===== -package user - -import ( - "time" -) - -type User struct { - ID uint32 `json:"id"` - Username string `json:"username"` - Password string `json:"password"` - PasswordResetCode string `json:"password_reset_code"` - Email string `json:"email"` - IsVerified bool `json:"is_verified"` - VerificationCode string `json:"verification_code"` - VerificationExpiration time.Time `json:"verification_expiration"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} - -func FromApi( - username string, - password string, - passwordResetCode string, - email string, - verificationCode string, -) User { - expiration := time.Now().Add(2 * time.Hour) - return User{ - Username: username, - Password: password, - PasswordResetCode: passwordResetCode, - Email: email, - IsVerified: false, - VerificationCode: verificationCode, - VerificationExpiration: expiration, - } -} - -func FromDb( - id uint32, - username string, - password string, - passwordResetCode string, - email string, - isVerified bool, - verificationCode string, - verificationExpiration time.Time, - createdAt time.Time, - updatedAt time.Time, -) User { - return User{ - ID: id, - Username: username, - Password: password, - PasswordResetCode: passwordResetCode, - Email: email, - IsVerified: isVerified, - VerificationCode: verificationCode, - VerificationExpiration: verificationExpiration, - CreatedAt: createdAt, - UpdatedAt: updatedAt, - } -} - -===== FILE: ./internal/domain/user/repository.go ===== -package user - -type Repository interface { - Get(id uint32) (User, error) - GetByUsername(username string) (User, error) - GetByEmail(email string) (User, error) - GetAll() []User - Create(user User) (User, error) - Delete(id uint32) error - UpdateVerificationStatus(userId uint32, isVerified bool) (User, error) - UpdatePassword(userId uint32, password string) (User, error) - UpdatePasswordResetCode(userId uint32, code string) (User, error) -} - -===== FILE: ./internal/domain/mail/repository.go ===== -package mail - -type Repository interface { - Send(receiverAddress string, templateName string, data any) error -} - -===== FILE: ./internal/infrastructure/persistence/userRepository.go ===== -package persistence - -import ( - domainUser "RenewCMS/internal/domain/user" - entity "RenewCMS/internal/infrastructure/persistence/models" - - "golang.org/x/crypto/bcrypt" - "gorm.io/gorm" -) - -type UserRepository struct { - db *gorm.DB -} - -func NewUserRepository(db *gorm.DB) *UserRepository { - return &UserRepository{db} -} - -func mapUserToDomain(user entity.User) domainUser.User { - return domainUser.FromDb( - user.ID, - user.Username, - user.Password, - user.PasswordResetCode, - user.Email, - user.IsVerified, - user.VerificationCode, - user.VerificationExpiration, - user.CreatedAt, user.UpdatedAt, - ) -} - -func (u *UserRepository) Get(id uint32) (domainUser.User, error) { - var localUser entity.User - err := u.db.Model(&entity.User{}).First(&localUser, id).Error - if err != nil { - return domainUser.User{}, err - } - - return mapUserToDomain(localUser), nil -} - -func (u *UserRepository) Create(user domainUser.User) (domainUser.User, error) { - hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(user.Password), 12) - hashedVerificationCode, _ := bcrypt.GenerateFromPassword([]byte(user.VerificationCode), 12) - - creationResult := u.db.Create(&entity.User{ - Username: user.Username, - Password: string(hashedPassword), - Email: user.Email, - VerificationCode: string(hashedVerificationCode), - VerificationExpiration: user.VerificationExpiration, - }) - if creationResult.Error != nil { - return domainUser.User{}, creationResult.Error - } - - var createdUser entity.User - creationResult.Scan(&createdUser) - - return mapUserToDomain(createdUser), - nil -} - -func (u *UserRepository) Delete(id uint32) error { - return u.db.Delete(&domainUser.User{}, id).Error -} - -func (u *UserRepository) GetAll() []domainUser.User { - var users []entity.User - u.db.Model(&entity.User{}).Find(&users) - - var domainUsers []domainUser.User - for _, localUser := range users { - domainUsers = append(domainUsers, mapUserToDomain(localUser)) - } - - return domainUsers -} - -func (u *UserRepository) GetByUsername(username string) (domainUser.User, error) { - var localUser entity.User - err := u.db.Model(&entity.User{}).Where("username = ?", username).First(&localUser).Error - if err != nil { - return domainUser.User{}, err - } - - return mapUserToDomain(localUser), nil -} - -func (u *UserRepository) GetByEmail(email string) (domainUser.User, error) { - var localUser entity.User - err := u.db.Model(&entity.User{}).Where("email = ?", email).First(&localUser).Error - if err != nil { - return domainUser.User{}, err - } - - return mapUserToDomain(localUser), nil -} - -func (u *UserRepository) UpdateVerificationStatus(userId uint32, isVerified bool) (domainUser.User, error) { - var localUser entity.User - err := u.db.Model(&entity.User{}).First(&localUser, userId).Error - if err != nil { - return domainUser.User{}, err - } - - localUser.IsVerified = isVerified - err = u.db.Save(&localUser).Error - if err != nil { - return domainUser.User{}, err - } - - return mapUserToDomain(localUser), nil -} - -func (u *UserRepository) UpdatePassword(userId uint32, password string) (domainUser.User, error) { - var localUser entity.User - err := u.db.Model(&entity.User{}).First(&localUser, userId).Error - if err != nil { - return domainUser.User{}, err - } - - hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), 12) - localUser.Password = string(hashedPassword) - err = u.db.Save(&localUser).Error - if err != nil { - return domainUser.User{}, err - } - - return mapUserToDomain(localUser), nil -} - -func (u *UserRepository) UpdatePasswordResetCode(userId uint32, code string) (domainUser.User, error) { - var localUser entity.User - err := u.db.Model(&entity.User{}).First(&localUser, userId).Error - if err != nil { - return domainUser.User{}, err - } - - hashedCode, _ := bcrypt.GenerateFromPassword([]byte(code), 12) - localUser.PasswordResetCode = string(hashedCode) - err = u.db.Save(&localUser).Error - if err != nil { - return domainUser.User{}, err - } - - return mapUserToDomain(localUser), nil -} - -var _ domainUser.Repository = &UserRepository{} - -===== FILE: ./internal/infrastructure/persistence/articleRepository.go ===== -package persistence - -import ( - domain "RenewCMS/internal/domain/article" - entity "RenewCMS/internal/infrastructure/persistence/models" - - "gorm.io/gorm" -) - -type ArticleRepository struct { - db *gorm.DB -} - -func NewArticleRepository(db *gorm.DB) *ArticleRepository { - return &ArticleRepository{db} -} - -func mapArticleToDomain(article entity.Article) domain.Article { - return domain.FromDb(article.ID, article.Title, article.Body, article.Images, article.IsOnline, article.CreatedAt, article.UpdatedAt) -} - -func (a *ArticleRepository) Get(id uint32) (domain.Article, error) { - var article entity.Article - err := a.db.Model(&entity.Article{}).Preload("Images").First(&article, id).Error - if err != nil { - return domain.Article{}, err - } - - return mapArticleToDomain(article), nil -} - -func (a *ArticleRepository) GetByName(name string) (domain.Article, error) { - var article entity.Article - err := a.db.Model(&entity.Article{}).Where("title = ?", name).First(&article).Error - if err != nil { - return domain.Article{}, err - } - - return mapArticleToDomain(article), nil -} - -func (a *ArticleRepository) Create(article domain.Article) (domain.Article, error) { - creationResult := a.db.Create(&entity.Article{ - Title: article.Title, - Body: article.Body, - }) - if creationResult.Error != nil { - return domain.Article{}, creationResult.Error - } - - var createdArticle entity.Article - creationResult.Scan(&createdArticle) - - return mapArticleToDomain(createdArticle), nil -} - -func (a *ArticleRepository) GetAll() []domain.Article { - var articles []entity.Article - err := a.db.Model(&entity.Article{}).Find(&articles).Error - if err != nil { - return []domain.Article{} - } - - var domainArticles = make([]domain.Article, 0) - for _, article := range articles { - domainArticles = append(domainArticles, mapArticleToDomain(article)) - } - - return domainArticles -} - -func (a *ArticleRepository) UpdateBody(id uint32, body string) (domain.Article, error) { - var localArticle entity.Article - err := a.db.Model(&entity.Article{}).First(&localArticle, id).Error - if err != nil { - return domain.Article{}, err - } - - localArticle.Body = body - err = a.db.Save(&localArticle).Error - if err != nil { - return domain.Article{}, err - } - - newArticle := domain.FromDb( - localArticle.ID, - localArticle.Title, - localArticle.Body, - localArticle.Images, - localArticle.IsOnline, - localArticle.CreatedAt, - localArticle.UpdatedAt, - ) - - return newArticle, nil -} - -func (a *ArticleRepository) UpdateIsOnline(id uint32, isOnline bool) (domain.Article, error) { - var localArticle entity.Article - err := a.db.Model(&entity.Article{}).First(&localArticle, id).Error - if err != nil { - return domain.Article{}, err - } - - localArticle.IsOnline = isOnline - err = a.db.Save(&localArticle).Error - if err != nil { - return domain.Article{}, err - } - - return mapArticleToDomain(localArticle), nil -} - -func (a *ArticleRepository) Delete(id uint32) error { - return a.db.Delete(&entity.Article{}, id).Error -} - -func (a *ArticleRepository) AddImage(articleId uint32, imageId uint32) error { - var localArticle entity.Article - err := a.db.Model(&entity.Article{}).First(&localArticle, articleId).Error - if err != nil { - return err - } - - var localImage entity.Image - err = a.db.Model(&entity.Image{}).First(&localImage, imageId).Error - if err != nil { - return err - } - - err = a.db.Model(&localArticle).Association("Images").Append(&localImage) - if err != nil { - return err - } - - return nil -} - -var _ domain.Repository = &ArticleRepository{} - -===== FILE: ./internal/infrastructure/persistence/imageRepository.go ===== -package persistence - -import ( - domain "RenewCMS/internal/domain/image" - entity "RenewCMS/internal/infrastructure/persistence/models" - "errors" - "mime/multipart" - "os" - - "github.com/google/uuid" - "gorm.io/gorm" -) - -type ImageRepository struct { - db *gorm.DB -} - -func NewImageRepository(db *gorm.DB) *ImageRepository { - return &ImageRepository{db} -} - -var contentTypeExtensions = map[string]string{ - "image/png": ".png", - "image/jpeg": ".jpeg", - "image/webp": ".webp", - "image/svg+xml": ".svg", -} - -func mapImageToDomain(image entity.Image) domain.Image { - return domain.FromDB( - image.ID, - image.Path, - image.ArticleID, - image.CreatedAt, - image.UpdatedAt, - ) -} - -func (i ImageRepository) Create(file multipart.File, fileHeader multipart.FileHeader) (domain.Image, error) { - fileBytes := make([]byte, fileHeader.Size) - _, err := file.Read(fileBytes) - if err != nil { - return domain.Image{}, err - } - - err = os.MkdirAll("api/static/uploadedImages/", 0666) - if err != nil { - return domain.Image{}, err - } - - contentType := fileHeader.Header.Get("Content-Type") - extension := contentTypeExtensions[contentType] - if extension == "" { - return domain.Image{}, errors.New("the file must be a PNG, JPEG, WEBP, or SVG image") - } - - newName := uuid.NewString() + extension - err = os.WriteFile("api/static/uploadedImages/"+newName, fileBytes, 0666) - if err != nil { - return domain.Image{}, err - } - - newImage := i.db.Create(&domain.Image{Path: "/static/uploadedImages/" + newName}) - var createdImage entity.Image - newImage.Scan(&createdImage) - - return mapImageToDomain(createdImage), nil -} - -func (i ImageRepository) Delete(id uint32) error { - var image entity.Image - err := i.db.Model(&entity.Image{}).First(&image, id).Error - if err != nil { - return err - } - - err = os.Remove("api" + image.Path) - if err != nil { - return err - } - - return i.db.Delete(&entity.Image{}, id).Error -} - -var _ domain.Repository = &ImageRepository{} - -===== FILE: ./internal/infrastructure/persistence/models/article.go ===== -package models - -import ( - "time" - - "gorm.io/gorm" -) - -type Article struct { - gorm.Model - ID uint32 `gorm:"primary_key;auto_increment;not_null"` - Title string - Body string - Images []*Image `gorm:"many2many:article_images;"` - IsOnline bool `gorm:"not_null;default:false"` - CreatedAt time.Time `gorm:"autoCreateTime"` - UpdatedAt time.Time `gorm:"autoUpdateTime"` -} - -===== FILE: ./internal/infrastructure/persistence/models/image.go ===== -package models - -import ( - "time" - - "gorm.io/gorm" -) - -type Image struct { - gorm.Model - ID uint32 `gorm:"primary_key;auto_increment;not_null"` - Path string - ArticleID uint32 - CreatedAt time.Time `gorm:"autoCreateTime"` - UpdatedAt time.Time `gorm:"autoUpdateTime"` -} - -===== FILE: ./internal/infrastructure/persistence/models/user.go ===== -package models - -import ( - "gorm.io/gorm" - "time" -) - -type User struct { - gorm.Model - ID uint32 `gorm:"primaryKey;autoIncrement"` - Username string `gorm:"unique;not null"` - Password string `gorm:"not null"` - PasswordResetCode string `gorm:"unique"` - Email string `gorm:"unique;not null"` - IsVerified bool `gorm:"default=false;not null"` - VerificationCode string `gorm:"unique"` - VerificationExpiration time.Time - CreatedAt time.Time `gorm:"autoCreateTime"` - UpdatedAt time.Time `gorm:"autoUpdateTime"` -} - -===== FILE: ./internal/infrastructure/mailer/mailRepository.go ===== -package mailer - -import ( - domain "RenewCMS/internal/domain/mail" - "bytes" - "embed" - "html/template" - "log" - "os" - "strconv" - - "gopkg.in/gomail.v2" -) - -type MailRepository struct{} - -//go:embed templates/* -var mailTemplateFiles embed.FS - -func NewMailRepository() *MailRepository { - return &MailRepository{} -} - -func (m MailRepository) Send(receiverAddress string, templateName string, data any) error { - from := os.Getenv("SMTP_EMAIL") - password := os.Getenv("SMTP_PASSWORD") - smtpHost := os.Getenv("SMTP_HOST") - smtpPort, _ := strconv.Atoi(os.Getenv("SMTP_PORT")) - - d := gomail.NewDialer(smtpHost, smtpPort, from, password) - - tmpl, err := template.ParseFS(mailTemplateFiles, "mail/templates/"+templateName+".html") - if err != nil { - log.Println("error when template.ParseFS:", err) - return err - } - - var body bytes.Buffer - err = tmpl.Execute(&body, data) - if err != nil { - log.Println("error when tmpl.Execute:", err) - return err - } - - msg := gomail.NewMessage() - msg.SetHeaders(map[string][]string{ - "From": {"RenewCMS <" + from + ">"}, - "To": {receiverAddress}, - "MIME-version": {"1.0"}, - "Content-Type": {"text/html"}, - "charset": {"UTF-8"}, - "Subject": {"RenewCMS | Action required"}, - }) - msg.SetBody("text/html", body.String()) - - if err := d.DialAndSend(msg); err != nil { - log.Println("error when sending email:", err) - return err - } - - return nil -} - -var _ domain.Repository = &MailRepository{} - -===== FILE: ./internal/infrastructure/useCases/CreateArticle.go ===== -package useCases - -import ( - "RenewCMS/internal/domain/article" -) - -type CreateArticleUseCase struct { - articleRepository article.Repository -} - -type CreateArticleCommand struct { - Title string - Body string -} - -func NewCreateArticleUseCase(articleRepository article.Repository) *CreateArticleUseCase { - return &CreateArticleUseCase{articleRepository} -} - -func (g *CreateArticleUseCase) CreateArticle(createArticle CreateArticleCommand) (article.Article, error) { - return g.articleRepository.Create(article.FromApi( - createArticle.Title, - createArticle.Body, - )) -} - -===== FILE: ./internal/infrastructure/useCases/CreateImage.go ===== -package useCases - -import ( - "RenewCMS/internal/domain/image" - "mime/multipart" -) - -type CreateImageUseCase struct { - imageRepository image.Repository -} - -func NewCreateImageUseCase(imageRepository image.Repository) *CreateImageUseCase { - return &CreateImageUseCase{imageRepository} -} - -func (g *CreateImageUseCase) CreateImage(file multipart.File, fileHeader multipart.FileHeader) (image.Image, error) { - return g.imageRepository.Create(file, fileHeader) -} - -===== FILE: ./internal/infrastructure/useCases/CreateUser.go ===== -package useCases - -import ( - "RenewCMS/internal/domain/user" - - "github.com/google/uuid" -) - -type CreateUserUseCase struct { - userRepository user.Repository -} - -type CreateUserCommand struct { - Username string - Password string - Email string -} - -func NewCreateUserUseCase(userRepository user.Repository) *CreateUserUseCase { - return &CreateUserUseCase{userRepository} -} - -func (g *CreateUserUseCase) CreateUser(createUser CreateUserCommand) (user.User, error) { - rawUuid := uuid.NewString() - - createdUser, err := g.userRepository.Create(user.FromApi( - createUser.Username, - createUser.Password, - "", - createUser.Email, - rawUuid, - )) - if err != nil { - return user.User{}, err - } - - createdUser.VerificationCode = rawUuid - return createdUser, nil -} - -===== FILE: ./internal/infrastructure/useCases/DeleteArticle.go ===== -package useCases - -import "RenewCMS/internal/domain/article" - -type DeleteArticleUseCase struct { - articleRepository article.Repository -} - -func NewDeleteArticleUseCase(articleRepository article.Repository) *DeleteArticleUseCase { - return &DeleteArticleUseCase{articleRepository} -} - -func (g *DeleteArticleUseCase) DeleteArticle(userId uint32) error { - return g.articleRepository.Delete(userId) -} - -===== FILE: ./internal/infrastructure/useCases/DeleteImage.go ===== -package useCases - -import "RenewCMS/internal/domain/image" - -type DeleteImageUseCase struct { - imageRepository image.Repository -} - -func NewDeleteImageUseCase(imageRepository image.Repository) *DeleteImageUseCase { - return &DeleteImageUseCase{imageRepository} -} - -func (g *DeleteImageUseCase) DeleteImage(imageId uint32) error { - return g.imageRepository.Delete(imageId) -} - -===== FILE: ./internal/infrastructure/useCases/DeleteUser.go ===== -package useCases - -import "RenewCMS/internal/domain/user" - -type DeleteUserUseCase struct { - userRepository user.Repository -} - -func NewDeleteUserUseCase(userRepository user.Repository) *DeleteUserUseCase { - return &DeleteUserUseCase{userRepository} -} - -func (g *DeleteUserUseCase) DeleteUser(userId uint32) error { - return g.userRepository.Delete(userId) -} - -===== FILE: ./internal/infrastructure/useCases/GetArticle.go ===== -package useCases - -import "RenewCMS/internal/domain/article" - -type GetArticleUseCase struct { - articleRepository article.Repository -} - -func NewGetArticleUseCase(articleRepository article.Repository) *GetArticleUseCase { - return &GetArticleUseCase{articleRepository} -} - -func (g *GetArticleUseCase) GetArticle(id uint32) (article.Article, error) { - return g.articleRepository.Get(id) -} - -func (g *GetArticleUseCase) GetArticleByName(name string) (article.Article, error) { - return g.articleRepository.GetByName(name) -} - -===== FILE: ./internal/infrastructure/useCases/GetUser.go ===== -package useCases - -import "RenewCMS/internal/domain/user" - -type GetUserUseCase struct { - userRepository user.Repository -} - -func NewGetUserUseCase(userRepository user.Repository) *GetUserUseCase { - return &GetUserUseCase{userRepository} -} - -func (g *GetUserUseCase) GetUser(id uint32) (user.User, error) { - return g.userRepository.Get(id) -} - -func (g *GetUserUseCase) GetUserByUsername(username string) (user.User, error) { - return g.userRepository.GetByUsername(username) -} - -func (g *GetUserUseCase) GetUserByEmail(email string) (user.User, error) { - return g.userRepository.GetByEmail(email) -} - -===== FILE: ./internal/infrastructure/useCases/ListArticles.go ===== -package useCases - -import "RenewCMS/internal/domain/article" - -type ListArticlesUseCase struct { - articleRepository article.Repository -} - -func NewListArticlesUseCase(articleRepository article.Repository) *ListArticlesUseCase { - return &ListArticlesUseCase{articleRepository} -} - -func (g *ListArticlesUseCase) ListArticles() []article.Article { - return g.articleRepository.GetAll() -} - -===== FILE: ./internal/infrastructure/useCases/ListUsers.go ===== -package useCases - -import "RenewCMS/internal/domain/user" - -type ListUsersUseCase struct { - userRepository user.Repository -} - -func NewListUsersUseCase(userRepository user.Repository) *ListUsersUseCase { - return &ListUsersUseCase{userRepository} -} - -func (g *ListUsersUseCase) ListUsers() []user.User { - return g.userRepository.GetAll() -} - -===== FILE: ./internal/infrastructure/useCases/SendMail.go ===== -package useCases - -import "RenewCMS/internal/domain/mail" - -type SendMailUseCase struct { - mailRepository mail.Repository -} - -func NewSendMailUseCase(mailRepository mail.Repository) *SendMailUseCase { - return &SendMailUseCase{mailRepository} -} - -func (g *SendMailUseCase) SendMail(receiverAddress string, templateName string, data any) error { - return g.mailRepository.Send(receiverAddress, templateName, data) -} - -===== FILE: ./internal/infrastructure/useCases/UpdateArticle.go ===== -package useCases - -import "RenewCMS/internal/domain/article" - -type UpdateArticleUseCase struct { - articleRepository article.Repository -} - -func NewUpdateArticleUseCase(articleRepository article.Repository) *UpdateArticleUseCase { - return &UpdateArticleUseCase{articleRepository} -} - -func (g *UpdateArticleUseCase) UpdateBody(id uint32, body string) (article.Article, error) { - return g.articleRepository.UpdateBody(id, body) -} - -func (g *UpdateArticleUseCase) AddImage(articleId uint32, imageId uint32) error { - return g.articleRepository.AddImage(articleId, imageId) -} - -func (g *UpdateArticleUseCase) UpdateIsOnline(id uint32, isOnline bool) (article.Article, error) { - return g.articleRepository.UpdateIsOnline(id, isOnline) -} - -===== FILE: ./internal/infrastructure/useCases/UpdateUser.go ===== -package useCases - -import "RenewCMS/internal/domain/user" - -type UpdateUserUseCase struct { - userRepository user.Repository -} - -func NewUpdateUserUseCase(userRepository user.Repository) *UpdateUserUseCase { - return &UpdateUserUseCase{userRepository} -} - -func (g *UpdateUserUseCase) UpdateVerificationStatus(userId uint32, isVerified bool) (user.User, error) { - return g.userRepository.UpdateVerificationStatus(userId, isVerified) -} - -func (g *UpdateUserUseCase) UpdatePasswordResetCode(userId uint32, code string) (user.User, error) { - return g.userRepository.UpdatePasswordResetCode(userId, code) -} - -func (g *UpdateUserUseCase) UpdatePassword(userId uint32, password string) (user.User, error) { - return g.userRepository.UpdatePassword(userId, password) -} -