Go · 3290 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 package lifecycle
4
5 import (
6 "context"
7 "errors"
8 "fmt"
9 "strings"
10
11 "github.com/jackc/pgx/v5/pgconn"
12 "github.com/jackc/pgx/v5/pgtype"
13
14 orgsdb "github.com/tenseleyFlow/shithub/internal/orgs/sqlc"
15 reposdb "github.com/tenseleyFlow/shithub/internal/repos/sqlc"
16 usersdb "github.com/tenseleyFlow/shithub/internal/users/sqlc"
17 )
18
19 type repoDiskPaths struct {
20 canonical string
21 deleted string
22 }
23
24 func lockRepoName(ctx context.Context, q *reposdb.Queries, db reposdb.DBTX, repo reposdb.Repo) error {
25 key, err := repoNameLockKey(repo.OwnerUserID, repo.OwnerOrgID, repo.Name)
26 if err != nil {
27 return err
28 }
29 if err := q.LockRepoOwnerName(ctx, db, key); err != nil {
30 return fmt.Errorf("lock repo owner/name: %w", err)
31 }
32 return nil
33 }
34
35 func repoNameLockKey(ownerUserID, ownerOrgID pgtype.Int8, name string) (string, error) {
36 name = strings.ToLower(name)
37 switch {
38 case ownerUserID.Valid && !ownerOrgID.Valid:
39 return fmt.Sprintf("repo-name:user:%d:%s", ownerUserID.Int64, name), nil
40 case ownerOrgID.Valid && !ownerUserID.Valid:
41 return fmt.Sprintf("repo-name:org:%d:%s", ownerOrgID.Int64, name), nil
42 default:
43 return "", errors.New("lifecycle: repo owner is not xor")
44 }
45 }
46
47 func diskPathsForRepo(ctx context.Context, deps Deps, repo reposdb.Repo) (repoDiskPaths, error) {
48 owner, err := ownerSlugForRepo(ctx, deps, repo)
49 if err != nil {
50 return repoDiskPaths{}, err
51 }
52 canonical, err := deps.RepoFS.RepoPath(owner, repo.Name)
53 if err != nil {
54 return repoDiskPaths{}, fmt.Errorf("canonical repo path: %w", err)
55 }
56 deleted, err := deps.RepoFS.DeletedRepoPath(owner, repo.Name, repo.ID)
57 if err != nil {
58 return repoDiskPaths{}, fmt.Errorf("deleted repo path: %w", err)
59 }
60 return repoDiskPaths{canonical: canonical, deleted: deleted}, nil
61 }
62
63 func ownerSlugForRepo(ctx context.Context, deps Deps, repo reposdb.Repo) (string, error) {
64 switch {
65 case repo.OwnerUserID.Valid && !repo.OwnerOrgID.Valid:
66 user, err := usersdb.New().GetUserIncludingDeleted(ctx, deps.Pool, repo.OwnerUserID.Int64)
67 if err != nil {
68 return "", fmt.Errorf("load repo owner user: %w", err)
69 }
70 return user.Username, nil
71 case repo.OwnerOrgID.Valid && !repo.OwnerUserID.Valid:
72 org, err := orgsdb.New().GetOrgByID(ctx, deps.Pool, repo.OwnerOrgID.Int64)
73 if err != nil {
74 return "", fmt.Errorf("load repo owner org: %w", err)
75 }
76 return string(org.Slug), nil
77 default:
78 return "", errors.New("lifecycle: repo owner is not xor")
79 }
80 }
81
82 func activeRepoNameExists(ctx context.Context, q *reposdb.Queries, db reposdb.DBTX, repo reposdb.Repo) (bool, error) {
83 switch {
84 case repo.OwnerUserID.Valid && !repo.OwnerOrgID.Valid:
85 return q.ExistsRepoForOwnerUser(ctx, db, reposdb.ExistsRepoForOwnerUserParams{
86 OwnerUserID: pgtype.Int8{Int64: repo.OwnerUserID.Int64, Valid: true},
87 Name: repo.Name,
88 })
89 case repo.OwnerOrgID.Valid && !repo.OwnerUserID.Valid:
90 return q.ExistsRepoForOwnerOrg(ctx, db, reposdb.ExistsRepoForOwnerOrgParams{
91 OwnerOrgID: pgtype.Int8{Int64: repo.OwnerOrgID.Int64, Valid: true},
92 Name: repo.Name,
93 })
94 default:
95 return false, errors.New("lifecycle: repo owner is not xor")
96 }
97 }
98
99 func isUniqueViolation(err error) bool {
100 var pgErr *pgconn.PgError
101 return errors.As(err, &pgErr) && pgErr.Code == "23505"
102 }
103