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,590 @@
package handlers
import (
"context"
"fmt"
"log"
"os"
"path/filepath"
"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"
)
func storageBase(ctx context.Context, projectID primitive.ObjectID) (string, error) {
var project models.Project
if err := database.GetCollection("projects").FindOne(ctx, bson.M{"_id": projectID}).Decode(&project); err != nil {
return "", err
}
if project.TeamID == primitive.NilObjectID {
return filepath.Join("../data/users", project.CreatedBy.Hex()), nil
}
return filepath.Join("../data/teams", project.TeamID.Hex()), nil
}
func ListFiles(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"})
}
projectID, err := primitive.ObjectIDFromHex(c.Params("projectId"))
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid project ID"})
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if _, err := getProjectRole(ctx, projectID, userID); err != nil {
log.Printf("ListFiles getProjectRole error: %v (projectID=%s userID=%s)", err, projectID.Hex(), userID.Hex())
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "Access denied"})
}
filter := bson.M{"project_id": projectID}
if parentID := c.Query("parent_id"); parentID != "" {
oid, err := primitive.ObjectIDFromHex(parentID)
if err == nil {
filter["parent_id"] = oid
}
} else {
filter["parent_id"] = bson.M{"$exists": false}
}
cursor, err := database.GetCollection("files").Find(ctx, filter,
options.Find().SetSort(bson.D{{Key: "type", Value: -1}, {Key: "name", Value: 1}}))
if err != nil {
log.Printf("ListFiles Find error: %v (filter=%v)", err, filter)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to fetch files"})
}
defer cursor.Close(ctx)
var files []models.File
cursor.All(ctx, &files)
if files == nil {
files = []models.File{}
}
return c.JSON(files)
}
func CreateFolder(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"})
}
projectID, err := primitive.ObjectIDFromHex(c.Params("projectId"))
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid project ID"})
}
var body struct {
Name string `json:"name"`
ParentID string `json:"parent_id"`
}
if err := c.BodyParser(&body); err != nil || body.Name == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Name is required"})
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
roleFlags, err := getProjectRole(ctx, projectID, userID)
if err != nil || !hasPermission(roleFlags, RoleEditor) {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "Insufficient permissions"})
}
now := time.Now()
file := &models.File{
ID: primitive.NewObjectID(),
ProjectID: projectID,
Name: body.Name,
Type: "folder",
CreatedBy: userID,
CreatedAt: now,
UpdatedAt: now,
}
if body.ParentID != "" {
if oid, err := primitive.ObjectIDFromHex(body.ParentID); err == nil {
file.ParentID = &oid
}
}
database.GetCollection("files").InsertOne(ctx, file)
return c.Status(fiber.StatusCreated).JSON(file)
}
func UploadFile(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"})
}
projectID, err := primitive.ObjectIDFromHex(c.Params("projectId"))
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid project ID"})
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
roleFlags, err := getProjectRole(ctx, projectID, userID)
if err != nil || !hasPermission(roleFlags, RoleEditor) {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "Insufficient permissions"})
}
fh, err := c.FormFile("file")
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "No file provided"})
}
base, err := storageBase(ctx, projectID)
if err != nil {
log.Printf("UploadFile storageBase error: %v (projectID=%s)", err, projectID.Hex())
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to resolve storage path"})
}
if err := os.MkdirAll(base, 0755); err != nil {
log.Printf("UploadFile MkdirAll error: %v (base=%s)", err, base)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to create storage directory"})
}
filename := fh.Filename
ext := filepath.Ext(filename)
stem := filename[:len(filename)-len(ext)]
destPath := filepath.Join(base, filename)
for n := 2; ; n++ {
if _, statErr := os.Stat(destPath); statErr != nil {
break
}
filename = fmt.Sprintf("%s (%d)%s", stem, n, ext)
destPath = filepath.Join(base, filename)
}
if err := c.SaveFile(fh, destPath); err != nil {
log.Printf("UploadFile SaveFile error: %v (destPath=%s)", err, destPath)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to save file"})
}
relPath := destPath[len("../data/"):]
now := time.Now()
file := &models.File{
ID: primitive.NewObjectID(),
ProjectID: projectID,
Name: fh.Filename,
Type: "file",
SizeBytes: fh.Size,
StorageURL: relPath,
CreatedBy: userID,
CreatedAt: now,
UpdatedAt: now,
}
parentID := c.FormValue("parent_id")
if parentID != "" {
if oid, err := primitive.ObjectIDFromHex(parentID); err == nil {
file.ParentID = &oid
}
}
database.GetCollection("files").InsertOne(ctx, file)
return c.Status(fiber.StatusCreated).JSON(file)
}
func ListTeamFiles(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"})
}
teamID, err := primitive.ObjectIDFromHex(c.Params("teamId"))
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid team ID"})
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if _, err := getTeamRole(ctx, teamID, userID); err != nil {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "Access denied"})
}
filter := bson.M{"team_id": teamID}
if parentID := c.Query("parent_id"); parentID != "" {
oid, err := primitive.ObjectIDFromHex(parentID)
if err == nil {
filter["parent_id"] = oid
}
} else {
filter["parent_id"] = bson.M{"$exists": false}
}
cursor, err := database.GetCollection("files").Find(ctx, filter,
options.Find().SetSort(bson.D{{Key: "type", Value: -1}, {Key: "name", Value: 1}}))
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to fetch files"})
}
defer cursor.Close(ctx)
var files []models.File
cursor.All(ctx, &files)
if files == nil {
files = []models.File{}
}
return c.JSON(files)
}
func CreateTeamFolder(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"})
}
teamID, err := primitive.ObjectIDFromHex(c.Params("teamId"))
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid team ID"})
}
var body struct {
Name string `json:"name"`
ParentID string `json:"parent_id"`
}
if err := c.BodyParser(&body); err != nil || body.Name == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Name is required"})
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
roleFlags, err := getTeamRole(ctx, teamID, userID)
if err != nil || !hasPermission(roleFlags, RoleEditor) {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "Insufficient permissions"})
}
now := time.Now()
file := &models.File{
ID: primitive.NewObjectID(),
TeamID: teamID,
Name: body.Name,
Type: "folder",
CreatedBy: userID,
CreatedAt: now,
UpdatedAt: now,
}
if body.ParentID != "" {
if oid, err := primitive.ObjectIDFromHex(body.ParentID); err == nil {
file.ParentID = &oid
}
}
database.GetCollection("files").InsertOne(ctx, file)
return c.Status(fiber.StatusCreated).JSON(file)
}
func UploadTeamFile(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"})
}
teamID, err := primitive.ObjectIDFromHex(c.Params("teamId"))
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid team ID"})
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
roleFlags, err := getTeamRole(ctx, teamID, userID)
if err != nil || !hasPermission(roleFlags, RoleEditor) {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "Insufficient permissions"})
}
fh, err := c.FormFile("file")
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "No file provided"})
}
base := filepath.Join("../data/teams", teamID.Hex(), "files")
if err := os.MkdirAll(base, 0755); err != nil {
log.Printf("UploadTeamFile MkdirAll error: %v (base=%s)", err, base)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to create storage directory"})
}
filename := fh.Filename
ext := filepath.Ext(filename)
stem := filename[:len(filename)-len(ext)]
destPath := filepath.Join(base, filename)
for n := 2; ; n++ {
if _, statErr := os.Stat(destPath); statErr != nil {
break
}
filename = fmt.Sprintf("%s (%d)%s", stem, n, ext)
destPath = filepath.Join(base, filename)
}
if err := c.SaveFile(fh, destPath); err != nil {
log.Printf("UploadTeamFile SaveFile error: %v (destPath=%s)", err, destPath)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to save file"})
}
relPath := destPath[len("../data/"):]
now := time.Now()
file := &models.File{
ID: primitive.NewObjectID(),
TeamID: teamID,
Name: fh.Filename,
Type: "file",
SizeBytes: fh.Size,
StorageURL: relPath,
CreatedBy: userID,
CreatedAt: now,
UpdatedAt: now,
}
parentID := c.FormValue("parent_id")
if parentID != "" {
if oid, err := primitive.ObjectIDFromHex(parentID); err == nil {
file.ParentID = &oid
}
}
database.GetCollection("files").InsertOne(ctx, file)
return c.Status(fiber.StatusCreated).JSON(file)
}
func ListUserFiles(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()
filter := bson.M{"user_id": userID}
if parentID := c.Query("parent_id"); parentID != "" {
oid, err := primitive.ObjectIDFromHex(parentID)
if err == nil {
filter["parent_id"] = oid
}
} else {
filter["parent_id"] = bson.M{"$exists": false}
}
cursor, err := database.GetCollection("files").Find(ctx, filter,
options.Find().SetSort(bson.D{{Key: "type", Value: -1}, {Key: "name", Value: 1}}))
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to fetch files"})
}
defer cursor.Close(ctx)
var files []models.File
cursor.All(ctx, &files)
if files == nil {
files = []models.File{}
}
return c.JSON(files)
}
func CreateUserFolder(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"`
ParentID string `json:"parent_id"`
}
if err := c.BodyParser(&body); err != nil || body.Name == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Name is required"})
}
now := time.Now()
file := &models.File{
ID: primitive.NewObjectID(),
UserID: userID,
Name: body.Name,
Type: "folder",
CreatedBy: userID,
CreatedAt: now,
UpdatedAt: now,
}
if body.ParentID != "" {
if oid, err := primitive.ObjectIDFromHex(body.ParentID); err == nil {
file.ParentID = &oid
}
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
database.GetCollection("files").InsertOne(ctx, file)
return c.Status(fiber.StatusCreated).JSON(file)
}
func UploadUserFile(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"})
}
base := filepath.Join("../data/users", userID.Hex(), "files")
if err := os.MkdirAll(base, 0755); err != nil {
log.Printf("UploadUserFile MkdirAll error: %v (base=%s)", err, base)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to create storage directory"})
}
filename := fh.Filename
ext := filepath.Ext(filename)
stem := filename[:len(filename)-len(ext)]
destPath := filepath.Join(base, filename)
for n := 2; ; n++ {
if _, statErr := os.Stat(destPath); statErr != nil {
break
}
filename = fmt.Sprintf("%s (%d)%s", stem, n, ext)
destPath = filepath.Join(base, filename)
}
if err := c.SaveFile(fh, destPath); err != nil {
log.Printf("UploadUserFile SaveFile error: %v (destPath=%s)", err, destPath)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to save file"})
}
relPath := destPath[len("../data/"):]
now := time.Now()
file := &models.File{
ID: primitive.NewObjectID(),
UserID: userID,
Name: fh.Filename,
Type: "file",
SizeBytes: fh.Size,
StorageURL: relPath,
CreatedBy: userID,
CreatedAt: now,
UpdatedAt: now,
}
parentID := c.FormValue("parent_id")
if parentID != "" {
if oid, err := primitive.ObjectIDFromHex(parentID); err == nil {
file.ParentID = &oid
}
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
database.GetCollection("files").InsertOne(ctx, file)
return c.Status(fiber.StatusCreated).JSON(file)
}
func DownloadFile(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"})
}
fileID, err := primitive.ObjectIDFromHex(c.Params("fileId"))
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid file ID"})
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
var file models.File
if err := database.GetCollection("files").FindOne(ctx, bson.M{"_id": fileID}).Decode(&file); err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "File not found"})
}
if file.TeamID != primitive.NilObjectID {
if _, err := getTeamRole(ctx, file.TeamID, userID); err != nil {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "Access denied"})
}
} else if file.UserID != primitive.NilObjectID {
if file.UserID != userID {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "Access denied"})
}
} else {
if _, err := getProjectRole(ctx, file.ProjectID, userID); err != nil {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "Access denied"})
}
}
if file.Type == "folder" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Cannot download a folder"})
}
diskPath := filepath.Join("../data", file.StorageURL)
if _, err := os.Stat(diskPath); err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "File not found on disk"})
}
c.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, file.Name))
return c.SendFile(diskPath)
}
func DeleteFile(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"})
}
fileID, err := primitive.ObjectIDFromHex(c.Params("fileId"))
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid file ID"})
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
var file models.File
if err := database.GetCollection("files").FindOne(ctx, bson.M{"_id": fileID}).Decode(&file); err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "File not found"})
}
var roleFlags int
if file.TeamID != primitive.NilObjectID {
roleFlags, err = getTeamRole(ctx, file.TeamID, userID)
} else if file.UserID != primitive.NilObjectID {
if file.UserID == userID {
roleFlags = RoleOwner
} else {
err = fmt.Errorf("access denied")
}
} else {
roleFlags, err = getProjectRole(ctx, file.ProjectID, userID)
}
if err != nil || !hasPermission(roleFlags, RoleEditor) {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "Insufficient permissions"})
}
database.GetCollection("files").DeleteOne(ctx, bson.M{"_id": fileID})
if file.Type == "folder" {
database.GetCollection("files").DeleteMany(ctx, bson.M{"parent_id": fileID})
} else if file.StorageURL != "" {
os.Remove(filepath.Join("../data", file.StorageURL))
}
return c.JSON(fiber.Map{"message": "Deleted"})
}