init
Some checks failed
Build and Release / release (push) Failing after 51s

This commit is contained in:
2026-03-25 22:29:07 +07:00
parent eedd300861
commit 79199f627d
65 changed files with 3215 additions and 689 deletions

View File

@@ -1 +1,294 @@
package services
package services
import (
"context"
"history-api/internal/dtos/request"
"history-api/internal/dtos/response"
"history-api/internal/gen/sqlc"
"history-api/internal/models"
"history-api/internal/repositories"
"history-api/pkg/config"
"history-api/pkg/constant"
"slices"
"time"
"github.com/gofiber/fiber/v3"
"github.com/golang-jwt/jwt/v5"
"github.com/jackc/pgx/v5/pgtype"
"golang.org/x/crypto/bcrypt"
)
type AuthService interface {
Signin(ctx context.Context, dto *request.SignInDto) (*response.AuthResponse, error)
Signup(ctx context.Context, dto *request.SignUpDto) (*response.AuthResponse, error)
ForgotPassword(ctx context.Context) error
VerifyToken(ctx context.Context) error
CreateToken(ctx context.Context) error
SigninWith3rd(ctx context.Context) error
RefreshToken(ctx context.Context, id string) (*response.AuthResponse, error)
}
type authService struct {
userRepo repositories.UserRepository
roleRepo repositories.RoleRepository
}
func NewAuthService(
userRepo repositories.UserRepository,
roleRepo repositories.RoleRepository,
) AuthService {
return &authService{
userRepo: userRepo,
roleRepo: roleRepo,
}
}
func (a *authService) genToken(Uid string, role []constant.Role) (*response.AuthResponse, error) {
jwtSecret, err := config.GetConfig("JWT_SECRET")
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "missing JWT_SECRET in environment")
}
jwtRefreshSecret, err := config.GetConfig("JWT_REFRESH_SECRET")
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "missing JWT_REFRESH_SECRET in environment")
}
if jwtSecret == "" || jwtRefreshSecret == "" {
return nil, fiber.NewError(fiber.StatusInternalServerError, "missing JWT secrets in environment")
}
claimsAccess := &response.JWTClaims{
UId: Uid,
Roles: role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(30 * time.Minute)),
},
}
claimsRefresh := &response.JWTClaims{
UId: Uid,
Roles: role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * 24 * time.Hour)),
},
}
accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claimsAccess)
at, err := accessToken.SignedString([]byte(jwtSecret))
if err != nil {
return nil, err
}
refreshToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claimsRefresh)
rt, err := refreshToken.SignedString([]byte(jwtRefreshSecret))
if err != nil {
return nil, err
}
res := response.AuthResponse{
AccessToken: at,
RefreshToken: rt,
}
return &res, nil
}
func (a *authService) saveNewRefreshToken(ctx context.Context, params sqlc.UpdateUserRefreshTokenParams) error {
err := a.userRepo.UpdateRefreshToken(ctx, params)
if err != nil {
return err
}
return nil
}
func (a *authService) Signin(ctx context.Context, dto *request.SignInDto) (*response.AuthResponse, error) {
if !constant.EMAIL_REGEX.MatchString(dto.Email) {
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid email")
}
err := constant.ValidatePassword(dto.Password)
if err != nil {
return nil, fiber.NewError(fiber.StatusBadRequest, err.Error())
}
user, err := a.userRepo.GetByEmail(ctx, dto.Email)
if err != nil || user == nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(dto.Password)); err != nil {
return nil, fiber.NewError(fiber.StatusUnauthorized, "Invalid identity or password!")
}
data, err := a.genToken(user.ID, models.RolesEntityToRoleConstant(user.Roles))
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
var pgID pgtype.UUID
err = pgID.Scan(user.ID)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
err = a.saveNewRefreshToken(
ctx,
sqlc.UpdateUserRefreshTokenParams{
ID: pgID,
RefreshToken: pgtype.Text{
String: data.RefreshToken,
Valid: data.RefreshToken != "",
},
},
)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
return data, nil
}
func (a *authService) RefreshToken(ctx context.Context, id string) (*response.AuthResponse, error) {
var pgID pgtype.UUID
err := pgID.Scan(id)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
user, err := a.userRepo.GetByID(ctx, pgID)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid user data")
}
roles := models.RolesEntityToRoleConstant(user.Roles)
if slices.Contains(roles, constant.BANNED) {
return nil, fiber.NewError(fiber.StatusUnauthorized, "User is banned!")
}
data, err := a.genToken(id, roles)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
err = a.saveNewRefreshToken(
ctx,
sqlc.UpdateUserRefreshTokenParams{
ID: pgID,
RefreshToken: pgtype.Text{
String: data.RefreshToken,
Valid: data.RefreshToken != "",
},
},
)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
return data, nil
}
func (a *authService) Signup(ctx context.Context, dto *request.SignUpDto) (*response.AuthResponse, error) {
if !constant.EMAIL_REGEX.MatchString(dto.Email) {
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid email")
}
err := constant.ValidatePassword(dto.Password)
if err != nil {
return nil, fiber.NewError(fiber.StatusBadRequest, err.Error())
}
user, err := a.userRepo.GetByEmail(ctx, dto.Email)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
if user != nil {
return nil, fiber.NewError(fiber.StatusBadRequest, "User already exists")
}
hashed, err := bcrypt.GenerateFromPassword([]byte(dto.Password), bcrypt.DefaultCost)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
user, err = a.userRepo.UpsertUser(
ctx,
sqlc.UpsertUserParams{
Email: dto.Email,
PasswordHash: pgtype.Text{
String: string(hashed),
Valid: len(hashed) != 0,
},
IsVerified: true,
},
)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
var userId pgtype.UUID
err = userId.Scan(user.ID)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
_, err = a.userRepo.CreateProfile(
ctx,
sqlc.CreateUserProfileParams{
UserID: userId,
DisplayName: pgtype.Text{
String: dto.DisplayName,
Valid: dto.DisplayName != "",
},
},
)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
err = a.roleRepo.AddUserRole(
ctx,
sqlc.AddUserRoleParams{
UserID: userId,
Name: constant.USER.String(),
},
)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
data, err := a.genToken(user.ID, constant.USER.ToSlice())
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
err = a.saveNewRefreshToken(
ctx,
sqlc.UpdateUserRefreshTokenParams{
ID: userId,
RefreshToken: pgtype.Text{
String: data.RefreshToken,
Valid: data.RefreshToken != "",
},
},
)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
return data, nil
}
// ForgotPassword implements [AuthService].
func (a *authService) ForgotPassword(ctx context.Context) error {
panic("unimplemented")
}
// SigninWith3rd implements [AuthService].
func (a *authService) SigninWith3rd(ctx context.Context) error {
panic("unimplemented")
}
// CreateToken implements [AuthService].
func (a *authService) CreateToken(ctx context.Context) error {
panic("unimplemented")
}
// Verify implements [AuthService].
func (a *authService) VerifyToken(ctx context.Context) error {
panic("unimplemented")
}

View File

@@ -0,0 +1,57 @@
package services
import (
"context"
"history-api/internal/repositories"
"github.com/gofiber/fiber/v3"
)
type TileService interface {
GetMetadata(ctx context.Context) (map[string]string, error)
GetTile(ctx context.Context, z, x, y int) ([]byte, map[string]string, error)
}
type tileService struct {
tileRepo repositories.TileRepository
}
func NewTileService(
TileRepo repositories.TileRepository,
) TileService {
return &tileService{
tileRepo: TileRepo,
}
}
func (t *tileService) GetMetadata(ctx context.Context) (map[string]string, error) {
metaData, err := t.tileRepo.GetMetadata(ctx)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
return metaData, nil
}
func (t *tileService) GetTile(ctx context.Context, z, x, y int) ([]byte, map[string]string, error) {
contentType := make(map[string]string)
data, format, isPBF, err := t.tileRepo.GetTile(ctx, z, x, y)
if err != nil {
return nil, contentType, fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
contentType["Content-Type"] = "image/png"
if format == "jpg" {
contentType["Content-Type"] = "image/jpeg"
}
if format == "pbf" {
contentType["Content-Type"] = "application/x-protobuf"
}
if isPBF {
contentType["Content-Encoding"] = "gzip"
}
return data, contentType, nil
}

View File

@@ -0,0 +1,83 @@
package services
import (
"context"
"history-api/internal/dtos/request"
"history-api/internal/dtos/response"
"history-api/internal/repositories"
)
type UserService interface {
//user
GetUserCurrent(ctx context.Context, dto *request.SignInDto) (*response.AuthResponse, error)
UpdateProfile(ctx context.Context, id string) (*response.UserResponse, error)
ChangePassword(ctx context.Context, id string) (*response.UserResponse, error)
//admin
DeleteUser(ctx context.Context, id string) (*response.UserResponse, error)
ChangeRoleUser(ctx context.Context, id string) (*response.UserResponse, error)
RestoreUser(ctx context.Context, id string) (*response.UserResponse, error)
GetUserByID(ctx context.Context, id string) (*response.UserResponse, error)
Search(ctx context.Context, id string) ([]*response.UserResponse, error)
GetAllUser(ctx context.Context, id string) ([]*response.UserResponse, error)
}
type userService struct {
userRepo repositories.UserRepository
roleRepo repositories.RoleRepository
}
func NewUserService(
userRepo repositories.UserRepository,
roleRepo repositories.RoleRepository,
) UserService {
return &userService{
userRepo: userRepo,
roleRepo: roleRepo,
}
}
// ChangePassword implements [UserService].
func (u *userService) ChangePassword(ctx context.Context, id string) (*response.UserResponse, error) {
panic("unimplemented")
}
// ChangeRoleUser implements [UserService].
func (u *userService) ChangeRoleUser(ctx context.Context, id string) (*response.UserResponse, error) {
panic("unimplemented")
}
// DeleteUser implements [UserService].
func (u *userService) DeleteUser(ctx context.Context, id string) (*response.UserResponse, error) {
panic("unimplemented")
}
// GetAllUser implements [UserService].
func (u *userService) GetAllUser(ctx context.Context, id string) ([]*response.UserResponse, error) {
panic("unimplemented")
}
// GetUserByID implements [UserService].
func (u *userService) GetUserByID(ctx context.Context, id string) (*response.UserResponse, error) {
panic("unimplemented")
}
// GetUserCurrent implements [UserService].
func (u *userService) GetUserCurrent(ctx context.Context, dto *request.SignInDto) (*response.AuthResponse, error) {
panic("unimplemented")
}
// RestoreUser implements [UserService].
func (u *userService) RestoreUser(ctx context.Context, id string) (*response.UserResponse, error) {
panic("unimplemented")
}
// Search implements [UserService].
func (u *userService) Search(ctx context.Context, id string) ([]*response.UserResponse, error) {
panic("unimplemented")
}
// UpdateProfile implements [UserService].
func (u *userService) UpdateProfile(ctx context.Context, id string) (*response.UserResponse, error) {
panic("unimplemented")
}