From 1cb382986e21efe7b5e31137ac8d127c5e281a99 Mon Sep 17 00:00:00 2001 From: KCarretto Date: Sun, 25 Feb 2024 22:15:12 +0000 Subject: [PATCH 1/6] support scp style git urls via regex and parsing --- tavern/internal/ent/schema/repository.go | 84 +++++++++++++++++-- .../mutations/createRepository/SCPURL.yml | 24 ++++++ .../createRepository/SCPURLWithoutUser.yml | 24 ++++++ .../createRepository/SSHURLWithUser.yml | 24 ++++++ .../createRepository/URLWithHTTP.yml | 24 ++++++ ...utSchema.yml => URLWithoutSchema copy.yml} | 2 +- .../mutations/createRepository/ValidURL.yml | 2 +- tavern/tomes/git.go | 14 +++- 8 files changed, 188 insertions(+), 10 deletions(-) create mode 100644 tavern/internal/graphql/testdata/mutations/createRepository/SCPURL.yml create mode 100644 tavern/internal/graphql/testdata/mutations/createRepository/SCPURLWithoutUser.yml create mode 100644 tavern/internal/graphql/testdata/mutations/createRepository/SSHURLWithUser.yml create mode 100644 tavern/internal/graphql/testdata/mutations/createRepository/URLWithHTTP.yml rename tavern/internal/graphql/testdata/mutations/createRepository/{URLWithoutSchema.yml => URLWithoutSchema copy.yml} (91%) diff --git a/tavern/internal/ent/schema/repository.go b/tavern/internal/ent/schema/repository.go index 927935005..2e123da91 100644 --- a/tavern/internal/ent/schema/repository.go +++ b/tavern/internal/ent/schema/repository.go @@ -6,6 +6,8 @@ import ( "crypto/rand" "encoding/pem" "fmt" + "net/url" + "regexp" "strings" "entgo.io/contrib/entgql" @@ -91,12 +93,12 @@ func (Repository) Mixin() []ent.Mixin { // Hooks defines middleware for mutations for the ent. func (Repository) Hooks() []ent.Hook { return []ent.Hook{ - hook.On(HookCreateRepoPrivateKey(), ent.OpCreate), + hook.On(HookDeriveRepoOnCreate(), ent.OpCreate), } } -// HookCreateRepoPrivateKey will generate private key for the repository upon creation. -func HookCreateRepoPrivateKey() ent.Hook { +// HookDeriveRepoOnCreate will generate private key for the repository upon creation. +func HookDeriveRepoOnCreate() ent.Hook { // Get the relevant methods from the Mutation // See this example: https://github.com/ent/ent/blob/master/entc/integration/hooks/ent/schema/user.go#L98 type tMutation interface { @@ -115,9 +117,14 @@ func HookCreateRepoPrivateKey() ent.Hook { return nil, fmt.Errorf("expected repository mutation in schema hook, got: %+v", m) } - // Prepend https schema if no schema specified - if u, ok := mut.URL(); ok && (!strings.HasPrefix(u, "https://") && !strings.HasPrefix(u, "http://") && !strings.HasPrefix(u, "ssh://")) { - mut.SetURL(fmt.Sprintf("https://%s", u)) + // Format the URL (and detect errors) + if rawurl, ok := mut.URL(); ok { + formattedURL, err := FormatGitURL(rawurl) + if err != nil { + return nil, fmt.Errorf("failed to format git url: %w", err) + } + + mut.SetURL(formattedURL.String()) } // Skip if key already set @@ -149,3 +156,68 @@ func HookCreateRepoPrivateKey() ent.Hook { }) } } + +var scpRegex = regexp.MustCompile(`^(ssh://)?([a-zA-Z0-9_]+)@([a-zA-Z0-9._-]+):([a-zA-Z0-9./._-]+)(?:\?||$)(.*)$`) + +func FormatGitURL(rawurl string) (*url.URL, error) { + rawurl = strings.TrimSpace(rawurl) + + // If it's http(s), return the parsed url + if strings.HasPrefix(rawurl, "http://") || strings.HasPrefix(rawurl, "https://") { + return url.Parse(rawurl) + } + + // Handle SCP (if user specified) + scpParts := scpRegex.FindStringSubmatch(rawurl) + if scpParts != nil { + var ( + scheme = "ssh" + user = "git" + rawquery = "" + ) + + if scpParts[1] != "" { + scheme = strings.TrimSuffix(scpParts[1], "://") + } + if scpParts[2] != "" { + user = scpParts[2] + } + if len(scpParts) > 4 { + rawquery = scpParts[5] + } + + return &url.URL{ + Scheme: scheme, + User: url.User(user), + Host: scpParts[3], + Path: scpParts[4], + RawQuery: rawquery, + }, nil + + } + + u, err := url.Parse(rawurl) + if err != nil { + return nil, err + } + + // Handle SCP : with no user specified + if u.Opaque != "" && u.Scheme != "ssh" && u.Scheme != "" && u.User == nil { + return &url.URL{ + Scheme: "ssh", + User: url.User("git"), + Host: u.Scheme, // How url will parse host with : instead of / + Path: u.Opaque, + RawQuery: u.RawQuery, + }, nil + } + + // Default to SSH + if u.Scheme == "" { + u.Scheme = "ssh" + } + if u.User == nil { + u.User = url.User("git") + } + return u, nil +} diff --git a/tavern/internal/graphql/testdata/mutations/createRepository/SCPURL.yml b/tavern/internal/graphql/testdata/mutations/createRepository/SCPURL.yml new file mode 100644 index 000000000..c99f81885 --- /dev/null +++ b/tavern/internal/graphql/testdata/mutations/createRepository/SCPURL.yml @@ -0,0 +1,24 @@ +state: | + INSERT INTO `users` (id,oauth_id,photo_url,name,session_token,access_token,is_activated,is_admin) + VALUES (5,"test_oauth_id","https://photos.com","test","secretToken","accessToken",true,true); +requestor: + session_token: secretToken +query: | + mutation CreateRepository($input: CreateRepositoryInput!) { + createRepository(input: $input) { + url + + owner { + id + } + } + } +variables: + input: + url: "git@github.com:spellshift/realm.git" + +expected: + createRepository: + url: "ssh://git@github.com/spellshift/realm.git" + owner: + id: "5" diff --git a/tavern/internal/graphql/testdata/mutations/createRepository/SCPURLWithoutUser.yml b/tavern/internal/graphql/testdata/mutations/createRepository/SCPURLWithoutUser.yml new file mode 100644 index 000000000..54fc6e5cb --- /dev/null +++ b/tavern/internal/graphql/testdata/mutations/createRepository/SCPURLWithoutUser.yml @@ -0,0 +1,24 @@ +state: | + INSERT INTO `users` (id,oauth_id,photo_url,name,session_token,access_token,is_activated,is_admin) + VALUES (5,"test_oauth_id","https://photos.com","test","secretToken","accessToken",true,true); +requestor: + session_token: secretToken +query: | + mutation CreateRepository($input: CreateRepositoryInput!) { + createRepository(input: $input) { + url + + owner { + id + } + } + } +variables: + input: + url: "github.com:spellshift/realm.git" + +expected: + createRepository: + url: "ssh://git@github.com/spellshift/realm.git" + owner: + id: "5" diff --git a/tavern/internal/graphql/testdata/mutations/createRepository/SSHURLWithUser.yml b/tavern/internal/graphql/testdata/mutations/createRepository/SSHURLWithUser.yml new file mode 100644 index 000000000..e237eb02f --- /dev/null +++ b/tavern/internal/graphql/testdata/mutations/createRepository/SSHURLWithUser.yml @@ -0,0 +1,24 @@ +state: | + INSERT INTO `users` (id,oauth_id,photo_url,name,session_token,access_token,is_activated,is_admin) + VALUES (5,"test_oauth_id","https://photos.com","test","secretToken","accessToken",true,true); +requestor: + session_token: secretToken +query: | + mutation CreateRepository($input: CreateRepositoryInput!) { + createRepository(input: $input) { + url + + owner { + id + } + } + } +variables: + input: + url: "ssh://user@github.com/spellshift/realm" + +expected: + createRepository: + url: "ssh://user@github.com/spellshift/realm" + owner: + id: "5" diff --git a/tavern/internal/graphql/testdata/mutations/createRepository/URLWithHTTP.yml b/tavern/internal/graphql/testdata/mutations/createRepository/URLWithHTTP.yml new file mode 100644 index 000000000..9d6ec5646 --- /dev/null +++ b/tavern/internal/graphql/testdata/mutations/createRepository/URLWithHTTP.yml @@ -0,0 +1,24 @@ +state: | + INSERT INTO `users` (id,oauth_id,photo_url,name,session_token,access_token,is_activated,is_admin) + VALUES (5,"test_oauth_id","https://photos.com","test","secretToken","accessToken",true,true); +requestor: + session_token: secretToken +query: | + mutation CreateRepository($input: CreateRepositoryInput!) { + createRepository(input: $input) { + url + + owner { + id + } + } + } +variables: + input: + url: "http://github.com/spellshift/realm" + +expected: + createRepository: + url: "http://github.com/spellshift/realm" + owner: + id: "5" diff --git a/tavern/internal/graphql/testdata/mutations/createRepository/URLWithoutSchema.yml b/tavern/internal/graphql/testdata/mutations/createRepository/URLWithoutSchema copy.yml similarity index 91% rename from tavern/internal/graphql/testdata/mutations/createRepository/URLWithoutSchema.yml rename to tavern/internal/graphql/testdata/mutations/createRepository/URLWithoutSchema copy.yml index e03cda668..01b30008b 100644 --- a/tavern/internal/graphql/testdata/mutations/createRepository/URLWithoutSchema.yml +++ b/tavern/internal/graphql/testdata/mutations/createRepository/URLWithoutSchema copy.yml @@ -19,6 +19,6 @@ variables: expected: createRepository: - url: "https://github.com/spellshift/realm" + url: "ssh://git@github.com/spellshift/realm" owner: id: "5" diff --git a/tavern/internal/graphql/testdata/mutations/createRepository/ValidURL.yml b/tavern/internal/graphql/testdata/mutations/createRepository/ValidURL.yml index 2f10a7a9d..58890a3f1 100644 --- a/tavern/internal/graphql/testdata/mutations/createRepository/ValidURL.yml +++ b/tavern/internal/graphql/testdata/mutations/createRepository/ValidURL.yml @@ -19,6 +19,6 @@ variables: expected: createRepository: - url: "ssh://github.com/spellshift/realm" + url: "ssh://git@github.com/spellshift/realm" owner: id: "5" diff --git a/tavern/tomes/git.go b/tavern/tomes/git.go index 0769ac50b..d7ca273cc 100644 --- a/tavern/tomes/git.go +++ b/tavern/tomes/git.go @@ -53,15 +53,25 @@ type GitImporter struct { // Provided filters on tome paths may be used to limit included directories by returning true if the // result should be included. func (importer *GitImporter) Import(ctx context.Context, entRepo *ent.Repository, filters ...func(path string) bool) error { + // Parse URL + repoURL, err := url.Parse(entRepo.URL) + if err != nil { + return fmt.Errorf("failed to parse repo url %q: %w", entRepo.URL, err) + } + // Use Private Key Auth for SSH var authMethod transport.AuthMethod - if strings.HasPrefix(entRepo.URL, "ssh://") { + if repoURL.Scheme == "ssh" { privKey, err := ssh.ParsePrivateKey([]byte(entRepo.PrivateKey)) if err != nil { return fmt.Errorf("failed to parse private key for repository: %w", err) } + user := "git" + if repoURL.User != nil && repoURL.User.Username() != "" { + user = repoURL.User.Username() + } authMethod = transport.AuthMethod(&gitssh.PublicKeys{ - User: "git", + User: user, Signer: privKey, HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{ // Ignore Host Keys From e4f2429f0096a6cef91882584a4c00006f8fee83 Mon Sep 17 00:00:00 2001 From: KCarretto Date: Sun, 25 Feb 2024 22:17:13 +0000 Subject: [PATCH 2/6] update comment --- tavern/internal/ent/schema/repository.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tavern/internal/ent/schema/repository.go b/tavern/internal/ent/schema/repository.go index 2e123da91..dcd8a595a 100644 --- a/tavern/internal/ent/schema/repository.go +++ b/tavern/internal/ent/schema/repository.go @@ -98,6 +98,7 @@ func (Repository) Hooks() []ent.Hook { } // HookDeriveRepoOnCreate will generate private key for the repository upon creation. +// It will also format the git URL (if one is specified). func HookDeriveRepoOnCreate() ent.Hook { // Get the relevant methods from the Mutation // See this example: https://github.com/ent/ent/blob/master/entc/integration/hooks/ent/schema/user.go#L98 From a9fde5def619ccfdc7c222893f81ead06f5eaefb Mon Sep 17 00:00:00 2001 From: KCarretto Date: Sun, 25 Feb 2024 22:28:16 +0000 Subject: [PATCH 3/6] added last_imported_at --- tavern/internal/ent/gql_collection.go | 5 + tavern/internal/ent/gql_pagination.go | 18 +++ tavern/internal/ent/gql_where_input.go | 42 ++++++ tavern/internal/ent/migrate/schema.go | 3 +- tavern/internal/ent/mutation.go | 80 +++++++++- tavern/internal/ent/repository.go | 13 +- tavern/internal/ent/repository/repository.go | 8 + tavern/internal/ent/repository/where.go | 55 +++++++ tavern/internal/ent/repository_create.go | 78 ++++++++++ tavern/internal/ent/repository_update.go | 52 +++++++ tavern/internal/ent/schema/repository.go | 7 + .../graphql/generated/ent.generated.go | 139 +++++++++++++++++- .../graphql/generated/mutation.generated.go | 4 + .../graphql/generated/root_.generated.go | 22 +++ tavern/internal/graphql/mutation.resolvers.go | 3 +- tavern/internal/graphql/schema.graphql | 14 ++ tavern/internal/graphql/schema/ent.graphql | 14 ++ tavern/internal/www/schema.graphql | 14 ++ 18 files changed, 565 insertions(+), 6 deletions(-) diff --git a/tavern/internal/ent/gql_collection.go b/tavern/internal/ent/gql_collection.go index dfc4b6d53..48f6c0172 100644 --- a/tavern/internal/ent/gql_collection.go +++ b/tavern/internal/ent/gql_collection.go @@ -1109,6 +1109,11 @@ func (r *RepositoryQuery) collectField(ctx context.Context, opCtx *graphql.Opera selectedFields = append(selectedFields, repository.FieldPublicKey) fieldSeen[repository.FieldPublicKey] = struct{}{} } + case "lastImportedAt": + if _, ok := fieldSeen[repository.FieldLastImportedAt]; !ok { + selectedFields = append(selectedFields, repository.FieldLastImportedAt) + fieldSeen[repository.FieldLastImportedAt] = struct{}{} + } case "id": case "__typename": default: diff --git a/tavern/internal/ent/gql_pagination.go b/tavern/internal/ent/gql_pagination.go index 73d239e22..2192730a7 100644 --- a/tavern/internal/ent/gql_pagination.go +++ b/tavern/internal/ent/gql_pagination.go @@ -2772,6 +2772,20 @@ var ( } }, } + // RepositoryOrderFieldLastImportedAt orders Repository by last_imported_at. + RepositoryOrderFieldLastImportedAt = &RepositoryOrderField{ + Value: func(r *Repository) (ent.Value, error) { + return r.LastImportedAt, nil + }, + column: repository.FieldLastImportedAt, + toTerm: repository.ByLastImportedAt, + toCursor: func(r *Repository) Cursor { + return Cursor{ + ID: r.ID, + Value: r.LastImportedAt, + } + }, + } ) // String implement fmt.Stringer interface. @@ -2782,6 +2796,8 @@ func (f RepositoryOrderField) String() string { str = "CREATED_AT" case RepositoryOrderFieldLastModifiedAt.column: str = "LAST_MODIFIED_AT" + case RepositoryOrderFieldLastImportedAt.column: + str = "LAST_IMPORTED_AT" } return str } @@ -2802,6 +2818,8 @@ func (f *RepositoryOrderField) UnmarshalGQL(v interface{}) error { *f = *RepositoryOrderFieldCreatedAt case "LAST_MODIFIED_AT": *f = *RepositoryOrderFieldLastModifiedAt + case "LAST_IMPORTED_AT": + *f = *RepositoryOrderFieldLastImportedAt default: return fmt.Errorf("%s is not a valid RepositoryOrderField", str) } diff --git a/tavern/internal/ent/gql_where_input.go b/tavern/internal/ent/gql_where_input.go index 6d3134e9e..2caac0db9 100644 --- a/tavern/internal/ent/gql_where_input.go +++ b/tavern/internal/ent/gql_where_input.go @@ -3547,6 +3547,18 @@ type RepositoryWhereInput struct { PublicKeyEqualFold *string `json:"publicKeyEqualFold,omitempty"` PublicKeyContainsFold *string `json:"publicKeyContainsFold,omitempty"` + // "last_imported_at" field predicates. + LastImportedAt *time.Time `json:"lastImportedAt,omitempty"` + LastImportedAtNEQ *time.Time `json:"lastImportedAtNEQ,omitempty"` + LastImportedAtIn []time.Time `json:"lastImportedAtIn,omitempty"` + LastImportedAtNotIn []time.Time `json:"lastImportedAtNotIn,omitempty"` + LastImportedAtGT *time.Time `json:"lastImportedAtGT,omitempty"` + LastImportedAtGTE *time.Time `json:"lastImportedAtGTE,omitempty"` + LastImportedAtLT *time.Time `json:"lastImportedAtLT,omitempty"` + LastImportedAtLTE *time.Time `json:"lastImportedAtLTE,omitempty"` + LastImportedAtIsNil bool `json:"lastImportedAtIsNil,omitempty"` + LastImportedAtNotNil bool `json:"lastImportedAtNotNil,omitempty"` + // "tomes" edge predicates. HasTomes *bool `json:"hasTomes,omitempty"` HasTomesWith []*TomeWhereInput `json:"hasTomesWith,omitempty"` @@ -3777,6 +3789,36 @@ func (i *RepositoryWhereInput) P() (predicate.Repository, error) { if i.PublicKeyContainsFold != nil { predicates = append(predicates, repository.PublicKeyContainsFold(*i.PublicKeyContainsFold)) } + if i.LastImportedAt != nil { + predicates = append(predicates, repository.LastImportedAtEQ(*i.LastImportedAt)) + } + if i.LastImportedAtNEQ != nil { + predicates = append(predicates, repository.LastImportedAtNEQ(*i.LastImportedAtNEQ)) + } + if len(i.LastImportedAtIn) > 0 { + predicates = append(predicates, repository.LastImportedAtIn(i.LastImportedAtIn...)) + } + if len(i.LastImportedAtNotIn) > 0 { + predicates = append(predicates, repository.LastImportedAtNotIn(i.LastImportedAtNotIn...)) + } + if i.LastImportedAtGT != nil { + predicates = append(predicates, repository.LastImportedAtGT(*i.LastImportedAtGT)) + } + if i.LastImportedAtGTE != nil { + predicates = append(predicates, repository.LastImportedAtGTE(*i.LastImportedAtGTE)) + } + if i.LastImportedAtLT != nil { + predicates = append(predicates, repository.LastImportedAtLT(*i.LastImportedAtLT)) + } + if i.LastImportedAtLTE != nil { + predicates = append(predicates, repository.LastImportedAtLTE(*i.LastImportedAtLTE)) + } + if i.LastImportedAtIsNil { + predicates = append(predicates, repository.LastImportedAtIsNil()) + } + if i.LastImportedAtNotNil { + predicates = append(predicates, repository.LastImportedAtNotNil()) + } if i.HasTomes != nil { p := repository.HasTomes() diff --git a/tavern/internal/ent/migrate/schema.go b/tavern/internal/ent/migrate/schema.go index 836e99126..2bff4d235 100644 --- a/tavern/internal/ent/migrate/schema.go +++ b/tavern/internal/ent/migrate/schema.go @@ -231,6 +231,7 @@ var ( {Name: "url", Type: field.TypeString, Unique: true}, {Name: "public_key", Type: field.TypeString, SchemaType: map[string]string{"mysql": "LONGTEXT"}}, {Name: "private_key", Type: field.TypeString, SchemaType: map[string]string{"mysql": "LONGTEXT"}}, + {Name: "last_imported_at", Type: field.TypeTime, Nullable: true}, {Name: "repository_owner", Type: field.TypeInt, Nullable: true}, } // RepositoriesTable holds the schema information for the "repositories" table. @@ -241,7 +242,7 @@ var ( ForeignKeys: []*schema.ForeignKey{ { Symbol: "repositories_users_owner", - Columns: []*schema.Column{RepositoriesColumns[6]}, + Columns: []*schema.Column{RepositoriesColumns[7]}, RefColumns: []*schema.Column{UsersColumns[0]}, OnDelete: schema.SetNull, }, diff --git a/tavern/internal/ent/mutation.go b/tavern/internal/ent/mutation.go index d03c02172..39e6f7e7e 100644 --- a/tavern/internal/ent/mutation.go +++ b/tavern/internal/ent/mutation.go @@ -6500,6 +6500,7 @@ type RepositoryMutation struct { url *string public_key *string private_key *string + last_imported_at *time.Time clearedFields map[string]struct{} tomes map[int]struct{} removedtomes map[int]struct{} @@ -6789,6 +6790,55 @@ func (m *RepositoryMutation) ResetPrivateKey() { m.private_key = nil } +// SetLastImportedAt sets the "last_imported_at" field. +func (m *RepositoryMutation) SetLastImportedAt(t time.Time) { + m.last_imported_at = &t +} + +// LastImportedAt returns the value of the "last_imported_at" field in the mutation. +func (m *RepositoryMutation) LastImportedAt() (r time.Time, exists bool) { + v := m.last_imported_at + if v == nil { + return + } + return *v, true +} + +// OldLastImportedAt returns the old "last_imported_at" field's value of the Repository entity. +// If the Repository object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *RepositoryMutation) OldLastImportedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldLastImportedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldLastImportedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldLastImportedAt: %w", err) + } + return oldValue.LastImportedAt, nil +} + +// ClearLastImportedAt clears the value of the "last_imported_at" field. +func (m *RepositoryMutation) ClearLastImportedAt() { + m.last_imported_at = nil + m.clearedFields[repository.FieldLastImportedAt] = struct{}{} +} + +// LastImportedAtCleared returns if the "last_imported_at" field was cleared in this mutation. +func (m *RepositoryMutation) LastImportedAtCleared() bool { + _, ok := m.clearedFields[repository.FieldLastImportedAt] + return ok +} + +// ResetLastImportedAt resets all changes to the "last_imported_at" field. +func (m *RepositoryMutation) ResetLastImportedAt() { + m.last_imported_at = nil + delete(m.clearedFields, repository.FieldLastImportedAt) +} + // AddTomeIDs adds the "tomes" edge to the Tome entity by ids. func (m *RepositoryMutation) AddTomeIDs(ids ...int) { if m.tomes == nil { @@ -6916,7 +6966,7 @@ func (m *RepositoryMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *RepositoryMutation) Fields() []string { - fields := make([]string, 0, 5) + fields := make([]string, 0, 6) if m.created_at != nil { fields = append(fields, repository.FieldCreatedAt) } @@ -6932,6 +6982,9 @@ func (m *RepositoryMutation) Fields() []string { if m.private_key != nil { fields = append(fields, repository.FieldPrivateKey) } + if m.last_imported_at != nil { + fields = append(fields, repository.FieldLastImportedAt) + } return fields } @@ -6950,6 +7003,8 @@ func (m *RepositoryMutation) Field(name string) (ent.Value, bool) { return m.PublicKey() case repository.FieldPrivateKey: return m.PrivateKey() + case repository.FieldLastImportedAt: + return m.LastImportedAt() } return nil, false } @@ -6969,6 +7024,8 @@ func (m *RepositoryMutation) OldField(ctx context.Context, name string) (ent.Val return m.OldPublicKey(ctx) case repository.FieldPrivateKey: return m.OldPrivateKey(ctx) + case repository.FieldLastImportedAt: + return m.OldLastImportedAt(ctx) } return nil, fmt.Errorf("unknown Repository field %s", name) } @@ -7013,6 +7070,13 @@ func (m *RepositoryMutation) SetField(name string, value ent.Value) error { } m.SetPrivateKey(v) return nil + case repository.FieldLastImportedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetLastImportedAt(v) + return nil } return fmt.Errorf("unknown Repository field %s", name) } @@ -7042,7 +7106,11 @@ func (m *RepositoryMutation) AddField(name string, value ent.Value) error { // ClearedFields returns all nullable fields that were cleared during this // mutation. func (m *RepositoryMutation) ClearedFields() []string { - return nil + var fields []string + if m.FieldCleared(repository.FieldLastImportedAt) { + fields = append(fields, repository.FieldLastImportedAt) + } + return fields } // FieldCleared returns a boolean indicating if a field with the given name was @@ -7055,6 +7123,11 @@ func (m *RepositoryMutation) FieldCleared(name string) bool { // ClearField clears the value of the field with the given name. It returns an // error if the field is not defined in the schema. func (m *RepositoryMutation) ClearField(name string) error { + switch name { + case repository.FieldLastImportedAt: + m.ClearLastImportedAt() + return nil + } return fmt.Errorf("unknown Repository nullable field %s", name) } @@ -7077,6 +7150,9 @@ func (m *RepositoryMutation) ResetField(name string) error { case repository.FieldPrivateKey: m.ResetPrivateKey() return nil + case repository.FieldLastImportedAt: + m.ResetLastImportedAt() + return nil } return fmt.Errorf("unknown Repository field %s", name) } diff --git a/tavern/internal/ent/repository.go b/tavern/internal/ent/repository.go index 1d1d3547d..366dfd85f 100644 --- a/tavern/internal/ent/repository.go +++ b/tavern/internal/ent/repository.go @@ -28,6 +28,8 @@ type Repository struct { PublicKey string `json:"public_key,omitempty"` // Private key used for authentication. PrivateKey string `json:"-"` + // Timestamp of when this repo was last imported + LastImportedAt time.Time `json:"last_imported_at,omitempty"` // Edges holds the relations/edges for other nodes in the graph. // The values are being populated by the RepositoryQuery when eager-loading is set. Edges RepositoryEdges `json:"edges"` @@ -81,7 +83,7 @@ func (*Repository) scanValues(columns []string) ([]any, error) { values[i] = new(sql.NullInt64) case repository.FieldURL, repository.FieldPublicKey, repository.FieldPrivateKey: values[i] = new(sql.NullString) - case repository.FieldCreatedAt, repository.FieldLastModifiedAt: + case repository.FieldCreatedAt, repository.FieldLastModifiedAt, repository.FieldLastImportedAt: values[i] = new(sql.NullTime) case repository.ForeignKeys[0]: // repository_owner values[i] = new(sql.NullInt64) @@ -136,6 +138,12 @@ func (r *Repository) assignValues(columns []string, values []any) error { } else if value.Valid { r.PrivateKey = value.String } + case repository.FieldLastImportedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field last_imported_at", values[i]) + } else if value.Valid { + r.LastImportedAt = value.Time + } case repository.ForeignKeys[0]: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for edge-field repository_owner", value) @@ -202,6 +210,9 @@ func (r *Repository) String() string { builder.WriteString(r.PublicKey) builder.WriteString(", ") builder.WriteString("private_key=") + builder.WriteString(", ") + builder.WriteString("last_imported_at=") + builder.WriteString(r.LastImportedAt.Format(time.ANSIC)) builder.WriteByte(')') return builder.String() } diff --git a/tavern/internal/ent/repository/repository.go b/tavern/internal/ent/repository/repository.go index 89f06bb08..e8d1de3ea 100644 --- a/tavern/internal/ent/repository/repository.go +++ b/tavern/internal/ent/repository/repository.go @@ -25,6 +25,8 @@ const ( FieldPublicKey = "public_key" // FieldPrivateKey holds the string denoting the private_key field in the database. FieldPrivateKey = "private_key" + // FieldLastImportedAt holds the string denoting the last_imported_at field in the database. + FieldLastImportedAt = "last_imported_at" // EdgeTomes holds the string denoting the tomes edge name in mutations. EdgeTomes = "tomes" // EdgeOwner holds the string denoting the owner edge name in mutations. @@ -55,6 +57,7 @@ var Columns = []string{ FieldURL, FieldPublicKey, FieldPrivateKey, + FieldLastImportedAt, } // ForeignKeys holds the SQL foreign-keys that are owned by the "repositories" @@ -132,6 +135,11 @@ func ByPrivateKey(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldPrivateKey, opts...).ToFunc() } +// ByLastImportedAt orders the results by the last_imported_at field. +func ByLastImportedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldLastImportedAt, opts...).ToFunc() +} + // ByTomesCount orders the results by tomes count. func ByTomesCount(opts ...sql.OrderTermOption) OrderOption { return func(s *sql.Selector) { diff --git a/tavern/internal/ent/repository/where.go b/tavern/internal/ent/repository/where.go index 6dbe0d724..038977e93 100644 --- a/tavern/internal/ent/repository/where.go +++ b/tavern/internal/ent/repository/where.go @@ -80,6 +80,11 @@ func PrivateKey(v string) predicate.Repository { return predicate.Repository(sql.FieldEQ(FieldPrivateKey, v)) } +// LastImportedAt applies equality check predicate on the "last_imported_at" field. It's identical to LastImportedAtEQ. +func LastImportedAt(v time.Time) predicate.Repository { + return predicate.Repository(sql.FieldEQ(FieldLastImportedAt, v)) +} + // CreatedAtEQ applies the EQ predicate on the "created_at" field. func CreatedAtEQ(v time.Time) predicate.Repository { return predicate.Repository(sql.FieldEQ(FieldCreatedAt, v)) @@ -355,6 +360,56 @@ func PrivateKeyContainsFold(v string) predicate.Repository { return predicate.Repository(sql.FieldContainsFold(FieldPrivateKey, v)) } +// LastImportedAtEQ applies the EQ predicate on the "last_imported_at" field. +func LastImportedAtEQ(v time.Time) predicate.Repository { + return predicate.Repository(sql.FieldEQ(FieldLastImportedAt, v)) +} + +// LastImportedAtNEQ applies the NEQ predicate on the "last_imported_at" field. +func LastImportedAtNEQ(v time.Time) predicate.Repository { + return predicate.Repository(sql.FieldNEQ(FieldLastImportedAt, v)) +} + +// LastImportedAtIn applies the In predicate on the "last_imported_at" field. +func LastImportedAtIn(vs ...time.Time) predicate.Repository { + return predicate.Repository(sql.FieldIn(FieldLastImportedAt, vs...)) +} + +// LastImportedAtNotIn applies the NotIn predicate on the "last_imported_at" field. +func LastImportedAtNotIn(vs ...time.Time) predicate.Repository { + return predicate.Repository(sql.FieldNotIn(FieldLastImportedAt, vs...)) +} + +// LastImportedAtGT applies the GT predicate on the "last_imported_at" field. +func LastImportedAtGT(v time.Time) predicate.Repository { + return predicate.Repository(sql.FieldGT(FieldLastImportedAt, v)) +} + +// LastImportedAtGTE applies the GTE predicate on the "last_imported_at" field. +func LastImportedAtGTE(v time.Time) predicate.Repository { + return predicate.Repository(sql.FieldGTE(FieldLastImportedAt, v)) +} + +// LastImportedAtLT applies the LT predicate on the "last_imported_at" field. +func LastImportedAtLT(v time.Time) predicate.Repository { + return predicate.Repository(sql.FieldLT(FieldLastImportedAt, v)) +} + +// LastImportedAtLTE applies the LTE predicate on the "last_imported_at" field. +func LastImportedAtLTE(v time.Time) predicate.Repository { + return predicate.Repository(sql.FieldLTE(FieldLastImportedAt, v)) +} + +// LastImportedAtIsNil applies the IsNil predicate on the "last_imported_at" field. +func LastImportedAtIsNil() predicate.Repository { + return predicate.Repository(sql.FieldIsNull(FieldLastImportedAt)) +} + +// LastImportedAtNotNil applies the NotNil predicate on the "last_imported_at" field. +func LastImportedAtNotNil() predicate.Repository { + return predicate.Repository(sql.FieldNotNull(FieldLastImportedAt)) +} + // HasTomes applies the HasEdge predicate on the "tomes" edge. func HasTomes() predicate.Repository { return predicate.Repository(func(s *sql.Selector) { diff --git a/tavern/internal/ent/repository_create.go b/tavern/internal/ent/repository_create.go index 7ac520ee4..fb7968b89 100644 --- a/tavern/internal/ent/repository_create.go +++ b/tavern/internal/ent/repository_create.go @@ -70,6 +70,20 @@ func (rc *RepositoryCreate) SetPrivateKey(s string) *RepositoryCreate { return rc } +// SetLastImportedAt sets the "last_imported_at" field. +func (rc *RepositoryCreate) SetLastImportedAt(t time.Time) *RepositoryCreate { + rc.mutation.SetLastImportedAt(t) + return rc +} + +// SetNillableLastImportedAt sets the "last_imported_at" field if the given value is not nil. +func (rc *RepositoryCreate) SetNillableLastImportedAt(t *time.Time) *RepositoryCreate { + if t != nil { + rc.SetLastImportedAt(*t) + } + return rc +} + // AddTomeIDs adds the "tomes" edge to the Tome entity by IDs. func (rc *RepositoryCreate) AddTomeIDs(ids ...int) *RepositoryCreate { rc.mutation.AddTomeIDs(ids...) @@ -237,6 +251,10 @@ func (rc *RepositoryCreate) createSpec() (*Repository, *sqlgraph.CreateSpec) { _spec.SetField(repository.FieldPrivateKey, field.TypeString, value) _node.PrivateKey = value } + if value, ok := rc.mutation.LastImportedAt(); ok { + _spec.SetField(repository.FieldLastImportedAt, field.TypeTime, value) + _node.LastImportedAt = value + } if nodes := rc.mutation.TomesIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, @@ -370,6 +388,24 @@ func (u *RepositoryUpsert) UpdatePrivateKey() *RepositoryUpsert { return u } +// SetLastImportedAt sets the "last_imported_at" field. +func (u *RepositoryUpsert) SetLastImportedAt(v time.Time) *RepositoryUpsert { + u.Set(repository.FieldLastImportedAt, v) + return u +} + +// UpdateLastImportedAt sets the "last_imported_at" field to the value that was provided on create. +func (u *RepositoryUpsert) UpdateLastImportedAt() *RepositoryUpsert { + u.SetExcluded(repository.FieldLastImportedAt) + return u +} + +// ClearLastImportedAt clears the value of the "last_imported_at" field. +func (u *RepositoryUpsert) ClearLastImportedAt() *RepositoryUpsert { + u.SetNull(repository.FieldLastImportedAt) + return u +} + // UpdateNewValues updates the mutable fields using the new values that were set on create. // Using this option is equivalent to using: // @@ -471,6 +507,27 @@ func (u *RepositoryUpsertOne) UpdatePrivateKey() *RepositoryUpsertOne { }) } +// SetLastImportedAt sets the "last_imported_at" field. +func (u *RepositoryUpsertOne) SetLastImportedAt(v time.Time) *RepositoryUpsertOne { + return u.Update(func(s *RepositoryUpsert) { + s.SetLastImportedAt(v) + }) +} + +// UpdateLastImportedAt sets the "last_imported_at" field to the value that was provided on create. +func (u *RepositoryUpsertOne) UpdateLastImportedAt() *RepositoryUpsertOne { + return u.Update(func(s *RepositoryUpsert) { + s.UpdateLastImportedAt() + }) +} + +// ClearLastImportedAt clears the value of the "last_imported_at" field. +func (u *RepositoryUpsertOne) ClearLastImportedAt() *RepositoryUpsertOne { + return u.Update(func(s *RepositoryUpsert) { + s.ClearLastImportedAt() + }) +} + // Exec executes the query. func (u *RepositoryUpsertOne) Exec(ctx context.Context) error { if len(u.create.conflict) == 0 { @@ -738,6 +795,27 @@ func (u *RepositoryUpsertBulk) UpdatePrivateKey() *RepositoryUpsertBulk { }) } +// SetLastImportedAt sets the "last_imported_at" field. +func (u *RepositoryUpsertBulk) SetLastImportedAt(v time.Time) *RepositoryUpsertBulk { + return u.Update(func(s *RepositoryUpsert) { + s.SetLastImportedAt(v) + }) +} + +// UpdateLastImportedAt sets the "last_imported_at" field to the value that was provided on create. +func (u *RepositoryUpsertBulk) UpdateLastImportedAt() *RepositoryUpsertBulk { + return u.Update(func(s *RepositoryUpsert) { + s.UpdateLastImportedAt() + }) +} + +// ClearLastImportedAt clears the value of the "last_imported_at" field. +func (u *RepositoryUpsertBulk) ClearLastImportedAt() *RepositoryUpsertBulk { + return u.Update(func(s *RepositoryUpsert) { + s.ClearLastImportedAt() + }) +} + // Exec executes the query. func (u *RepositoryUpsertBulk) Exec(ctx context.Context) error { if u.create.err != nil { diff --git a/tavern/internal/ent/repository_update.go b/tavern/internal/ent/repository_update.go index e98c7182b..5800f7869 100644 --- a/tavern/internal/ent/repository_update.go +++ b/tavern/internal/ent/repository_update.go @@ -54,6 +54,26 @@ func (ru *RepositoryUpdate) SetPrivateKey(s string) *RepositoryUpdate { return ru } +// SetLastImportedAt sets the "last_imported_at" field. +func (ru *RepositoryUpdate) SetLastImportedAt(t time.Time) *RepositoryUpdate { + ru.mutation.SetLastImportedAt(t) + return ru +} + +// SetNillableLastImportedAt sets the "last_imported_at" field if the given value is not nil. +func (ru *RepositoryUpdate) SetNillableLastImportedAt(t *time.Time) *RepositoryUpdate { + if t != nil { + ru.SetLastImportedAt(*t) + } + return ru +} + +// ClearLastImportedAt clears the value of the "last_imported_at" field. +func (ru *RepositoryUpdate) ClearLastImportedAt() *RepositoryUpdate { + ru.mutation.ClearLastImportedAt() + return ru +} + // AddTomeIDs adds the "tomes" edge to the Tome entity by IDs. func (ru *RepositoryUpdate) AddTomeIDs(ids ...int) *RepositoryUpdate { ru.mutation.AddTomeIDs(ids...) @@ -206,6 +226,12 @@ func (ru *RepositoryUpdate) sqlSave(ctx context.Context) (n int, err error) { if value, ok := ru.mutation.PrivateKey(); ok { _spec.SetField(repository.FieldPrivateKey, field.TypeString, value) } + if value, ok := ru.mutation.LastImportedAt(); ok { + _spec.SetField(repository.FieldLastImportedAt, field.TypeTime, value) + } + if ru.mutation.LastImportedAtCleared() { + _spec.ClearField(repository.FieldLastImportedAt, field.TypeTime) + } if ru.mutation.TomesCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, @@ -324,6 +350,26 @@ func (ruo *RepositoryUpdateOne) SetPrivateKey(s string) *RepositoryUpdateOne { return ruo } +// SetLastImportedAt sets the "last_imported_at" field. +func (ruo *RepositoryUpdateOne) SetLastImportedAt(t time.Time) *RepositoryUpdateOne { + ruo.mutation.SetLastImportedAt(t) + return ruo +} + +// SetNillableLastImportedAt sets the "last_imported_at" field if the given value is not nil. +func (ruo *RepositoryUpdateOne) SetNillableLastImportedAt(t *time.Time) *RepositoryUpdateOne { + if t != nil { + ruo.SetLastImportedAt(*t) + } + return ruo +} + +// ClearLastImportedAt clears the value of the "last_imported_at" field. +func (ruo *RepositoryUpdateOne) ClearLastImportedAt() *RepositoryUpdateOne { + ruo.mutation.ClearLastImportedAt() + return ruo +} + // AddTomeIDs adds the "tomes" edge to the Tome entity by IDs. func (ruo *RepositoryUpdateOne) AddTomeIDs(ids ...int) *RepositoryUpdateOne { ruo.mutation.AddTomeIDs(ids...) @@ -506,6 +552,12 @@ func (ruo *RepositoryUpdateOne) sqlSave(ctx context.Context) (_node *Repository, if value, ok := ruo.mutation.PrivateKey(); ok { _spec.SetField(repository.FieldPrivateKey, field.TypeString, value) } + if value, ok := ruo.mutation.LastImportedAt(); ok { + _spec.SetField(repository.FieldLastImportedAt, field.TypeTime, value) + } + if ruo.mutation.LastImportedAtCleared() { + _spec.ClearField(repository.FieldLastImportedAt, field.TypeTime) + } if ruo.mutation.TomesCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.O2M, diff --git a/tavern/internal/ent/schema/repository.go b/tavern/internal/ent/schema/repository.go index dcd8a595a..1b6d515b4 100644 --- a/tavern/internal/ent/schema/repository.go +++ b/tavern/internal/ent/schema/repository.go @@ -52,6 +52,13 @@ func (Repository) Fields() []ent.Field { entgql.Skip(entgql.SkipAll), ). Comment("Private key used for authentication."), + field.Time("last_imported_at"). + Optional(). + Annotations( + entgql.OrderField("LAST_IMPORTED_AT"), + entgql.Skip(entgql.SkipMutationCreateInput, entgql.SkipMutationUpdateInput), + ). + Comment("Timestamp of when this repo was last imported"), } } diff --git a/tavern/internal/graphql/generated/ent.generated.go b/tavern/internal/graphql/generated/ent.generated.go index 0ecfbbac9..f4d859de7 100644 --- a/tavern/internal/graphql/generated/ent.generated.go +++ b/tavern/internal/graphql/generated/ent.generated.go @@ -5460,6 +5460,47 @@ func (ec *executionContext) fieldContext_Repository_publicKey(ctx context.Contex return fc, nil } +func (ec *executionContext) _Repository_lastImportedAt(ctx context.Context, field graphql.CollectedField, obj *ent.Repository) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Repository_lastImportedAt(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.LastImportedAt, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(time.Time) + fc.Result = res + return ec.marshalOTime2timeᚐTime(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Repository_lastImportedAt(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Repository", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Time does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _Repository_tomes(ctx context.Context, field graphql.CollectedField, obj *ent.Repository) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Repository_tomes(ctx, field) if err != nil { @@ -5775,6 +5816,8 @@ func (ec *executionContext) fieldContext_RepositoryEdge_node(ctx context.Context return ec.fieldContext_Repository_url(ctx, field) case "publicKey": return ec.fieldContext_Repository_publicKey(ctx, field) + case "lastImportedAt": + return ec.fieldContext_Repository_lastImportedAt(ctx, field) case "tomes": return ec.fieldContext_Repository_tomes(ctx, field) case "owner": @@ -7594,6 +7637,8 @@ func (ec *executionContext) fieldContext_Tome_repository(ctx context.Context, fi return ec.fieldContext_Repository_url(ctx, field) case "publicKey": return ec.fieldContext_Repository_publicKey(ctx, field) + case "lastImportedAt": + return ec.fieldContext_Repository_lastImportedAt(ctx, field) case "tomes": return ec.fieldContext_Repository_tomes(ctx, field) case "owner": @@ -14329,7 +14374,7 @@ func (ec *executionContext) unmarshalInputRepositoryWhereInput(ctx context.Conte asMap[k] = v } - fieldsInOrder := [...]string{"not", "and", "or", "id", "idNEQ", "idIn", "idNotIn", "idGT", "idGTE", "idLT", "idLTE", "createdAt", "createdAtNEQ", "createdAtIn", "createdAtNotIn", "createdAtGT", "createdAtGTE", "createdAtLT", "createdAtLTE", "lastModifiedAt", "lastModifiedAtNEQ", "lastModifiedAtIn", "lastModifiedAtNotIn", "lastModifiedAtGT", "lastModifiedAtGTE", "lastModifiedAtLT", "lastModifiedAtLTE", "url", "urlNEQ", "urlIn", "urlNotIn", "urlGT", "urlGTE", "urlLT", "urlLTE", "urlContains", "urlHasPrefix", "urlHasSuffix", "urlEqualFold", "urlContainsFold", "publicKey", "publicKeyNEQ", "publicKeyIn", "publicKeyNotIn", "publicKeyGT", "publicKeyGTE", "publicKeyLT", "publicKeyLTE", "publicKeyContains", "publicKeyHasPrefix", "publicKeyHasSuffix", "publicKeyEqualFold", "publicKeyContainsFold", "hasTomes", "hasTomesWith", "hasOwner", "hasOwnerWith"} + fieldsInOrder := [...]string{"not", "and", "or", "id", "idNEQ", "idIn", "idNotIn", "idGT", "idGTE", "idLT", "idLTE", "createdAt", "createdAtNEQ", "createdAtIn", "createdAtNotIn", "createdAtGT", "createdAtGTE", "createdAtLT", "createdAtLTE", "lastModifiedAt", "lastModifiedAtNEQ", "lastModifiedAtIn", "lastModifiedAtNotIn", "lastModifiedAtGT", "lastModifiedAtGTE", "lastModifiedAtLT", "lastModifiedAtLTE", "url", "urlNEQ", "urlIn", "urlNotIn", "urlGT", "urlGTE", "urlLT", "urlLTE", "urlContains", "urlHasPrefix", "urlHasSuffix", "urlEqualFold", "urlContainsFold", "publicKey", "publicKeyNEQ", "publicKeyIn", "publicKeyNotIn", "publicKeyGT", "publicKeyGTE", "publicKeyLT", "publicKeyLTE", "publicKeyContains", "publicKeyHasPrefix", "publicKeyHasSuffix", "publicKeyEqualFold", "publicKeyContainsFold", "lastImportedAt", "lastImportedAtNEQ", "lastImportedAtIn", "lastImportedAtNotIn", "lastImportedAtGT", "lastImportedAtGTE", "lastImportedAtLT", "lastImportedAtLTE", "lastImportedAtIsNil", "lastImportedAtNotNil", "hasTomes", "hasTomesWith", "hasOwner", "hasOwnerWith"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -14813,6 +14858,96 @@ func (ec *executionContext) unmarshalInputRepositoryWhereInput(ctx context.Conte return it, err } it.PublicKeyContainsFold = data + case "lastImportedAt": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("lastImportedAt")) + data, err := ec.unmarshalOTime2ᚖtimeᚐTime(ctx, v) + if err != nil { + return it, err + } + it.LastImportedAt = data + case "lastImportedAtNEQ": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("lastImportedAtNEQ")) + data, err := ec.unmarshalOTime2ᚖtimeᚐTime(ctx, v) + if err != nil { + return it, err + } + it.LastImportedAtNEQ = data + case "lastImportedAtIn": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("lastImportedAtIn")) + data, err := ec.unmarshalOTime2ᚕtimeᚐTimeᚄ(ctx, v) + if err != nil { + return it, err + } + it.LastImportedAtIn = data + case "lastImportedAtNotIn": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("lastImportedAtNotIn")) + data, err := ec.unmarshalOTime2ᚕtimeᚐTimeᚄ(ctx, v) + if err != nil { + return it, err + } + it.LastImportedAtNotIn = data + case "lastImportedAtGT": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("lastImportedAtGT")) + data, err := ec.unmarshalOTime2ᚖtimeᚐTime(ctx, v) + if err != nil { + return it, err + } + it.LastImportedAtGT = data + case "lastImportedAtGTE": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("lastImportedAtGTE")) + data, err := ec.unmarshalOTime2ᚖtimeᚐTime(ctx, v) + if err != nil { + return it, err + } + it.LastImportedAtGTE = data + case "lastImportedAtLT": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("lastImportedAtLT")) + data, err := ec.unmarshalOTime2ᚖtimeᚐTime(ctx, v) + if err != nil { + return it, err + } + it.LastImportedAtLT = data + case "lastImportedAtLTE": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("lastImportedAtLTE")) + data, err := ec.unmarshalOTime2ᚖtimeᚐTime(ctx, v) + if err != nil { + return it, err + } + it.LastImportedAtLTE = data + case "lastImportedAtIsNil": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("lastImportedAtIsNil")) + data, err := ec.unmarshalOBoolean2bool(ctx, v) + if err != nil { + return it, err + } + it.LastImportedAtIsNil = data + case "lastImportedAtNotNil": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("lastImportedAtNotNil")) + data, err := ec.unmarshalOBoolean2bool(ctx, v) + if err != nil { + return it, err + } + it.LastImportedAtNotNil = data case "hasTomes": var err error @@ -19686,6 +19821,8 @@ func (ec *executionContext) _Repository(ctx context.Context, sel ast.SelectionSe if out.Values[i] == graphql.Null { atomic.AddUint32(&out.Invalids, 1) } + case "lastImportedAt": + out.Values[i] = ec._Repository_lastImportedAt(ctx, field, obj) case "tomes": field := field diff --git a/tavern/internal/graphql/generated/mutation.generated.go b/tavern/internal/graphql/generated/mutation.generated.go index f552afcd4..f3f07fcaf 100644 --- a/tavern/internal/graphql/generated/mutation.generated.go +++ b/tavern/internal/graphql/generated/mutation.generated.go @@ -1190,6 +1190,8 @@ func (ec *executionContext) fieldContext_Mutation_createRepository(ctx context.C return ec.fieldContext_Repository_url(ctx, field) case "publicKey": return ec.fieldContext_Repository_publicKey(ctx, field) + case "lastImportedAt": + return ec.fieldContext_Repository_lastImportedAt(ctx, field) case "tomes": return ec.fieldContext_Repository_tomes(ctx, field) case "owner": @@ -1285,6 +1287,8 @@ func (ec *executionContext) fieldContext_Mutation_importRepository(ctx context.C return ec.fieldContext_Repository_url(ctx, field) case "publicKey": return ec.fieldContext_Repository_publicKey(ctx, field) + case "lastImportedAt": + return ec.fieldContext_Repository_lastImportedAt(ctx, field) case "tomes": return ec.fieldContext_Repository_tomes(ctx, field) case "owner": diff --git a/tavern/internal/graphql/generated/root_.generated.go b/tavern/internal/graphql/generated/root_.generated.go index 6bdb27e5a..914f435f9 100644 --- a/tavern/internal/graphql/generated/root_.generated.go +++ b/tavern/internal/graphql/generated/root_.generated.go @@ -178,6 +178,7 @@ type ComplexityRoot struct { Repository struct { CreatedAt func(childComplexity int) int ID func(childComplexity int) int + LastImportedAt func(childComplexity int) int LastModifiedAt func(childComplexity int) int Owner func(childComplexity int) int PublicKey func(childComplexity int) int @@ -1107,6 +1108,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Repository.ID(childComplexity), true + case "Repository.lastImportedAt": + if e.complexity.Repository.LastImportedAt == nil { + break + } + + return e.complexity.Repository.LastImportedAt(childComplexity), true + case "Repository.lastModifiedAt": if e.complexity.Repository.LastModifiedAt == nil { break @@ -2757,6 +2765,8 @@ type Repository implements Node { url: String! """Public key associated with this repositories private key""" publicKey: String! + """Timestamp of when this repo was last imported""" + lastImportedAt: Time """Tomes imported using this repository.""" tomes: [Tome!] """User that created this repository.""" @@ -2789,6 +2799,7 @@ input RepositoryOrder { enum RepositoryOrderField { CREATED_AT LAST_MODIFIED_AT + LAST_IMPORTED_AT } """ RepositoryWhereInput is used for filtering Repository objects. @@ -2853,6 +2864,17 @@ input RepositoryWhereInput { publicKeyHasSuffix: String publicKeyEqualFold: String publicKeyContainsFold: String + """last_imported_at field predicates""" + lastImportedAt: Time + lastImportedAtNEQ: Time + lastImportedAtIn: [Time!] + lastImportedAtNotIn: [Time!] + lastImportedAtGT: Time + lastImportedAtGTE: Time + lastImportedAtLT: Time + lastImportedAtLTE: Time + lastImportedAtIsNil: Boolean + lastImportedAtNotNil: Boolean """tomes edge predicates""" hasTomes: Boolean hasTomesWith: [TomeWhereInput!] diff --git a/tavern/internal/graphql/mutation.resolvers.go b/tavern/internal/graphql/mutation.resolvers.go index 91643c245..ae3223824 100644 --- a/tavern/internal/graphql/mutation.resolvers.go +++ b/tavern/internal/graphql/mutation.resolvers.go @@ -8,6 +8,7 @@ import ( "context" "fmt" "strings" + "time" "realm.pub/tavern/internal/auth" "realm.pub/tavern/internal/ent" @@ -229,7 +230,7 @@ func (r *mutationResolver) ImportRepository(ctx context.Context, repoID int, inp return nil, err } - return repo, nil + return repo.Update().SetLastImportedAt(time.Now()).Save(ctx) } // UpdateUser is the resolver for the updateUser field. diff --git a/tavern/internal/graphql/schema.graphql b/tavern/internal/graphql/schema.graphql index 2eabab32f..9cd0248fa 100644 --- a/tavern/internal/graphql/schema.graphql +++ b/tavern/internal/graphql/schema.graphql @@ -1143,6 +1143,8 @@ type Repository implements Node { url: String! """Public key associated with this repositories private key""" publicKey: String! + """Timestamp of when this repo was last imported""" + lastImportedAt: Time """Tomes imported using this repository.""" tomes: [Tome!] """User that created this repository.""" @@ -1175,6 +1177,7 @@ input RepositoryOrder { enum RepositoryOrderField { CREATED_AT LAST_MODIFIED_AT + LAST_IMPORTED_AT } """ RepositoryWhereInput is used for filtering Repository objects. @@ -1239,6 +1242,17 @@ input RepositoryWhereInput { publicKeyHasSuffix: String publicKeyEqualFold: String publicKeyContainsFold: String + """last_imported_at field predicates""" + lastImportedAt: Time + lastImportedAtNEQ: Time + lastImportedAtIn: [Time!] + lastImportedAtNotIn: [Time!] + lastImportedAtGT: Time + lastImportedAtGTE: Time + lastImportedAtLT: Time + lastImportedAtLTE: Time + lastImportedAtIsNil: Boolean + lastImportedAtNotNil: Boolean """tomes edge predicates""" hasTomes: Boolean hasTomesWith: [TomeWhereInput!] diff --git a/tavern/internal/graphql/schema/ent.graphql b/tavern/internal/graphql/schema/ent.graphql index 707561839..9e6860754 100644 --- a/tavern/internal/graphql/schema/ent.graphql +++ b/tavern/internal/graphql/schema/ent.graphql @@ -1138,6 +1138,8 @@ type Repository implements Node { url: String! """Public key associated with this repositories private key""" publicKey: String! + """Timestamp of when this repo was last imported""" + lastImportedAt: Time """Tomes imported using this repository.""" tomes: [Tome!] """User that created this repository.""" @@ -1170,6 +1172,7 @@ input RepositoryOrder { enum RepositoryOrderField { CREATED_AT LAST_MODIFIED_AT + LAST_IMPORTED_AT } """ RepositoryWhereInput is used for filtering Repository objects. @@ -1234,6 +1237,17 @@ input RepositoryWhereInput { publicKeyHasSuffix: String publicKeyEqualFold: String publicKeyContainsFold: String + """last_imported_at field predicates""" + lastImportedAt: Time + lastImportedAtNEQ: Time + lastImportedAtIn: [Time!] + lastImportedAtNotIn: [Time!] + lastImportedAtGT: Time + lastImportedAtGTE: Time + lastImportedAtLT: Time + lastImportedAtLTE: Time + lastImportedAtIsNil: Boolean + lastImportedAtNotNil: Boolean """tomes edge predicates""" hasTomes: Boolean hasTomesWith: [TomeWhereInput!] diff --git a/tavern/internal/www/schema.graphql b/tavern/internal/www/schema.graphql index 2eabab32f..9cd0248fa 100644 --- a/tavern/internal/www/schema.graphql +++ b/tavern/internal/www/schema.graphql @@ -1143,6 +1143,8 @@ type Repository implements Node { url: String! """Public key associated with this repositories private key""" publicKey: String! + """Timestamp of when this repo was last imported""" + lastImportedAt: Time """Tomes imported using this repository.""" tomes: [Tome!] """User that created this repository.""" @@ -1175,6 +1177,7 @@ input RepositoryOrder { enum RepositoryOrderField { CREATED_AT LAST_MODIFIED_AT + LAST_IMPORTED_AT } """ RepositoryWhereInput is used for filtering Repository objects. @@ -1239,6 +1242,17 @@ input RepositoryWhereInput { publicKeyHasSuffix: String publicKeyEqualFold: String publicKeyContainsFold: String + """last_imported_at field predicates""" + lastImportedAt: Time + lastImportedAtNEQ: Time + lastImportedAtIn: [Time!] + lastImportedAtNotIn: [Time!] + lastImportedAtGT: Time + lastImportedAtGTE: Time + lastImportedAtLT: Time + lastImportedAtLTE: Time + lastImportedAtIsNil: Boolean + lastImportedAtNotNil: Boolean """tomes edge predicates""" hasTomes: Boolean hasTomesWith: [TomeWhereInput!] From ba287bee293177707f54dbecaa3dc792fa85845b Mon Sep 17 00:00:00 2001 From: KCarretto Date: Sun, 25 Feb 2024 22:36:46 +0000 Subject: [PATCH 4/6] fix test name --- .../{URLWithoutSchema copy.yml => URLWithoutSchema.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tavern/internal/graphql/testdata/mutations/createRepository/{URLWithoutSchema copy.yml => URLWithoutSchema.yml} (100%) diff --git a/tavern/internal/graphql/testdata/mutations/createRepository/URLWithoutSchema copy.yml b/tavern/internal/graphql/testdata/mutations/createRepository/URLWithoutSchema.yml similarity index 100% rename from tavern/internal/graphql/testdata/mutations/createRepository/URLWithoutSchema copy.yml rename to tavern/internal/graphql/testdata/mutations/createRepository/URLWithoutSchema.yml From 2aa2742ee1add9176b27cbe212fc86580288ede5 Mon Sep 17 00:00:00 2001 From: KCarretto Date: Sun, 25 Feb 2024 22:38:11 +0000 Subject: [PATCH 5/6] extra test case --- .../createRepository/SCPURLWIthSSH.yml | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tavern/internal/graphql/testdata/mutations/createRepository/SCPURLWIthSSH.yml diff --git a/tavern/internal/graphql/testdata/mutations/createRepository/SCPURLWIthSSH.yml b/tavern/internal/graphql/testdata/mutations/createRepository/SCPURLWIthSSH.yml new file mode 100644 index 000000000..0ed7b4693 --- /dev/null +++ b/tavern/internal/graphql/testdata/mutations/createRepository/SCPURLWIthSSH.yml @@ -0,0 +1,24 @@ +state: | + INSERT INTO `users` (id,oauth_id,photo_url,name,session_token,access_token,is_activated,is_admin) + VALUES (5,"test_oauth_id","https://photos.com","test","secretToken","accessToken",true,true); +requestor: + session_token: secretToken +query: | + mutation CreateRepository($input: CreateRepositoryInput!) { + createRepository(input: $input) { + url + + owner { + id + } + } + } +variables: + input: + url: "ssh://git@github.com:spellshift/realm.git" + +expected: + createRepository: + url: "ssh://git@github.com/spellshift/realm.git" + owner: + id: "5" From 0ef43cb577842f015b2cfdcc5ff80d6e12b11d30 Mon Sep 17 00:00:00 2001 From: KCarretto Date: Sun, 25 Feb 2024 22:47:12 +0000 Subject: [PATCH 6/6] update default tome naming to use metadata.yml --- tavern/tomes/parse.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tavern/tomes/parse.go b/tavern/tomes/parse.go index 232ac8f84..89a23d794 100644 --- a/tavern/tomes/parse.go +++ b/tavern/tomes/parse.go @@ -160,7 +160,7 @@ func UploadTomes(ctx context.Context, graph *ent.Client, fileSystem fs.ReadDirFS // Create the tome if _, err := graph.Tome.Create(). - SetName(entry.Name()). + SetName(metadata.Name). SetDescription(metadata.Description). SetAuthor(metadata.Author). SetParamDefs(string(paramdefs)). @@ -169,7 +169,7 @@ func UploadTomes(ctx context.Context, graph *ent.Client, fileSystem fs.ReadDirFS SetEldritch(eldritch). AddFiles(tomeFiles...). Save(ctx); err != nil { - return rollback(tx, fmt.Errorf("failed to create tome %q: %w", entry.Name(), err)) + return rollback(tx, fmt.Errorf("failed to create tome %q: %w", metadata.Name, err)) } } return nil