ALL 0.1.0 Code

This commit is contained in:
2026-02-28 04:21:27 +00:00
commit 7958510989
76 changed files with 17135 additions and 0 deletions

View File

@@ -0,0 +1,199 @@
package handlers
import (
"context"
"os"
"time"
"github.com/fpmb/server/internal/database"
"github.com/fpmb/server/internal/middleware"
"github.com/fpmb/server/internal/models"
"github.com/gofiber/fiber/v2"
"github.com/golang-jwt/jwt/v5"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"golang.org/x/crypto/bcrypt"
)
func jwtSecret() []byte {
s := os.Getenv("JWT_SECRET")
if s == "" {
s = "changeme-jwt-secret"
}
return []byte(s)
}
func jwtRefreshSecret() []byte {
s := os.Getenv("JWT_REFRESH_SECRET")
if s == "" {
s = "changeme-refresh-secret"
}
return []byte(s)
}
func generateTokens(user *models.User) (string, string, error) {
accessClaims := &middleware.JWTClaims{
UserID: user.ID.Hex(),
Email: user.Email,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
accessToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims).SignedString(jwtSecret())
if err != nil {
return "", "", err
}
refreshClaims := &middleware.JWTClaims{
UserID: user.ID.Hex(),
Email: user.Email,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(7 * 24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
refreshToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims).SignedString(jwtRefreshSecret())
if err != nil {
return "", "", err
}
return accessToken, refreshToken, nil
}
func Register(c *fiber.Ctx) error {
var body struct {
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"password"`
}
if err := c.BodyParser(&body); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request body"})
}
if body.Name == "" || body.Email == "" || body.Password == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Name, email and password are required"})
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
col := database.GetCollection("users")
existing := col.FindOne(ctx, bson.M{"email": body.Email})
if existing.Err() == nil {
return c.Status(fiber.StatusConflict).JSON(fiber.Map{"error": "Email already in use"})
}
hash, err := bcrypt.GenerateFromPassword([]byte(body.Password), bcrypt.DefaultCost)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to hash password"})
}
now := time.Now()
user := &models.User{
ID: primitive.NewObjectID(),
Name: body.Name,
Email: body.Email,
PasswordHash: string(hash),
CreatedAt: now,
UpdatedAt: now,
}
if _, err := col.InsertOne(ctx, user); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to create user"})
}
access, refresh, err := generateTokens(user)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to generate tokens"})
}
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
"access_token": access,
"refresh_token": refresh,
"user": fiber.Map{"id": user.ID, "name": user.Name, "email": user.Email},
})
}
func Login(c *fiber.Ctx) error {
var body struct {
Email string `json:"email"`
Password string `json:"password"`
}
if err := c.BodyParser(&body); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request body"})
}
if body.Email == "" || body.Password == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Email and password are required"})
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
var user models.User
err := database.GetCollection("users").FindOne(ctx, bson.M{"email": body.Email}).Decode(&user)
if err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid credentials"})
}
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(body.Password)); err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid credentials"})
}
access, refresh, err := generateTokens(&user)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to generate tokens"})
}
return c.JSON(fiber.Map{
"access_token": access,
"refresh_token": refresh,
"user": fiber.Map{"id": user.ID, "name": user.Name, "email": user.Email},
})
}
func RefreshToken(c *fiber.Ctx) error {
var body struct {
RefreshToken string `json:"refresh_token"`
}
if err := c.BodyParser(&body); err != nil || body.RefreshToken == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "refresh_token is required"})
}
claims := &middleware.JWTClaims{}
token, err := jwt.ParseWithClaims(body.RefreshToken, claims, func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fiber.ErrUnauthorized
}
return jwtRefreshSecret(), nil
})
if err != nil || !token.Valid {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid or expired refresh token"})
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
userID, err := primitive.ObjectIDFromHex(claims.UserID)
if err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid token claims"})
}
var user models.User
if err := database.GetCollection("users").FindOne(ctx, bson.M{"_id": userID}).Decode(&user); err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "User not found"})
}
access, newRefresh, err := generateTokens(&user)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to generate tokens"})
}
return c.JSON(fiber.Map{
"access_token": access,
"refresh_token": newRefresh,
})
}
func Logout(c *fiber.Ctx) error {
return c.JSON(fiber.Map{"message": "Logged out successfully"})
}