Files
FPMB/server/internal/handlers/docs.go
2026-02-28 04:21:27 +00:00

222 lines
6.8 KiB
Go

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 ListDocs(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"})
}
cursor, err := database.GetCollection("docs").Find(ctx,
bson.M{"team_id": teamID},
options.Find().SetSort(bson.M{"updated_at": -1}).SetProjection(bson.M{"content": 0}))
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to fetch docs"})
}
defer cursor.Close(ctx)
var docs []models.Doc
cursor.All(ctx, &docs)
if docs == nil {
docs = []models.Doc{}
}
return c.JSON(docs)
}
func CreateDoc(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 {
Title string `json:"title"`
Content string `json:"content"`
}
if err := c.BodyParser(&body); err != nil || body.Title == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Title 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()
doc := &models.Doc{
ID: primitive.NewObjectID(),
TeamID: teamID,
Title: body.Title,
Content: body.Content,
CreatedBy: userID,
CreatedAt: now,
UpdatedAt: now,
}
database.GetCollection("docs").InsertOne(ctx, doc)
docDir := filepath.Join("../data/teams", teamID.Hex(), "docs")
if err := os.MkdirAll(docDir, 0755); err != nil {
log.Printf("CreateDoc: mkdir %s: %v", docDir, err)
} else {
content := fmt.Sprintf("# %s\n\n%s", doc.Title, doc.Content)
if err := os.WriteFile(filepath.Join(docDir, doc.ID.Hex()+".md"), []byte(content), 0644); err != nil {
log.Printf("CreateDoc: write file: %v", err)
}
}
return c.Status(fiber.StatusCreated).JSON(doc)
}
func GetDoc(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"})
}
docID, err := primitive.ObjectIDFromHex(c.Params("docId"))
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid doc ID"})
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
var doc models.Doc
if err := database.GetCollection("docs").FindOne(ctx, bson.M{"_id": docID}).Decode(&doc); err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Doc not found"})
}
if _, err := getTeamRole(ctx, doc.TeamID, userID); err != nil {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "Access denied"})
}
return c.JSON(doc)
}
func UpdateDoc(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"})
}
docID, err := primitive.ObjectIDFromHex(c.Params("docId"))
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid doc ID"})
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
var existing models.Doc
if err := database.GetCollection("docs").FindOne(ctx, bson.M{"_id": docID}).Decode(&existing); err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Doc not found"})
}
roleFlags, err := getTeamRole(ctx, existing.TeamID, userID)
if err != nil || !hasPermission(roleFlags, RoleEditor) {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "Insufficient permissions"})
}
var body struct {
Title string `json:"title"`
Content string `json:"content"`
}
c.BodyParser(&body)
update := bson.M{"updated_at": time.Now()}
if body.Title != "" {
update["title"] = body.Title
}
if body.Content != "" {
update["content"] = body.Content
}
col := database.GetCollection("docs")
col.UpdateOne(ctx, bson.M{"_id": docID}, bson.M{"$set": update})
var doc models.Doc
col.FindOne(ctx, bson.M{"_id": docID}).Decode(&doc)
docDir := filepath.Join("../data/teams", existing.TeamID.Hex(), "docs")
if err := os.MkdirAll(docDir, 0755); err != nil {
log.Printf("UpdateDoc: mkdir %s: %v", docDir, err)
} else {
content := fmt.Sprintf("# %s\n\n%s", doc.Title, doc.Content)
if err := os.WriteFile(filepath.Join(docDir, doc.ID.Hex()+".md"), []byte(content), 0644); err != nil {
log.Printf("UpdateDoc: write file: %v", err)
}
}
return c.JSON(doc)
}
func DeleteDoc(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"})
}
docID, err := primitive.ObjectIDFromHex(c.Params("docId"))
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid doc ID"})
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
var doc models.Doc
if err := database.GetCollection("docs").FindOne(ctx, bson.M{"_id": docID}).Decode(&doc); err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Doc not found"})
}
roleFlags, err := getTeamRole(ctx, doc.TeamID, userID)
if err != nil || !hasPermission(roleFlags, RoleAdmin) {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "Insufficient permissions"})
}
database.GetCollection("docs").DeleteOne(ctx, bson.M{"_id": docID})
mdPath := filepath.Join("../data/teams", doc.TeamID.Hex(), "docs", docID.Hex()+".md")
if err := os.Remove(mdPath); err != nil && !os.IsNotExist(err) {
log.Printf("DeleteDoc: remove file %s: %v", mdPath, err)
}
return c.JSON(fiber.Map{"message": "Doc deleted"})
}