668 lines
18 KiB
Go
668 lines
18 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"github.com/yourusername/akiyama-manga/internal/auth"
|
|
"github.com/yourusername/akiyama-manga/internal/models"
|
|
"github.com/yourusername/akiyama-manga/internal/services"
|
|
"github.com/yourusername/akiyama-manga/internal/storage"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// Home handler returns the home page
|
|
func Home(c *gin.Context) {
|
|
c.HTML(http.StatusOK, "index.html", gin.H{
|
|
"title": "Akiyama Manga - Read and Share Manga",
|
|
})
|
|
}
|
|
|
|
// Reader handler returns the manga reader page
|
|
func Reader(c *gin.Context) {
|
|
c.HTML(http.StatusOK, "reader.html", gin.H{
|
|
"title": "Manga Reader - Akiyama Manga",
|
|
})
|
|
}
|
|
|
|
// GetMangas returns a list of mangas with pagination
|
|
func GetMangas(db interface{}) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
gormDB := db.(*gorm.DB)
|
|
|
|
page := c.DefaultQuery("page", "1")
|
|
pageNum, err := strconv.Atoi(page)
|
|
if err != nil || pageNum < 1 {
|
|
pageNum = 1
|
|
}
|
|
|
|
limit := 20
|
|
offset := (pageNum - 1) * limit
|
|
|
|
var mangas []models.Manga
|
|
var total int64
|
|
|
|
// Get total count
|
|
if err := gormDB.Model(&models.Manga{}).Count(&total).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch manga list"})
|
|
return
|
|
}
|
|
|
|
// Get paginated mangas
|
|
if err := gormDB.
|
|
Preload("Chapters", func(db *gorm.DB) *gorm.DB {
|
|
return db.Order("number DESC").Limit(5)
|
|
}).
|
|
Order("created_at DESC").
|
|
Offset(offset).
|
|
Limit(limit).
|
|
Find(&mangas).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch mangas"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"data": mangas,
|
|
"pagination": gin.H{
|
|
"page": pageNum,
|
|
"limit": limit,
|
|
"total": total,
|
|
"total_pages": (total + int64(limit) - 1) / int64(limit),
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
// GetMangaDetail returns details of a specific manga
|
|
func GetMangaDetail(db interface{}) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
gormDB := db.(*gorm.DB)
|
|
mangaID := c.Param("id")
|
|
|
|
var manga models.Manga
|
|
if err := gormDB.
|
|
Preload("Chapters", func(db *gorm.DB) *gorm.DB {
|
|
return db.Order("number DESC")
|
|
}).
|
|
Preload("Chapters.Pages", func(db *gorm.DB) *gorm.DB {
|
|
return db.Order("page_num ASC")
|
|
}).
|
|
First(&manga, "id = ?", mangaID).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "manga not found"})
|
|
} else {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to fetch manga"})
|
|
}
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, manga)
|
|
}
|
|
}
|
|
|
|
// GetChapterPages returns pages for a specific chapter
|
|
func GetChapterPages(db interface{}) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
c.JSON(http.StatusOK, gin.H{})
|
|
}
|
|
}
|
|
|
|
// SearchMangas searches for mangas
|
|
func SearchMangas(db interface{}) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"results": []gin.H{},
|
|
})
|
|
}
|
|
}
|
|
|
|
// SignUp handles user registration
|
|
func SignUp(db interface{}) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
gormDB := db.(*gorm.DB)
|
|
|
|
var req struct {
|
|
DisplayName string `json:"displayName" binding:"required"`
|
|
Username string `json:"username" binding:"required"`
|
|
Email string `json:"email" binding:"required,email"`
|
|
Password string `json:"password" binding:"required,min=6"`
|
|
}
|
|
|
|
// Parse request body
|
|
if err := c.BindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Check if user already exists
|
|
var existingUser models.User
|
|
if err := gormDB.Where("email = ? OR username = ?", req.Email, req.Username).First(&existingUser).Error; err == nil {
|
|
c.JSON(http.StatusConflict, gin.H{"error": "Email or username already exists"})
|
|
return
|
|
} else if err != gorm.ErrRecordNotFound {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error"})
|
|
return
|
|
}
|
|
|
|
// Hash password
|
|
hashedPassword, err := auth.HashPassword(req.Password)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Password hashing failed"})
|
|
return
|
|
}
|
|
|
|
// Create new user
|
|
newUser := models.User{
|
|
ID: uuid.New(),
|
|
Email: req.Email,
|
|
Username: req.Username,
|
|
Password: hashedPassword,
|
|
}
|
|
|
|
// Save to database
|
|
if err := gormDB.Create(&newUser).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create user"})
|
|
return
|
|
}
|
|
|
|
// Generate JWT token
|
|
token, err := auth.GenerateToken(newUser.ID, newUser.Email, newUser.Username, newUser.IsAdmin)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Token generation failed"})
|
|
return
|
|
}
|
|
|
|
// Set auth cookie
|
|
c.SetCookie("auth_token", token, 86400, "/", "", false, true)
|
|
|
|
c.JSON(http.StatusCreated, gin.H{
|
|
"token": token,
|
|
"user": gin.H{
|
|
"id": newUser.ID,
|
|
"email": newUser.Email,
|
|
"name": newUser.Username,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
// SignIn handles user login
|
|
func SignIn(db interface{}) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
gormDB := db.(*gorm.DB)
|
|
|
|
var req struct {
|
|
Email string `json:"email" binding:"required,email"`
|
|
Password string `json:"password" binding:"required"`
|
|
}
|
|
|
|
// Parse request body
|
|
if err := c.BindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Find user by email
|
|
var user models.User
|
|
if err := gormDB.Where("email = ?", req.Email).First(&user).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"})
|
|
} else {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error"})
|
|
}
|
|
return
|
|
}
|
|
|
|
// Verify password
|
|
if !auth.CheckPassword(user.Password, req.Password) {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"})
|
|
return
|
|
}
|
|
|
|
// Generate JWT token
|
|
token, err := auth.GenerateToken(user.ID, user.Email, user.Username, user.IsAdmin)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Token generation failed"})
|
|
return
|
|
}
|
|
|
|
// Set auth cookie
|
|
c.SetCookie("auth_token", token, 86400, "/", "", false, true)
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"token": token,
|
|
"user": gin.H{
|
|
"id": user.ID,
|
|
"email": user.Email,
|
|
"name": user.Username,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
// GetMe returns the current authenticated user
|
|
func GetMe(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
// Get user ID from JWT claims (set by middleware)
|
|
userIDInterface, exists := c.Get("user_id")
|
|
if !exists {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "user not found in context"})
|
|
return
|
|
}
|
|
|
|
// Convert UUID to string
|
|
var userID string
|
|
switch v := userIDInterface.(type) {
|
|
case string:
|
|
userID = v
|
|
case uuid.UUID:
|
|
userID = v.String()
|
|
default:
|
|
userID = fmt.Sprintf("%v", userIDInterface)
|
|
}
|
|
|
|
// Fetch user from database
|
|
var user models.User
|
|
if err := db.First(&user, "id = ?", userID).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
|
|
} else {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "database error"})
|
|
}
|
|
return
|
|
}
|
|
|
|
// Return user info (excluding password)
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"id": user.ID,
|
|
"email": user.Email,
|
|
"name": user.Username, // Return username as name for frontend
|
|
})
|
|
}
|
|
}
|
|
|
|
// Logout handles user logout
|
|
func Logout(c *gin.Context) {
|
|
// Clear the JWT cookie
|
|
c.SetCookie("auth_token", "", -1, "/", "", false, true)
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "successfully logged out",
|
|
})
|
|
}
|
|
|
|
// GetUserLibrary returns user's manga library
|
|
func GetUserLibrary(db interface{}) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
c.JSON(http.StatusOK, gin.H{})
|
|
}
|
|
}
|
|
|
|
// AddToLibrary adds a manga to user's library
|
|
func AddToLibrary(db interface{}) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
c.JSON(http.StatusCreated, gin.H{})
|
|
}
|
|
}
|
|
|
|
// RemoveFromLibrary removes a manga from user's library
|
|
func RemoveFromLibrary(db interface{}) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
c.JSON(http.StatusOK, gin.H{})
|
|
}
|
|
}
|
|
|
|
// TrackReadingProgress tracks the user's reading progress
|
|
func TrackReadingProgress(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
// Get user ID from context (should be set by auth middleware)
|
|
userID, exists := c.Get("user_id")
|
|
if !exists {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "user not authenticated"})
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
MangaID string `json:"manga_id" binding:"required"`
|
|
ChapterID string `json:"chapter_id"`
|
|
PageNum int `json:"page_num" binding:"required"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
|
|
return
|
|
}
|
|
|
|
// Parse UUIDs
|
|
mangaID, err := uuid.Parse(req.MangaID)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid manga_id"})
|
|
return
|
|
}
|
|
|
|
userUUID := userID.(uuid.UUID)
|
|
|
|
// Upsert reading progress
|
|
progress := models.ReadingProgress{
|
|
UserID: userUUID,
|
|
MangaID: mangaID,
|
|
PageNum: req.PageNum,
|
|
}
|
|
|
|
if req.ChapterID != "" {
|
|
chapterID, err := uuid.Parse(req.ChapterID)
|
|
if err == nil {
|
|
progress.ChapterID = chapterID
|
|
}
|
|
}
|
|
|
|
// Use FirstOrCreate to insert or update
|
|
if err := db.
|
|
Where("user_id = ? AND manga_id = ?", userUUID, mangaID).
|
|
Assign(progress).
|
|
FirstOrCreate(&progress).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save reading progress"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "reading progress saved",
|
|
"progress": progress,
|
|
})
|
|
}
|
|
}
|
|
|
|
// UploadManga handles manga volume upload (create new series or add to existing)
|
|
func UploadManga(db *gorm.DB, s3Client *storage.S3Client) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
// Parse form data
|
|
title := c.PostForm("title")
|
|
author := c.PostForm("author")
|
|
artist := c.PostForm("artist")
|
|
description := c.PostForm("description")
|
|
status := c.PostForm("status")
|
|
chapterNumStr := c.PostForm("chapter_number")
|
|
mangaIDStr := c.PostForm("manga_id") // Optional: for adding volumes to existing series
|
|
|
|
// Validate required fields
|
|
if chapterNumStr == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "chapter_number is required"})
|
|
return
|
|
}
|
|
|
|
// If no manga_id, then we're creating a new series - require title and author
|
|
if mangaIDStr == "" {
|
|
if title == "" || author == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "title and author are required for new series"})
|
|
return
|
|
}
|
|
}
|
|
|
|
// Parse chapter number
|
|
chapterNum, err := strconv.ParseFloat(chapterNumStr, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid chapter_number format"})
|
|
return
|
|
}
|
|
|
|
// Get files from request
|
|
file, err := c.FormFile("file")
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "file is required"})
|
|
return
|
|
}
|
|
|
|
// Determine manga ID: use existing or create new
|
|
var mangaID uuid.UUID
|
|
var manga *models.Manga
|
|
|
|
if mangaIDStr != "" {
|
|
// Add to existing manga series
|
|
parsedID, err := uuid.Parse(mangaIDStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid manga_id format"})
|
|
return
|
|
}
|
|
|
|
// Verify manga exists
|
|
if err := db.First(&manga, "id = ?", parsedID).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "manga not found"})
|
|
return
|
|
}
|
|
mangaID = parsedID
|
|
} else {
|
|
// Create new manga series
|
|
mangaID = uuid.New()
|
|
manga = &models.Manga{
|
|
ID: mangaID,
|
|
Title: title,
|
|
Author: author,
|
|
Artist: artist,
|
|
Description: description,
|
|
Status: status,
|
|
}
|
|
|
|
// Handle cover image upload for new series
|
|
coverFile, err := c.FormFile("cover")
|
|
if err == nil && coverFile != nil {
|
|
// Upload cover to S3
|
|
coverSrc, err := coverFile.Open()
|
|
if err == nil {
|
|
defer coverSrc.Close()
|
|
coverData := make([]byte, coverFile.Size)
|
|
if _, err := coverSrc.Read(coverData); err == nil {
|
|
// Extract file extension
|
|
ext := filepath.Ext(coverFile.Filename)
|
|
coverKey := fmt.Sprintf("assets/%s/cover%s", mangaID.String(), ext)
|
|
coverURL, _ := s3Client.UploadFile(context.Background(), coverKey, coverData, coverFile.Header.Get("Content-Type"))
|
|
manga.CoverImage = coverURL
|
|
}
|
|
}
|
|
}
|
|
|
|
// Save manga to database
|
|
if err := db.Create(manga).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create manga"})
|
|
return
|
|
}
|
|
}
|
|
|
|
// Process file and extract pages
|
|
processor := services.NewFileProcessor(s3Client, "/tmp/akiyama-manga-uploads")
|
|
pages, tempDir, err := processor.ProcessMangaFile(context.Background(), file)
|
|
if err != nil {
|
|
// Only delete manga if we created a new one
|
|
if mangaIDStr == "" {
|
|
db.Delete(manga)
|
|
}
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("file processing error: %v", err)})
|
|
return
|
|
}
|
|
|
|
// Upload pages to S3
|
|
chapter, pageRecords, err := processor.UploadPagesToS3(context.Background(), mangaID, chapterNum, pages)
|
|
if err != nil {
|
|
// Cleanup temp directory
|
|
processor.CleanupTempDir(tempDir)
|
|
// Only delete manga if we created a new one
|
|
if mangaIDStr == "" {
|
|
db.Delete(manga)
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("S3 upload error: %v", err)})
|
|
return
|
|
}
|
|
|
|
// Save chapter to database (explicitly omit Pages to prevent auto-save)
|
|
if err := db.Omit("Pages").Create(chapter).Error; err != nil {
|
|
// Cleanup temp directory
|
|
processor.CleanupTempDir(tempDir)
|
|
// Only delete manga if we created a new one
|
|
if mangaIDStr == "" {
|
|
db.Delete(manga)
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save chapter to database"})
|
|
return
|
|
}
|
|
|
|
// Save pages to database
|
|
for _, page := range pageRecords {
|
|
if err := db.Create(&page).Error; err != nil {
|
|
// Cleanup on failure - delete in cascade order
|
|
processor.CleanupTempDir(tempDir)
|
|
// Delete all pages for this chapter
|
|
db.Where("chapter_id = ?", chapter.ID).Delete(&models.Page{})
|
|
// Delete chapter
|
|
db.Delete(chapter)
|
|
// Only delete manga if we created a new one
|
|
if mangaIDStr == "" {
|
|
db.Delete(manga)
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save pages to database"})
|
|
return
|
|
}
|
|
}
|
|
|
|
// Cleanup temporary directory after successful upload
|
|
if err := processor.CleanupTempDir(tempDir); err != nil {
|
|
// Log warning but don't fail the request
|
|
fmt.Printf("warning: failed to cleanup temp directory: %v\n", err)
|
|
}
|
|
|
|
// Return success response
|
|
c.JSON(http.StatusCreated, gin.H{
|
|
"message": "Manga uploaded successfully",
|
|
"manga_id": manga.ID,
|
|
"chapter": gin.H{
|
|
"id": chapter.ID,
|
|
"number": chapter.Number,
|
|
"page_count": chapter.PageCount,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
// ExtractCBZMetadata extracts metadata from uploaded CBZ file
|
|
func ExtractCBZMetadata(s3Client *storage.S3Client) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
// Get the uploaded file
|
|
file, err := c.FormFile("file")
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "file is required"})
|
|
return
|
|
}
|
|
|
|
// Only accept CBZ files
|
|
if !strings.HasSuffix(strings.ToLower(file.Filename), ".cbz") {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "only CBZ files are supported for metadata extraction"})
|
|
return
|
|
}
|
|
|
|
// Extract metadata
|
|
processor := services.NewFileProcessor(s3Client, "/tmp/akiyama-manga-uploads")
|
|
metadata, err := processor.ExtractCBZMetadata(file)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to extract metadata: %v", err)})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"title": metadata.Title,
|
|
"author": metadata.Author,
|
|
"artist": metadata.Artist,
|
|
"description": metadata.Description,
|
|
"chapter": metadata.ChapterNum,
|
|
})
|
|
}
|
|
}
|
|
|
|
// UploadChapter handles chapter upload to S3
|
|
func UploadChapter(db *gorm.DB, s3Client *storage.S3Client) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
mangaIDStr := c.Param("id")
|
|
chapterNumStr := c.PostForm("chapter_number")
|
|
|
|
// Parse manga ID
|
|
mangaID, err := uuid.Parse(mangaIDStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid manga ID"})
|
|
return
|
|
}
|
|
|
|
// Parse chapter number
|
|
chapterNum, err := strconv.ParseFloat(chapterNumStr, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid chapter_number format"})
|
|
return
|
|
}
|
|
|
|
// Verify manga exists
|
|
var manga models.Manga
|
|
if err := db.First(&manga, "id = ?", mangaID).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "manga not found"})
|
|
return
|
|
}
|
|
|
|
// Get file from request
|
|
file, err := c.FormFile("file")
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "file is required"})
|
|
return
|
|
}
|
|
|
|
// Process file and extract pages
|
|
processor := services.NewFileProcessor(s3Client, "/tmp/akiyama-manga-uploads")
|
|
pages, tempDir, err := processor.ProcessMangaFile(context.Background(), file)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("file processing error: %v", err)})
|
|
return
|
|
}
|
|
|
|
// Upload pages to S3
|
|
chapter, pageRecords, err := processor.UploadPagesToS3(context.Background(), mangaID, chapterNum, pages)
|
|
if err != nil {
|
|
processor.CleanupTempDir(tempDir)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("S3 upload error: %v", err)})
|
|
return
|
|
}
|
|
|
|
// Save chapter to database (explicitly omit Pages to prevent auto-save)
|
|
if err := db.Omit("Pages").Create(chapter).Error; err != nil {
|
|
processor.CleanupTempDir(tempDir)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save chapter to database"})
|
|
return
|
|
}
|
|
|
|
// Save pages to database
|
|
for _, page := range pageRecords {
|
|
if err := db.Create(&page).Error; err != nil {
|
|
processor.CleanupTempDir(tempDir)
|
|
db.Where("chapter_id = ?", chapter.ID).Delete(&models.Page{})
|
|
db.Delete(chapter)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save pages to database"})
|
|
return
|
|
}
|
|
}
|
|
|
|
// Cleanup temporary directory
|
|
if err := processor.CleanupTempDir(tempDir); err != nil {
|
|
fmt.Printf("warning: failed to cleanup temp directory: %v\n", err)
|
|
}
|
|
|
|
// Return success response
|
|
c.JSON(http.StatusCreated, gin.H{
|
|
"message": "Chapter uploaded successfully",
|
|
"chapter": gin.H{
|
|
"id": chapter.ID,
|
|
"number": chapter.Number,
|
|
"page_count": chapter.PageCount,
|
|
},
|
|
})
|
|
}
|
|
}
|