construct transactional update

This commit is contained in:
Quaternions 2024-11-27 18:57:42 -08:00
parent f4fea2963d
commit f5fb895aeb
3 changed files with 38 additions and 80 deletions

View File

@ -19,6 +19,7 @@ type Submissions interface {
GetList(ctx context.Context, id []int64) ([]model.Submission, error) GetList(ctx context.Context, id []int64) ([]model.Submission, error)
Create(ctx context.Context, smap model.Submission) (model.Submission, error) Create(ctx context.Context, smap model.Submission) (model.Submission, error)
Update(ctx context.Context, id int64, values OptionalMap) error Update(ctx context.Context, id int64, values OptionalMap) error
IfStatusThenUpdate(ctx context.Context, id int64, statuses []model.Status, values OptionalMap) error
Delete(ctx context.Context, id int64) error Delete(ctx context.Context, id int64) error
List(ctx context.Context, filters OptionalMap, page model.Page) ([]model.Submission, error) List(ctx context.Context, filters OptionalMap, page model.Page) ([]model.Submission, error)
} }

View File

@ -3,6 +3,9 @@ package gormstore
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"strings"
"git.itzana.me/strafesnet/maps-service/pkg/datastore" "git.itzana.me/strafesnet/maps-service/pkg/datastore"
"git.itzana.me/strafesnet/maps-service/pkg/model" "git.itzana.me/strafesnet/maps-service/pkg/model"
"gorm.io/gorm" "gorm.io/gorm"
@ -50,6 +53,24 @@ func (env *Submissions) Update(ctx context.Context, id int64, values datastore.O
return nil return nil
} }
// the update can only occur if the status matches one of the provided values.
func (env *Submissions) IfStatusThenUpdate(ctx context.Context, id int64, statuses []model.Status, values datastore.OptionalMap) error {
tx := env.db.Model(&model.Submission{}).Where("id = ?", id)
expr := make([]string, 0,len(statuses))
for i, status := range statuses{
expr[i] = fmt.Sprintf("status = %d", int32(status))
}
tx = tx.Where(strings.Join(expr, " OR "))
if err := tx.Updates(values.Map()).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return datastore.ErrNotExist
}
return err
}
return nil
}
func (env *Submissions) Delete(ctx context.Context, id int64) error { func (env *Submissions) Delete(ctx context.Context, id int64) error {
if err := env.db.Delete(&model.Submission{}, id).Error; err != nil { if err := env.db.Delete(&model.Submission{}, id).Error; err != nil {
if err == gorm.ErrRecordNotFound { if err == gorm.ErrRecordNotFound {

View File

@ -144,22 +144,14 @@ func (svc *Service) PatchSubmissionModel(ctx context.Context, params api.PatchSu
// //
// PATCH /submissions/{SubmissionID}/status/publish // PATCH /submissions/{SubmissionID}/status/publish
func (svc *Service) ActionSubmissionPublish(ctx context.Context, params api.ActionSubmissionPublishParams) error { func (svc *Service) ActionSubmissionPublish(ctx context.Context, params api.ActionSubmissionPublishParams) error {
submission, err := svc.DB.Submissions().Get(ctx, params.SubmissionID)
if err != nil{
return err
}
// check if caller has required role // check if caller has required role
// if !CALLER_ROLES.INCLUDES(ROLE_VALIDATOR){ // if !CALLER_ROLES.INCLUDES(ROLE_VALIDATOR){
// return ErrPermissionDenied // return ErrPermissionDenied
// } // }
// check source status // transaction
if submission.StatusID != model.StatusPublishing{
return ErrInvalidSourceStatus
}
// set status
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id",model.StatusPublished) smap.Add("status_id",model.StatusPublished)
return svc.DB.Submissions().Update(ctx, params.SubmissionID, smap) return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusPublishing}, smap)
} }
// ActionSubmissionReject invokes actionSubmissionReject operation. // ActionSubmissionReject invokes actionSubmissionReject operation.
// //
@ -167,22 +159,14 @@ func (svc *Service) ActionSubmissionPublish(ctx context.Context, params api.Acti
// //
// PATCH /submissions/{SubmissionID}/status/reject // PATCH /submissions/{SubmissionID}/status/reject
func (svc *Service) ActionSubmissionReject(ctx context.Context, params api.ActionSubmissionRejectParams) error { func (svc *Service) ActionSubmissionReject(ctx context.Context, params api.ActionSubmissionRejectParams) error {
submission, err := svc.DB.Submissions().Get(ctx, params.SubmissionID)
if err != nil{
return err
}
// check if caller has required role // check if caller has required role
// if !CALLER_ROLES.INCLUDES(ROLE_Reviewer){ // if !CALLER_ROLES.INCLUDES(ROLE_Reviewer){
// return ErrPermissionDenied // return ErrPermissionDenied
// } // }
// check source status // transaction
if submission.StatusID != model.StatusSubmitted{
return ErrInvalidSourceStatus
}
// set status
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id",model.StatusRejected) smap.Add("status_id",model.StatusRejected)
return svc.DB.Submissions().Update(ctx, params.SubmissionID, smap) return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusSubmitted}, smap)
} }
// ActionSubmissionRequestChanges invokes actionSubmissionRequestChanges operation. // ActionSubmissionRequestChanges invokes actionSubmissionRequestChanges operation.
// //
@ -190,22 +174,14 @@ func (svc *Service) ActionSubmissionReject(ctx context.Context, params api.Actio
// //
// PATCH /submissions/{SubmissionID}/status/request-changes // PATCH /submissions/{SubmissionID}/status/request-changes
func (svc *Service) ActionSubmissionRequestChanges(ctx context.Context, params api.ActionSubmissionRequestChangesParams) error { func (svc *Service) ActionSubmissionRequestChanges(ctx context.Context, params api.ActionSubmissionRequestChangesParams) error {
submission, err := svc.DB.Submissions().Get(ctx, params.SubmissionID)
if err != nil{
return err
}
// check if caller has required role // check if caller has required role
// if !CALLER_ROLES.INCLUDES(ROLE_Reviewer){ // if !CALLER_ROLES.INCLUDES(ROLE_Reviewer){
// return ErrPermissionDenied // return ErrPermissionDenied
// } // }
// check source status // transaction
if !(submission.StatusID == model.StatusValidated||submission.StatusID == model.StatusAccepted||submission.StatusID == model.StatusSubmitted){
return ErrInvalidSourceStatus
}
// set status
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id",model.StatusChangesRequested) smap.Add("status_id",model.StatusChangesRequested)
return svc.DB.Submissions().Update(ctx, params.SubmissionID, smap) return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusValidated,model.StatusAccepted,model.StatusSubmitted}, smap)
} }
// ActionSubmissionRevoke invokes actionSubmissionRevoke operation. // ActionSubmissionRevoke invokes actionSubmissionRevoke operation.
// //
@ -213,22 +189,14 @@ func (svc *Service) ActionSubmissionRequestChanges(ctx context.Context, params a
// //
// PATCH /submissions/{SubmissionID}/status/revoke // PATCH /submissions/{SubmissionID}/status/revoke
func (svc *Service) ActionSubmissionRevoke(ctx context.Context, params api.ActionSubmissionRevokeParams) error { func (svc *Service) ActionSubmissionRevoke(ctx context.Context, params api.ActionSubmissionRevokeParams) error {
submission, err := svc.DB.Submissions().Get(ctx, params.SubmissionID)
if err != nil{
return err
}
// check if caller has required role // check if caller has required role
// if !CALLER_ROLES.INCLUDES(ROLE_Submitter){ // if !CALLER_ROLES.INCLUDES(ROLE_Submitter){
// return ErrPermissionDenied // return ErrPermissionDenied
// } // }
// check source status // transaction
if !(submission.StatusID == model.StatusSubmitted||submission.StatusID == model.StatusChangesRequested){
return ErrInvalidSourceStatus
}
// set status
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id",model.StatusUnderConstruction) smap.Add("status_id",model.StatusUnderConstruction)
return svc.DB.Submissions().Update(ctx, params.SubmissionID, smap) return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusSubmitted,model.StatusChangesRequested}, smap)
} }
// ActionSubmissionSubmit invokes actionSubmissionSubmit operation. // ActionSubmissionSubmit invokes actionSubmissionSubmit operation.
// //
@ -236,22 +204,14 @@ func (svc *Service) ActionSubmissionRevoke(ctx context.Context, params api.Actio
// //
// PATCH /submissions/{SubmissionID}/status/submit // PATCH /submissions/{SubmissionID}/status/submit
func (svc *Service) ActionSubmissionSubmit(ctx context.Context, params api.ActionSubmissionSubmitParams) error { func (svc *Service) ActionSubmissionSubmit(ctx context.Context, params api.ActionSubmissionSubmitParams) error {
submission, err := svc.DB.Submissions().Get(ctx, params.SubmissionID)
if err != nil{
return err
}
// check if caller has required role // check if caller has required role
// if !CALLER_ROLES.INCLUDES(ROLE_Submitter){ // if !CALLER_ROLES.INCLUDES(ROLE_Submitter){
// return ErrPermissionDenied // return ErrPermissionDenied
// } // }
// check source status // transaction
if !(submission.StatusID == model.StatusUnderConstruction||submission.StatusID == model.StatusChangesRequested){
return ErrInvalidSourceStatus
}
// set status
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id",model.StatusSubmitted) smap.Add("status_id",model.StatusSubmitted)
return svc.DB.Submissions().Update(ctx, params.SubmissionID, smap) return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusUnderConstruction,model.StatusChangesRequested}, smap)
} }
// ActionSubmissionTriggerPublish invokes actionSubmissionTriggerPublish operation. // ActionSubmissionTriggerPublish invokes actionSubmissionTriggerPublish operation.
// //
@ -259,22 +219,14 @@ func (svc *Service) ActionSubmissionSubmit(ctx context.Context, params api.Actio
// //
// PATCH /submissions/{SubmissionID}/status/trigger-publish // PATCH /submissions/{SubmissionID}/status/trigger-publish
func (svc *Service) ActionSubmissionTriggerPublish(ctx context.Context, params api.ActionSubmissionTriggerPublishParams) error { func (svc *Service) ActionSubmissionTriggerPublish(ctx context.Context, params api.ActionSubmissionTriggerPublishParams) error {
submission, err := svc.DB.Submissions().Get(ctx, params.SubmissionID)
if err != nil{
return err
}
// check if caller has required role // check if caller has required role
// if !CALLER_ROLES.INCLUDES(ROLE_Admin){ // if !CALLER_ROLES.INCLUDES(ROLE_Admin){
// return ErrPermissionDenied // return ErrPermissionDenied
// } // }
// check source status // transaction
if !(submission.StatusID == model.StatusValidated){
return ErrInvalidSourceStatus
}
// set status
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id",model.StatusPublishing) smap.Add("status_id",model.StatusPublishing)
return svc.DB.Submissions().Update(ctx, params.SubmissionID, smap) return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusValidated}, smap)
} }
// ActionSubmissionTriggerValidate invokes actionSubmissionTriggerValidate operation. // ActionSubmissionTriggerValidate invokes actionSubmissionTriggerValidate operation.
// //
@ -282,22 +234,14 @@ func (svc *Service) ActionSubmissionTriggerPublish(ctx context.Context, params a
// //
// PATCH /submissions/{SubmissionID}/status/trigger-validate // PATCH /submissions/{SubmissionID}/status/trigger-validate
func (svc *Service) ActionSubmissionTriggerValidate(ctx context.Context, params api.ActionSubmissionTriggerValidateParams) error { func (svc *Service) ActionSubmissionTriggerValidate(ctx context.Context, params api.ActionSubmissionTriggerValidateParams) error {
submission, err := svc.DB.Submissions().Get(ctx, params.SubmissionID)
if err != nil{
return err
}
// check if caller has required role // check if caller has required role
// if !CALLER_ROLES.INCLUDES(ROLE_Reviewer){ // if !CALLER_ROLES.INCLUDES(ROLE_Reviewer){
// return ErrPermissionDenied // return ErrPermissionDenied
// } // }
// check source status // transaction
if !(submission.StatusID == model.StatusSubmitted||submission.StatusID == model.StatusAccepted){
return ErrInvalidSourceStatus
}
// set status
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id",model.StatusValidating) smap.Add("status_id",model.StatusValidating)
return svc.DB.Submissions().Update(ctx, params.SubmissionID, smap) return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusSubmitted,model.StatusAccepted}, smap)
} }
// ActionSubmissionValidate invokes actionSubmissionValidate operation. // ActionSubmissionValidate invokes actionSubmissionValidate operation.
// //
@ -305,20 +249,12 @@ func (svc *Service) ActionSubmissionTriggerValidate(ctx context.Context, params
// //
// PATCH /submissions/{SubmissionID}/status/validate // PATCH /submissions/{SubmissionID}/status/validate
func (svc *Service) ActionSubmissionValidate(ctx context.Context, params api.ActionSubmissionValidateParams) error { func (svc *Service) ActionSubmissionValidate(ctx context.Context, params api.ActionSubmissionValidateParams) error {
submission, err := svc.DB.Submissions().Get(ctx, params.SubmissionID)
if err != nil{
return err
}
// check if caller has required role // check if caller has required role
// if !CALLER_ROLES.INCLUDES(ROLE_VALIDATOR){ // if !CALLER_ROLES.INCLUDES(ROLE_VALIDATOR){
// return ErrPermissionDenied // return ErrPermissionDenied
// } // }
// check source status // transaction
if submission.StatusID != model.StatusValidating{
return ErrInvalidSourceStatus
}
// set status
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id",model.StatusValidated) smap.Add("status_id",model.StatusValidated)
return svc.DB.Submissions().Update(ctx, params.SubmissionID, smap) return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusValidating}, smap)
} }