tenseleyflow/shithub / 0b94e81

Browse files

Store repository source remotes

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
0b94e811729595dd6c1ebf68dc69ef8b60ed9e7e
Parents
21eebc4
Tree
8a14295

21 changed files

StatusFile+-
M internal/actions/sqlc/models.go 9 0
M internal/admin/sqlc/models.go 9 0
M internal/auth/policy/sqlc/models.go 9 0
M internal/checks/sqlc/models.go 9 0
M internal/issues/sqlc/models.go 9 0
M internal/meta/sqlc/models.go 9 0
A internal/migrationsfs/migrations/0051_repo_source_remotes.sql 16 0
M internal/notif/sqlc/models.go 9 0
M internal/orgs/sqlc/models.go 9 0
M internal/pulls/sqlc/models.go 9 0
M internal/ratelimit/sqlc/models.go 9 0
A internal/repos/queries/source_remotes.sql 32 0
A internal/repos/source_remote.go 69 0
A internal/repos/source_remote_test.go 58 0
M internal/repos/sqlc/models.go 9 0
M internal/repos/sqlc/querier.go 6 0
A internal/repos/sqlc/source_remotes.sql.go 103 0
M internal/social/sqlc/models.go 9 0
M internal/users/sqlc/models.go 9 0
M internal/webhook/sqlc/models.go 9 0
M internal/worker/sqlc/models.go 9 0
internal/actions/sqlc/models.gomodified
@@ -1990,6 +1990,15 @@ type RepoRedirect struct {
19901990
 	RedirectedAt   pgtype.Timestamptz
19911991
 }
19921992
 
1993
+type RepoSourceRemote struct {
1994
+	RepoID        int64
1995
+	RemoteUrl     string
1996
+	LastFetchedAt pgtype.Timestamptz
1997
+	LastError     pgtype.Text
1998
+	CreatedAt     pgtype.Timestamptz
1999
+	UpdatedAt     pgtype.Timestamptz
2000
+}
2001
+
19932002
 type RepoTopic struct {
19942003
 	RepoID    int64
19952004
 	Topic     string
internal/admin/sqlc/models.gomodified
@@ -1990,6 +1990,15 @@ type RepoRedirect struct {
19901990
 	RedirectedAt   pgtype.Timestamptz
19911991
 }
19921992
 
1993
+type RepoSourceRemote struct {
1994
+	RepoID        int64
1995
+	RemoteUrl     string
1996
+	LastFetchedAt pgtype.Timestamptz
1997
+	LastError     pgtype.Text
1998
+	CreatedAt     pgtype.Timestamptz
1999
+	UpdatedAt     pgtype.Timestamptz
2000
+}
2001
+
19932002
 type RepoTopic struct {
19942003
 	RepoID    int64
19952004
 	Topic     string
internal/auth/policy/sqlc/models.gomodified
@@ -1990,6 +1990,15 @@ type RepoRedirect struct {
19901990
 	RedirectedAt   pgtype.Timestamptz
19911991
 }
19921992
 
1993
+type RepoSourceRemote struct {
1994
+	RepoID        int64
1995
+	RemoteUrl     string
1996
+	LastFetchedAt pgtype.Timestamptz
1997
+	LastError     pgtype.Text
1998
+	CreatedAt     pgtype.Timestamptz
1999
+	UpdatedAt     pgtype.Timestamptz
2000
+}
2001
+
19932002
 type RepoTopic struct {
19942003
 	RepoID    int64
19952004
 	Topic     string
internal/checks/sqlc/models.gomodified
@@ -1990,6 +1990,15 @@ type RepoRedirect struct {
19901990
 	RedirectedAt   pgtype.Timestamptz
19911991
 }
19921992
 
1993
+type RepoSourceRemote struct {
1994
+	RepoID        int64
1995
+	RemoteUrl     string
1996
+	LastFetchedAt pgtype.Timestamptz
1997
+	LastError     pgtype.Text
1998
+	CreatedAt     pgtype.Timestamptz
1999
+	UpdatedAt     pgtype.Timestamptz
2000
+}
2001
+
19932002
 type RepoTopic struct {
19942003
 	RepoID    int64
19952004
 	Topic     string
internal/issues/sqlc/models.gomodified
@@ -1990,6 +1990,15 @@ type RepoRedirect struct {
19901990
 	RedirectedAt   pgtype.Timestamptz
19911991
 }
19921992
 
1993
+type RepoSourceRemote struct {
1994
+	RepoID        int64
1995
+	RemoteUrl     string
1996
+	LastFetchedAt pgtype.Timestamptz
1997
+	LastError     pgtype.Text
1998
+	CreatedAt     pgtype.Timestamptz
1999
+	UpdatedAt     pgtype.Timestamptz
2000
+}
2001
+
19932002
 type RepoTopic struct {
19942003
 	RepoID    int64
19952004
 	Topic     string
internal/meta/sqlc/models.gomodified
@@ -1990,6 +1990,15 @@ type RepoRedirect struct {
19901990
 	RedirectedAt   pgtype.Timestamptz
19911991
 }
19921992
 
1993
+type RepoSourceRemote struct {
1994
+	RepoID        int64
1995
+	RemoteUrl     string
1996
+	LastFetchedAt pgtype.Timestamptz
1997
+	LastError     pgtype.Text
1998
+	CreatedAt     pgtype.Timestamptz
1999
+	UpdatedAt     pgtype.Timestamptz
2000
+}
2001
+
19932002
 type RepoTopic struct {
19942003
 	RepoID    int64
19952004
 	Topic     string
internal/migrationsfs/migrations/0051_repo_source_remotes.sqladded
@@ -0,0 +1,16 @@
1
+-- SPDX-License-Identifier: AGPL-3.0-or-later
2
+
3
+CREATE TABLE repo_source_remotes (
4
+    repo_id bigint PRIMARY KEY REFERENCES repos(id) ON DELETE CASCADE,
5
+    remote_url text NOT NULL,
6
+    last_fetched_at timestamptz,
7
+    last_error text,
8
+    created_at timestamptz NOT NULL DEFAULT now(),
9
+    updated_at timestamptz NOT NULL DEFAULT now(),
10
+    CHECK (remote_url <> ''),
11
+    CHECK (length(remote_url) <= 2048)
12
+);
13
+
14
+CREATE TRIGGER repo_source_remotes_set_updated_at
15
+BEFORE UPDATE ON repo_source_remotes
16
+FOR EACH ROW EXECUTE FUNCTION tg_set_updated_at();
internal/notif/sqlc/models.gomodified
@@ -1990,6 +1990,15 @@ type RepoRedirect struct {
19901990
 	RedirectedAt   pgtype.Timestamptz
19911991
 }
19921992
 
1993
+type RepoSourceRemote struct {
1994
+	RepoID        int64
1995
+	RemoteUrl     string
1996
+	LastFetchedAt pgtype.Timestamptz
1997
+	LastError     pgtype.Text
1998
+	CreatedAt     pgtype.Timestamptz
1999
+	UpdatedAt     pgtype.Timestamptz
2000
+}
2001
+
19932002
 type RepoTopic struct {
19942003
 	RepoID    int64
19952004
 	Topic     string
internal/orgs/sqlc/models.gomodified
@@ -1990,6 +1990,15 @@ type RepoRedirect struct {
19901990
 	RedirectedAt   pgtype.Timestamptz
19911991
 }
19921992
 
1993
+type RepoSourceRemote struct {
1994
+	RepoID        int64
1995
+	RemoteUrl     string
1996
+	LastFetchedAt pgtype.Timestamptz
1997
+	LastError     pgtype.Text
1998
+	CreatedAt     pgtype.Timestamptz
1999
+	UpdatedAt     pgtype.Timestamptz
2000
+}
2001
+
19932002
 type RepoTopic struct {
19942003
 	RepoID    int64
19952004
 	Topic     string
internal/pulls/sqlc/models.gomodified
@@ -1990,6 +1990,15 @@ type RepoRedirect struct {
19901990
 	RedirectedAt   pgtype.Timestamptz
19911991
 }
19921992
 
1993
+type RepoSourceRemote struct {
1994
+	RepoID        int64
1995
+	RemoteUrl     string
1996
+	LastFetchedAt pgtype.Timestamptz
1997
+	LastError     pgtype.Text
1998
+	CreatedAt     pgtype.Timestamptz
1999
+	UpdatedAt     pgtype.Timestamptz
2000
+}
2001
+
19932002
 type RepoTopic struct {
19942003
 	RepoID    int64
19952004
 	Topic     string
internal/ratelimit/sqlc/models.gomodified
@@ -1990,6 +1990,15 @@ type RepoRedirect struct {
19901990
 	RedirectedAt   pgtype.Timestamptz
19911991
 }
19921992
 
1993
+type RepoSourceRemote struct {
1994
+	RepoID        int64
1995
+	RemoteUrl     string
1996
+	LastFetchedAt pgtype.Timestamptz
1997
+	LastError     pgtype.Text
1998
+	CreatedAt     pgtype.Timestamptz
1999
+	UpdatedAt     pgtype.Timestamptz
2000
+}
2001
+
19932002
 type RepoTopic struct {
19942003
 	RepoID    int64
19952004
 	Topic     string
internal/repos/queries/source_remotes.sqladded
@@ -0,0 +1,32 @@
1
+-- SPDX-License-Identifier: AGPL-3.0-or-later
2
+
3
+-- name: GetRepoSourceRemote :one
4
+SELECT repo_id, remote_url, last_fetched_at, last_error, created_at, updated_at
5
+FROM repo_source_remotes
6
+WHERE repo_id = $1;
7
+
8
+-- name: UpsertRepoSourceRemote :one
9
+INSERT INTO repo_source_remotes (repo_id, remote_url, last_error)
10
+VALUES ($1, $2, NULL)
11
+ON CONFLICT (repo_id) DO UPDATE
12
+   SET remote_url = EXCLUDED.remote_url,
13
+       last_error = NULL,
14
+       updated_at = now()
15
+RETURNING repo_id, remote_url, last_fetched_at, last_error, created_at, updated_at;
16
+
17
+-- name: DeleteRepoSourceRemote :exec
18
+DELETE FROM repo_source_remotes
19
+WHERE repo_id = $1;
20
+
21
+-- name: MarkRepoSourceRemoteFetched :exec
22
+UPDATE repo_source_remotes
23
+   SET last_fetched_at = now(),
24
+       last_error = NULL,
25
+       updated_at = now()
26
+ WHERE repo_id = $1;
27
+
28
+-- name: MarkRepoSourceRemoteFetchError :exec
29
+UPDATE repo_source_remotes
30
+   SET last_error = $2,
31
+       updated_at = now()
32
+ WHERE repo_id = $1;
internal/repos/source_remote.goadded
@@ -0,0 +1,69 @@
1
+// SPDX-License-Identifier: AGPL-3.0-or-later
2
+
3
+package repos
4
+
5
+import (
6
+	"context"
7
+	"errors"
8
+	"fmt"
9
+	"net/url"
10
+	"strings"
11
+
12
+	"github.com/tenseleyFlow/shithub/internal/security/ssrf"
13
+)
14
+
15
+const MaxSourceRemoteURLLen = 2048
16
+
17
+var ErrInvalidSourceRemote = errors.New("repos: invalid source remote URL")
18
+
19
+// NormalizeSourceRemoteURL validates and canonicalizes the public Git
20
+// remote URL shithub is allowed to fetch from for source imports and
21
+// submodule commit backfills. Credentials are deliberately not allowed
22
+// here; private import credentials need a separate secret-backed design.
23
+func NormalizeSourceRemoteURL(raw string) (string, error) {
24
+	raw = strings.TrimSpace(raw)
25
+	if raw == "" {
26
+		return "", nil
27
+	}
28
+	if len(raw) > MaxSourceRemoteURLLen {
29
+		return "", fmt.Errorf("%w: too long", ErrInvalidSourceRemote)
30
+	}
31
+	u, err := url.Parse(raw)
32
+	if err != nil {
33
+		return "", fmt.Errorf("%w: malformed URL", ErrInvalidSourceRemote)
34
+	}
35
+	switch strings.ToLower(u.Scheme) {
36
+	case "http", "https":
37
+	default:
38
+		return "", fmt.Errorf("%w: source imports currently support http(s) git remotes", ErrInvalidSourceRemote)
39
+	}
40
+	if u.Hostname() == "" {
41
+		return "", fmt.Errorf("%w: missing host", ErrInvalidSourceRemote)
42
+	}
43
+	if u.User != nil {
44
+		return "", fmt.Errorf("%w: credentials are not supported in source remote URLs", ErrInvalidSourceRemote)
45
+	}
46
+	if u.RawQuery != "" || u.Fragment != "" {
47
+		return "", fmt.Errorf("%w: query strings and fragments are not supported", ErrInvalidSourceRemote)
48
+	}
49
+	if strings.Trim(u.EscapedPath(), "/") == "" {
50
+		return "", fmt.Errorf("%w: missing repository path", ErrInvalidSourceRemote)
51
+	}
52
+	u.Scheme = strings.ToLower(u.Scheme)
53
+	u.Host = strings.ToLower(u.Host)
54
+	return u.String(), nil
55
+}
56
+
57
+// ValidateSourceRemoteURL runs the same SSRF defenses used for webhooks
58
+// before a URL is persisted or fetched by git. Git still receives the URL
59
+// as argv (never through a shell), and fetch disables submodule recursion.
60
+func ValidateSourceRemoteURL(ctx context.Context, raw string) (string, error) {
61
+	normalized, err := NormalizeSourceRemoteURL(raw)
62
+	if err != nil || normalized == "" {
63
+		return normalized, err
64
+	}
65
+	if err := ssrf.Default().ValidateWithResolve(ctx, normalized); err != nil {
66
+		return "", fmt.Errorf("%w: %v", ErrInvalidSourceRemote, err)
67
+	}
68
+	return normalized, nil
69
+}
internal/repos/source_remote_test.goadded
@@ -0,0 +1,58 @@
1
+// SPDX-License-Identifier: AGPL-3.0-or-later
2
+
3
+package repos_test
4
+
5
+import (
6
+	"errors"
7
+	"strings"
8
+	"testing"
9
+
10
+	"github.com/tenseleyFlow/shithub/internal/repos"
11
+)
12
+
13
+func TestNormalizeSourceRemoteURL(t *testing.T) {
14
+	t.Parallel()
15
+	for _, tt := range []struct {
16
+		name string
17
+		raw  string
18
+		want string
19
+	}{
20
+		{name: "https", raw: " https://github.com/FortranGoingOnForty/fgof-process.git ", want: "https://github.com/FortranGoingOnForty/fgof-process.git"},
21
+		{name: "http", raw: "HTTP://git.example.com/owner/repo.git", want: "http://git.example.com/owner/repo.git"},
22
+		{name: "empty clears", raw: " ", want: ""},
23
+	} {
24
+		tt := tt
25
+		t.Run(tt.name, func(t *testing.T) {
26
+			t.Parallel()
27
+			got, err := repos.NormalizeSourceRemoteURL(tt.raw)
28
+			if err != nil {
29
+				t.Fatalf("NormalizeSourceRemoteURL(%q): %v", tt.raw, err)
30
+			}
31
+			if got != tt.want {
32
+				t.Fatalf("NormalizeSourceRemoteURL(%q) = %q, want %q", tt.raw, got, tt.want)
33
+			}
34
+		})
35
+	}
36
+}
37
+
38
+func TestNormalizeSourceRemoteURLRejectsUnsafeShapes(t *testing.T) {
39
+	t.Parallel()
40
+	for _, raw := range []string{
41
+		"git@github.com:owner/repo.git",
42
+		"ssh://git@github.com/owner/repo.git",
43
+		"file:///tmp/repo.git",
44
+		"https://user:pass@example.com/owner/repo.git",
45
+		"https://example.com",
46
+		"https://example.com/owner/repo.git?token=secret",
47
+		"https://example.com/owner/repo.git#main",
48
+		strings.Repeat("a", repos.MaxSourceRemoteURLLen+1),
49
+	} {
50
+		raw := raw
51
+		t.Run(raw, func(t *testing.T) {
52
+			t.Parallel()
53
+			if got, err := repos.NormalizeSourceRemoteURL(raw); !errors.Is(err, repos.ErrInvalidSourceRemote) {
54
+				t.Fatalf("NormalizeSourceRemoteURL(%q) = %q, %v; want ErrInvalidSourceRemote", raw, got, err)
55
+			}
56
+		})
57
+	}
58
+}
internal/repos/sqlc/models.gomodified
@@ -1990,6 +1990,15 @@ type RepoRedirect struct {
19901990
 	RedirectedAt   pgtype.Timestamptz
19911991
 }
19921992
 
1993
+type RepoSourceRemote struct {
1994
+	RepoID        int64
1995
+	RemoteUrl     string
1996
+	LastFetchedAt pgtype.Timestamptz
1997
+	LastError     pgtype.Text
1998
+	CreatedAt     pgtype.Timestamptz
1999
+	UpdatedAt     pgtype.Timestamptz
2000
+}
2001
+
19932002
 type RepoTopic struct {
19942003
 	RepoID    int64
19952004
 	Topic     string
internal/repos/sqlc/querier.gomodified
@@ -45,6 +45,7 @@ type Querier interface {
4545
 	// (they would dangle once the repos row is gone; the FK ON DELETE
4646
 	// CASCADE would handle it, but explicit is auditable).
4747
 	DeleteRedirectsForRepo(ctx context.Context, db DBTX, repoID int64) error
48
+	DeleteRepoSourceRemote(ctx context.Context, db DBTX, repoID int64) error
4849
 	ExistsRepoForOwnerOrg(ctx context.Context, db DBTX, arg ExistsRepoForOwnerOrgParams) (bool, error)
4950
 	ExistsRepoForOwnerUser(ctx context.Context, db DBTX, arg ExistsRepoForOwnerUserParams) (bool, error)
5051
 	// Called by the periodic worker (transfers:expire) — flips pending
@@ -64,6 +65,8 @@ type Querier interface {
6465
 	// jobs that need to derive the bare-repo on-disk path without round-
6566
 	// tripping through the full user row.
6667
 	GetRepoOwnerUsernameByID(ctx context.Context, db DBTX, id int64) (GetRepoOwnerUsernameByIDRow, error)
68
+	// SPDX-License-Identifier: AGPL-3.0-or-later
69
+	GetRepoSourceRemote(ctx context.Context, db DBTX, repoID int64) (RepoSourceRemote, error)
6770
 	GetTransferRequest(ctx context.Context, db DBTX, id int64) (RepoTransferRequest, error)
6871
 	HardDeleteRepo(ctx context.Context, db DBTX, id int64) error
6972
 	InsertProfilePin(ctx context.Context, db DBTX, arg InsertProfilePinParams) error
@@ -111,6 +114,8 @@ type Querier interface {
111114
 	// Returns the current repo_id when (old_owner_user_id, old_name) hits
112115
 	// a redirect row.
113116
 	LookupRedirectByUserOwner(ctx context.Context, db DBTX, arg LookupRedirectByUserOwnerParams) (int64, error)
117
+	MarkRepoSourceRemoteFetchError(ctx context.Context, db DBTX, arg MarkRepoSourceRemoteFetchErrorParams) error
118
+	MarkRepoSourceRemoteFetched(ctx context.Context, db DBTX, repoID int64) error
114119
 	// ─── fork-anchor cleanup on hard delete ────────────────────────────────
115120
 	// Children pointing at this repo lose their fork-of pointer. Mirrors
116121
 	// GitHub's behavior when an upstream is deleted.
@@ -171,6 +176,7 @@ type Querier interface {
171176
 	UpsertBranchProtectionRule(ctx context.Context, db DBTX, arg UpsertBranchProtectionRuleParams) (int64, error)
172177
 	UpsertProfilePinSetForOrg(ctx context.Context, db DBTX, ownerOrgID pgtype.Int8) (int64, error)
173178
 	UpsertProfilePinSetForUser(ctx context.Context, db DBTX, ownerUserID pgtype.Int8) (int64, error)
179
+	UpsertRepoSourceRemote(ctx context.Context, db DBTX, arg UpsertRepoSourceRemoteParams) (RepoSourceRemote, error)
174180
 }
175181
 
176182
 var _ Querier = (*Queries)(nil)
internal/repos/sqlc/source_remotes.sql.goadded
@@ -0,0 +1,103 @@
1
+// Code generated by sqlc. DO NOT EDIT.
2
+// versions:
3
+//   sqlc v1.31.1
4
+// source: source_remotes.sql
5
+
6
+package reposdb
7
+
8
+import (
9
+	"context"
10
+
11
+	"github.com/jackc/pgx/v5/pgtype"
12
+)
13
+
14
+const deleteRepoSourceRemote = `-- name: DeleteRepoSourceRemote :exec
15
+DELETE FROM repo_source_remotes
16
+WHERE repo_id = $1
17
+`
18
+
19
+func (q *Queries) DeleteRepoSourceRemote(ctx context.Context, db DBTX, repoID int64) error {
20
+	_, err := db.Exec(ctx, deleteRepoSourceRemote, repoID)
21
+	return err
22
+}
23
+
24
+const getRepoSourceRemote = `-- name: GetRepoSourceRemote :one
25
+
26
+SELECT repo_id, remote_url, last_fetched_at, last_error, created_at, updated_at
27
+FROM repo_source_remotes
28
+WHERE repo_id = $1
29
+`
30
+
31
+// SPDX-License-Identifier: AGPL-3.0-or-later
32
+func (q *Queries) GetRepoSourceRemote(ctx context.Context, db DBTX, repoID int64) (RepoSourceRemote, error) {
33
+	row := db.QueryRow(ctx, getRepoSourceRemote, repoID)
34
+	var i RepoSourceRemote
35
+	err := row.Scan(
36
+		&i.RepoID,
37
+		&i.RemoteUrl,
38
+		&i.LastFetchedAt,
39
+		&i.LastError,
40
+		&i.CreatedAt,
41
+		&i.UpdatedAt,
42
+	)
43
+	return i, err
44
+}
45
+
46
+const markRepoSourceRemoteFetchError = `-- name: MarkRepoSourceRemoteFetchError :exec
47
+UPDATE repo_source_remotes
48
+   SET last_error = $2,
49
+       updated_at = now()
50
+ WHERE repo_id = $1
51
+`
52
+
53
+type MarkRepoSourceRemoteFetchErrorParams struct {
54
+	RepoID    int64
55
+	LastError pgtype.Text
56
+}
57
+
58
+func (q *Queries) MarkRepoSourceRemoteFetchError(ctx context.Context, db DBTX, arg MarkRepoSourceRemoteFetchErrorParams) error {
59
+	_, err := db.Exec(ctx, markRepoSourceRemoteFetchError, arg.RepoID, arg.LastError)
60
+	return err
61
+}
62
+
63
+const markRepoSourceRemoteFetched = `-- name: MarkRepoSourceRemoteFetched :exec
64
+UPDATE repo_source_remotes
65
+   SET last_fetched_at = now(),
66
+       last_error = NULL,
67
+       updated_at = now()
68
+ WHERE repo_id = $1
69
+`
70
+
71
+func (q *Queries) MarkRepoSourceRemoteFetched(ctx context.Context, db DBTX, repoID int64) error {
72
+	_, err := db.Exec(ctx, markRepoSourceRemoteFetched, repoID)
73
+	return err
74
+}
75
+
76
+const upsertRepoSourceRemote = `-- name: UpsertRepoSourceRemote :one
77
+INSERT INTO repo_source_remotes (repo_id, remote_url, last_error)
78
+VALUES ($1, $2, NULL)
79
+ON CONFLICT (repo_id) DO UPDATE
80
+   SET remote_url = EXCLUDED.remote_url,
81
+       last_error = NULL,
82
+       updated_at = now()
83
+RETURNING repo_id, remote_url, last_fetched_at, last_error, created_at, updated_at
84
+`
85
+
86
+type UpsertRepoSourceRemoteParams struct {
87
+	RepoID    int64
88
+	RemoteUrl string
89
+}
90
+
91
+func (q *Queries) UpsertRepoSourceRemote(ctx context.Context, db DBTX, arg UpsertRepoSourceRemoteParams) (RepoSourceRemote, error) {
92
+	row := db.QueryRow(ctx, upsertRepoSourceRemote, arg.RepoID, arg.RemoteUrl)
93
+	var i RepoSourceRemote
94
+	err := row.Scan(
95
+		&i.RepoID,
96
+		&i.RemoteUrl,
97
+		&i.LastFetchedAt,
98
+		&i.LastError,
99
+		&i.CreatedAt,
100
+		&i.UpdatedAt,
101
+	)
102
+	return i, err
103
+}
internal/social/sqlc/models.gomodified
@@ -1990,6 +1990,15 @@ type RepoRedirect struct {
19901990
 	RedirectedAt   pgtype.Timestamptz
19911991
 }
19921992
 
1993
+type RepoSourceRemote struct {
1994
+	RepoID        int64
1995
+	RemoteUrl     string
1996
+	LastFetchedAt pgtype.Timestamptz
1997
+	LastError     pgtype.Text
1998
+	CreatedAt     pgtype.Timestamptz
1999
+	UpdatedAt     pgtype.Timestamptz
2000
+}
2001
+
19932002
 type RepoTopic struct {
19942003
 	RepoID    int64
19952004
 	Topic     string
internal/users/sqlc/models.gomodified
@@ -1990,6 +1990,15 @@ type RepoRedirect struct {
19901990
 	RedirectedAt   pgtype.Timestamptz
19911991
 }
19921992
 
1993
+type RepoSourceRemote struct {
1994
+	RepoID        int64
1995
+	RemoteUrl     string
1996
+	LastFetchedAt pgtype.Timestamptz
1997
+	LastError     pgtype.Text
1998
+	CreatedAt     pgtype.Timestamptz
1999
+	UpdatedAt     pgtype.Timestamptz
2000
+}
2001
+
19932002
 type RepoTopic struct {
19942003
 	RepoID    int64
19952004
 	Topic     string
internal/webhook/sqlc/models.gomodified
@@ -1990,6 +1990,15 @@ type RepoRedirect struct {
19901990
 	RedirectedAt   pgtype.Timestamptz
19911991
 }
19921992
 
1993
+type RepoSourceRemote struct {
1994
+	RepoID        int64
1995
+	RemoteUrl     string
1996
+	LastFetchedAt pgtype.Timestamptz
1997
+	LastError     pgtype.Text
1998
+	CreatedAt     pgtype.Timestamptz
1999
+	UpdatedAt     pgtype.Timestamptz
2000
+}
2001
+
19932002
 type RepoTopic struct {
19942003
 	RepoID    int64
19952004
 	Topic     string
internal/worker/sqlc/models.gomodified
@@ -1990,6 +1990,15 @@ type RepoRedirect struct {
19901990
 	RedirectedAt   pgtype.Timestamptz
19911991
 }
19921992
 
1993
+type RepoSourceRemote struct {
1994
+	RepoID        int64
1995
+	RemoteUrl     string
1996
+	LastFetchedAt pgtype.Timestamptz
1997
+	LastError     pgtype.Text
1998
+	CreatedAt     pgtype.Timestamptz
1999
+	UpdatedAt     pgtype.Timestamptz
2000
+}
2001
+
19932002
 type RepoTopic struct {
19942003
 	RepoID    int64
19952004
 	Topic     string