lms-backend/routers/router/api/v1/lessons/lessons.go

674 lines
17 KiB
Go

package lessons
import (
"sort"
"strings"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
"gorm.io/gorm"
"lms.de/backend/modules/database"
"lms.de/backend/modules/structs"
"lms.de/backend/modules/utils"
)
func GetLessons(c *fiber.Ctx) error {
// swagger:operation GET /v1/lessons lessons GetLessons
// ---
// summary: Get lessons.
// consumes:
// - application/json
// produces:
// - application/json
// responses:
// '200':
// description: Lessons retrieved successfully.
// schema:
// type: array
// items:
// "$ref": "#/definitions/LessonResponse"
// '500':
// description: Failed to retrieve lessons.
var lessons []structs.LessonResponse
database.DB.Model(&structs.Lesson{}).Where("organization_id = ?", c.Locals("organizationId")).Find(&lessons)
return c.JSON(lessons)
}
func CreateLesson(c *fiber.Ctx) error {
// swagger:operation POST /v1/lessons lessons CreateLesson
// ---
// summary: Create lesson.
// consumes:
// - application/json
// produces:
// - application/json
// responses:
// '200':
// description: Lesson created successfully.
// schema:
// "$ref": "#/definitions/CreateLessonResponse"
// '500':
// description: Failed to create lesson.
lesson := structs.Lesson{
Id: uuid.New().String(),
OrganizationId: c.Locals("organizationId").(string),
State: structs.LessonStateDraft,
Title: "Test",
CreatorUserId: c.Locals("userId").(string),
}
database.DB.Create(&lesson)
return c.JSON(
structs.CreateLessonResponse{
Id: lesson.Id,
},
)
}
func GetLessonContents(c *fiber.Ctx) error {
// swagger:operation GET /v1/lessons/{lessonId}/contents lessons GetLessonContents
// ---
// summary: Get lesson contents.
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: lessonId
// in: path
// required: true
// type: string
// responses:
// '200':
// description: Lesson contents retrieved successfully.
// schema:
// type: array
// items:
// "$ref": "#/definitions/LessonContent"
// '500':
// description: Failed to retrieve lesson contents.
var params structs.LessonIdParam
if err := c.ParamsParser(&params); err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}
var lessonContents []structs.GetLessonContentsResponse
database.DB.Model(&structs.LessonContent{}).Where("lesson_id = ?", params.LessonId).Find(&lessonContents)
// sort contents by position
sort.SliceStable(lessonContents, func(i, j int) bool {
return lessonContents[i].Position < lessonContents[j].Position
})
return c.JSON(lessonContents)
}
func GetLessonSettings(c *fiber.Ctx) error {
// swagger:operation GET /v1/lessons/{lessonId}/settings lessons GetLessonSettings
// ---
// summary: Get lesson settings.
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: lessonId
// in: path
// required: true
// type: string
// responses:
// '200':
// description: Lesson settings retrieved successfully.
// schema:
// "$ref": "#/definitions/LessonSettings"
// '500':
// description: Failed to retrieve lesson settings.
var params structs.LessonIdParam
if err := c.ParamsParser(&params); err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}
var lessonSettings structs.LessonSettings
database.DB.Model(&structs.Lesson{}).Where("id = ?", params.LessonId).First(&lessonSettings)
return c.JSON(lessonSettings)
}
func UpdateLessonPreviewTitle(c *fiber.Ctx) error {
// swagger:operation PATCH /v1/lessons/{lessonId}/preview/title lessons UpdateLessonPreviewTitle
// ---
// summary: Update lesson preview title.
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: lessonId
// in: path
// required: true
// type: string
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/UpdateLessonPreviewRequest"
// responses:
// '200':
// description: Lesson preview updated successfully.
// schema:
// "$ref": "#/definitions/UpdateLessonPreviewResponse"
// '500':
// description: Failed to update lesson preview.
var params structs.LessonIdParam
if err := c.ParamsParser(&params); err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}
var body structs.UpdateLessonPreviewTitleRequest
if err := c.BodyParser(&body); err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}
database.DB.Model(&structs.Lesson{}).Where("id = ?", params.LessonId).Update("title", body.Title)
return c.JSON(
fiber.Map{
"message": "Lesson preview updated successfully",
},
)
}
func UpdateLessonPreviewThumbnail(c *fiber.Ctx) error {
// swagger:operation POST /v1/lessons/{lessonId}/preview/thumbnail lessons UpdateLessonPreviewThumbnail
// ---
// summary: Update lesson preview thumbnail.
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: lessonId
// in: path
// required: true
// type: string
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/UpdateLessonPreviewThumbnailRequest"
// responses:
// '200':
// description: Lesson preview updated successfully.
// '500':
// description: Failed to update lesson preview.
var params structs.LessonIdParam
if err := c.ParamsParser(&params); err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}
fileHeader, err := c.FormFile("file")
if err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}
if fileHeader.Size > utils.MaxImageSize {
return c.SendStatus(fiber.StatusBadRequest)
}
if !utils.IsFileTypeAllowed(fileHeader.Header.Get("Content-Type"), utils.AcceptedImageFileTypes) {
return c.SendStatus(fiber.StatusBadRequest)
}
// get current thumbnail
lesson := structs.Lesson{}
database.DB.Model(&structs.Lesson{}).Where("id = ?", params.LessonId).First(&lesson)
// delete current thumbnail
if lesson.ThumbnailUrl != "" {
utils.DeleteFile(lesson.ThumbnailUrl)
}
fileName := uuid.New().String() + "." + strings.Split(fileHeader.Header["Content-Type"][0], "/")[1]
databasePath, publicPath := utils.GetFullImagePath(c.Locals("organizationId").(string), params.LessonId)
utils.CreateFolderStructureIfNotExists(publicPath)
database.DB.Model(&structs.Lesson{}).Where("id = ?", params.LessonId).Update("thumbnail_url", databasePath+fileName)
return c.SaveFile(fileHeader, publicPath+fileName)
}
func UpdateLessonState(c *fiber.Ctx) error {
// swagger:operation PATCH /v1/lessons/{lessonId}/state lessons UpdateLessonState
// ---
// summary: Update lesson state.
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: lessonId
// in: path
// required: true
// type: string
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/UpdateLessonStateRequest"
// responses:
// '200':
// description: Lesson state updated successfully.
// '500':
// description: Failed to update lesson state.
var params structs.LessonIdParam
if err := c.ParamsParser(&params); err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}
var body structs.UpdateLessonStateRequest
if err := c.BodyParser(&body); err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}
database.DB.Model(&structs.Lesson{}).Where("id = ?", params.LessonId).Update("state", body.State)
return c.JSON(
fiber.Map{
"message": "Lesson state updated successfully",
},
)
}
func AddLessonContent(c *fiber.Ctx) error {
// swagger:operation POST /v1/lessons/{lessonId}/contents lessons AddLessonContent
// ---
// summary: Add lesson content.
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: lessonId
// in: path
// required: true
// type: string
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/AddLessonContentRequest"
// responses:
// '200':
// description: Lesson content added successfully.
// schema:
// "$ref": "#/definitions/AddLessonContentResponse"
// '500':
// description: Failed to add lesson content.
var params structs.LessonIdParam
if err := c.ParamsParser(&params); err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}
var body structs.AddLessonContentRequest
if err := c.BodyParser(&body); err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}
// get last position
var lastContent structs.LessonContent
database.DB.Select("position").Model(&structs.LessonContent{}).Where("lesson_id = ?", params.LessonId).Order("position desc").First(&lastContent)
// create new content
content := structs.LessonContent{
Id: uuid.New().String(),
LessonId: params.LessonId,
Page: 1,
Position: lastContent.Position + 1,
Type: body.Type,
Data: body.Data,
}
database.DB.Create(&content)
return c.JSON(
structs.AddLessonContentResponse{
Id: content.Id,
},
)
}
func UploadLessonContentFile(c *fiber.Ctx) error {
// swagger:operation POST /v1/lessons/{lessonId}/contents/{contentId}/file/{type} lessons UploadLessonContentFile
// ---
// summary: Upload lesson content file.
// consumes:
// - multipart/form-data
// produces:
// - application/json
// parameters:
// - name: lessonId
// in: path
// required: true
// type: string
// - name: contentId
// in: path
// required: true
// type: string
// - name: file
// in: formData
// required: true
// type: file
// responses:
// '200':
// description: Lesson content file uploaded successfully.
// '500':
// description: Failed to upload lesson content file.
var params structs.UploadLessonContentFileParam
if err := c.ParamsParser(&params); err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}
fileHeader, err := c.FormFile("file")
if err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}
if params.Type == "image" {
if fileHeader.Size > utils.MaxImageSize {
return c.SendStatus(fiber.StatusBadRequest)
}
if !utils.IsFileTypeAllowed(fileHeader.Header.Get("Content-Type"), utils.AcceptedImageFileTypes) {
return c.SendStatus(fiber.StatusBadRequest)
}
} else if params.Type == "video" {
if fileHeader.Size > utils.MaxVideoSize {
return c.SendStatus(fiber.StatusBadRequest)
}
if !utils.IsFileTypeAllowed(fileHeader.Header.Get("Content-Type"), utils.AcceptedVideoFileTypes) {
return c.SendStatus(fiber.StatusBadRequest)
}
} else {
return c.SendStatus(fiber.StatusBadRequest)
}
// get current file
content := structs.LessonContent{}
database.DB.Select("type", "data").Model(&structs.LessonContent{}).Where("id = ?", params.ContentId).First(&content)
// delete current image
if content.Type == utils.LessonContentTypeImage && content.Data != "" {
utils.DeleteFile(content.Data)
}
fileName := uuid.New().String() + "." + strings.Split(fileHeader.Header["Content-Type"][0], "/")[1]
databasePath, publicPath := utils.GetFullImagePath(c.Locals("organizationId").(string), params.LessonId)
utils.CreateFolderStructureIfNotExists(publicPath)
database.DB.Model(&structs.LessonContent{}).Where("id = ?", params.ContentId).Update("data", databasePath+fileName)
if err := c.SaveFile(fileHeader, publicPath+fileName); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Failed to save file",
})
}
return c.JSON(fiber.Map{
"Data": databasePath + fileName,
})
}
func UpdateLessonContent(c *fiber.Ctx) error {
// swagger:operation PATCH /v1/lessons/{lessonId}/contents/{contentId} lessons UpdateLessonContent
// ---
// summary: Update lesson content.
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: lessonId
// in: path
// required: true
// type: string
// - name: contentId
// in: path
// required: true
// type: string
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/UpdateLessonContentRequest"
// responses:
// '200':
// description: Lesson content updated successfully.
// '500':
// description: Failed to update lesson content.
var params structs.LessonIdAndContentIdParam
if err := c.ParamsParser(&params); err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}
var body structs.UpdateLessonContentRequest
if err := c.BodyParser(&body); err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}
database.DB.Model(&structs.LessonContent{}).Where("id = ?", params.ContentId).Update("data", body.Data)
return c.JSON(
fiber.Map{
"message": "Lesson content updated successfully",
},
)
}
func UpdateLessonContentPosition(c *fiber.Ctx) error {
// swagger:operation PATCH /v1/lessons/{lessonId}/contents/{contentId}/position lessons UpdateLessonContentPosition
// ---
// summary: Update lesson content position.
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: lessonId
// in: path
// required: true
// type: string
// - name: contentId
// in: path
// required: true
// type: string
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/UpdateLessonContentPositionRequest"
// responses:
// '200':
// description: Lesson content position updated successfully.
// '500':
// description: Failed to update lesson content position.
var params structs.LessonIdAndContentIdParam
if err := c.ParamsParser(&params); err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}
var body structs.UpdateLessonContentPositionRequest
if err := c.BodyParser(&body); err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}
// update position
newPosition := body.Position
// Begin a transaction to ensure consistency
tx := database.DB.Begin()
if tx.Error != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
// Fetch the current position of the content being moved
var currentContent structs.LessonContent
if err := tx.Model(&structs.LessonContent{}).Where("id = ?", params.ContentId).First(&currentContent).Error; err != nil {
tx.Rollback()
return c.SendStatus(fiber.StatusNotFound)
}
oldPosition := currentContent.Position
if oldPosition == newPosition {
// No need to update if the position hasn't changed
return c.JSON(fiber.Map{
"message": "Lesson content position updated successfully",
})
}
if oldPosition < newPosition {
// Items between oldPosition and newPosition need to be shifted down
if err := tx.Model(&structs.LessonContent{}).Where("lesson_id = ?", params.LessonId).
Where("position > ? AND position <= ?", oldPosition, newPosition).
Update("position", gorm.Expr("position - 1")).Error; err != nil {
tx.Rollback()
return c.SendStatus(fiber.StatusInternalServerError)
}
} else {
// Items between newPosition and oldPosition need to be shifted up
if err := tx.Model(&structs.LessonContent{}).Where("lesson_id = ?", params.LessonId).
Where("position >= ? AND position < ?", newPosition, oldPosition).
Update("position", gorm.Expr("position + 1")).Error; err != nil {
tx.Rollback()
return c.SendStatus(fiber.StatusInternalServerError)
}
}
// Update the position of the moved content
if err := tx.Model(&structs.LessonContent{}).Where("id = ?", params.ContentId).Update("position", newPosition).Error; err != nil {
tx.Rollback()
return c.SendStatus(fiber.StatusInternalServerError)
}
// Commit the transaction
if err := tx.Commit().Error; err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.JSON(
fiber.Map{
"message": "Lesson content position updated successfully",
},
)
}
func DeleteLessonContent(c *fiber.Ctx) error {
// swagger:operation DELETE /v1/lessons/{lessonId}/contents/{contentId} lessons DeleteLessonContent
// ---
// summary: Delete lesson content.
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: lessonId
// in: path
// required: true
// type: string
// - name: contentId
// in: path
// required: true
// type: string
// responses:
// '200':
// description: Lesson content deleted successfully.
// '500':
// description: Failed to delete lesson content.
var params structs.LessonIdAndContentIdParam
if err := c.ParamsParser(&params); err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}
// Begin a transaction to ensure consistency
tx := database.DB.Begin()
if tx.Error != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
// Fetch the current position of the content being deleted
var content structs.LessonContent
if err := tx.Model(&structs.LessonContent{}).Where("id = ?", params.ContentId).First(&content).Error; err != nil {
tx.Rollback()
return c.SendStatus(fiber.StatusNotFound)
}
// Delete the content
if err := tx.Delete(&content).Error; err != nil {
tx.Rollback()
return c.SendStatus(fiber.StatusInternalServerError)
}
// Shift down the positions of all contents with a higher position
if err := tx.Model(&structs.LessonContent{}).Where("lesson_id = ?", params.LessonId).
Where("position > ?", content.Position).
Update("position", gorm.Expr("position - 1")).Error; err != nil {
tx.Rollback()
return c.SendStatus(fiber.StatusInternalServerError)
}
// Commit the transaction
if err := tx.Commit().Error; err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.JSON(
fiber.Map{
"message": "Lesson content deleted successfully",
},
)
}