Refatcor policies management

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2025-04-28 16:03:42 -07:00
parent c208131b85
commit a7cd94956a
59 changed files with 10112 additions and 2303 deletions

View File

@@ -31,4 +31,6 @@ const (
EmailEntityType
ControlEntityType
RiskEntityType
PolicyVersionEntityType
PolicyVersionSignatureEntityType
)

View File

@@ -0,0 +1,60 @@
CREATE TABLE policy_versions (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
policy_id TEXT NOT NULL REFERENCES policies(id) ON DELETE CASCADE,
version_number INTEGER NOT NULL,
content TEXT NOT NULL,
changelog TEXT NOT NULL,
created_by TEXT NOT NULL,
status policy_status NOT NULL,
published_by TEXT REFERENCES peoples(id),
published_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
UNIQUE(policy_id, version_number)
);
INSERT INTO policy_versions (
id,
tenant_id,
policy_id,
version_number,
content,
changelog,
created_by,
status,
published_by,
published_at,
created_at,
updated_at
)
SELECT
generate_gid(decode_base64_unpadded(p.tenant_id), 16),
p.tenant_id,
p.id,
1,
'',
'Initial version',
p.owner_id,
'DRAFT',
NULL,
NULL,
p.created_at,
p.updated_at
FROM policies p;
ALTER TABLE policies RENAME COLUMN name TO title;
ALTER TABLE policies DROP COLUMN content;
ALTER TABLE policies ADD COLUMN description TEXT NOT NULL DEFAULT '';
ALTER TABLE policies ALTER COLUMN owner_id DROP NOT NULL;
ALTER TABLE policies DROP COLUMN review_date;
ALTER TABLE policies DROP COLUMN status;
ALTER TABLE policies ADD COLUMN current_published_version INTEGER;
UPDATE policies p
SET current_published_version = 1
WHERE EXISTS (
SELECT 1 FROM policy_versions pv
WHERE pv.policy_id = p.id
AND pv.status = 'published'
);

View File

@@ -0,0 +1,22 @@
CREATE TYPE policy_status_new AS ENUM ('DRAFT', 'PUBLISHED');
ALTER TABLE policy_versions ADD COLUMN temp_status TEXT;
UPDATE policy_versions SET temp_status =
CASE WHEN status::TEXT = 'ACTIVE' THEN 'PUBLISHED'
ELSE status::TEXT
END;
ALTER TABLE policy_versions DROP COLUMN status;
ALTER TABLE policy_versions ADD COLUMN status policy_status_new;
UPDATE policy_versions SET status = temp_status::policy_status_new;
ALTER TABLE policy_versions DROP COLUMN temp_status;
DROP TYPE policy_status;
ALTER TYPE policy_status_new RENAME TO policy_status;
CREATE UNIQUE INDEX policy_one_draft_version_idx ON policy_versions (policy_id, status)
WHERE status = 'DRAFT';

View File

@@ -0,0 +1,15 @@
CREATE TYPE policy_version_signature_state AS ENUM ('REQUESTED', 'SIGNED');
CREATE TABLE policy_version_signatures (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
policy_version_id TEXT NOT NULL REFERENCES policy_versions(id) ON DELETE CASCADE,
state policy_version_signature_state NOT NULL,
signed_by TEXT NOT NULL REFERENCES peoples(id),
signed_at TIMESTAMP WITH TIME ZONE,
requested_at TIMESTAMP WITH TIME ZONE NOT NULL,
requested_by TEXT NOT NULL REFERENCES peoples(id),
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
UNIQUE (policy_version_id, signed_by)
);

View File

@@ -102,7 +102,6 @@ func (p *People) LoadByUserID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
organizationID gid.GID,
userID gid.GID,
) error {
q := `
@@ -120,14 +119,13 @@ FROM
peoples
WHERE
%s
AND organization_id = @organization_id
AND user_id = @user_id
LIMIT 1;
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{"organization_id": organizationID, "user_id": userID}
args := pgx.StrictNamedArgs{"user_id": userID}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)

View File

@@ -1,3 +1,17 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.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.
package coredata
import (
@@ -14,15 +28,13 @@ import (
type (
Policy struct {
ID gid.GID `db:"id"`
OrganizationID gid.GID `db:"organization_id"`
OwnerID gid.GID `db:"owner_id"`
Status PolicyStatus `db:"status"`
Name string `db:"name"`
Content string `db:"content"`
ReviewDate *time.Time `db:"review_date"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
ID gid.GID `db:"id"`
OrganizationID gid.GID `db:"organization_id"`
OwnerID gid.GID `db:"owner_id"`
Title string `db:"title"`
CurrentPublishedVersion *int `db:"current_published_version"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
Policies []*Policy
@@ -32,8 +44,8 @@ func (p Policy) CursorKey(orderBy PolicyOrderField) page.CursorKey {
switch orderBy {
case PolicyOrderFieldCreatedAt:
return page.NewCursorKey(p.ID, p.CreatedAt)
case PolicyOrderFieldName:
return page.NewCursorKey(p.ID, p.Name)
case PolicyOrderFieldTitle:
return page.NewCursorKey(p.ID, p.Title)
}
panic(fmt.Sprintf("unsupported order by: %s", orderBy))
@@ -50,10 +62,8 @@ SELECT
id,
organization_id,
owner_id,
name,
status,
content,
review_date,
title,
current_published_version,
created_at,
updated_at
FROM
@@ -93,13 +103,11 @@ func (p *Policies) LoadByOrganizationID(
) error {
q := `
SELECT
id,
id,
organization_id,
owner_id,
name,
status,
content,
review_date,
title,
current_published_version,
created_at,
updated_at
FROM
@@ -140,41 +148,35 @@ func (p Policy) Insert(
INSERT INTO
policies (
tenant_id,
id,
organization_id,
owner_id,
name,
status,
content,
review_date,
created_at,
updated_at
id,
organization_id,
owner_id,
title,
current_published_version,
created_at,
updated_at
)
VALUES (
@tenant_id,
@policy_id,
@organization_id,
@owner_id,
@name,
@status,
@content,
@review_date,
@title,
@current_published_version,
@created_at,
@updated_at
);
`
args := pgx.StrictNamedArgs{
"tenant_id": scope.GetTenantID(),
"policy_id": p.ID,
"organization_id": p.OrganizationID,
"owner_id": p.OwnerID,
"name": p.Name,
"status": p.Status,
"content": p.Content,
"review_date": p.ReviewDate,
"created_at": p.CreatedAt,
"updated_at": p.UpdatedAt,
"tenant_id": scope.GetTenantID(),
"policy_id": p.ID,
"organization_id": p.OrganizationID,
"owner_id": p.OwnerID,
"title": p.Title,
"current_published_version": p.CurrentPublishedVersion,
"created_at": p.CreatedAt,
"updated_at": p.UpdatedAt,
}
_, err := conn.Exec(ctx, q, args)
return err
@@ -207,10 +209,8 @@ func (p *Policy) Update(
UPDATE
policies
SET
name = @name,
status = @status,
content = @content,
review_date = @review_date,
title = @title,
current_published_version = @current_published_version,
owner_id = @owner_id,
updated_at = @updated_at
WHERE %s
@@ -219,13 +219,11 @@ WHERE %s
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{
"policy_id": p.ID,
"updated_at": time.Now(),
"name": p.Name,
"content": p.Content,
"status": p.Status,
"review_date": p.ReviewDate,
"owner_id": p.OwnerID,
"policy_id": p.ID,
"updated_at": time.Now(),
"title": p.Title,
"current_published_version": p.CurrentPublishedVersion,
"owner_id": p.OwnerID,
}
maps.Copy(args, scope.SQLArguments())
@@ -251,10 +249,8 @@ WITH plcs AS (
p.tenant_id,
p.organization_id,
p.owner_id,
p.name,
p.content,
p.status,
p.review_date,
p.title,
p.current_published_version,
p.created_at,
p.updated_at
FROM
@@ -268,10 +264,8 @@ SELECT
id,
organization_id,
owner_id,
name,
content,
status,
review_date,
title,
current_published_version,
created_at,
updated_at
FROM
@@ -314,10 +308,8 @@ WITH plcs AS (
p.tenant_id,
p.organization_id,
p.owner_id,
p.name,
p.content,
p.status,
p.review_date,
p.title,
p.current_published_version,
p.created_at,
p.updated_at
FROM
@@ -331,10 +323,8 @@ SELECT
id,
organization_id,
owner_id,
name,
content,
status,
review_date,
title,
current_published_version,
created_at,
updated_at
FROM

View File

@@ -20,7 +20,7 @@ type (
const (
PolicyOrderFieldCreatedAt PolicyOrderField = "CREATED_AT"
PolicyOrderFieldName PolicyOrderField = "NAME"
PolicyOrderFieldTitle PolicyOrderField = "TITLE"
)
func (p PolicyOrderField) Column() string {

View File

@@ -25,7 +25,7 @@ type (
const (
PolicyStatusDraft PolicyStatus = iota
PolicyStatusActive
PolicyStatusPublished
)
func (ps PolicyStatus) MarshalText() ([]byte, error) {
@@ -38,8 +38,8 @@ func (ps *PolicyStatus) UnmarshalText(data []byte) error {
switch val {
case PolicyStatusDraft.String():
*ps = PolicyStatusDraft
case PolicyStatusActive.String():
*ps = PolicyStatusActive
case PolicyStatusPublished.String():
*ps = PolicyStatusPublished
default:
return fmt.Errorf("invalid PolicyStatus value: %q", val)
}
@@ -53,8 +53,8 @@ func (ps PolicyStatus) String() string {
switch ps {
case PolicyStatusDraft:
val = "DRAFT"
case PolicyStatusActive:
val = "ACTIVE"
case PolicyStatusPublished:
val = "PUBLISHED"
}
return val

View File

@@ -0,0 +1,344 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.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.
package coredata
import (
"context"
"fmt"
"maps"
"time"
"github.com/getprobo/probo/pkg/gid"
"github.com/getprobo/probo/pkg/page"
"github.com/jackc/pgx/v5"
"go.gearno.de/kit/pg"
)
type (
PolicyVersion struct {
ID gid.GID `db:"id"`
PolicyID gid.GID `db:"policy_id"`
VersionNumber int `db:"version_number"`
Content string `db:"content"`
Changelog string `db:"changelog"`
CreatedBy gid.GID `db:"created_by"`
Status PolicyStatus `db:"status"`
PublishedBy *gid.GID `db:"published_by"`
PublishedAt *time.Time `db:"published_at"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
PolicyVersions []*PolicyVersion
)
func (p *PolicyVersions) LoadByPolicyID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
policyID gid.GID,
cursor *page.Cursor[PolicyVersionOrderField],
) error {
q := `
SELECT
id,
policy_id,
version_number,
content,
changelog,
created_by,
status,
published_by,
published_at,
created_at,
updated_at
FROM
policy_versions
WHERE
%s
AND policy_id = @policy_id
AND %s
`
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
args := pgx.StrictNamedArgs{"policy_id": policyID}
maps.Copy(args, scope.SQLArguments())
maps.Copy(args, cursor.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query policy versions: %w", err)
}
policyVersions, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[PolicyVersion])
if err != nil {
return fmt.Errorf("cannot collect policy versions: %w", err)
}
*p = policyVersions
return nil
}
func (p PolicyVersion) CursorKey(orderBy PolicyVersionOrderField) page.CursorKey {
switch orderBy {
case PolicyVersionOrderFieldCreatedAt:
return page.NewCursorKey(p.ID, p.CreatedAt)
}
panic(fmt.Sprintf("unsupported order by: %s", orderBy))
}
func (p *PolicyVersion) LoadByID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
policyVersionID gid.GID,
) error {
q := `
SELECT
id,
policy_id,
version_number,
content,
changelog,
created_by,
status,
published_by,
published_at,
created_at,
updated_at
FROM
policy_versions
WHERE
%s
AND id = @policy_version_id
LIMIT 1;
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{"policy_version_id": policyVersionID}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query policy versions: %w", err)
}
policyVersion, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[PolicyVersion])
if err != nil {
return fmt.Errorf("cannot collect policy version: %w", err)
}
*p = policyVersion
return nil
}
func (p PolicyVersion) Insert(
ctx context.Context,
conn pg.Conn,
scope Scoper,
) error {
q := `
INSERT INTO policy_versions (
tenant_id,
id,
policy_id,
version_number,
content,
changelog,
created_by,
status,
created_at,
updated_at
) VALUES (
@tenant_id,
@id,
@policy_id,
@version_number,
@content,
@changelog,
@created_by,
@status,
@created_at,
@updated_at
)
`
now := time.Now()
args := pgx.StrictNamedArgs{
"tenant_id": scope.GetTenantID(),
"id": p.ID,
"policy_id": p.PolicyID,
"version_number": p.VersionNumber,
"content": p.Content,
"changelog": p.Changelog,
"created_by": p.CreatedBy,
"status": PolicyStatusDraft,
"created_at": now,
"updated_at": now,
}
_, err := conn.Exec(ctx, q, args)
if err != nil {
return fmt.Errorf("error creating/updating policy version: %w", err)
}
return nil
}
func (p *PolicyVersion) LoadByPolicyIDAndVersionNumber(
ctx context.Context,
conn pg.Conn,
scope Scoper,
policyID gid.GID,
versionNumber int,
) error {
q := `
SELECT
id,
policy_id,
version_number,
content,
changelog,
created_by,
status,
published_by,
published_at,
created_at,
updated_at
FROM
policy_versions
WHERE
%s
AND policy_id = @policy_id
AND version_number = @version_number
LIMIT 1;
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{
"policy_id": policyID,
"version_number": versionNumber,
}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query policy versions: %w", err)
}
policyVersion, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[PolicyVersion])
if err != nil {
return fmt.Errorf("cannot collect policy version: %w", err)
}
*p = policyVersion
return nil
}
func (p *PolicyVersion) LoadLatestVersion(
ctx context.Context,
conn pg.Conn,
scope Scoper,
policyID gid.GID,
) error {
q := `
SELECT
id,
policy_id,
version_number,
content,
changelog,
created_by,
status,
published_by,
published_at,
created_at,
updated_at
FROM
policy_versions
WHERE
%s
AND policy_id = @policy_id
ORDER BY created_at DESC
LIMIT 1;
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{
"policy_id": policyID,
}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query policy versions: %w", err)
}
policyVersion, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[PolicyVersion])
if err != nil {
return fmt.Errorf("cannot collect policy version: %w", err)
}
*p = policyVersion
return nil
}
func (p PolicyVersion) Update(
ctx context.Context,
conn pg.Conn,
scope Scoper,
) error {
q := `
UPDATE policy_versions SET
changelog = @changelog,
status = @status,
content = @content,
published_by = @published_by,
published_at = @published_at,
updated_at = @updated_at
WHERE %s
AND id = @policy_version_id;`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{
"policy_version_id": p.ID,
"changelog": p.Changelog,
"status": p.Status,
"content": p.Content,
"published_by": p.PublishedBy,
"published_at": p.PublishedAt,
"updated_at": p.UpdatedAt,
}
maps.Copy(args, scope.SQLArguments())
_, err := conn.Exec(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot update policy version: %w", err)
}
return nil
}

View File

@@ -0,0 +1,41 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.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.
package coredata
type (
PolicyVersionOrderField string
)
const (
PolicyVersionOrderFieldCreatedAt PolicyVersionOrderField = "CREATED_AT"
PolicyVersionOrderFieldVersion PolicyVersionOrderField = "VERSION"
)
func (p PolicyVersionOrderField) Column() string {
return string(p)
}
func (p PolicyVersionOrderField) String() string {
return string(p)
}
func (p PolicyVersionOrderField) MarshalText() ([]byte, error) {
return []byte(p.String()), nil
}
func (p *PolicyVersionOrderField) UnmarshalText(text []byte) error {
*p = PolicyVersionOrderField(text)
return nil
}

View File

@@ -0,0 +1,197 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.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.
package coredata
import (
"context"
"fmt"
"maps"
"time"
"github.com/getprobo/probo/pkg/gid"
"github.com/getprobo/probo/pkg/page"
"github.com/jackc/pgx/v5"
"go.gearno.de/kit/pg"
)
type (
PolicyVersionSignature struct {
ID gid.GID `json:"id"`
PolicyVersionID gid.GID `json:"policy_version_id"`
State PolicyVersionSignatureState `json:"state"`
SignedBy gid.GID `json:"signed_by"`
SignedAt *time.Time `json:"signed_at"`
RequestedAt time.Time `json:"requested_at"`
RequestedBy gid.GID `json:"requested_by"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
PolicyVersionSignatures []*PolicyVersionSignature
)
func (pvs PolicyVersionSignature) CursorKey(orderBy PolicyVersionSignatureOrderField) page.CursorKey {
switch orderBy {
case PolicyVersionSignatureOrderFieldCreatedAt:
return page.NewCursorKey(pvs.ID, pvs.CreatedAt)
case PolicyVersionSignatureOrderFieldSignedAt:
return page.NewCursorKey(pvs.ID, pvs.SignedAt)
}
panic(fmt.Sprintf("unsupported order by: %s", orderBy))
}
func (pvs *PolicyVersionSignature) LoadByID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
signatureID gid.GID,
) error {
q := `
SELECT
id,
policy_version_id,
state,
signed_by,
signed_at,
requested_at,
requested_by,
created_at,
updated_at
FROM
policy_version_signatures
WHERE
id = @policy_version_signature_id
AND %s
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{"policy_version_signature_id": signatureID}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query policy version signature: %w", err)
}
policyVersionSignature, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[PolicyVersionSignature])
if err != nil {
return fmt.Errorf("cannot collect policy version signature: %w", err)
}
*pvs = policyVersionSignature
return nil
}
func (pvs PolicyVersionSignature) Insert(
ctx context.Context,
conn pg.Conn,
scope Scoper,
) error {
q := `
INSERT INTO policy_version_signatures (
id,
tenant_id,
policy_version_id,
state,
signed_by,
signed_at,
requested_at,
requested_by,
created_at,
updated_at
) VALUES (
@id,
@tenant_id,
@policy_version_id,
@state,
@signed_by,
@signed_at,
@requested_at,
@requested_by,
@created_at,
@updated_at
)
`
args := pgx.StrictNamedArgs{
"id": pvs.ID,
"tenant_id": scope.GetTenantID(),
"policy_version_id": pvs.PolicyVersionID,
"state": pvs.State,
"signed_by": pvs.SignedBy,
"signed_at": pvs.SignedAt,
"requested_at": pvs.RequestedAt,
"requested_by": pvs.RequestedBy,
"created_at": pvs.CreatedAt,
"updated_at": pvs.UpdatedAt,
}
_, err := conn.Exec(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot insert policy version signature: %w", err)
}
return nil
}
func (pvss *PolicyVersionSignatures) LoadByPolicyVersionID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
policyVersionID gid.GID,
cursor *page.Cursor[PolicyVersionSignatureOrderField],
) error {
q := `
SELECT
id,
policy_version_id,
state,
signed_by,
signed_at,
requested_at,
requested_by,
created_at,
updated_at
FROM
policy_version_signatures
WHERE
%s
AND policy_version_id = @policy_version_id
AND %s
`
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
args := pgx.StrictNamedArgs{"policy_version_id": policyVersionID}
maps.Copy(args, scope.SQLArguments())
maps.Copy(args, cursor.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query policy version signatures: %w", err)
}
policyVersionSignatures, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[PolicyVersionSignature])
if err != nil {
return fmt.Errorf("cannot collect policy version signatures: %w", err)
}
*pvss = policyVersionSignatures
return nil
}

View File

@@ -0,0 +1,41 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.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.
package coredata
type (
PolicyVersionSignatureOrderField string
)
const (
PolicyVersionSignatureOrderFieldCreatedAt PolicyVersionSignatureOrderField = "CREATED_AT"
PolicyVersionSignatureOrderFieldSignedAt PolicyVersionSignatureOrderField = "SIGNED_AT"
)
func (p PolicyVersionSignatureOrderField) Column() string {
return string(p)
}
func (p PolicyVersionSignatureOrderField) String() string {
return string(p)
}
func (p PolicyVersionSignatureOrderField) MarshalText() ([]byte, error) {
return []byte(p.String()), nil
}
func (p *PolicyVersionSignatureOrderField) UnmarshalText(text []byte) error {
*p = PolicyVersionSignatureOrderField(text)
return nil
}

View File

@@ -0,0 +1,74 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.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.
package coredata
import (
"database/sql/driver"
"fmt"
)
type (
PolicyVersionSignatureState string
)
const (
PolicyVersionSignatureStateRequested PolicyVersionSignatureState = "REQUESTED"
PolicyVersionSignatureStateSigned PolicyVersionSignatureState = "SIGNED"
)
func (pvs PolicyVersionSignatureState) MarshalText() ([]byte, error) {
return []byte(pvs.String()), nil
}
func (pvs *PolicyVersionSignatureState) UnmarshalText(data []byte) error {
val := string(data)
switch val {
case PolicyVersionSignatureStateRequested.String():
*pvs = PolicyVersionSignatureStateRequested
case PolicyVersionSignatureStateSigned.String():
*pvs = PolicyVersionSignatureStateSigned
default:
return fmt.Errorf("invalid MesureState value: %q", val)
}
return nil
}
func (pvs PolicyVersionSignatureState) String() string {
var val string
switch pvs {
case PolicyVersionSignatureStateRequested:
val = "REQUESTED"
case PolicyVersionSignatureStateSigned:
val = "SIGNED"
}
return val
}
func (pvs *PolicyVersionSignatureState) Scan(value any) error {
val, ok := value.(string)
if !ok {
return fmt.Errorf("invalid scan source for MesureState, expected string got %T", value)
}
return pvs.UnmarshalText([]byte(val))
}
func (pvs PolicyVersionSignatureState) Value() (driver.Value, error) {
return pvs.String(), nil
}