Add reject button + email on trust center access request
Signed-off-by: Émile Ré <nemile.re@gmail.com>
This commit is contained in:
@@ -132,23 +132,23 @@ func (p *Document) LoadByIDWithFilter(
|
||||
filter *DocumentFilter,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
owner_id,
|
||||
title,
|
||||
document_type,
|
||||
classification,
|
||||
current_published_version,
|
||||
trust_center_visibility,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
documents
|
||||
WHERE
|
||||
%s
|
||||
AND deleted_at IS NULL
|
||||
AND id = @document_id
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
owner_id,
|
||||
title,
|
||||
document_type,
|
||||
classification,
|
||||
current_published_version,
|
||||
trust_center_visibility,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
documents
|
||||
WHERE
|
||||
%s
|
||||
AND deleted_at IS NULL
|
||||
AND id = @document_id
|
||||
AND %s
|
||||
LIMIT 1;
|
||||
`
|
||||
@@ -178,6 +178,52 @@ LIMIT 1;
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Documents) LoadByIDs(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
documentIDs []gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
owner_id,
|
||||
title,
|
||||
document_type,
|
||||
classification,
|
||||
current_published_version,
|
||||
trust_center_visibility,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
documents
|
||||
WHERE
|
||||
%s
|
||||
AND deleted_at IS NULL
|
||||
AND id IN (SELECT id FROM UNNEST(@document_ids::text[]) AS t(id));;
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"document_ids": documentIDs}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query documents: %w", err)
|
||||
}
|
||||
|
||||
documents, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Document])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect documents: %w", err)
|
||||
}
|
||||
|
||||
*p = documents
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Documents) CountByOrganizationID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
|
||||
@@ -111,6 +111,53 @@ LIMIT 1;
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Files) LoadByIDs(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
fileIDs []gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
bucket_name,
|
||||
mime_type,
|
||||
file_name,
|
||||
file_key,
|
||||
file_size,
|
||||
created_at,
|
||||
updated_at,
|
||||
deleted_at
|
||||
FROM
|
||||
files
|
||||
WHERE
|
||||
%s
|
||||
AND id (SELECT id FROM UNNEST(@file_ids::text[]) AS t(id))
|
||||
LIMIT 1;
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"file_ids": fileIDs}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query file: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
files, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[File])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect file: %w", err)
|
||||
}
|
||||
|
||||
*f = files
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f File) Insert(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
|
||||
@@ -16,6 +16,7 @@ package coredata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"time"
|
||||
@@ -77,6 +78,10 @@ LIMIT 1;
|
||||
|
||||
report, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[Report])
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return &ErrDocumentNotFound{Identifier: reportID.String()}
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot collect report: %w", err)
|
||||
}
|
||||
|
||||
@@ -85,6 +90,50 @@ LIMIT 1;
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reports) LoadByIDs(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
reportIDs []gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
object_key,
|
||||
mime_type,
|
||||
filename,
|
||||
size,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
reports
|
||||
WHERE
|
||||
%s
|
||||
AND id IN (SELECT id FROM UNNEST(@report_ids::text[]) AS t(id))
|
||||
LIMIT 1;
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"report_ids": reportIDs}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query report: %w", err)
|
||||
}
|
||||
|
||||
reports, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Report])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect reports: %w", err)
|
||||
}
|
||||
|
||||
*r = reports
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Report) Insert(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
|
||||
@@ -688,6 +688,37 @@ WHERE
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeleteByDocumentIDs(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
trustCenterAccessID gid.GID,
|
||||
documentIDs []gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
DELETE FROM trust_center_document_accesses
|
||||
WHERE
|
||||
%s
|
||||
AND trust_center_access_id = @trust_center_access_id
|
||||
AND document_id = ANY(@document_ids)
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"trust_center_access_id": trustCenterAccessID,
|
||||
"document_ids": documentIDs,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot delete trust center document accesses by document IDs: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ActivateByReportIDs(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
@@ -722,6 +753,37 @@ WHERE
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeleteByReportIDs(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
trustCenterAccessID gid.GID,
|
||||
reportIDs []gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
DELETE FROM trust_center_document_accesses
|
||||
WHERE
|
||||
%s
|
||||
AND trust_center_access_id = @trust_center_access_id
|
||||
AND report_id = ANY(@report_ids)
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"trust_center_access_id": trustCenterAccessID,
|
||||
"report_ids": reportIDs,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot delete trust center document accesses by report IDs: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tcdas TrustCenterDocumentAccesses) BulkInsertDocumentAccesses(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
@@ -914,6 +976,37 @@ WHERE
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeleteByTrustCenterFileIDs(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
trustCenterAccessID gid.GID,
|
||||
trustCenterFileIDs []gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
DELETE FROM trust_center_document_accesses
|
||||
WHERE
|
||||
%s
|
||||
AND trust_center_access_id = @trust_center_access_id
|
||||
AND trust_center_file_id = ANY(@trust_center_file_ids)
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"trust_center_access_id": trustCenterAccessID,
|
||||
"trust_center_file_ids": trustCenterFileIDs,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot delete trust center document accesses by trust center file IDs: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tcdas TrustCenterDocumentAccesses) BulkInsertTrustCenterFileAccesses(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
|
||||
@@ -338,14 +338,8 @@ func (s TrustCenterAccessService) Update(
|
||||
return fmt.Errorf("cannot update trust center access: %w", err)
|
||||
}
|
||||
|
||||
if err := s.upsertDocumentAccesses(ctx, tx, access.ID, access.OrganizationID, req.DocumentIDs, req.ReportIDs, req.TrustCenterFileIDs, now); err != nil {
|
||||
return fmt.Errorf("cannot upsert document accesses: %w", err)
|
||||
}
|
||||
|
||||
if req.ReportIDs != nil {
|
||||
if err := coredata.ActivateByReportIDs(ctx, tx, s.svc.scope, access.ID, req.ReportIDs, now); err != nil {
|
||||
return fmt.Errorf("cannot activate report accesses: %w", err)
|
||||
}
|
||||
if err := s.upsertDocumentAccesses(ctx, tx, access.ID, access.OrganizationID, req.DocumentIDs, req.ReportIDs, req.TrustCenterFileIDs, now); err != nil {
|
||||
return fmt.Errorf("cannot upsert document accesses: %w", err)
|
||||
}
|
||||
|
||||
if shouldSendEmail {
|
||||
|
||||
@@ -23,11 +23,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.gearno.de/kit/httpserver"
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/slack"
|
||||
"go.probo.inc/probo/pkg/trust"
|
||||
"go.gearno.de/kit/httpserver"
|
||||
"go.gearno.de/kit/log"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -151,7 +151,7 @@ func slackHandler(trustSvc *trust.Service, slackSigningSecret string, logger *lo
|
||||
var fileIDs []gid.GID
|
||||
|
||||
switch action.ActionID {
|
||||
case "accept_all":
|
||||
case "accept_all", "reject_all":
|
||||
currentMessageId, err := gid.ParseGID(action.Value)
|
||||
if err != nil {
|
||||
httpserver.RenderJSON(w, http.StatusBadRequest, SlackInteractiveResponse{Success: false, Message: "invalid message ID"})
|
||||
@@ -164,7 +164,6 @@ func slackHandler(trustSvc *trust.Service, slackSigningSecret string, logger *lo
|
||||
httpserver.RenderJSON(w, http.StatusInternalServerError, SlackInteractiveResponse{Success: false, Message: "internal server error"})
|
||||
return
|
||||
}
|
||||
|
||||
case "accept_document":
|
||||
docID, err := gid.ParseGID(action.Value)
|
||||
if err != nil {
|
||||
@@ -194,17 +193,34 @@ func slackHandler(trustSvc *trust.Service, slackSigningSecret string, logger *lo
|
||||
return
|
||||
}
|
||||
|
||||
if err := tenantSvc.TrustCenterAccesses.AcceptByIDs(
|
||||
ctx,
|
||||
initialSlackMessage.OrganizationID,
|
||||
requesterEmail,
|
||||
documentIDs,
|
||||
reportIDs,
|
||||
fileIDs,
|
||||
); err != nil {
|
||||
logger.ErrorCtx(ctx, "cannot grant access", log.Error(err))
|
||||
httpserver.RenderJSON(w, http.StatusInternalServerError, SlackInteractiveResponse{Success: false, Message: "internal server error"})
|
||||
return
|
||||
if strings.HasPrefix(action.ActionID, "accept_") {
|
||||
if err := tenantSvc.TrustCenterAccesses.AcceptByIDs(
|
||||
ctx,
|
||||
initialSlackMessage.OrganizationID,
|
||||
requesterEmail,
|
||||
documentIDs,
|
||||
reportIDs,
|
||||
fileIDs,
|
||||
); err != nil {
|
||||
logger.ErrorCtx(ctx, "cannot grant access", log.Error(err))
|
||||
httpserver.RenderJSON(w, http.StatusInternalServerError, SlackInteractiveResponse{Success: false, Message: "internal server error"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(action.ActionID, "reject_") {
|
||||
if err := tenantSvc.TrustCenterAccesses.RejectByIDs(
|
||||
ctx,
|
||||
initialSlackMessage.OrganizationID,
|
||||
requesterEmail,
|
||||
documentIDs,
|
||||
reportIDs,
|
||||
fileIDs,
|
||||
); err != nil {
|
||||
logger.ErrorCtx(ctx, "cannot reject access", log.Error(err))
|
||||
httpserver.RenderJSON(w, http.StatusInternalServerError, SlackInteractiveResponse{Success: false, Message: "internal server error"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := tenantSvc.SlackMessages.UpdateSlackAccessMessage(
|
||||
|
||||
@@ -549,6 +549,127 @@ func (s *TrustCenterAccessService) sendTrustCenterAccessEmail(
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *TrustCenterAccessService) RejectByIDs(
|
||||
ctx context.Context,
|
||||
organizationID gid.GID,
|
||||
email string,
|
||||
documentIDs []gid.GID,
|
||||
reportIDs []gid.GID,
|
||||
fileIDs []gid.GID,
|
||||
) error {
|
||||
return s.svc.pg.WithTx(ctx, func(tx pg.Conn) error {
|
||||
trustCenter := &coredata.TrustCenter{}
|
||||
if err := trustCenter.LoadByOrganizationID(ctx, tx, s.svc.scope, organizationID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center: %w", err)
|
||||
}
|
||||
|
||||
access := &coredata.TrustCenterAccess{}
|
||||
if err := access.LoadByTrustCenterIDAndEmail(ctx, tx, s.svc.scope, trustCenter.ID, email); err != nil {
|
||||
return fmt.Errorf("cannot load trust center access: %w", err)
|
||||
}
|
||||
|
||||
shouldSendEmail := false
|
||||
|
||||
if len(documentIDs) > 0 {
|
||||
shouldSendEmail = true
|
||||
if err := coredata.DeleteByDocumentIDs(ctx, tx, s.svc.scope, access.ID, documentIDs); err != nil {
|
||||
return fmt.Errorf("cannot delete document accesses: %w", err)
|
||||
}
|
||||
}
|
||||
if len(reportIDs) > 0 {
|
||||
shouldSendEmail = true
|
||||
if err := coredata.DeleteByReportIDs(ctx, tx, s.svc.scope, access.ID, reportIDs); err != nil {
|
||||
return fmt.Errorf("cannot delete report accesses: %w", err)
|
||||
}
|
||||
}
|
||||
if len(fileIDs) > 0 {
|
||||
shouldSendEmail = true
|
||||
if err := coredata.DeleteByTrustCenterFileIDs(ctx, tx, s.svc.scope, access.ID, fileIDs); err != nil {
|
||||
return fmt.Errorf("cannot delete trust center file accesses: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if shouldSendEmail {
|
||||
if err := s.sendDocumentAccessRejectedEmail(ctx, tx, access, documentIDs, reportIDs, fileIDs); err != nil {
|
||||
return fmt.Errorf("cannot send access email: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *TrustCenterAccessService) sendDocumentAccessRejectedEmail(
|
||||
ctx context.Context,
|
||||
tx pg.Conn,
|
||||
access *coredata.TrustCenterAccess,
|
||||
documentIDs []gid.GID,
|
||||
reportIDs []gid.GID,
|
||||
fileIDs []gid.GID,
|
||||
) error {
|
||||
trustCenter := &coredata.TrustCenter{}
|
||||
if err := trustCenter.LoadByID(ctx, tx, s.svc.scope, access.TrustCenterID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center: %w", err)
|
||||
}
|
||||
|
||||
organization := &coredata.Organization{}
|
||||
if err := organization.LoadByID(ctx, tx, s.svc.scope, trustCenter.OrganizationID); err != nil {
|
||||
return fmt.Errorf("cannot load organization: %w", err)
|
||||
}
|
||||
|
||||
var fileNames []string
|
||||
var documents coredata.Documents
|
||||
if len(documentIDs) > 0 {
|
||||
if err := documents.LoadByIDs(ctx, tx, s.svc.scope, documentIDs); err != nil {
|
||||
return fmt.Errorf("cannot load documents by IDs: %w", err)
|
||||
}
|
||||
for _, d := range documents {
|
||||
fileNames = append(fileNames, d.Title)
|
||||
}
|
||||
}
|
||||
var reports coredata.Reports
|
||||
if len(reportIDs) > 0 {
|
||||
if err := reports.LoadByIDs(ctx, tx, s.svc.scope, reportIDs); err != nil {
|
||||
return fmt.Errorf("cannot load reports by IDs: %w", err)
|
||||
}
|
||||
for _, r := range reports {
|
||||
fileNames = append(fileNames, r.Filename)
|
||||
}
|
||||
}
|
||||
var files coredata.Files
|
||||
if len(fileIDs) > 0 {
|
||||
if err := files.LoadByIDs(ctx, tx, s.svc.scope, fileIDs); err != nil {
|
||||
return fmt.Errorf("cannot load files by IDs: %w", err)
|
||||
}
|
||||
for _, f := range files {
|
||||
fileNames = append(fileNames, f.FileName)
|
||||
}
|
||||
}
|
||||
|
||||
subject, textBody, htmlBody, err := emails.RenderTrustCenterDocumentAccessRejected(
|
||||
s.svc.baseURL,
|
||||
access.Name,
|
||||
organization.Name,
|
||||
fileNames,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot render trust center documents access rejected email: %w", err)
|
||||
}
|
||||
|
||||
accessEmail := coredata.NewEmail(
|
||||
access.Name,
|
||||
access.Email,
|
||||
subject,
|
||||
textBody,
|
||||
htmlBody,
|
||||
)
|
||||
|
||||
if err := accessEmail.Insert(ctx, tx); err != nil {
|
||||
return fmt.Errorf("cannot insert access email: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractExistingIDs(accesses coredata.TrustCenterDocumentAccesses) ([]gid.GID, []gid.GID, []gid.GID) {
|
||||
var documentIDs []gid.GID
|
||||
var reportIDs []gid.GID
|
||||
|
||||
Reference in New Issue
Block a user