From 5a757986090f4a3d6d3e74dd4fddd80397344a96 Mon Sep 17 00:00:00 2001 From: AzenKain Date: Sun, 24 May 2026 13:11:40 +0700 Subject: [PATCH] feat: implement submission repository and service layer with caching support --- db/query/submission.sql | 10 +++ internal/gen/sqlc/submission.sql.go | 44 ++++++++++++ internal/repositories/submissionRepository.go | 23 ++++++ internal/services/submissionService.go | 70 +++++++++++-------- 4 files changed, 117 insertions(+), 30 deletions(-) diff --git a/db/query/submission.sql b/db/query/submission.sql index 0b8cc64..570c868 100644 --- a/db/query/submission.sql +++ b/db/query/submission.sql @@ -216,3 +216,13 @@ WHERE project_id = $1 ORDER BY reviewed_at DESC, created_at DESC LIMIT 1; +-- name: GetLatestApprovedSubmission :one +SELECT + id, project_id, commit_id, user_id, created_at, status, reviewed_by, reviewed_at, review_note, content, is_deleted +FROM submissions +WHERE project_id = $1 + AND status = 2 + AND is_deleted = false +ORDER BY reviewed_at DESC, created_at DESC +LIMIT 1; + diff --git a/internal/gen/sqlc/submission.sql.go b/internal/gen/sqlc/submission.sql.go index f84aa6b..a6552fb 100644 --- a/internal/gen/sqlc/submission.sql.go +++ b/internal/gen/sqlc/submission.sql.go @@ -159,6 +159,50 @@ func (q *Queries) DeleteSubmission(ctx context.Context, id pgtype.UUID) error { return err } +const getLatestApprovedSubmission = `-- name: GetLatestApprovedSubmission :one +SELECT + id, project_id, commit_id, user_id, created_at, status, reviewed_by, reviewed_at, review_note, content, is_deleted +FROM submissions +WHERE project_id = $1 + AND status = 2 + AND is_deleted = false +ORDER BY reviewed_at DESC, created_at DESC +LIMIT 1 +` + +type GetLatestApprovedSubmissionRow struct { + ID pgtype.UUID `json:"id"` + ProjectID pgtype.UUID `json:"project_id"` + CommitID pgtype.UUID `json:"commit_id"` + UserID pgtype.UUID `json:"user_id"` + CreatedAt pgtype.Timestamptz `json:"created_at"` + Status int16 `json:"status"` + ReviewedBy pgtype.UUID `json:"reviewed_by"` + ReviewedAt pgtype.Timestamptz `json:"reviewed_at"` + ReviewNote pgtype.Text `json:"review_note"` + Content pgtype.Text `json:"content"` + IsDeleted bool `json:"is_deleted"` +} + +func (q *Queries) GetLatestApprovedSubmission(ctx context.Context, projectID pgtype.UUID) (GetLatestApprovedSubmissionRow, error) { + row := q.db.QueryRow(ctx, getLatestApprovedSubmission, projectID) + var i GetLatestApprovedSubmissionRow + err := row.Scan( + &i.ID, + &i.ProjectID, + &i.CommitID, + &i.UserID, + &i.CreatedAt, + &i.Status, + &i.ReviewedBy, + &i.ReviewedAt, + &i.ReviewNote, + &i.Content, + &i.IsDeleted, + ) + return i, err +} + const getLatestApprovedSubmissionExcluding = `-- name: GetLatestApprovedSubmissionExcluding :one SELECT id, project_id, commit_id, user_id, created_at, status, reviewed_by, reviewed_at, review_note, content, is_deleted diff --git a/internal/repositories/submissionRepository.go b/internal/repositories/submissionRepository.go index 66f8060..0ce230d 100644 --- a/internal/repositories/submissionRepository.go +++ b/internal/repositories/submissionRepository.go @@ -24,6 +24,7 @@ type SubmissionRepository interface { Update(ctx context.Context, params sqlc.UpdateSubmissionParams) (*models.SubmissionEntity, error) Delete(ctx context.Context, id pgtype.UUID) error GetLatestApprovedSubmissionExcluding(ctx context.Context, projectID pgtype.UUID, id pgtype.UUID) (*models.SubmissionEntity, error) + GetLatestApprovedSubmission(ctx context.Context, projectID pgtype.UUID) (*models.SubmissionEntity, error) WithTx(tx pgx.Tx) SubmissionRepository } @@ -349,3 +350,25 @@ func (r *submissionRepository) GetLatestApprovedSubmissionExcluding(ctx context. } return entity, nil } + +func (r *submissionRepository) GetLatestApprovedSubmission(ctx context.Context, projectID pgtype.UUID) (*models.SubmissionEntity, error) { + row, err := r.q.GetLatestApprovedSubmission(ctx, projectID) + if err != nil { + return nil, err + } + + entity := &models.SubmissionEntity{ + ID: convert.UUIDToString(row.ID), + ProjectID: convert.UUIDToString(row.ProjectID), + CommitID: convert.UUIDToString(row.CommitID), + UserID: convert.UUIDToString(row.UserID), + CreatedAt: convert.TimeToPtr(row.CreatedAt), + Status: constants.ParseStatusType(row.Status), + ReviewedBy: convert.UUIDToStringPtr(row.ReviewedBy), + ReviewedAt: convert.TimeToPtr(row.ReviewedAt), + ReviewNote: convert.TextToPtr(row.ReviewNote), + Content: convert.TextToPtr(row.Content), + IsDeleted: row.IsDeleted, + } + return entity, nil +} diff --git a/internal/services/submissionService.go b/internal/services/submissionService.go index 9e2a3eb..6d5cb90 100644 --- a/internal/services/submissionService.go +++ b/internal/services/submissionService.go @@ -421,44 +421,54 @@ func (s *submissionService) DeleteSubmission(ctx context.Context, userID string, submissionRepo := s.submissionRepo.WithTx(tx) + isLatestApprovedDeleted := false + if submission.Status == constants.StatusTypeApproved { projectUUID, err := convert.StringToUUID(submission.ProjectID) if err != nil { return fiber.NewError(fiber.StatusBadRequest, "Invalid project ID") } - prevSubmission, err := s.submissionRepo.GetLatestApprovedSubmissionExcluding(ctx, projectUUID, submissionUUID) - if err != nil { - if errors.Is(err, pgx.ErrNoRows) { - if err := s.clearProjectItems(ctx, tx, projectUUID); err != nil { + latestApproved, err := s.submissionRepo.GetLatestApprovedSubmission(ctx, projectUUID) + if err != nil && !errors.Is(err, pgx.ErrNoRows) { + return fiber.NewError(fiber.StatusInternalServerError, "Failed to check latest approved submission: "+err.Error()) + } + + if err == nil && latestApproved != nil && latestApproved.ID == submission.ID { + isLatestApprovedDeleted = true + prevSubmission, err := s.submissionRepo.GetLatestApprovedSubmissionExcluding(ctx, projectUUID, submissionUUID) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + if err := s.clearProjectItems(ctx, tx, projectUUID); err != nil { + return err + } + } else { + return fiber.NewError(fiber.StatusInternalServerError, "Failed to get previous approved submission: "+err.Error()) + } + } else if prevSubmission != nil { + prevCommitUUID, err := convert.StringToUUID(prevSubmission.CommitID) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid previous commit ID") + } + + prevCommit, err := s.commitRepo.GetByID(ctx, prevCommitUUID) + if err != nil { + return fiber.NewError(fiber.StatusNotFound, "Previous commit not found") + } + + var prevSnapshotData request.CommitSnapshot + err = json.Unmarshal(prevCommit.SnapshotJson, &prevSnapshotData) + if err != nil { + return fiber.NewError(fiber.StatusInternalServerError, "Failed to parse previous commit snapshot") + } + + if err := s.applySnapshot(ctx, tx, projectUUID, prevCommitUUID, &prevSnapshotData); err != nil { return err } } else { - return fiber.NewError(fiber.StatusInternalServerError, "Failed to get previous approved submission: "+err.Error()) - } - } else if prevSubmission != nil { - prevCommitUUID, err := convert.StringToUUID(prevSubmission.CommitID) - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, "Invalid previous commit ID") - } - - prevCommit, err := s.commitRepo.GetByID(ctx, prevCommitUUID) - if err != nil { - return fiber.NewError(fiber.StatusNotFound, "Previous commit not found") - } - - var prevSnapshotData request.CommitSnapshot - err = json.Unmarshal(prevCommit.SnapshotJson, &prevSnapshotData) - if err != nil { - return fiber.NewError(fiber.StatusInternalServerError, "Failed to parse previous commit snapshot") - } - - if err := s.applySnapshot(ctx, tx, projectUUID, prevCommitUUID, &prevSnapshotData); err != nil { - return err - } - } else { - if err := s.clearProjectItems(ctx, tx, projectUUID); err != nil { - return err + if err := s.clearProjectItems(ctx, tx, projectUUID); err != nil { + return err + } } } } @@ -473,7 +483,7 @@ func (s *submissionService) DeleteSubmission(ctx context.Context, userID string, return fiber.NewError(fiber.StatusInternalServerError, "Failed to commit transaction: "+err.Error()) } - if submission.Status == constants.StatusTypeApproved { + if isLatestApprovedDeleted { go func() { bgCtx := context.Background() _ = s.c.DelByPattern(bgCtx, "entity:search*")