ALL 0.1.0 Code
This commit is contained in:
230
server/internal/handlers/users.go
Normal file
230
server/internal/handlers/users.go
Normal file
@@ -0,0 +1,230 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fpmb/server/internal/database"
|
||||
"github.com/fpmb/server/internal/models"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func GetMe(c *fiber.Ctx) error {
|
||||
userID, err := primitive.ObjectIDFromHex(c.Locals("user_id").(string))
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid user"})
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var user models.User
|
||||
if err := database.GetCollection("users").FindOne(ctx, bson.M{"_id": userID}).Decode(&user); err != nil {
|
||||
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
|
||||
}
|
||||
|
||||
return c.JSON(user)
|
||||
}
|
||||
|
||||
func UpdateMe(c *fiber.Ctx) error {
|
||||
userID, err := primitive.ObjectIDFromHex(c.Locals("user_id").(string))
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid user"})
|
||||
}
|
||||
|
||||
var body struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
}
|
||||
if err := c.BodyParser(&body); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request body"})
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
update := bson.M{"updated_at": time.Now()}
|
||||
if body.Name != "" {
|
||||
update["name"] = body.Name
|
||||
}
|
||||
if body.Email != "" {
|
||||
update["email"] = body.Email
|
||||
}
|
||||
if body.AvatarURL != "" {
|
||||
update["avatar_url"] = body.AvatarURL
|
||||
}
|
||||
|
||||
col := database.GetCollection("users")
|
||||
if _, err := col.UpdateOne(ctx, bson.M{"_id": userID}, bson.M{"$set": update}); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to update user"})
|
||||
}
|
||||
|
||||
var user models.User
|
||||
if err := col.FindOne(ctx, bson.M{"_id": userID}).Decode(&user); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to fetch updated user"})
|
||||
}
|
||||
|
||||
return c.JSON(user)
|
||||
}
|
||||
|
||||
func SearchUsers(c *fiber.Ctx) error {
|
||||
q := c.Query("q")
|
||||
if len(q) < 1 {
|
||||
return c.JSON([]fiber.Map{})
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
filter := bson.M{
|
||||
"$or": bson.A{
|
||||
bson.M{"name": bson.M{"$regex": q, "$options": "i"}},
|
||||
bson.M{"email": bson.M{"$regex": q, "$options": "i"}},
|
||||
},
|
||||
}
|
||||
|
||||
cursor, err := database.GetCollection("users").Find(ctx, filter, options.Find().SetLimit(10))
|
||||
if err != nil {
|
||||
return c.JSON([]fiber.Map{})
|
||||
}
|
||||
defer cursor.Close(ctx)
|
||||
|
||||
var users []models.User
|
||||
cursor.All(ctx, &users)
|
||||
|
||||
type UserResult struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
result := []UserResult{}
|
||||
for _, u := range users {
|
||||
result = append(result, UserResult{ID: u.ID.Hex(), Name: u.Name, Email: u.Email})
|
||||
}
|
||||
return c.JSON(result)
|
||||
}
|
||||
|
||||
func UploadUserAvatar(c *fiber.Ctx) error {
|
||||
userID, err := primitive.ObjectIDFromHex(c.Locals("user_id").(string))
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid user"})
|
||||
}
|
||||
|
||||
fh, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "No file provided"})
|
||||
}
|
||||
|
||||
ext := strings.ToLower(filepath.Ext(fh.Filename))
|
||||
if !allowedImageExts[ext] {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid image type"})
|
||||
}
|
||||
|
||||
dir := filepath.Join("../data/users", userID.Hex())
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
log.Printf("UploadUserAvatar MkdirAll error: %v", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to create storage directory"})
|
||||
}
|
||||
|
||||
existingGlob := filepath.Join(dir, "avatar.*")
|
||||
if matches, _ := filepath.Glob(existingGlob); len(matches) > 0 {
|
||||
for _, m := range matches {
|
||||
os.Remove(m)
|
||||
}
|
||||
}
|
||||
|
||||
destPath := filepath.Join(dir, "avatar"+ext)
|
||||
if err := c.SaveFile(fh, destPath); err != nil {
|
||||
log.Printf("UploadUserAvatar SaveFile error: %v", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to save image"})
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
col := database.GetCollection("users")
|
||||
if _, err := col.UpdateOne(ctx, bson.M{"_id": userID}, bson.M{"$set": bson.M{
|
||||
"avatar_url": "/api/users/me/avatar",
|
||||
"updated_at": time.Now(),
|
||||
}}); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to update user"})
|
||||
}
|
||||
|
||||
var user models.User
|
||||
if err := col.FindOne(ctx, bson.M{"_id": userID}).Decode(&user); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to fetch updated user"})
|
||||
}
|
||||
|
||||
return c.JSON(user)
|
||||
}
|
||||
|
||||
func ServeUserAvatar(c *fiber.Ctx) error {
|
||||
userID, err := primitive.ObjectIDFromHex(c.Locals("user_id").(string))
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid user"})
|
||||
}
|
||||
|
||||
dir := filepath.Join("../data/users", userID.Hex())
|
||||
for ext := range allowedImageExts {
|
||||
p := filepath.Join(dir, "avatar"+ext)
|
||||
if _, err := os.Stat(p); err == nil {
|
||||
return c.SendFile(p)
|
||||
}
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Avatar not found"})
|
||||
}
|
||||
|
||||
func ChangePassword(c *fiber.Ctx) error {
|
||||
userID, err := primitive.ObjectIDFromHex(c.Locals("user_id").(string))
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid user"})
|
||||
}
|
||||
|
||||
var body struct {
|
||||
CurrentPassword string `json:"current_password"`
|
||||
NewPassword string `json:"new_password"`
|
||||
}
|
||||
if err := c.BodyParser(&body); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request body"})
|
||||
}
|
||||
if body.CurrentPassword == "" || body.NewPassword == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "current_password and new_password are required"})
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
col := database.GetCollection("users")
|
||||
var user models.User
|
||||
if err := col.FindOne(ctx, bson.M{"_id": userID}).Decode(&user); err != nil {
|
||||
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
|
||||
}
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(body.CurrentPassword)); err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Current password is incorrect"})
|
||||
}
|
||||
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(body.NewPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to hash password"})
|
||||
}
|
||||
|
||||
if _, err := col.UpdateOne(ctx, bson.M{"_id": userID}, bson.M{"$set": bson.M{
|
||||
"password_hash": string(hash),
|
||||
"updated_at": time.Now(),
|
||||
}}); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to update password"})
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"message": "Password updated successfully"})
|
||||
}
|
||||
Reference in New Issue
Block a user