mirror of
https://github.com/coder/coder.git
synced 2025-07-15 22:20:27 +00:00
feat: implement 'is_default' org field (#12142)
The first organization created is now marked as "default". This is to allow "single org" behavior as we move to a multi org codebase. It is intentional that the user cannot change the default org at this stage. Only 1 default org can exist, and it is always the first org. Closes: https://github.com/coder/coder/issues/11961
This commit is contained in:
@ -5285,6 +5285,7 @@ func (q *FakeQuerier) InsertOrganization(_ context.Context, arg database.InsertO
|
||||
Name: arg.Name,
|
||||
CreatedAt: arg.CreatedAt,
|
||||
UpdatedAt: arg.UpdatedAt,
|
||||
IsDefault: len(q.organizations) == 0,
|
||||
}
|
||||
q.organizations = append(q.organizations, organization)
|
||||
return organization, nil
|
||||
|
5
coderd/database/dump.sql
generated
5
coderd/database/dump.sql
generated
@ -502,7 +502,8 @@ CREATE TABLE organizations (
|
||||
name text NOT NULL,
|
||||
description text NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
is_default boolean DEFAULT false NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE parameter_schemas (
|
||||
@ -1506,6 +1507,8 @@ CREATE UNIQUE INDEX idx_users_email ON users USING btree (email) WHERE (deleted
|
||||
|
||||
CREATE UNIQUE INDEX idx_users_username ON users USING btree (username) WHERE (deleted = false);
|
||||
|
||||
CREATE UNIQUE INDEX organizations_single_default_org ON organizations USING btree (is_default) WHERE (is_default = true);
|
||||
|
||||
CREATE INDEX provisioner_job_logs_id_job_id_idx ON provisioner_job_logs USING btree (job_id, id);
|
||||
|
||||
CREATE INDEX provisioner_jobs_started_at_idx ON provisioner_jobs USING btree (started_at) WHERE (started_at IS NULL);
|
||||
|
@ -0,0 +1,2 @@
|
||||
DROP INDEX organizations_single_default_org;
|
||||
ALTER TABLE organizations DROP COLUMN is_default;
|
@ -0,0 +1,16 @@
|
||||
-- This migration is intended to maintain the existing behavior of single org
|
||||
-- deployments, while allowing for multi-org deployments. By default, this organization
|
||||
-- will be used when no organization is specified.
|
||||
ALTER TABLE organizations ADD COLUMN is_default BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
|
||||
-- Only 1 org should ever be set to is_default.
|
||||
create unique index organizations_single_default_org on organizations (is_default)
|
||||
where is_default = true;
|
||||
|
||||
UPDATE
|
||||
organizations
|
||||
SET
|
||||
is_default = true
|
||||
WHERE
|
||||
-- The first organization created will be the default.
|
||||
id = (SELECT id FROM organizations ORDER BY organizations.created_at ASC LIMIT 1 );
|
@ -1823,6 +1823,7 @@ type Organization struct {
|
||||
Description string `db:"description" json:"description"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
IsDefault bool `db:"is_default" json:"is_default"`
|
||||
}
|
||||
|
||||
type OrganizationMember struct {
|
||||
|
@ -494,6 +494,34 @@ func TestUserChangeLoginType(t *testing.T) {
|
||||
require.Equal(t, bobExpPass, bob.HashedPassword, "hashed password should not change")
|
||||
}
|
||||
|
||||
func TestDefaultOrg(t *testing.T) {
|
||||
t.Parallel()
|
||||
if testing.Short() {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
sqlDB := testSQLDB(t)
|
||||
err := migrations.Up(sqlDB)
|
||||
require.NoError(t, err)
|
||||
db := database.New(sqlDB)
|
||||
ctx := context.Background()
|
||||
|
||||
// Should start with 0 orgs
|
||||
all, err := db.GetOrganizations(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, all, 0)
|
||||
|
||||
org, err := db.InsertOrganization(ctx, database.InsertOrganizationParams{
|
||||
ID: uuid.New(),
|
||||
Name: "default",
|
||||
Description: "",
|
||||
CreatedAt: dbtime.Now(),
|
||||
UpdatedAt: dbtime.Now(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.True(t, org.IsDefault, "first org should always be default")
|
||||
}
|
||||
|
||||
type tvArgs struct {
|
||||
Status database.ProvisionerJobStatus
|
||||
// CreateWorkspace is true if we should create a workspace for the template version
|
||||
|
@ -3144,7 +3144,7 @@ func (q *sqlQuerier) UpdateMemberRoles(ctx context.Context, arg UpdateMemberRole
|
||||
|
||||
const getOrganizationByID = `-- name: GetOrganizationByID :one
|
||||
SELECT
|
||||
id, name, description, created_at, updated_at
|
||||
id, name, description, created_at, updated_at, is_default
|
||||
FROM
|
||||
organizations
|
||||
WHERE
|
||||
@ -3160,13 +3160,14 @@ func (q *sqlQuerier) GetOrganizationByID(ctx context.Context, id uuid.UUID) (Org
|
||||
&i.Description,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.IsDefault,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getOrganizationByName = `-- name: GetOrganizationByName :one
|
||||
SELECT
|
||||
id, name, description, created_at, updated_at
|
||||
id, name, description, created_at, updated_at, is_default
|
||||
FROM
|
||||
organizations
|
||||
WHERE
|
||||
@ -3184,13 +3185,14 @@ func (q *sqlQuerier) GetOrganizationByName(ctx context.Context, name string) (Or
|
||||
&i.Description,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.IsDefault,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getOrganizations = `-- name: GetOrganizations :many
|
||||
SELECT
|
||||
id, name, description, created_at, updated_at
|
||||
id, name, description, created_at, updated_at, is_default
|
||||
FROM
|
||||
organizations
|
||||
`
|
||||
@ -3210,6 +3212,7 @@ func (q *sqlQuerier) GetOrganizations(ctx context.Context) ([]Organization, erro
|
||||
&i.Description,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.IsDefault,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -3226,7 +3229,7 @@ func (q *sqlQuerier) GetOrganizations(ctx context.Context) ([]Organization, erro
|
||||
|
||||
const getOrganizationsByUserID = `-- name: GetOrganizationsByUserID :many
|
||||
SELECT
|
||||
id, name, description, created_at, updated_at
|
||||
id, name, description, created_at, updated_at, is_default
|
||||
FROM
|
||||
organizations
|
||||
WHERE
|
||||
@ -3255,6 +3258,7 @@ func (q *sqlQuerier) GetOrganizationsByUserID(ctx context.Context, userID uuid.U
|
||||
&i.Description,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.IsDefault,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -3271,9 +3275,10 @@ func (q *sqlQuerier) GetOrganizationsByUserID(ctx context.Context, userID uuid.U
|
||||
|
||||
const insertOrganization = `-- name: InsertOrganization :one
|
||||
INSERT INTO
|
||||
organizations (id, "name", description, created_at, updated_at)
|
||||
organizations (id, "name", description, created_at, updated_at, is_default)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5) RETURNING id, name, description, created_at, updated_at
|
||||
-- If no organizations exist, and this is the first, make it the default.
|
||||
($1, $2, $3, $4, $5, (SELECT TRUE FROM organizations LIMIT 1) IS NULL) RETURNING id, name, description, created_at, updated_at, is_default
|
||||
`
|
||||
|
||||
type InsertOrganizationParams struct {
|
||||
@ -3299,6 +3304,7 @@ func (q *sqlQuerier) InsertOrganization(ctx context.Context, arg InsertOrganizat
|
||||
&i.Description,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.IsDefault,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ WHERE
|
||||
|
||||
-- name: InsertOrganization :one
|
||||
INSERT INTO
|
||||
organizations (id, "name", description, created_at, updated_at)
|
||||
organizations (id, "name", description, created_at, updated_at, is_default)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5) RETURNING *;
|
||||
-- If no organizations exist, and this is the first, make it the default.
|
||||
($1, $2, $3, $4, $5, (SELECT TRUE FROM organizations LIMIT 1) IS NULL) RETURNING *;
|
||||
|
@ -74,6 +74,7 @@ const (
|
||||
UniqueIndexProvisionerDaemonsNameOwnerKey UniqueConstraint = "idx_provisioner_daemons_name_owner_key" // CREATE UNIQUE INDEX idx_provisioner_daemons_name_owner_key ON provisioner_daemons USING btree (name, lower(COALESCE((tags ->> 'owner'::text), ''::text)));
|
||||
UniqueIndexUsersEmail UniqueConstraint = "idx_users_email" // CREATE UNIQUE INDEX idx_users_email ON users USING btree (email) WHERE (deleted = false);
|
||||
UniqueIndexUsersUsername UniqueConstraint = "idx_users_username" // CREATE UNIQUE INDEX idx_users_username ON users USING btree (username) WHERE (deleted = false);
|
||||
UniqueOrganizationsSingleDefaultOrg UniqueConstraint = "organizations_single_default_org" // CREATE UNIQUE INDEX organizations_single_default_org ON organizations USING btree (is_default) WHERE (is_default = true);
|
||||
UniqueTemplatesOrganizationIDNameIndex UniqueConstraint = "templates_organization_id_name_idx" // CREATE UNIQUE INDEX templates_organization_id_name_idx ON templates USING btree (organization_id, lower((name)::text)) WHERE (deleted = false);
|
||||
UniqueUsersEmailLowerIndex UniqueConstraint = "users_email_lower_idx" // CREATE UNIQUE INDEX users_email_lower_idx ON users USING btree (lower(email)) WHERE (deleted = false);
|
||||
UniqueUsersUsernameLowerIndex UniqueConstraint = "users_username_lower_idx" // CREATE UNIQUE INDEX users_username_lower_idx ON users USING btree (lower(username)) WHERE (deleted = false);
|
||||
|
Reference in New Issue
Block a user