Hide accepted invitations

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2025-10-23 11:59:48 +02:00
parent d40fa943ce
commit 00bba05faf
19 changed files with 255 additions and 112 deletions

View File

@@ -378,13 +378,14 @@ func (s *TenantAuthzService) GetInvitationsByOrganizationID(
ctx context.Context,
orgID gid.GID,
cursor *page.Cursor[coredata.InvitationOrderField],
filter *coredata.InvitationFilter,
) (*page.Page[*coredata.Invitation, coredata.InvitationOrderField], error) {
var invitations coredata.Invitations
err := s.pg.WithConn(
ctx,
func(conn pg.Conn) error {
if err := invitations.LoadByOrganizationID(ctx, conn, s.scope, orgID, cursor); err != nil {
if err := invitations.LoadByOrganizationID(ctx, conn, s.scope, orgID, cursor, filter); err != nil {
return fmt.Errorf("failed to load organization invitations: %w", err)
}
@@ -401,6 +402,7 @@ func (s *TenantAuthzService) GetInvitationsByOrganizationID(
func (s *TenantAuthzService) CountOrganizationInvitations(
ctx context.Context,
orgID gid.GID,
filter *coredata.InvitationFilter,
) (int, error) {
var count int
err := s.pg.WithConn(
@@ -408,7 +410,7 @@ func (s *TenantAuthzService) CountOrganizationInvitations(
func(conn pg.Conn) error {
var invitations coredata.Invitations
var err error
count, err = invitations.CountByOrganizationID(ctx, conn, s.scope, orgID)
count, err = invitations.CountByOrganizationID(ctx, conn, s.scope, orgID, filter)
return err
},
)

View File

@@ -297,6 +297,7 @@ func (i *Invitations) LoadByOrganizationID(
scope Scoper,
orgID gid.GID,
cursor *page.Cursor[InvitationOrderField],
filter *InvitationFilter,
) error {
query := `
SELECT
@@ -319,14 +320,16 @@ WHERE
organization_id = @organization_id
AND %s
AND %s
AND %s
`
query = fmt.Sprintf(query, scope.SQLFragment(), cursor.SQLFragment())
query = fmt.Sprintf(query, scope.SQLFragment(), filter.SQLFragment(), cursor.SQLFragment())
args := pgx.StrictNamedArgs{
"organization_id": orgID,
}
maps.Copy(args, scope.SQLArguments())
maps.Copy(args, filter.SQLArguments())
maps.Copy(args, cursor.SQLArguments())
rows, err := conn.Query(ctx, query, args)
@@ -348,6 +351,7 @@ func (i *Invitations) CountByOrganizationID(
conn pg.Conn,
scope Scoper,
orgID gid.GID,
filter *InvitationFilter,
) (int, error) {
q := `
SELECT
@@ -355,15 +359,16 @@ SELECT
FROM
authz_invitations
WHERE
organization_id = @organization_id AND %s
organization_id = @organization_id AND %s AND %s
`
q = fmt.Sprintf(q, scope.SQLFragment())
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment())
args := pgx.StrictNamedArgs{
"organization_id": orgID,
}
maps.Copy(args, scope.SQLArguments())
maps.Copy(args, filter.SQLArguments())
row := conn.QueryRow(ctx, q, args)

View File

@@ -20,19 +20,19 @@ import (
type (
InvitationFilter struct {
status *InvitationStatus
statuses InvitationStatuses
}
)
func NewInvitationFilter(status *InvitationStatus) *InvitationFilter {
func NewInvitationFilter(statuses []InvitationStatus) *InvitationFilter {
return &InvitationFilter{
status: status,
statuses: InvitationStatuses(statuses),
}
}
func (f *InvitationFilter) SQLArguments() pgx.NamedArgs {
return pgx.NamedArgs{
"status": f.status,
"statuses": f.statuses,
}
}
@@ -40,12 +40,12 @@ func (f *InvitationFilter) SQLFragment() string {
return `
(
CASE
WHEN @status::text IS NOT NULL THEN
WHEN @statuses::text[] IS NOT NULL THEN
(CASE
WHEN accepted_at IS NOT NULL THEN 'ACCEPTED'
WHEN expires_at < NOW() THEN 'EXPIRED'
ELSE 'PENDING'
END) = @status::text
END) = ANY(@statuses::text[])
ELSE TRUE
END
)`

View File

@@ -17,9 +17,13 @@ package coredata
import (
"database/sql/driver"
"fmt"
"strings"
)
type InvitationStatus string
type (
InvitationStatus string
InvitationStatuses []InvitationStatus
)
const (
InvitationStatusPending InvitationStatus = "PENDING"
@@ -58,3 +62,20 @@ func (tcv *InvitationStatus) Scan(value any) error {
func (tcv InvitationStatus) Value() (driver.Value, error) {
return tcv.String(), nil
}
func (statuses InvitationStatuses) Value() (driver.Value, error) {
if len(statuses) == 0 {
return nil, nil
}
var result strings.Builder
result.WriteString("{")
for i, status := range statuses {
if i > 0 {
result.WriteString(",")
}
result.WriteString(fmt.Sprintf("%q", status.String()))
}
result.WriteString("}")
return result.String(), nil
}

View File

@@ -1508,7 +1508,7 @@ input InvitationOrder {
}
input InvitationFilter {
status: InvitationStatus
statuses: [InvitationStatus!]
}
input DocumentVersionFilter {
@@ -3797,7 +3797,8 @@ input UpdateProcessingActivityInput {
recipients: String @goField(omittable: true)
location: String @goField(omittable: true)
internationalTransfers: Boolean
transferSafeguards: ProcessingActivityTransferSafeguards @goField(omittable: true)
transferSafeguards: ProcessingActivityTransferSafeguards
@goField(omittable: true)
retentionPeriod: String @goField(omittable: true)
securityMeasures: String @goField(omittable: true)
dataProtectionImpactAssessment: ProcessingActivityDataProtectionImpactAssessment

View File

@@ -10575,7 +10575,7 @@ input InvitationOrder {
}
input InvitationFilter {
status: InvitationStatus
statuses: [InvitationStatus!]
}
input DocumentVersionFilter {
@@ -12864,7 +12864,8 @@ input UpdateProcessingActivityInput {
recipients: String @goField(omittable: true)
location: String @goField(omittable: true)
internationalTransfers: Boolean
transferSafeguards: ProcessingActivityTransferSafeguards @goField(omittable: true)
transferSafeguards: ProcessingActivityTransferSafeguards
@goField(omittable: true)
retentionPeriod: String @goField(omittable: true)
securityMeasures: String @goField(omittable: true)
dataProtectionImpactAssessment: ProcessingActivityDataProtectionImpactAssessment
@@ -72331,20 +72332,20 @@ func (ec *executionContext) unmarshalInputInvitationFilter(ctx context.Context,
asMap[k] = v
}
fieldsInOrder := [...]string{"status"}
fieldsInOrder := [...]string{"statuses"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
continue
}
switch k {
case "status":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("status"))
data, err := ec.unmarshalOInvitationStatus2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐInvitationStatus(ctx, v)
case "statuses":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("statuses"))
data, err := ec.unmarshalOInvitationStatus2ᚕgithubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐInvitationStatusᚄ(ctx, v)
if err != nil {
return it, err
}
it.Status = data
it.Statuses = data
}
}
@@ -101735,32 +101736,78 @@ func (ec *executionContext) unmarshalOInvitationOrder2ᚖgithubᚗcomᚋgetprobo
return &res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) unmarshalOInvitationStatus2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐInvitationStatus(ctx context.Context, v any) (*coredata.InvitationStatus, error) {
func (ec *executionContext) unmarshalOInvitationStatus2ᚕgithubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐInvitationStatusᚄ(ctx context.Context, v any) ([]coredata.InvitationStatus, error) {
if v == nil {
return nil, nil
}
tmp, err := graphql.UnmarshalString(v)
res := unmarshalOInvitationStatus2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐInvitationStatus[tmp]
return &res, graphql.ErrorOnPath(ctx, err)
var vSlice []any
vSlice = graphql.CoerceList(v)
var err error
res := make([]coredata.InvitationStatus, len(vSlice))
for i := range vSlice {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i))
res[i], err = ec.unmarshalNInvitationStatus2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐInvitationStatus(ctx, vSlice[i])
if err != nil {
return nil, err
}
}
return res, nil
}
func (ec *executionContext) marshalOInvitationStatus2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐInvitationStatus(ctx context.Context, sel ast.SelectionSet, v *coredata.InvitationStatus) graphql.Marshaler {
func (ec *executionContext) marshalOInvitationStatus2ᚕgithubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐInvitationStatusᚄ(ctx context.Context, sel ast.SelectionSet, v []coredata.InvitationStatus) graphql.Marshaler {
if v == nil {
return graphql.Null
}
_ = sel
_ = ctx
res := graphql.MarshalString(marshalOInvitationStatus2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐInvitationStatus[*v])
return res
ret := make(graphql.Array, len(v))
var wg sync.WaitGroup
isLen1 := len(v) == 1
if !isLen1 {
wg.Add(len(v))
}
for i := range v {
i := i
fc := &graphql.FieldContext{
Index: &i,
Result: &v[i],
}
ctx := graphql.WithFieldContext(ctx, fc)
f := func(i int) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = nil
}
}()
if !isLen1 {
defer wg.Done()
}
ret[i] = ec.marshalNInvitationStatus2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐInvitationStatus(ctx, sel, v[i])
}
if isLen1 {
f(i)
} else {
go f(i)
}
}
wg.Wait()
for _, e := range ret {
if e == graphql.Null {
return graphql.Null
}
}
return ret
}
var (
unmarshalOInvitationStatus2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐInvitationStatus = map[string]coredata.InvitationStatus{
unmarshalOInvitationStatus2ᚕgithubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐInvitationStatusᚄ = map[string]coredata.InvitationStatus{
"PENDING": coredata.InvitationStatusPending,
"ACCEPTED": coredata.InvitationStatusAccepted,
"EXPIRED": coredata.InvitationStatusExpired,
}
marshalOInvitationStatus2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐInvitationStatus = map[coredata.InvitationStatus]string{
marshalOInvitationStatus2ᚕgithubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐInvitationStatusᚄ = map[coredata.InvitationStatus]string{
coredata.InvitationStatusPending: "PENDING",
coredata.InvitationStatusAccepted: "ACCEPTED",
coredata.InvitationStatusExpired: "EXPIRED",

View File

@@ -1235,7 +1235,7 @@ type InvitationEdge struct {
}
type InvitationFilter struct {
Status *coredata.InvitationStatus `json:"status,omitempty"`
Statuses []coredata.InvitationStatus `json:"statuses,omitempty"`
}
type InvitationOrder struct {

View File

@@ -911,8 +911,13 @@ func (r *invitationResolver) Organization(ctx context.Context, obj *types.Invita
func (r *invitationConnectionResolver) TotalCount(ctx context.Context, obj *types.InvitationConnection) (int, error) {
switch obj.Resolver.(type) {
case *organizationResolver:
invitationFilter := coredata.NewInvitationFilter(nil)
if obj.Filter != nil {
invitationFilter = coredata.NewInvitationFilter(obj.Filter.Statuses)
}
authzSvc := r.AuthzService(ctx, obj.ParentID.TenantID())
count, err := authzSvc.CountOrganizationInvitations(ctx, obj.ParentID)
count, err := authzSvc.CountOrganizationInvitations(ctx, obj.ParentID, invitationFilter)
if err != nil {
panic(fmt.Errorf("failed to count organization invitations: %w", err))
}
@@ -925,7 +930,7 @@ func (r *invitationConnectionResolver) TotalCount(ctx context.Context, obj *type
invitationFilter := coredata.NewInvitationFilter(nil)
if obj.Filter != nil {
invitationFilter = coredata.NewInvitationFilter(obj.Filter.Status)
invitationFilter = coredata.NewInvitationFilter(obj.Filter.Statuses)
}
count, err := r.authzSvc.CountUserInvitations(ctx, user.EmailAddress, invitationFilter)
@@ -3647,8 +3652,13 @@ func (r *organizationResolver) Invitations(ctx context.Context, obj *types.Organ
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
invitationFilter := coredata.NewInvitationFilter(nil)
if filter != nil {
invitationFilter = coredata.NewInvitationFilter(filter.Statuses)
}
authzSvc := r.AuthzService(ctx, obj.ID.TenantID())
page, err := authzSvc.GetInvitationsByOrganizationID(ctx, obj.ID, cursor)
page, err := authzSvc.GetInvitationsByOrganizationID(ctx, obj.ID, cursor, invitationFilter)
if err != nil {
panic(fmt.Errorf("cannot list invitations: %w", err))
}
@@ -5363,7 +5373,7 @@ func (r *viewerResolver) Invitations(ctx context.Context, obj *types.Viewer, fir
invitationFilter := coredata.NewInvitationFilter(nil)
if filter != nil {
invitationFilter = coredata.NewInvitationFilter(filter.Status)
invitationFilter = coredata.NewInvitationFilter(filter.Statuses)
}
invitations, err := r.authzSvc.GetUserInvitations(ctx, user.EmailAddress, cursor, invitationFilter)