REFACTOR: Optimized Catagory IDs INput Validation
Build, Push and Deploy CMS / build-push-deploy (push) Successful in 2m47s
Deploy Infrastructure / pulumi-up (push) Failing after 26s

This commit is contained in:
FahdShalhoub
2026-08-17 01:17:08 +03:00
parent 56a2992206
commit b77afbfed6
4 changed files with 82 additions and 41 deletions
+11 -41
View File
@@ -9,6 +9,7 @@ import (
"log"
"net/http"
"path/filepath"
"slices"
"strconv"
"strings"
"thamanyah/cms/v2/internal/api"
@@ -47,11 +48,6 @@ func ListCategories(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, api.CategoriesResponse{Categories: body})
}
// allowedUploadContentTypes is the set of media types accepted for upload,
// keyed by the exact string a client must send. Matched verbatim rather than
// normalised: the value is signed into the presigned URL, so rewriting it
// here would leave the client PUTting a header that no longer matches the
// signature.
var allowedUploadContentTypes = map[string]struct{}{
"video/mp4": {}, // .mp4
"video/quicktime": {}, // .mov
@@ -147,18 +143,13 @@ func CompleteVideoUpload(w http.ResponseWriter, r *http.Request) {
return
}
categoryIDs, unknown, err := resolveCategoryIDs(r.Context(), req.CategoryIDs)
err := resolveCategoryIDs(r.Context(), req.CategoryIDs)
if err != nil {
log.Printf("Something Went Wrong Loading Categories: %s", err)
writeProblem(w, http.StatusInternalServerError, "Something Went Wrong Loading Categories",
"The submitted category ids could not be checked against the database, so no video record was created. This is a server-side fault; the uploaded file is still in storage, so retry this request with the same 'key'.")
return
}
if len(unknown) > 0 {
writeProblem(w, http.StatusUnprocessableEntity, "Unknown category.",
fmt.Sprintf("The 'categoryIds' field referenced %s, which do not exist. Send ids taken from the 'id' field of GET /api/categories.", formatCategoryIDs(unknown)))
return
}
jobID, err := services.MediaConvertClient.QueueEncodingJob(r.Context(), key)
if err != nil {
@@ -171,7 +162,7 @@ func CompleteVideoUpload(w http.ResponseWriter, r *http.Request) {
video, err := services.DB.CreateVideo(r.Context(), services.Video{
Title: title,
Description: strings.TrimSpace(req.Description),
CategoryIDs: categoryIDs,
CategoryIDs: req.CategoryIDs,
Tags: strings.TrimSpace(req.Tags),
FileName: strings.TrimSpace(req.FileName),
StorageKey: key,
@@ -202,40 +193,19 @@ func CompleteVideoUpload(w http.ResponseWriter, r *http.Request) {
})
}
// resolveCategoryIDs deduplicates the submitted category ids, preserving the
// order they arrived in, and splits them into ids that exist and ids that do
// not. Checking them here rather than leaning on the video_categories foreign
// key keeps an unknown id from surfacing only after a transcoding job has
// already been queued — and the duplicate pass keeps a repeated id from
// tripping the join table's composite primary key. Callers reject an empty
// list before calling, so a non-empty submitted list always yields either at
// least one known id or at least one unknown one.
func resolveCategoryIDs(ctx context.Context, submitted []int16) (known, unknown []int16, err error) {
known = make([]int16, 0, len(submitted))
categories, err := services.DB.ListCategories(ctx)
func resolveCategoryIDs(ctx context.Context, submitted []int16) (err error) {
categoryIDS, err := services.DB.ListCategoriesIDs(ctx)
if err != nil {
return nil, nil, err
}
exists := make(map[int16]struct{}, len(categories))
for _, category := range categories {
exists[category.ID] = struct{}{}
return err
}
seen := make(map[int16]struct{}, len(submitted))
for _, id := range submitted {
if _, duplicate := seen[id]; duplicate {
continue
}
seen[id] = struct{}{}
if _, ok := exists[id]; ok {
known = append(known, id)
} else {
unknown = append(unknown, id)
for _, submittedID := range submitted {
if !slices.Contains(categoryIDS, submittedID) {
return fmt.Errorf("Incorrect Category IDs")
}
}
return known, unknown, nil
return nil
}
func formatCategoryIDs(ids []int16) string {
+19
View File
@@ -35,6 +35,7 @@ type Video struct {
type DBClient interface {
CreateVideo(ctx context.Context, v Video) (Video, error)
ListCategories(ctx context.Context) ([]Category, error)
ListCategoriesIDs(ctx context.Context) ([]int16, error)
}
type DBConcrete struct {
@@ -102,6 +103,24 @@ func (svc DBConcrete) ListCategories(ctx context.Context) ([]Category, error) {
return categories, rows.Err()
}
func (svc DBConcrete) ListCategoriesIDs(ctx context.Context) ([]int16, error) {
rows, err := svc.SQLDB.QueryContext(ctx, `SELECT id FROM categories`)
if err != nil {
return nil, err
}
defer rows.Close()
var categories []int16
for rows.Next() {
var category int16
if err := rows.Scan(&category); err != nil {
return nil, err
}
categories = append(categories, category)
}
return categories, rows.Err()
}
func (svc DBConcrete) Migrate() {
if err := db.Migrate(svc.SQLDB); err != nil {
panic(err)