Rename external URLs and move profile fields
Rename the compliance external URL concept to compliance custom links, and move the public-facing profile fields (description, website, email, headquarter address) off the organization onto the trust center. Backfill managed default domains for pages that lack them. Signed-off-by: Bryan Frimin <bryan@probo.com>
This commit is contained in:
@@ -105,8 +105,8 @@ func ResourceTypeName(entityType uint16) string {
|
||||
return "WebhookSubscription"
|
||||
case ComplianceFrameworkEntityType:
|
||||
return "ComplianceFramework"
|
||||
case ComplianceExternalURLEntityType:
|
||||
return "ComplianceExternalURL"
|
||||
case ComplianceCustomLinkEntityType:
|
||||
return "ComplianceCustomLink"
|
||||
case MailingListEntityType:
|
||||
return "MailingList"
|
||||
case MailingListSubscriberEntityType:
|
||||
|
||||
@@ -35,7 +35,7 @@ import (
|
||||
)
|
||||
|
||||
type (
|
||||
ComplianceExternalURL struct {
|
||||
ComplianceCustomLink struct {
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
TrustCenterID gid.GID `db:"trust_center_id"`
|
||||
@@ -46,26 +46,26 @@ type (
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
ComplianceExternalURLs []*ComplianceExternalURL
|
||||
ComplianceCustomLinks []*ComplianceCustomLink
|
||||
)
|
||||
|
||||
func (c ComplianceExternalURL) CursorKey(orderBy ComplianceExternalURLOrderField) page.CursorKey {
|
||||
func (c ComplianceCustomLink) CursorKey(orderBy ComplianceCustomLinkOrderField) page.CursorKey {
|
||||
switch orderBy {
|
||||
case ComplianceExternalURLOrderFieldCreatedAt:
|
||||
case ComplianceCustomLinkOrderFieldCreatedAt:
|
||||
return page.NewCursorKey(c.ID, c.CreatedAt)
|
||||
case ComplianceExternalURLOrderFieldRank:
|
||||
case ComplianceCustomLinkOrderFieldRank:
|
||||
return page.NewCursorKey(c.ID, c.Rank)
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("unsupported order by: %s", orderBy))
|
||||
}
|
||||
|
||||
func (c *ComplianceExternalURL) AuthorizationAttributes(
|
||||
func (c *ComplianceCustomLink) AuthorizationAttributes(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
resourceIDs []gid.GID,
|
||||
) (policy.AttributesByID, error) {
|
||||
q := `SELECT id, organization_id FROM compliance_external_urls WHERE id = ANY(@resource_ids::text[])`
|
||||
q := `SELECT id, organization_id FROM compliance_custom_links WHERE id = ANY(@resource_ids::text[])`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"resource_ids": resourceIDs,
|
||||
@@ -99,7 +99,7 @@ func (c *ComplianceExternalURL) AuthorizationAttributes(
|
||||
return attrsByID, nil
|
||||
}
|
||||
|
||||
func (c *ComplianceExternalURL) LoadByID(
|
||||
func (c *ComplianceCustomLink) LoadByID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
@@ -116,7 +116,7 @@ SELECT
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
compliance_external_urls
|
||||
compliance_custom_links
|
||||
WHERE
|
||||
%s
|
||||
AND id = @id
|
||||
@@ -129,16 +129,16 @@ LIMIT 1;
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query compliance_external_urls: %w", err)
|
||||
return fmt.Errorf("cannot query compliance_custom_links: %w", err)
|
||||
}
|
||||
|
||||
result, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[ComplianceExternalURL])
|
||||
result, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[ComplianceCustomLink])
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return ErrResourceNotFound
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot collect compliance external URL: %w", err)
|
||||
return fmt.Errorf("cannot collect compliance custom link: %w", err)
|
||||
}
|
||||
|
||||
*c = result
|
||||
@@ -146,14 +146,14 @@ LIMIT 1;
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ComplianceExternalURL) Insert(
|
||||
func (c *ComplianceCustomLink) Insert(
|
||||
ctx context.Context,
|
||||
conn pg.Tx,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
INSERT INTO
|
||||
compliance_external_urls (
|
||||
compliance_custom_links (
|
||||
id,
|
||||
tenant_id,
|
||||
organization_id,
|
||||
@@ -171,7 +171,7 @@ VALUES (
|
||||
@trust_center_id,
|
||||
@name,
|
||||
@url,
|
||||
(SELECT COALESCE(MAX(rank), 0) + 1 FROM compliance_external_urls WHERE trust_center_id = @trust_center_id),
|
||||
(SELECT COALESCE(MAX(rank), 0) + 1 FROM compliance_custom_links WHERE trust_center_id = @trust_center_id),
|
||||
@created_at,
|
||||
@updated_at
|
||||
)
|
||||
@@ -190,19 +190,19 @@ RETURNING rank;
|
||||
}
|
||||
|
||||
if err := conn.QueryRow(ctx, q, args).Scan(&c.Rank); err != nil {
|
||||
return fmt.Errorf("cannot insert compliance external URL: %w", err)
|
||||
return fmt.Errorf("cannot insert compliance custom link: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ComplianceExternalURL) Update(
|
||||
func (c *ComplianceCustomLink) Update(
|
||||
ctx context.Context,
|
||||
conn pg.Tx,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
UPDATE compliance_external_urls
|
||||
UPDATE compliance_custom_links
|
||||
SET
|
||||
name = @name,
|
||||
url = @url,
|
||||
@@ -223,13 +223,13 @@ WHERE
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update compliance external URL: %w", err)
|
||||
return fmt.Errorf("cannot update compliance custom link: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ComplianceExternalURL) UpdateRank(
|
||||
func (c *ComplianceCustomLink) UpdateRank(
|
||||
ctx context.Context,
|
||||
conn pg.Tx,
|
||||
scope Scoper,
|
||||
@@ -238,11 +238,11 @@ func (c *ComplianceExternalURL) UpdateRank(
|
||||
WITH old AS (
|
||||
SELECT
|
||||
rank AS old_rank
|
||||
FROM compliance_external_urls
|
||||
FROM compliance_custom_links
|
||||
WHERE %s AND id = @id AND trust_center_id = @trust_center_id
|
||||
)
|
||||
|
||||
UPDATE compliance_external_urls
|
||||
UPDATE compliance_custom_links
|
||||
SET
|
||||
rank = CASE
|
||||
WHEN id = @id THEN @new_rank
|
||||
@@ -274,20 +274,20 @@ WHERE %s
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update compliance external URL rank: %w", err)
|
||||
return fmt.Errorf("cannot update compliance custom link rank: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ComplianceExternalURL) Delete(
|
||||
func (c *ComplianceCustomLink) Delete(
|
||||
ctx context.Context,
|
||||
conn pg.Tx,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
DELETE FROM
|
||||
compliance_external_urls
|
||||
compliance_custom_links
|
||||
WHERE
|
||||
%s
|
||||
AND id = @id;
|
||||
@@ -299,18 +299,18 @@ WHERE
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot delete compliance external URL: %w", err)
|
||||
return fmt.Errorf("cannot delete compliance custom link: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ComplianceExternalURLs) LoadByTrustCenterID(
|
||||
func (c *ComplianceCustomLinks) LoadByTrustCenterID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
trustCenterID gid.GID,
|
||||
cursor *page.Cursor[ComplianceExternalURLOrderField],
|
||||
cursor *page.Cursor[ComplianceCustomLinkOrderField],
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
@@ -323,7 +323,7 @@ SELECT
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
compliance_external_urls
|
||||
compliance_custom_links
|
||||
WHERE
|
||||
%s
|
||||
AND trust_center_id = @trust_center_id
|
||||
@@ -337,12 +337,12 @@ WHERE
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query compliance_external_urls: %w", err)
|
||||
return fmt.Errorf("cannot query compliance_custom_links: %w", err)
|
||||
}
|
||||
|
||||
results, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[ComplianceExternalURL])
|
||||
results, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[ComplianceCustomLink])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect compliance external URLs: %w", err)
|
||||
return fmt.Errorf("cannot collect compliance custom links: %w", err)
|
||||
}
|
||||
|
||||
*c = results
|
||||
@@ -28,51 +28,51 @@ import (
|
||||
)
|
||||
|
||||
type (
|
||||
ComplianceExternalURLOrderField string
|
||||
ComplianceCustomLinkOrderField string
|
||||
)
|
||||
|
||||
const (
|
||||
ComplianceExternalURLOrderFieldCreatedAt ComplianceExternalURLOrderField = "CREATED_AT"
|
||||
ComplianceExternalURLOrderFieldRank ComplianceExternalURLOrderField = "RANK"
|
||||
ComplianceCustomLinkOrderFieldCreatedAt ComplianceCustomLinkOrderField = "CREATED_AT"
|
||||
ComplianceCustomLinkOrderFieldRank ComplianceCustomLinkOrderField = "RANK"
|
||||
)
|
||||
|
||||
var (
|
||||
_ page.OrderField = ComplianceExternalURLOrderField("")
|
||||
_ fmt.Stringer = ComplianceExternalURLOrderField("")
|
||||
_ encoding.TextMarshaler = ComplianceExternalURLOrderField("")
|
||||
_ encoding.TextUnmarshaler = (*ComplianceExternalURLOrderField)(nil)
|
||||
_ page.OrderField = ComplianceCustomLinkOrderField("")
|
||||
_ fmt.Stringer = ComplianceCustomLinkOrderField("")
|
||||
_ encoding.TextMarshaler = ComplianceCustomLinkOrderField("")
|
||||
_ encoding.TextUnmarshaler = (*ComplianceCustomLinkOrderField)(nil)
|
||||
)
|
||||
|
||||
func ComplianceExternalURLOrderFields() []ComplianceExternalURLOrderField {
|
||||
return []ComplianceExternalURLOrderField{
|
||||
ComplianceExternalURLOrderFieldCreatedAt,
|
||||
ComplianceExternalURLOrderFieldRank,
|
||||
func ComplianceCustomLinkOrderFields() []ComplianceCustomLinkOrderField {
|
||||
return []ComplianceCustomLinkOrderField{
|
||||
ComplianceCustomLinkOrderFieldCreatedAt,
|
||||
ComplianceCustomLinkOrderFieldRank,
|
||||
}
|
||||
}
|
||||
|
||||
func (v ComplianceExternalURLOrderField) IsValid() bool {
|
||||
func (v ComplianceCustomLinkOrderField) IsValid() bool {
|
||||
switch v {
|
||||
case
|
||||
ComplianceExternalURLOrderFieldCreatedAt,
|
||||
ComplianceExternalURLOrderFieldRank:
|
||||
ComplianceCustomLinkOrderFieldCreatedAt,
|
||||
ComplianceCustomLinkOrderFieldRank:
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (v ComplianceExternalURLOrderField) String() string {
|
||||
func (v ComplianceCustomLinkOrderField) String() string {
|
||||
return string(v)
|
||||
}
|
||||
|
||||
func (v ComplianceExternalURLOrderField) MarshalText() ([]byte, error) {
|
||||
func (v ComplianceCustomLinkOrderField) MarshalText() ([]byte, error) {
|
||||
return []byte(v.String()), nil
|
||||
}
|
||||
|
||||
func (v *ComplianceExternalURLOrderField) UnmarshalText(text []byte) error {
|
||||
val := ComplianceExternalURLOrderField(text)
|
||||
func (v *ComplianceCustomLinkOrderField) UnmarshalText(text []byte) error {
|
||||
val := ComplianceCustomLinkOrderField(text)
|
||||
if !val.IsValid() {
|
||||
return fmt.Errorf("invalid ComplianceExternalURLOrderField value: %q", string(text))
|
||||
return fmt.Errorf("invalid ComplianceCustomLinkOrderField value: %q", string(text))
|
||||
}
|
||||
|
||||
*v = val
|
||||
@@ -80,11 +80,11 @@ func (v *ComplianceExternalURLOrderField) UnmarshalText(text []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p ComplianceExternalURLOrderField) Column() string {
|
||||
func (p ComplianceCustomLinkOrderField) Column() string {
|
||||
switch p {
|
||||
case ComplianceExternalURLOrderFieldCreatedAt:
|
||||
case ComplianceCustomLinkOrderFieldCreatedAt:
|
||||
return "created_at"
|
||||
case ComplianceExternalURLOrderFieldRank:
|
||||
case ComplianceCustomLinkOrderFieldRank:
|
||||
return "rank"
|
||||
default:
|
||||
return string(p)
|
||||
19
pkg/coredata/migrations/20260710T081759Z.sql
Normal file
19
pkg/coredata/migrations/20260710T081759Z.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
-- Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software for any
|
||||
-- purpose with or without fee is hereby granted, provided that the above
|
||||
-- copyright notice and this permission notice appear in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
-- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
-- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
-- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
-- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
-- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
-- PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
ALTER TABLE compliance_external_urls RENAME TO compliance_custom_links;
|
||||
|
||||
ALTER TABLE compliance_custom_links
|
||||
RENAME CONSTRAINT compliance_external_urls_trust_center_id_rank_key
|
||||
TO compliance_custom_links_trust_center_id_rank_key;
|
||||
34
pkg/coredata/migrations/20260710T085314Z.sql
Normal file
34
pkg/coredata/migrations/20260710T085314Z.sql
Normal file
@@ -0,0 +1,34 @@
|
||||
-- Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software for any
|
||||
-- purpose with or without fee is hereby granted, provided that the above
|
||||
-- copyright notice and this permission notice appear in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
-- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
-- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
-- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
-- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
-- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
-- PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
ALTER TABLE trust_centers
|
||||
ADD COLUMN description TEXT,
|
||||
ADD COLUMN website_url TEXT,
|
||||
ADD COLUMN email CITEXT,
|
||||
ADD COLUMN headquarter_address TEXT;
|
||||
|
||||
UPDATE trust_centers tc
|
||||
SET
|
||||
description = o.description,
|
||||
website_url = o.website_url,
|
||||
email = o.email,
|
||||
headquarter_address = o.headquarter_address
|
||||
FROM organizations o
|
||||
WHERE tc.organization_id = o.id;
|
||||
|
||||
ALTER TABLE organizations
|
||||
DROP COLUMN description,
|
||||
DROP COLUMN website_url,
|
||||
DROP COLUMN email,
|
||||
DROP COLUMN headquarter_address;
|
||||
107
pkg/coredata/migrations/20260710T121004Z.sql
Normal file
107
pkg/coredata/migrations/20260710T121004Z.sql
Normal file
@@ -0,0 +1,107 @@
|
||||
-- Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software for any
|
||||
-- purpose with or without fee is hereby granted, provided that the above
|
||||
-- copyright notice and this permission notice appear in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
-- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
-- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
-- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
-- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
-- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
-- PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
-- Backfill managed default domains for compliance pages created before
|
||||
-- default-domain provisioning existed. Hostnames use the managed base domain
|
||||
-- suffix configured on the database (probo.trust_center_base_domain), falling
|
||||
-- back to probopage.com for production installs.
|
||||
|
||||
WITH pending_pages AS (
|
||||
SELECT
|
||||
tc.id AS trust_center_id,
|
||||
tc.tenant_id,
|
||||
tc.organization_id,
|
||||
tc.slug,
|
||||
tc.created_at,
|
||||
(
|
||||
tc.slug || '.' || COALESCE(
|
||||
NULLIF(current_setting('probo.trust_center_base_domain', true), ''),
|
||||
'probopage.com'
|
||||
)
|
||||
)::citext AS hostname
|
||||
FROM trust_centers tc
|
||||
WHERE tc.default_domain_id IS NULL
|
||||
),
|
||||
minted_certificates AS (
|
||||
INSERT INTO certificates (
|
||||
id,
|
||||
tenant_id,
|
||||
hostname,
|
||||
status,
|
||||
ssl_retry_count,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
translate(
|
||||
encode(
|
||||
substring(decode(translate(pp.trust_center_id, '-_', '+/'), 'base64') FROM 1 FOR 8)
|
||||
|| int2send(104::smallint)
|
||||
|| int8send((floor(extract(epoch FROM clock_timestamp()) * 1000))::bigint)
|
||||
|| substring(decode(md5(random()::text || pp.trust_center_id), 'hex') FROM 1 FOR 6),
|
||||
'base64'
|
||||
),
|
||||
'+/',
|
||||
'-_'
|
||||
),
|
||||
pp.tenant_id,
|
||||
pp.hostname,
|
||||
'PENDING'::custom_domain_ssl_status,
|
||||
0,
|
||||
pp.created_at,
|
||||
clock_timestamp()
|
||||
FROM pending_pages pp
|
||||
RETURNING id, hostname, tenant_id
|
||||
),
|
||||
minted_domains AS (
|
||||
INSERT INTO custom_domains (
|
||||
id,
|
||||
tenant_id,
|
||||
organization_id,
|
||||
domain,
|
||||
managed,
|
||||
certificate_id,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
translate(
|
||||
encode(
|
||||
substring(decode(translate(mc.id, '-_', '+/'), 'base64') FROM 1 FOR 8)
|
||||
|| int2send(37::smallint)
|
||||
|| int8send((floor(extract(epoch FROM clock_timestamp()) * 1000))::bigint)
|
||||
|| substring(decode(md5(random()::text || mc.id), 'hex') FROM 1 FOR 6),
|
||||
'base64'
|
||||
),
|
||||
'+/',
|
||||
'-_'
|
||||
),
|
||||
mc.tenant_id,
|
||||
pp.organization_id,
|
||||
pp.hostname,
|
||||
true,
|
||||
mc.id,
|
||||
pp.created_at,
|
||||
clock_timestamp()
|
||||
FROM minted_certificates mc
|
||||
JOIN pending_pages pp ON pp.hostname = mc.hostname
|
||||
RETURNING id, domain
|
||||
)
|
||||
UPDATE trust_centers tc
|
||||
SET
|
||||
default_domain_id = md.id,
|
||||
updated_at = clock_timestamp()
|
||||
FROM minted_domains md
|
||||
JOIN pending_pages pp ON pp.hostname = md.domain
|
||||
WHERE tc.id = pp.trust_center_id;
|
||||
@@ -41,11 +41,6 @@ type (
|
||||
Name string `db:"name"`
|
||||
LogoFileID *gid.GID `db:"logo_file_id"`
|
||||
HorizontalLogoFileID *gid.GID `db:"horizontal_logo_file_id"`
|
||||
Description *string `db:"description"`
|
||||
WebsiteURL *string `db:"website_url"`
|
||||
Email *string `db:"email"`
|
||||
HeadquarterAddress *string `db:"headquarter_address"`
|
||||
CustomDomainID *gid.GID `db:"custom_domain_id"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
@@ -117,11 +112,6 @@ SELECT
|
||||
name,
|
||||
logo_file_id,
|
||||
horizontal_logo_file_id,
|
||||
description,
|
||||
website_url,
|
||||
email,
|
||||
headquarter_address,
|
||||
custom_domain_id,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
@@ -169,11 +159,6 @@ SELECT
|
||||
name,
|
||||
logo_file_id,
|
||||
horizontal_logo_file_id,
|
||||
description,
|
||||
website_url,
|
||||
email,
|
||||
headquarter_address,
|
||||
custom_domain_id,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
@@ -229,11 +214,6 @@ SELECT
|
||||
name,
|
||||
logo_file_id,
|
||||
horizontal_logo_file_id,
|
||||
description,
|
||||
website_url,
|
||||
email,
|
||||
headquarter_address,
|
||||
custom_domain_id,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
@@ -290,11 +270,6 @@ SELECT
|
||||
name,
|
||||
logo_file_id,
|
||||
horizontal_logo_file_id,
|
||||
description,
|
||||
website_url,
|
||||
email,
|
||||
headquarter_address,
|
||||
custom_domain_id,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
@@ -338,14 +313,9 @@ INSERT INTO organizations (
|
||||
name,
|
||||
logo_file_id,
|
||||
horizontal_logo_file_id,
|
||||
description,
|
||||
website_url,
|
||||
email,
|
||||
headquarter_address,
|
||||
custom_domain_id,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (@tenant_id, @id, @name, @logo_file_id, @horizontal_logo_file_id, @description, @website_url, @email, @headquarter_address, @custom_domain_id, @created_at, @updated_at)
|
||||
) VALUES (@tenant_id, @id, @name, @logo_file_id, @horizontal_logo_file_id, @created_at, @updated_at)
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
@@ -354,11 +324,6 @@ INSERT INTO organizations (
|
||||
"name": o.Name,
|
||||
"logo_file_id": o.LogoFileID,
|
||||
"horizontal_logo_file_id": o.HorizontalLogoFileID,
|
||||
"description": o.Description,
|
||||
"website_url": o.WebsiteURL,
|
||||
"email": o.Email,
|
||||
"headquarter_address": o.HeadquarterAddress,
|
||||
"custom_domain_id": o.CustomDomainID,
|
||||
"created_at": o.CreatedAt,
|
||||
"updated_at": o.UpdatedAt,
|
||||
}
|
||||
@@ -382,11 +347,6 @@ SET
|
||||
name = @name,
|
||||
logo_file_id = @logo_file_id,
|
||||
horizontal_logo_file_id = @horizontal_logo_file_id,
|
||||
description = @description,
|
||||
website_url = @website_url,
|
||||
email = @email,
|
||||
headquarter_address = @headquarter_address,
|
||||
custom_domain_id = @custom_domain_id,
|
||||
updated_at = @updated_at
|
||||
WHERE
|
||||
%s
|
||||
@@ -400,11 +360,6 @@ WHERE
|
||||
"name": o.Name,
|
||||
"logo_file_id": o.LogoFileID,
|
||||
"horizontal_logo_file_id": o.HorizontalLogoFileID,
|
||||
"description": o.Description,
|
||||
"website_url": o.WebsiteURL,
|
||||
"email": o.Email,
|
||||
"headquarter_address": o.HeadquarterAddress,
|
||||
"custom_domain_id": o.CustomDomainID,
|
||||
"updated_at": o.UpdatedAt,
|
||||
}
|
||||
|
||||
@@ -437,55 +392,3 @@ WHERE id = @id
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Organization) LoadByCustomDomainID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
customDomainID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
tenant_id,
|
||||
id,
|
||||
name,
|
||||
logo_file_id,
|
||||
horizontal_logo_file_id,
|
||||
description,
|
||||
website_url,
|
||||
email,
|
||||
headquarter_address,
|
||||
custom_domain_id,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
organizations
|
||||
WHERE
|
||||
%s
|
||||
AND custom_domain_id = @custom_domain_id
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"custom_domain_id": customDomainID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query organization by custom domain: %w", err)
|
||||
}
|
||||
|
||||
organization, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[Organization])
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return ErrResourceNotFound
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot collect organization: %w", err)
|
||||
}
|
||||
|
||||
*o = organization
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -69,10 +69,6 @@ type (
|
||||
Name *string
|
||||
LogoFile *UploadedFile
|
||||
HorizontalLogoFile *UploadedFile
|
||||
Description **string
|
||||
WebsiteURL **string
|
||||
Email **string
|
||||
HeadquarterAddress **string
|
||||
}
|
||||
|
||||
CreateSAMLConfigurationRequest struct {
|
||||
@@ -191,10 +187,6 @@ func (req UpdateOrganizationRequest) Validate() error {
|
||||
fv := filevalidation.NewValidator(filevalidation.WithCategories(filevalidation.CategoryImage))
|
||||
|
||||
v.Check(req.Name, "name", validator.SafeTextNoNewLine(255))
|
||||
v.Check(req.Description, "description", validator.SafeText(ContentMaxLength))
|
||||
v.Check(req.WebsiteURL, "website_url", validator.SafeText(2048))
|
||||
v.Check(req.Email, "email", validator.SafeText(255))
|
||||
v.Check(req.HeadquarterAddress, "headquarter_address", validator.SafeText(2048))
|
||||
v.Check(req.LogoFile, "logo_file", validator.NotEmpty())
|
||||
|
||||
if req.LogoFile != nil {
|
||||
@@ -763,6 +755,28 @@ func (s *OrganizationService) CreateOrganization(
|
||||
return fmt.Errorf("cannot insert mailing list: %w", err)
|
||||
}
|
||||
|
||||
defaultDomainHostname := trustCenter.Slug + "." + s.trustCenterBaseDomain
|
||||
|
||||
defaultDomain := coredata.NewCustomDomain(
|
||||
tenantID,
|
||||
organization.ID,
|
||||
defaultDomainHostname,
|
||||
true,
|
||||
)
|
||||
|
||||
certificate, err := s.certManager.EnsureCertificate(ctx, tx, scope, defaultDomainHostname)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot ensure certificate for default custom domain: %w", err)
|
||||
}
|
||||
|
||||
defaultDomain.CertificateID = &certificate.ID
|
||||
|
||||
if err := defaultDomain.Insert(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot insert default custom domain: %w", err)
|
||||
}
|
||||
|
||||
trustCenter.DefaultDomainID = &defaultDomain.ID
|
||||
|
||||
if err := trustCenter.Insert(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot insert trust center: %w", err)
|
||||
}
|
||||
@@ -905,28 +919,6 @@ func (s *OrganizationService) UpdateOrganization(ctx context.Context, organizati
|
||||
organization.Name = *req.Name
|
||||
}
|
||||
|
||||
if req.Description != nil {
|
||||
organization.Description = *req.Description
|
||||
}
|
||||
|
||||
if req.WebsiteURL != nil {
|
||||
organization.WebsiteURL = *req.WebsiteURL
|
||||
}
|
||||
|
||||
if req.Email != nil {
|
||||
if *req.Email != nil {
|
||||
if _, err := mail.ParseAddr(**req.Email); err != nil {
|
||||
return fmt.Errorf("invalid email address: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
organization.Email = *req.Email
|
||||
}
|
||||
|
||||
if req.HeadquarterAddress != nil {
|
||||
organization.HeadquarterAddress = *req.HeadquarterAddress
|
||||
}
|
||||
|
||||
if logoFile != nil {
|
||||
if err := logoFile.Insert(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot insert file: %w", err)
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"mime"
|
||||
"net/mail"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
@@ -49,10 +48,6 @@ type (
|
||||
Name *string
|
||||
File *File
|
||||
HorizontalLogoFile *File
|
||||
Description **string
|
||||
WebsiteURL **string
|
||||
Email **string
|
||||
HeadquarterAddress **string
|
||||
}
|
||||
|
||||
UpdateOrganizationContextRequest struct {
|
||||
@@ -70,10 +65,6 @@ func (uor *UpdateOrganizationRequest) Validate() error {
|
||||
|
||||
v.Check(uor.ID, "id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||
v.Check(uor.Name, "name", validator.SafeTextNoNewLine(TitleMaxLength))
|
||||
v.Check(uor.Description, "description", validator.SafeText(ContentMaxLength))
|
||||
v.Check(uor.WebsiteURL, "website_url", validator.SafeText(2048))
|
||||
v.Check(uor.Email, "email", validator.SafeText(255))
|
||||
v.Check(uor.HeadquarterAddress, "headquarter_address", validator.SafeText(2048))
|
||||
v.Check(uor.File, "file", validator.NotEmpty())
|
||||
v.Check(uor.HorizontalLogoFile, "horizontal_logo_file", validator.NotEmpty())
|
||||
|
||||
@@ -256,28 +247,6 @@ func (s OrganizationService) Update(
|
||||
organization.Name = *req.Name
|
||||
}
|
||||
|
||||
if req.Description != nil {
|
||||
organization.Description = *req.Description
|
||||
}
|
||||
|
||||
if req.WebsiteURL != nil {
|
||||
organization.WebsiteURL = *req.WebsiteURL
|
||||
}
|
||||
|
||||
if req.Email != nil {
|
||||
if *req.Email != nil {
|
||||
if _, err := mail.ParseAddress(**req.Email); err != nil {
|
||||
return fmt.Errorf("invalid email address: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
organization.Email = *req.Email
|
||||
}
|
||||
|
||||
if req.HeadquarterAddress != nil {
|
||||
organization.HeadquarterAddress = *req.HeadquarterAddress
|
||||
}
|
||||
|
||||
if err := organization.Update(ctx, scope, tx); err != nil {
|
||||
return fmt.Errorf("cannot update organization: %w", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user