Refatcor policies management
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
@@ -31,4 +31,6 @@ const (
|
||||
EmailEntityType
|
||||
ControlEntityType
|
||||
RiskEntityType
|
||||
PolicyVersionEntityType
|
||||
PolicyVersionSignatureEntityType
|
||||
)
|
||||
|
||||
60
pkg/coredata/migrations/20250424T094500Z.sql
Normal file
60
pkg/coredata/migrations/20250424T094500Z.sql
Normal 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'
|
||||
);
|
||||
22
pkg/coredata/migrations/20250425T012700Z.sql
Normal file
22
pkg/coredata/migrations/20250425T012700Z.sql
Normal 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';
|
||||
15
pkg/coredata/migrations/20250428T024800Z.sql
Normal file
15
pkg/coredata/migrations/20250428T024800Z.sql
Normal 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)
|
||||
);
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -20,7 +20,7 @@ type (
|
||||
|
||||
const (
|
||||
PolicyOrderFieldCreatedAt PolicyOrderField = "CREATED_AT"
|
||||
PolicyOrderFieldName PolicyOrderField = "NAME"
|
||||
PolicyOrderFieldTitle PolicyOrderField = "TITLE"
|
||||
)
|
||||
|
||||
func (p PolicyOrderField) Column() string {
|
||||
|
||||
@@ -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
|
||||
|
||||
344
pkg/coredata/policy_version.go
Normal file
344
pkg/coredata/policy_version.go
Normal 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
|
||||
}
|
||||
41
pkg/coredata/policy_version_order_field.go
Normal file
41
pkg/coredata/policy_version_order_field.go
Normal 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
|
||||
}
|
||||
197
pkg/coredata/policy_version_signature.go
Normal file
197
pkg/coredata/policy_version_signature.go
Normal 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
|
||||
}
|
||||
41
pkg/coredata/policy_version_signature_order_field.go
Normal file
41
pkg/coredata/policy_version_signature_order_field.go
Normal 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
|
||||
}
|
||||
74
pkg/coredata/policy_version_signature_state.go
Normal file
74
pkg/coredata/policy_version_signature_state.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user