diff --git a/coderd/audit/diff_test.go b/coderd/audit/diff_test.go index 93e0d1bc33824..593f0598d1f65 100644 --- a/coderd/audit/diff_test.go +++ b/coderd/audit/diff_test.go @@ -114,12 +114,14 @@ func TestDiff(t *testing.T) { UpdatedAt: time.Now(), OrganizationID: uuid.UUID{3}, Name: "rust", + CreatedBy: uuid.UUID{4}, }, exp: audit.Map{ "id": uuid.UUID{1}.String(), "template_id": uuid.UUID{2}.String(), "organization_id": uuid.UUID{3}.String(), "name": "rust", + "created_by": uuid.UUID{4}.String(), }, }, { @@ -132,11 +134,13 @@ func TestDiff(t *testing.T) { UpdatedAt: time.Now(), OrganizationID: uuid.UUID{3}, Name: "rust", + CreatedBy: uuid.UUID{4}, }, exp: audit.Map{ "id": uuid.UUID{1}.String(), "organization_id": uuid.UUID{3}.String(), "name": "rust", + "created_by": uuid.UUID{4}.String(), }, }, }) diff --git a/coderd/audit/table.go b/coderd/audit/table.go index 7562472f3c583..c842956e6cf24 100644 --- a/coderd/audit/table.go +++ b/coderd/audit/table.go @@ -83,6 +83,7 @@ var AuditableResources = auditMap(map[any]map[string]Action{ "name": ActionTrack, "readme": ActionTrack, "job_id": ActionIgnore, // Not helpful in a diff because jobs aren't tracked in audit logs. + "created_by": ActionTrack, }, &database.User{}: { "id": ActionTrack, diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index a0f5c08f9b4b3..5122029d9d31c 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -1511,6 +1511,7 @@ func (q *fakeQuerier) InsertTemplateVersion(_ context.Context, arg database.Inse Name: arg.Name, Readme: arg.Readme, JobID: arg.JobID, + CreatedBy: arg.CreatedBy, } q.templateVersions = append(q.templateVersions, version) return version, nil diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 9c757a29b4362..130de72ccae32 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -247,7 +247,8 @@ CREATE TABLE template_versions ( updated_at timestamp with time zone NOT NULL, name character varying(64) NOT NULL, readme character varying(1048576) NOT NULL, - job_id uuid NOT NULL + job_id uuid NOT NULL, + created_by uuid NOT NULL ); CREATE TABLE templates ( @@ -486,6 +487,9 @@ ALTER TABLE ONLY provisioner_job_logs ALTER TABLE ONLY provisioner_jobs ADD CONSTRAINT provisioner_jobs_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; +ALTER TABLE ONLY template_versions + ADD CONSTRAINT template_versions_created_by_fkey FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE RESTRICT; + ALTER TABLE ONLY template_versions ADD CONSTRAINT template_versions_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; diff --git a/coderd/database/migrations/000030_template_version_created_by.down.sql b/coderd/database/migrations/000030_template_version_created_by.down.sql new file mode 100644 index 0000000000000..66e4f2d6f0fa7 --- /dev/null +++ b/coderd/database/migrations/000030_template_version_created_by.down.sql @@ -0,0 +1 @@ +ALTER TABLE ONLY template_versions DROP COLUMN IF EXISTS created_by; diff --git a/coderd/database/migrations/000030_template_version_created_by.up.sql b/coderd/database/migrations/000030_template_version_created_by.up.sql new file mode 100644 index 0000000000000..63800f80d0134 --- /dev/null +++ b/coderd/database/migrations/000030_template_version_created_by.up.sql @@ -0,0 +1,14 @@ +ALTER TABLE ONLY template_versions ADD COLUMN IF NOT EXISTS created_by uuid REFERENCES users (id) ON DELETE RESTRICT; + +UPDATE + template_versions +SET + created_by = ( + SELECT created_by FROM templates + WHERE template_versions.template_id = templates.id + LIMIT 1 + ) +WHERE + created_by IS NULL; + +ALTER TABLE ONLY template_versions ALTER COLUMN created_by SET NOT NULL; diff --git a/coderd/database/models.go b/coderd/database/models.go index 931f66349d288..1f61714ab9b52 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -478,6 +478,7 @@ type TemplateVersion struct { Name string `db:"name" json:"name"` Readme string `db:"readme" json:"readme"` JobID uuid.UUID `db:"job_id" json:"job_id"` + CreatedBy uuid.UUID `db:"created_by" json:"created_by"` } type User struct { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 8addc7a2bdaab..6bb3d05bde2a7 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -2145,7 +2145,7 @@ func (q *sqlQuerier) UpdateTemplateMetaByID(ctx context.Context, arg UpdateTempl const getTemplateVersionByID = `-- name: GetTemplateVersionByID :one SELECT - id, template_id, organization_id, created_at, updated_at, name, readme, job_id + id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by FROM template_versions WHERE @@ -2164,13 +2164,14 @@ func (q *sqlQuerier) GetTemplateVersionByID(ctx context.Context, id uuid.UUID) ( &i.Name, &i.Readme, &i.JobID, + &i.CreatedBy, ) return i, err } const getTemplateVersionByJobID = `-- name: GetTemplateVersionByJobID :one SELECT - id, template_id, organization_id, created_at, updated_at, name, readme, job_id + id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by FROM template_versions WHERE @@ -2189,13 +2190,14 @@ func (q *sqlQuerier) GetTemplateVersionByJobID(ctx context.Context, jobID uuid.U &i.Name, &i.Readme, &i.JobID, + &i.CreatedBy, ) return i, err } const getTemplateVersionByTemplateIDAndName = `-- name: GetTemplateVersionByTemplateIDAndName :one SELECT - id, template_id, organization_id, created_at, updated_at, name, readme, job_id + id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by FROM template_versions WHERE @@ -2220,13 +2222,14 @@ func (q *sqlQuerier) GetTemplateVersionByTemplateIDAndName(ctx context.Context, &i.Name, &i.Readme, &i.JobID, + &i.CreatedBy, ) return i, err } const getTemplateVersionsByTemplateID = `-- name: GetTemplateVersionsByTemplateID :many SELECT - id, template_id, organization_id, created_at, updated_at, name, readme, job_id + id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by FROM template_versions WHERE @@ -2289,6 +2292,7 @@ func (q *sqlQuerier) GetTemplateVersionsByTemplateID(ctx context.Context, arg Ge &i.Name, &i.Readme, &i.JobID, + &i.CreatedBy, ); err != nil { return nil, err } @@ -2304,7 +2308,7 @@ func (q *sqlQuerier) GetTemplateVersionsByTemplateID(ctx context.Context, arg Ge } const getTemplateVersionsCreatedAfter = `-- name: GetTemplateVersionsCreatedAfter :many -SELECT id, template_id, organization_id, created_at, updated_at, name, readme, job_id FROM template_versions WHERE created_at > $1 +SELECT id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by FROM template_versions WHERE created_at > $1 ` func (q *sqlQuerier) GetTemplateVersionsCreatedAfter(ctx context.Context, createdAt time.Time) ([]TemplateVersion, error) { @@ -2325,6 +2329,7 @@ func (q *sqlQuerier) GetTemplateVersionsCreatedAfter(ctx context.Context, create &i.Name, &i.Readme, &i.JobID, + &i.CreatedBy, ); err != nil { return nil, err } @@ -2349,10 +2354,11 @@ INSERT INTO updated_at, "name", readme, - job_id + job_id, + created_by ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id, template_id, organization_id, created_at, updated_at, name, readme, job_id + ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by ` type InsertTemplateVersionParams struct { @@ -2364,6 +2370,7 @@ type InsertTemplateVersionParams struct { Name string `db:"name" json:"name"` Readme string `db:"readme" json:"readme"` JobID uuid.UUID `db:"job_id" json:"job_id"` + CreatedBy uuid.UUID `db:"created_by" json:"created_by"` } func (q *sqlQuerier) InsertTemplateVersion(ctx context.Context, arg InsertTemplateVersionParams) (TemplateVersion, error) { @@ -2376,6 +2383,7 @@ func (q *sqlQuerier) InsertTemplateVersion(ctx context.Context, arg InsertTempla arg.Name, arg.Readme, arg.JobID, + arg.CreatedBy, ) var i TemplateVersion err := row.Scan( @@ -2387,6 +2395,7 @@ func (q *sqlQuerier) InsertTemplateVersion(ctx context.Context, arg InsertTempla &i.Name, &i.Readme, &i.JobID, + &i.CreatedBy, ) return i, err } diff --git a/coderd/database/queries/templateversions.sql b/coderd/database/queries/templateversions.sql index 12b9041df7c93..b92598e5051fc 100644 --- a/coderd/database/queries/templateversions.sql +++ b/coderd/database/queries/templateversions.sql @@ -70,10 +70,11 @@ INSERT INTO updated_at, "name", readme, - job_id + job_id, + created_by ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *; + ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *; -- name: UpdateTemplateVersionByID :exec UPDATE diff --git a/coderd/templates.go b/coderd/templates.go index 5eb2f5c8e218b..884b26fabe385 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -209,6 +209,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque UUID: dbTemplate.ID, Valid: true, }, + UpdatedAt: database.Now(), }) if err != nil { return xerrors.Errorf("insert template version: %s", err) diff --git a/coderd/templateversions.go b/coderd/templateversions.go index 5b385fca946e5..3bef06c0fe8af 100644 --- a/coderd/templateversions.go +++ b/coderd/templateversions.go @@ -1,6 +1,7 @@ package coderd import ( + "context" "database/sql" "encoding/json" "errors" @@ -36,7 +37,16 @@ func (api *API) templateVersion(rw http.ResponseWriter, r *http.Request) { return } - httpapi.Write(rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job))) + createdByName, err := getUsernameByUserID(r.Context(), api.Database, templateVersion.CreatedBy) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching creator name.", + Detail: err.Error(), + }) + return + } + + httpapi.Write(rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job), createdByName)) } func (api *API) patchCancelTemplateVersion(rw http.ResponseWriter, r *http.Request) { @@ -476,7 +486,15 @@ func (api *API) templateVersionsByTemplate(rw http.ResponseWriter, r *http.Reque }) return err } - apiVersions = append(apiVersions, convertTemplateVersion(version, convertProvisionerJob(job))) + createdByName, err := getUsernameByUserID(r.Context(), store, version.CreatedBy) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching creator name.", + Detail: err.Error(), + }) + return err + } + apiVersions = append(apiVersions, convertTemplateVersion(version, convertProvisionerJob(job), createdByName)) } return nil @@ -525,7 +543,16 @@ func (api *API) templateVersionByName(rw http.ResponseWriter, r *http.Request) { return } - httpapi.Write(rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job))) + createdByName, err := getUsernameByUserID(r.Context(), api.Database, templateVersion.CreatedBy) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching creator name.", + Detail: err.Error(), + }) + return + } + + httpapi.Write(rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job), createdByName)) } func (api *API) patchActiveTemplateVersion(rw http.ResponseWriter, r *http.Request) { @@ -735,6 +762,7 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht Name: namesgenerator.GetRandomName(1), Readme: "", JobID: provisionerJob.ID, + CreatedBy: apiKey.UserID, }) if err != nil { return xerrors.Errorf("insert template version: %w", err) @@ -748,7 +776,16 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht return } - httpapi.Write(rw, http.StatusCreated, convertTemplateVersion(templateVersion, convertProvisionerJob(provisionerJob))) + createdByName, err := getUsernameByUserID(r.Context(), api.Database, templateVersion.CreatedBy) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching creator name.", + Detail: err.Error(), + }) + return + } + + httpapi.Write(rw, http.StatusCreated, convertTemplateVersion(templateVersion, convertProvisionerJob(provisionerJob), createdByName)) } // templateVersionResources returns the workspace agent resources associated @@ -796,7 +833,15 @@ func (api *API) templateVersionLogs(rw http.ResponseWriter, r *http.Request) { api.provisionerJobLogs(rw, r, job) } -func convertTemplateVersion(version database.TemplateVersion, job codersdk.ProvisionerJob) codersdk.TemplateVersion { +func getUsernameByUserID(ctx context.Context, db database.Store, userID uuid.UUID) (string, error) { + user, err := db.GetUserByID(ctx, userID) + if err != nil { + return "", err + } + return user.Username, nil +} + +func convertTemplateVersion(version database.TemplateVersion, job codersdk.ProvisionerJob, createdByName string) codersdk.TemplateVersion { return codersdk.TemplateVersion{ ID: version.ID, TemplateID: &version.TemplateID.UUID, @@ -806,5 +851,7 @@ func convertTemplateVersion(version database.TemplateVersion, job codersdk.Provi Name: version.Name, Job: job, Readme: version.Readme, + CreatedByID: version.CreatedBy, + CreatedByName: createdByName, } } diff --git a/codersdk/templateversions.go b/codersdk/templateversions.go index c41b93a17781f..3562906e0c1ee 100644 --- a/codersdk/templateversions.go +++ b/codersdk/templateversions.go @@ -20,6 +20,8 @@ type TemplateVersion struct { Name string `json:"name"` Job ProvisionerJob `json:"job"` Readme string `json:"readme"` + CreatedByID uuid.UUID `json:"created_by_id"` + CreatedByName string `json:"created_by_name"` } // TemplateVersion returns a template version by ID. diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index be3acf0a7ca98..7714ad1965d43 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -87,7 +87,7 @@ export interface CreateTemplateRequest { readonly min_autostart_interval_ms?: number } -// From codersdk/templateversions.go:106:6 +// From codersdk/templateversions.go:108:6 export interface CreateTemplateVersionDryRunRequest { readonly WorkspaceName: string readonly ParameterValues: CreateParameterRequest[] @@ -290,6 +290,8 @@ export interface TemplateVersion { readonly name: string readonly job: ProvisionerJob readonly readme: string + readonly created_by_id: string + readonly created_by_name: string } // From codersdk/templates.go:100:6 diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 6544002edf9bf..769a5d547c0c4 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -112,6 +112,8 @@ name:Template test You can add instructions here [Some link info](https://coder.com)`, + created_by_id: "test-creator-id", + created_by_name: "test_creator", } export const MockTemplate: TypesGen.Template = {
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: