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"
|
return "WebhookSubscription"
|
||||||
case ComplianceFrameworkEntityType:
|
case ComplianceFrameworkEntityType:
|
||||||
return "ComplianceFramework"
|
return "ComplianceFramework"
|
||||||
case ComplianceExternalURLEntityType:
|
case ComplianceCustomLinkEntityType:
|
||||||
return "ComplianceExternalURL"
|
return "ComplianceCustomLink"
|
||||||
case MailingListEntityType:
|
case MailingListEntityType:
|
||||||
return "MailingList"
|
return "MailingList"
|
||||||
case MailingListSubscriberEntityType:
|
case MailingListSubscriberEntityType:
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
ComplianceExternalURL struct {
|
ComplianceCustomLink struct {
|
||||||
ID gid.GID `db:"id"`
|
ID gid.GID `db:"id"`
|
||||||
OrganizationID gid.GID `db:"organization_id"`
|
OrganizationID gid.GID `db:"organization_id"`
|
||||||
TrustCenterID gid.GID `db:"trust_center_id"`
|
TrustCenterID gid.GID `db:"trust_center_id"`
|
||||||
@@ -46,26 +46,26 @@ type (
|
|||||||
UpdatedAt time.Time `db:"updated_at"`
|
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 {
|
switch orderBy {
|
||||||
case ComplianceExternalURLOrderFieldCreatedAt:
|
case ComplianceCustomLinkOrderFieldCreatedAt:
|
||||||
return page.NewCursorKey(c.ID, c.CreatedAt)
|
return page.NewCursorKey(c.ID, c.CreatedAt)
|
||||||
case ComplianceExternalURLOrderFieldRank:
|
case ComplianceCustomLinkOrderFieldRank:
|
||||||
return page.NewCursorKey(c.ID, c.Rank)
|
return page.NewCursorKey(c.ID, c.Rank)
|
||||||
}
|
}
|
||||||
|
|
||||||
panic(fmt.Sprintf("unsupported order by: %s", orderBy))
|
panic(fmt.Sprintf("unsupported order by: %s", orderBy))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ComplianceExternalURL) AuthorizationAttributes(
|
func (c *ComplianceCustomLink) AuthorizationAttributes(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
conn pg.Querier,
|
conn pg.Querier,
|
||||||
resourceIDs []gid.GID,
|
resourceIDs []gid.GID,
|
||||||
) (policy.AttributesByID, error) {
|
) (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{
|
args := pgx.StrictNamedArgs{
|
||||||
"resource_ids": resourceIDs,
|
"resource_ids": resourceIDs,
|
||||||
@@ -99,7 +99,7 @@ func (c *ComplianceExternalURL) AuthorizationAttributes(
|
|||||||
return attrsByID, nil
|
return attrsByID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ComplianceExternalURL) LoadByID(
|
func (c *ComplianceCustomLink) LoadByID(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
conn pg.Querier,
|
conn pg.Querier,
|
||||||
scope Scoper,
|
scope Scoper,
|
||||||
@@ -116,7 +116,7 @@ SELECT
|
|||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
FROM
|
FROM
|
||||||
compliance_external_urls
|
compliance_custom_links
|
||||||
WHERE
|
WHERE
|
||||||
%s
|
%s
|
||||||
AND id = @id
|
AND id = @id
|
||||||
@@ -129,16 +129,16 @@ LIMIT 1;
|
|||||||
|
|
||||||
rows, err := conn.Query(ctx, q, args)
|
rows, err := conn.Query(ctx, q, args)
|
||||||
if err != nil {
|
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 err != nil {
|
||||||
if errors.Is(err, pgx.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
return ErrResourceNotFound
|
return ErrResourceNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("cannot collect compliance external URL: %w", err)
|
return fmt.Errorf("cannot collect compliance custom link: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
*c = result
|
*c = result
|
||||||
@@ -146,14 +146,14 @@ LIMIT 1;
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ComplianceExternalURL) Insert(
|
func (c *ComplianceCustomLink) Insert(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
conn pg.Tx,
|
conn pg.Tx,
|
||||||
scope Scoper,
|
scope Scoper,
|
||||||
) error {
|
) error {
|
||||||
q := `
|
q := `
|
||||||
INSERT INTO
|
INSERT INTO
|
||||||
compliance_external_urls (
|
compliance_custom_links (
|
||||||
id,
|
id,
|
||||||
tenant_id,
|
tenant_id,
|
||||||
organization_id,
|
organization_id,
|
||||||
@@ -171,7 +171,7 @@ VALUES (
|
|||||||
@trust_center_id,
|
@trust_center_id,
|
||||||
@name,
|
@name,
|
||||||
@url,
|
@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,
|
@created_at,
|
||||||
@updated_at
|
@updated_at
|
||||||
)
|
)
|
||||||
@@ -190,19 +190,19 @@ RETURNING rank;
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := conn.QueryRow(ctx, q, args).Scan(&c.Rank); err != nil {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ComplianceExternalURL) Update(
|
func (c *ComplianceCustomLink) Update(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
conn pg.Tx,
|
conn pg.Tx,
|
||||||
scope Scoper,
|
scope Scoper,
|
||||||
) error {
|
) error {
|
||||||
q := `
|
q := `
|
||||||
UPDATE compliance_external_urls
|
UPDATE compliance_custom_links
|
||||||
SET
|
SET
|
||||||
name = @name,
|
name = @name,
|
||||||
url = @url,
|
url = @url,
|
||||||
@@ -223,13 +223,13 @@ WHERE
|
|||||||
|
|
||||||
_, err := conn.Exec(ctx, q, args)
|
_, err := conn.Exec(ctx, q, args)
|
||||||
if err != nil {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ComplianceExternalURL) UpdateRank(
|
func (c *ComplianceCustomLink) UpdateRank(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
conn pg.Tx,
|
conn pg.Tx,
|
||||||
scope Scoper,
|
scope Scoper,
|
||||||
@@ -238,11 +238,11 @@ func (c *ComplianceExternalURL) UpdateRank(
|
|||||||
WITH old AS (
|
WITH old AS (
|
||||||
SELECT
|
SELECT
|
||||||
rank AS old_rank
|
rank AS old_rank
|
||||||
FROM compliance_external_urls
|
FROM compliance_custom_links
|
||||||
WHERE %s AND id = @id AND trust_center_id = @trust_center_id
|
WHERE %s AND id = @id AND trust_center_id = @trust_center_id
|
||||||
)
|
)
|
||||||
|
|
||||||
UPDATE compliance_external_urls
|
UPDATE compliance_custom_links
|
||||||
SET
|
SET
|
||||||
rank = CASE
|
rank = CASE
|
||||||
WHEN id = @id THEN @new_rank
|
WHEN id = @id THEN @new_rank
|
||||||
@@ -274,20 +274,20 @@ WHERE %s
|
|||||||
|
|
||||||
_, err := conn.Exec(ctx, q, args)
|
_, err := conn.Exec(ctx, q, args)
|
||||||
if err != nil {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ComplianceExternalURL) Delete(
|
func (c *ComplianceCustomLink) Delete(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
conn pg.Tx,
|
conn pg.Tx,
|
||||||
scope Scoper,
|
scope Scoper,
|
||||||
) error {
|
) error {
|
||||||
q := `
|
q := `
|
||||||
DELETE FROM
|
DELETE FROM
|
||||||
compliance_external_urls
|
compliance_custom_links
|
||||||
WHERE
|
WHERE
|
||||||
%s
|
%s
|
||||||
AND id = @id;
|
AND id = @id;
|
||||||
@@ -299,18 +299,18 @@ WHERE
|
|||||||
|
|
||||||
_, err := conn.Exec(ctx, q, args)
|
_, err := conn.Exec(ctx, q, args)
|
||||||
if err != nil {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ComplianceExternalURLs) LoadByTrustCenterID(
|
func (c *ComplianceCustomLinks) LoadByTrustCenterID(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
conn pg.Querier,
|
conn pg.Querier,
|
||||||
scope Scoper,
|
scope Scoper,
|
||||||
trustCenterID gid.GID,
|
trustCenterID gid.GID,
|
||||||
cursor *page.Cursor[ComplianceExternalURLOrderField],
|
cursor *page.Cursor[ComplianceCustomLinkOrderField],
|
||||||
) error {
|
) error {
|
||||||
q := `
|
q := `
|
||||||
SELECT
|
SELECT
|
||||||
@@ -323,7 +323,7 @@ SELECT
|
|||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
FROM
|
FROM
|
||||||
compliance_external_urls
|
compliance_custom_links
|
||||||
WHERE
|
WHERE
|
||||||
%s
|
%s
|
||||||
AND trust_center_id = @trust_center_id
|
AND trust_center_id = @trust_center_id
|
||||||
@@ -337,12 +337,12 @@ WHERE
|
|||||||
|
|
||||||
rows, err := conn.Query(ctx, q, args)
|
rows, err := conn.Query(ctx, q, args)
|
||||||
if err != nil {
|
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 {
|
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
|
*c = results
|
||||||
@@ -28,51 +28,51 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
ComplianceExternalURLOrderField string
|
ComplianceCustomLinkOrderField string
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ComplianceExternalURLOrderFieldCreatedAt ComplianceExternalURLOrderField = "CREATED_AT"
|
ComplianceCustomLinkOrderFieldCreatedAt ComplianceCustomLinkOrderField = "CREATED_AT"
|
||||||
ComplianceExternalURLOrderFieldRank ComplianceExternalURLOrderField = "RANK"
|
ComplianceCustomLinkOrderFieldRank ComplianceCustomLinkOrderField = "RANK"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ page.OrderField = ComplianceExternalURLOrderField("")
|
_ page.OrderField = ComplianceCustomLinkOrderField("")
|
||||||
_ fmt.Stringer = ComplianceExternalURLOrderField("")
|
_ fmt.Stringer = ComplianceCustomLinkOrderField("")
|
||||||
_ encoding.TextMarshaler = ComplianceExternalURLOrderField("")
|
_ encoding.TextMarshaler = ComplianceCustomLinkOrderField("")
|
||||||
_ encoding.TextUnmarshaler = (*ComplianceExternalURLOrderField)(nil)
|
_ encoding.TextUnmarshaler = (*ComplianceCustomLinkOrderField)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
func ComplianceExternalURLOrderFields() []ComplianceExternalURLOrderField {
|
func ComplianceCustomLinkOrderFields() []ComplianceCustomLinkOrderField {
|
||||||
return []ComplianceExternalURLOrderField{
|
return []ComplianceCustomLinkOrderField{
|
||||||
ComplianceExternalURLOrderFieldCreatedAt,
|
ComplianceCustomLinkOrderFieldCreatedAt,
|
||||||
ComplianceExternalURLOrderFieldRank,
|
ComplianceCustomLinkOrderFieldRank,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v ComplianceExternalURLOrderField) IsValid() bool {
|
func (v ComplianceCustomLinkOrderField) IsValid() bool {
|
||||||
switch v {
|
switch v {
|
||||||
case
|
case
|
||||||
ComplianceExternalURLOrderFieldCreatedAt,
|
ComplianceCustomLinkOrderFieldCreatedAt,
|
||||||
ComplianceExternalURLOrderFieldRank:
|
ComplianceCustomLinkOrderFieldRank:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v ComplianceExternalURLOrderField) String() string {
|
func (v ComplianceCustomLinkOrderField) String() string {
|
||||||
return string(v)
|
return string(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v ComplianceExternalURLOrderField) MarshalText() ([]byte, error) {
|
func (v ComplianceCustomLinkOrderField) MarshalText() ([]byte, error) {
|
||||||
return []byte(v.String()), nil
|
return []byte(v.String()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *ComplianceExternalURLOrderField) UnmarshalText(text []byte) error {
|
func (v *ComplianceCustomLinkOrderField) UnmarshalText(text []byte) error {
|
||||||
val := ComplianceExternalURLOrderField(text)
|
val := ComplianceCustomLinkOrderField(text)
|
||||||
if !val.IsValid() {
|
if !val.IsValid() {
|
||||||
return fmt.Errorf("invalid ComplianceExternalURLOrderField value: %q", string(text))
|
return fmt.Errorf("invalid ComplianceCustomLinkOrderField value: %q", string(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
*v = val
|
*v = val
|
||||||
@@ -80,11 +80,11 @@ func (v *ComplianceExternalURLOrderField) UnmarshalText(text []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ComplianceExternalURLOrderField) Column() string {
|
func (p ComplianceCustomLinkOrderField) Column() string {
|
||||||
switch p {
|
switch p {
|
||||||
case ComplianceExternalURLOrderFieldCreatedAt:
|
case ComplianceCustomLinkOrderFieldCreatedAt:
|
||||||
return "created_at"
|
return "created_at"
|
||||||
case ComplianceExternalURLOrderFieldRank:
|
case ComplianceCustomLinkOrderFieldRank:
|
||||||
return "rank"
|
return "rank"
|
||||||
default:
|
default:
|
||||||
return string(p)
|
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"`
|
Name string `db:"name"`
|
||||||
LogoFileID *gid.GID `db:"logo_file_id"`
|
LogoFileID *gid.GID `db:"logo_file_id"`
|
||||||
HorizontalLogoFileID *gid.GID `db:"horizontal_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"`
|
CreatedAt time.Time `db:"created_at"`
|
||||||
UpdatedAt time.Time `db:"updated_at"`
|
UpdatedAt time.Time `db:"updated_at"`
|
||||||
}
|
}
|
||||||
@@ -117,11 +112,6 @@ SELECT
|
|||||||
name,
|
name,
|
||||||
logo_file_id,
|
logo_file_id,
|
||||||
horizontal_logo_file_id,
|
horizontal_logo_file_id,
|
||||||
description,
|
|
||||||
website_url,
|
|
||||||
email,
|
|
||||||
headquarter_address,
|
|
||||||
custom_domain_id,
|
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
FROM
|
FROM
|
||||||
@@ -169,11 +159,6 @@ SELECT
|
|||||||
name,
|
name,
|
||||||
logo_file_id,
|
logo_file_id,
|
||||||
horizontal_logo_file_id,
|
horizontal_logo_file_id,
|
||||||
description,
|
|
||||||
website_url,
|
|
||||||
email,
|
|
||||||
headquarter_address,
|
|
||||||
custom_domain_id,
|
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
FROM
|
FROM
|
||||||
@@ -229,11 +214,6 @@ SELECT
|
|||||||
name,
|
name,
|
||||||
logo_file_id,
|
logo_file_id,
|
||||||
horizontal_logo_file_id,
|
horizontal_logo_file_id,
|
||||||
description,
|
|
||||||
website_url,
|
|
||||||
email,
|
|
||||||
headquarter_address,
|
|
||||||
custom_domain_id,
|
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
FROM
|
FROM
|
||||||
@@ -290,11 +270,6 @@ SELECT
|
|||||||
name,
|
name,
|
||||||
logo_file_id,
|
logo_file_id,
|
||||||
horizontal_logo_file_id,
|
horizontal_logo_file_id,
|
||||||
description,
|
|
||||||
website_url,
|
|
||||||
email,
|
|
||||||
headquarter_address,
|
|
||||||
custom_domain_id,
|
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
FROM
|
FROM
|
||||||
@@ -338,14 +313,9 @@ INSERT INTO organizations (
|
|||||||
name,
|
name,
|
||||||
logo_file_id,
|
logo_file_id,
|
||||||
horizontal_logo_file_id,
|
horizontal_logo_file_id,
|
||||||
description,
|
|
||||||
website_url,
|
|
||||||
email,
|
|
||||||
headquarter_address,
|
|
||||||
custom_domain_id,
|
|
||||||
created_at,
|
created_at,
|
||||||
updated_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{
|
args := pgx.StrictNamedArgs{
|
||||||
@@ -354,11 +324,6 @@ INSERT INTO organizations (
|
|||||||
"name": o.Name,
|
"name": o.Name,
|
||||||
"logo_file_id": o.LogoFileID,
|
"logo_file_id": o.LogoFileID,
|
||||||
"horizontal_logo_file_id": o.HorizontalLogoFileID,
|
"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,
|
"created_at": o.CreatedAt,
|
||||||
"updated_at": o.UpdatedAt,
|
"updated_at": o.UpdatedAt,
|
||||||
}
|
}
|
||||||
@@ -382,11 +347,6 @@ SET
|
|||||||
name = @name,
|
name = @name,
|
||||||
logo_file_id = @logo_file_id,
|
logo_file_id = @logo_file_id,
|
||||||
horizontal_logo_file_id = @horizontal_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
|
updated_at = @updated_at
|
||||||
WHERE
|
WHERE
|
||||||
%s
|
%s
|
||||||
@@ -400,11 +360,6 @@ WHERE
|
|||||||
"name": o.Name,
|
"name": o.Name,
|
||||||
"logo_file_id": o.LogoFileID,
|
"logo_file_id": o.LogoFileID,
|
||||||
"horizontal_logo_file_id": o.HorizontalLogoFileID,
|
"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,
|
"updated_at": o.UpdatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,55 +392,3 @@ WHERE id = @id
|
|||||||
|
|
||||||
return nil
|
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
|
Name *string
|
||||||
LogoFile *UploadedFile
|
LogoFile *UploadedFile
|
||||||
HorizontalLogoFile *UploadedFile
|
HorizontalLogoFile *UploadedFile
|
||||||
Description **string
|
|
||||||
WebsiteURL **string
|
|
||||||
Email **string
|
|
||||||
HeadquarterAddress **string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateSAMLConfigurationRequest struct {
|
CreateSAMLConfigurationRequest struct {
|
||||||
@@ -191,10 +187,6 @@ func (req UpdateOrganizationRequest) Validate() error {
|
|||||||
fv := filevalidation.NewValidator(filevalidation.WithCategories(filevalidation.CategoryImage))
|
fv := filevalidation.NewValidator(filevalidation.WithCategories(filevalidation.CategoryImage))
|
||||||
|
|
||||||
v.Check(req.Name, "name", validator.SafeTextNoNewLine(255))
|
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())
|
v.Check(req.LogoFile, "logo_file", validator.NotEmpty())
|
||||||
|
|
||||||
if req.LogoFile != nil {
|
if req.LogoFile != nil {
|
||||||
@@ -763,6 +755,28 @@ func (s *OrganizationService) CreateOrganization(
|
|||||||
return fmt.Errorf("cannot insert mailing list: %w", err)
|
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 {
|
if err := trustCenter.Insert(ctx, tx, scope); err != nil {
|
||||||
return fmt.Errorf("cannot insert trust center: %w", err)
|
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
|
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 logoFile != nil {
|
||||||
if err := logoFile.Insert(ctx, tx, scope); err != nil {
|
if err := logoFile.Insert(ctx, tx, scope); err != nil {
|
||||||
return fmt.Errorf("cannot insert file: %w", err)
|
return fmt.Errorf("cannot insert file: %w", err)
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"mime"
|
"mime"
|
||||||
"net/mail"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -49,10 +48,6 @@ type (
|
|||||||
Name *string
|
Name *string
|
||||||
File *File
|
File *File
|
||||||
HorizontalLogoFile *File
|
HorizontalLogoFile *File
|
||||||
Description **string
|
|
||||||
WebsiteURL **string
|
|
||||||
Email **string
|
|
||||||
HeadquarterAddress **string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateOrganizationContextRequest struct {
|
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.ID, "id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||||
v.Check(uor.Name, "name", validator.SafeTextNoNewLine(TitleMaxLength))
|
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.File, "file", validator.NotEmpty())
|
||||||
v.Check(uor.HorizontalLogoFile, "horizontal_logo_file", validator.NotEmpty())
|
v.Check(uor.HorizontalLogoFile, "horizontal_logo_file", validator.NotEmpty())
|
||||||
|
|
||||||
@@ -256,28 +247,6 @@ func (s OrganizationService) Update(
|
|||||||
organization.Name = *req.Name
|
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 {
|
if err := organization.Update(ctx, scope, tx); err != nil {
|
||||||
return fmt.Errorf("cannot update organization: %w", err)
|
return fmt.Errorf("cannot update organization: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user