同步前后端联调与文档更新
This commit is contained in:
@@ -16,6 +16,10 @@ type SessionService struct {
|
||||
store *postgres.Store
|
||||
}
|
||||
|
||||
type sessionTokenPolicy struct {
|
||||
AllowExpired bool
|
||||
}
|
||||
|
||||
type SessionResult struct {
|
||||
Session struct {
|
||||
ID string `json:"id"`
|
||||
@@ -99,57 +103,11 @@ func (s *SessionService) ListMySessions(ctx context.Context, userID string, limi
|
||||
}
|
||||
|
||||
func (s *SessionService) StartSession(ctx context.Context, input SessionActionInput) (*SessionResult, error) {
|
||||
session, err := s.validateSessionAction(ctx, input.SessionPublicID, input.SessionToken)
|
||||
session, err := s.validateSessionAction(ctx, input.SessionPublicID, input.SessionToken, sessionTokenPolicy{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if session.Status == "finished" || session.Status == "cancelled" || session.Status == "failed" {
|
||||
return nil, apperr.New(http.StatusConflict, "session_not_startable", "session cannot be started")
|
||||
}
|
||||
|
||||
tx, err := s.store.Begin(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
|
||||
locked, err := s.store.GetSessionByPublicIDForUpdate(ctx, tx, input.SessionPublicID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if locked == nil {
|
||||
return nil, apperr.New(http.StatusNotFound, "session_not_found", "session not found")
|
||||
}
|
||||
if err := s.verifySessionToken(locked, input.SessionToken); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if locked.Status == "finished" || locked.Status == "cancelled" || locked.Status == "failed" {
|
||||
return nil, apperr.New(http.StatusConflict, "session_not_startable", "session cannot be started")
|
||||
}
|
||||
|
||||
if err := s.store.StartSession(ctx, tx, locked.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updated, err := s.store.GetSessionByPublicIDForUpdate(ctx, tx, input.SessionPublicID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buildSessionResult(updated), nil
|
||||
}
|
||||
|
||||
func (s *SessionService) FinishSession(ctx context.Context, input FinishSessionInput) (*SessionResult, error) {
|
||||
input.Status = normalizeFinishStatus(input.Status)
|
||||
session, err := s.validateSessionAction(ctx, input.SessionPublicID, input.SessionToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if session.Status == "finished" || session.Status == "cancelled" || session.Status == "failed" {
|
||||
if session.Status == SessionStatusRunning || isSessionTerminalStatus(session.Status) {
|
||||
return buildSessionResult(session), nil
|
||||
}
|
||||
|
||||
@@ -166,11 +124,73 @@ func (s *SessionService) FinishSession(ctx context.Context, input FinishSessionI
|
||||
if locked == nil {
|
||||
return nil, apperr.New(http.StatusNotFound, "session_not_found", "session not found")
|
||||
}
|
||||
if err := s.verifySessionToken(locked, input.SessionToken); err != nil {
|
||||
if err := s.verifySessionToken(locked, input.SessionToken, sessionTokenPolicy{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if locked.Status == SessionStatusRunning || isSessionTerminalStatus(locked.Status) {
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buildSessionResult(locked), nil
|
||||
}
|
||||
|
||||
if locked.Status == SessionStatusLaunched {
|
||||
if err := s.store.StartSession(ctx, tx, locked.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
updated, err := s.store.GetSessionByPublicIDForUpdate(ctx, tx, input.SessionPublicID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if updated == nil {
|
||||
return nil, apperr.New(http.StatusNotFound, "session_not_found", "session not found")
|
||||
}
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buildSessionResult(updated), nil
|
||||
}
|
||||
|
||||
func (s *SessionService) FinishSession(ctx context.Context, input FinishSessionInput) (*SessionResult, error) {
|
||||
status, err := normalizeFinishStatus(input.Status)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
input.Status = status
|
||||
|
||||
session, err := s.validateSessionAction(ctx, input.SessionPublicID, input.SessionToken, sessionTokenPolicy{
|
||||
AllowExpired: input.Status == SessionStatusCancelled,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if locked.Status == "finished" || locked.Status == "cancelled" || locked.Status == "failed" {
|
||||
if isSessionTerminalStatus(session.Status) {
|
||||
return buildSessionResult(session), nil
|
||||
}
|
||||
|
||||
tx, err := s.store.Begin(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
|
||||
locked, err := s.store.GetSessionByPublicIDForUpdate(ctx, tx, input.SessionPublicID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if locked == nil {
|
||||
return nil, apperr.New(http.StatusNotFound, "session_not_found", "session not found")
|
||||
}
|
||||
if err := s.verifySessionToken(locked, input.SessionToken, sessionTokenPolicy{
|
||||
AllowExpired: input.Status == SessionStatusCancelled || isSessionTerminalStatus(locked.Status),
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if isSessionTerminalStatus(locked.Status) {
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -208,7 +228,7 @@ func (s *SessionService) FinishSession(ctx context.Context, input FinishSessionI
|
||||
return buildSessionResult(updated), nil
|
||||
}
|
||||
|
||||
func (s *SessionService) validateSessionAction(ctx context.Context, sessionPublicID, sessionToken string) (*postgres.Session, error) {
|
||||
func (s *SessionService) validateSessionAction(ctx context.Context, sessionPublicID, sessionToken string, policy sessionTokenPolicy) (*postgres.Session, error) {
|
||||
sessionPublicID = strings.TrimSpace(sessionPublicID)
|
||||
sessionToken = strings.TrimSpace(sessionToken)
|
||||
if sessionPublicID == "" || sessionToken == "" {
|
||||
@@ -222,19 +242,19 @@ func (s *SessionService) validateSessionAction(ctx context.Context, sessionPubli
|
||||
if session == nil {
|
||||
return nil, apperr.New(http.StatusNotFound, "session_not_found", "session not found")
|
||||
}
|
||||
if err := s.verifySessionToken(session, sessionToken); err != nil {
|
||||
if err := s.verifySessionToken(session, sessionToken, policy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (s *SessionService) verifySessionToken(session *postgres.Session, sessionToken string) error {
|
||||
if session.SessionTokenExpiresAt.Before(time.Now().UTC()) {
|
||||
return apperr.New(http.StatusUnauthorized, "session_token_expired", "session token expired")
|
||||
}
|
||||
func (s *SessionService) verifySessionToken(session *postgres.Session, sessionToken string, policy sessionTokenPolicy) error {
|
||||
if session.SessionTokenHash != security.HashText(sessionToken) {
|
||||
return apperr.New(http.StatusUnauthorized, "invalid_session_token", "invalid session token")
|
||||
}
|
||||
if !policy.AllowExpired && session.SessionTokenExpiresAt.Before(time.Now().UTC()) {
|
||||
return apperr.New(http.StatusUnauthorized, "session_token_expired", "session token expired")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -265,14 +285,16 @@ func buildSessionResult(session *postgres.Session) *SessionResult {
|
||||
return result
|
||||
}
|
||||
|
||||
func normalizeFinishStatus(value string) string {
|
||||
func normalizeFinishStatus(value string) (string, error) {
|
||||
switch strings.TrimSpace(value) {
|
||||
case "failed":
|
||||
return "failed"
|
||||
case "cancelled":
|
||||
return "cancelled"
|
||||
case "", SessionStatusFinished:
|
||||
return SessionStatusFinished, nil
|
||||
case SessionStatusFailed:
|
||||
return SessionStatusFailed, nil
|
||||
case SessionStatusCancelled:
|
||||
return SessionStatusCancelled, nil
|
||||
default:
|
||||
return "finished"
|
||||
return "", apperr.New(http.StatusBadRequest, "invalid_finish_status", "status must be finished, failed or cancelled")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user