Move UnwrapOmittable in a dedicated helper
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
@@ -81,11 +81,8 @@ func NewMux(
|
||||
|
||||
safeRedirect := &saferedirect.SafeRedirect{AllowedHost: baseURL.Host()}
|
||||
|
||||
sessionMiddleware := connect_v1.NewSessionMiddleware(iamSvc, cookieConfig)
|
||||
apiKeyMiddleware := connect_v1.NewAPIKeyMiddleware(iamSvc)
|
||||
|
||||
r.Use(sessionMiddleware)
|
||||
r.Use(apiKeyMiddleware)
|
||||
r.Use(connect_v1.NewSessionMiddleware(iamSvc, cookieConfig))
|
||||
r.Use(connect_v1.NewAPIKeyMiddleware(iamSvc))
|
||||
|
||||
config := schema.Config{
|
||||
Resolvers: &Resolver{
|
||||
@@ -315,14 +312,6 @@ func (r *Resolver) ProboService(ctx context.Context, tenantID gid.TenantID) *pro
|
||||
return GetTenantService(ctx, r.probo, tenantID)
|
||||
}
|
||||
|
||||
func UnwrapOmittable[T any](field graphql.Omittable[T]) *T {
|
||||
if !field.IsSet() {
|
||||
return nil
|
||||
}
|
||||
value := field.Value()
|
||||
return &value
|
||||
}
|
||||
|
||||
func GetTenantService(ctx context.Context, proboSvc *probo.Service, tenantID gid.TenantID) *probo.TenantService {
|
||||
return proboSvc.WithTenant(tenantID)
|
||||
}
|
||||
|
||||
@@ -1449,7 +1449,7 @@ func (r *mutationResolver) UpdateOrganizationContext(ctx context.Context, input
|
||||
|
||||
req := probo.UpdateOrganizationContextRequest{
|
||||
OrganizationID: input.OrganizationID,
|
||||
Summary: UnwrapOmittable(input.Summary),
|
||||
Summary: gqlutils.UnwrapOmittable(input.Summary),
|
||||
}
|
||||
|
||||
organizationContext, err := prb.Organizations.UpdateContext(ctx, req)
|
||||
@@ -1637,7 +1637,7 @@ func (r *mutationResolver) UpdateTrustCenterReference(ctx context.Context, input
|
||||
req := &probo.UpdateTrustCenterReferenceRequest{
|
||||
ID: input.ID,
|
||||
Name: input.Name,
|
||||
Description: UnwrapOmittable(input.Description),
|
||||
Description: gqlutils.UnwrapOmittable(input.Description),
|
||||
WebsiteURL: input.WebsiteURL,
|
||||
Rank: input.Rank,
|
||||
}
|
||||
@@ -1804,11 +1804,11 @@ func (r *mutationResolver) UpdatePeople(ctx context.Context, input types.UpdateP
|
||||
ID: input.ID,
|
||||
FullName: input.FullName,
|
||||
PrimaryEmailAddress: input.PrimaryEmailAddress,
|
||||
AdditionalEmailAddresses: UnwrapOmittable(input.AdditionalEmailAddresses),
|
||||
AdditionalEmailAddresses: gqlutils.UnwrapOmittable(input.AdditionalEmailAddresses),
|
||||
Kind: input.Kind,
|
||||
Position: UnwrapOmittable(input.Position),
|
||||
ContractStartDate: UnwrapOmittable(input.ContractStartDate),
|
||||
ContractEndDate: UnwrapOmittable(input.ContractEndDate),
|
||||
Position: gqlutils.UnwrapOmittable(input.Position),
|
||||
ContractStartDate: gqlutils.UnwrapOmittable(input.ContractStartDate),
|
||||
ContractEndDate: gqlutils.UnwrapOmittable(input.ContractEndDate),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -1892,23 +1892,23 @@ func (r *mutationResolver) UpdateVendor(ctx context.Context, input types.UpdateV
|
||||
vendor, err := prb.Vendors.Update(ctx, probo.UpdateVendorRequest{
|
||||
ID: input.ID,
|
||||
Name: input.Name,
|
||||
Description: UnwrapOmittable(input.Description),
|
||||
StatusPageURL: UnwrapOmittable(input.StatusPageURL),
|
||||
TermsOfServiceURL: UnwrapOmittable(input.TermsOfServiceURL),
|
||||
PrivacyPolicyURL: UnwrapOmittable(input.PrivacyPolicyURL),
|
||||
ServiceLevelAgreementURL: UnwrapOmittable(input.ServiceLevelAgreementURL),
|
||||
DataProcessingAgreementURL: UnwrapOmittable(input.DataProcessingAgreementURL),
|
||||
BusinessAssociateAgreementURL: UnwrapOmittable(input.BusinessAssociateAgreementURL),
|
||||
SubprocessorsListURL: UnwrapOmittable(input.SubprocessorsListURL),
|
||||
SecurityPageURL: UnwrapOmittable(input.SecurityPageURL),
|
||||
TrustPageURL: UnwrapOmittable(input.TrustPageURL),
|
||||
HeadquarterAddress: UnwrapOmittable(input.HeadquarterAddress),
|
||||
LegalName: UnwrapOmittable(input.LegalName),
|
||||
WebsiteURL: UnwrapOmittable(input.WebsiteURL),
|
||||
Description: gqlutils.UnwrapOmittable(input.Description),
|
||||
StatusPageURL: gqlutils.UnwrapOmittable(input.StatusPageURL),
|
||||
TermsOfServiceURL: gqlutils.UnwrapOmittable(input.TermsOfServiceURL),
|
||||
PrivacyPolicyURL: gqlutils.UnwrapOmittable(input.PrivacyPolicyURL),
|
||||
ServiceLevelAgreementURL: gqlutils.UnwrapOmittable(input.ServiceLevelAgreementURL),
|
||||
DataProcessingAgreementURL: gqlutils.UnwrapOmittable(input.DataProcessingAgreementURL),
|
||||
BusinessAssociateAgreementURL: gqlutils.UnwrapOmittable(input.BusinessAssociateAgreementURL),
|
||||
SubprocessorsListURL: gqlutils.UnwrapOmittable(input.SubprocessorsListURL),
|
||||
SecurityPageURL: gqlutils.UnwrapOmittable(input.SecurityPageURL),
|
||||
TrustPageURL: gqlutils.UnwrapOmittable(input.TrustPageURL),
|
||||
HeadquarterAddress: gqlutils.UnwrapOmittable(input.HeadquarterAddress),
|
||||
LegalName: gqlutils.UnwrapOmittable(input.LegalName),
|
||||
WebsiteURL: gqlutils.UnwrapOmittable(input.WebsiteURL),
|
||||
Category: input.Category,
|
||||
Certifications: input.Certifications,
|
||||
BusinessOwnerID: UnwrapOmittable(input.BusinessOwnerID),
|
||||
SecurityOwnerID: UnwrapOmittable(input.SecurityOwnerID),
|
||||
BusinessOwnerID: gqlutils.UnwrapOmittable(input.BusinessOwnerID),
|
||||
SecurityOwnerID: gqlutils.UnwrapOmittable(input.SecurityOwnerID),
|
||||
ShowOnTrustCenter: input.ShowOnTrustCenter,
|
||||
Countries: input.Countries,
|
||||
})
|
||||
@@ -1969,10 +1969,10 @@ func (r *mutationResolver) UpdateVendorContact(ctx context.Context, input types.
|
||||
|
||||
req := probo.UpdateVendorContactRequest{
|
||||
ID: input.ID,
|
||||
FullName: UnwrapOmittable(input.FullName),
|
||||
Email: UnwrapOmittable(input.Email),
|
||||
Phone: UnwrapOmittable(input.Phone),
|
||||
Role: UnwrapOmittable(input.Role),
|
||||
FullName: gqlutils.UnwrapOmittable(input.FullName),
|
||||
Email: gqlutils.UnwrapOmittable(input.Email),
|
||||
Phone: gqlutils.UnwrapOmittable(input.Phone),
|
||||
Role: gqlutils.UnwrapOmittable(input.Role),
|
||||
}
|
||||
|
||||
vendorContact, err := prb.VendorContacts.Update(ctx, req)
|
||||
@@ -2032,7 +2032,7 @@ func (r *mutationResolver) UpdateVendorService(ctx context.Context, input types.
|
||||
req := probo.UpdateVendorServiceRequest{
|
||||
ID: input.ID,
|
||||
Name: input.Name,
|
||||
Description: UnwrapOmittable(input.Description),
|
||||
Description: gqlutils.UnwrapOmittable(input.Description),
|
||||
}
|
||||
|
||||
vendorService, err := prb.VendorServices.Update(ctx, req)
|
||||
@@ -2089,7 +2089,7 @@ func (r *mutationResolver) UpdateFramework(ctx context.Context, input types.Upda
|
||||
framework, err := prb.Frameworks.Update(ctx, probo.UpdateFrameworkRequest{
|
||||
ID: input.ID,
|
||||
Name: input.Name,
|
||||
Description: UnwrapOmittable(input.Description),
|
||||
Description: gqlutils.UnwrapOmittable(input.Description),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot update framework: %w", err)
|
||||
@@ -2219,7 +2219,7 @@ func (r *mutationResolver) UpdateControl(ctx context.Context, input types.Update
|
||||
control, err := prb.Controls.Update(ctx, probo.UpdateControlRequest{
|
||||
ID: input.ID,
|
||||
Name: input.Name,
|
||||
Description: UnwrapOmittable(input.Description),
|
||||
Description: gqlutils.UnwrapOmittable(input.Description),
|
||||
SectionTitle: input.SectionTitle,
|
||||
Status: input.Status,
|
||||
ExclusionJustification: input.ExclusionJustification,
|
||||
@@ -2286,7 +2286,7 @@ func (r *mutationResolver) UpdateMeasure(ctx context.Context, input types.Update
|
||||
measure, err := prb.Measures.Update(ctx, probo.UpdateMeasureRequest{
|
||||
ID: input.ID,
|
||||
Name: input.Name,
|
||||
Description: UnwrapOmittable(input.Description),
|
||||
Description: gqlutils.UnwrapOmittable(input.Description),
|
||||
Category: input.Category,
|
||||
State: input.State,
|
||||
})
|
||||
@@ -2586,12 +2586,12 @@ func (r *mutationResolver) UpdateTask(ctx context.Context, input types.UpdateTas
|
||||
task, err := prb.Tasks.Update(ctx, probo.UpdateTaskRequest{
|
||||
TaskID: input.TaskID,
|
||||
Name: input.Name,
|
||||
Description: UnwrapOmittable(input.Description),
|
||||
Description: gqlutils.UnwrapOmittable(input.Description),
|
||||
State: input.State,
|
||||
TimeEstimate: UnwrapOmittable(input.TimeEstimate),
|
||||
Deadline: UnwrapOmittable(input.Deadline),
|
||||
AssignedToID: UnwrapOmittable(input.AssignedToID),
|
||||
MeasureID: UnwrapOmittable(input.MeasureID),
|
||||
TimeEstimate: gqlutils.UnwrapOmittable(input.TimeEstimate),
|
||||
Deadline: gqlutils.UnwrapOmittable(input.Deadline),
|
||||
AssignedToID: gqlutils.UnwrapOmittable(input.AssignedToID),
|
||||
MeasureID: gqlutils.UnwrapOmittable(input.MeasureID),
|
||||
})
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot update task: %w", err))
|
||||
@@ -2664,10 +2664,10 @@ func (r *mutationResolver) UpdateRisk(ctx context.Context, input types.UpdateRis
|
||||
probo.UpdateRiskRequest{
|
||||
ID: input.ID,
|
||||
Name: input.Name,
|
||||
Description: UnwrapOmittable(input.Description),
|
||||
Description: gqlutils.UnwrapOmittable(input.Description),
|
||||
Category: input.Category,
|
||||
Treatment: input.Treatment,
|
||||
OwnerID: UnwrapOmittable(input.OwnerID),
|
||||
OwnerID: gqlutils.UnwrapOmittable(input.OwnerID),
|
||||
InherentLikelihood: input.InherentLikelihood,
|
||||
InherentImpact: input.InherentImpact,
|
||||
ResidualLikelihood: input.ResidualLikelihood,
|
||||
@@ -2921,8 +2921,8 @@ func (r *mutationResolver) UpdateVendorBusinessAssociateAgreement(ctx context.Co
|
||||
ctx,
|
||||
input.VendorID,
|
||||
&probo.VendorBusinessAssociateAgreementUpdateRequest{
|
||||
ValidFrom: UnwrapOmittable(input.ValidFrom),
|
||||
ValidUntil: UnwrapOmittable(input.ValidUntil),
|
||||
ValidFrom: gqlutils.UnwrapOmittable(input.ValidFrom),
|
||||
ValidUntil: gqlutils.UnwrapOmittable(input.ValidUntil),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -2985,8 +2985,8 @@ func (r *mutationResolver) UpdateVendorDataPrivacyAgreement(ctx context.Context,
|
||||
ctx,
|
||||
input.VendorID,
|
||||
&probo.VendorDataPrivacyAgreementUpdateRequest{
|
||||
ValidFrom: UnwrapOmittable(input.ValidFrom),
|
||||
ValidUntil: UnwrapOmittable(input.ValidUntil),
|
||||
ValidFrom: gqlutils.UnwrapOmittable(input.ValidFrom),
|
||||
ValidUntil: gqlutils.UnwrapOmittable(input.ValidUntil),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -3131,7 +3131,7 @@ func (r *mutationResolver) UpdateMeeting(ctx context.Context, input types.Update
|
||||
Name: input.Name,
|
||||
Date: input.Date,
|
||||
AttendeeIDs: attendeeIDs,
|
||||
Minutes: UnwrapOmittable(input.Minutes),
|
||||
Minutes: gqlutils.UnwrapOmittable(input.Minutes),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -3874,7 +3874,7 @@ func (r *mutationResolver) UpdateAudit(ctx context.Context, input types.UpdateAu
|
||||
|
||||
req := probo.UpdateAuditRequest{
|
||||
ID: input.ID,
|
||||
Name: UnwrapOmittable(input.Name),
|
||||
Name: gqlutils.UnwrapOmittable(input.Name),
|
||||
ValidFrom: input.ValidFrom,
|
||||
ValidUntil: input.ValidUntil,
|
||||
State: input.State,
|
||||
@@ -3988,15 +3988,15 @@ func (r *mutationResolver) UpdateNonconformity(ctx context.Context, input types.
|
||||
req := probo.UpdateNonconformityRequest{
|
||||
ID: input.ID,
|
||||
ReferenceID: input.ReferenceID,
|
||||
Description: UnwrapOmittable(input.Description),
|
||||
DateIdentified: UnwrapOmittable(input.DateIdentified),
|
||||
Description: gqlutils.UnwrapOmittable(input.Description),
|
||||
DateIdentified: gqlutils.UnwrapOmittable(input.DateIdentified),
|
||||
RootCause: input.RootCause,
|
||||
CorrectiveAction: UnwrapOmittable(input.CorrectiveAction),
|
||||
CorrectiveAction: gqlutils.UnwrapOmittable(input.CorrectiveAction),
|
||||
OwnerID: input.OwnerID,
|
||||
AuditID: UnwrapOmittable(input.AuditID),
|
||||
DueDate: UnwrapOmittable(input.DueDate),
|
||||
AuditID: gqlutils.UnwrapOmittable(input.AuditID),
|
||||
DueDate: gqlutils.UnwrapOmittable(input.DueDate),
|
||||
Status: input.Status,
|
||||
EffectivenessCheck: UnwrapOmittable(input.EffectivenessCheck),
|
||||
EffectivenessCheck: gqlutils.UnwrapOmittable(input.EffectivenessCheck),
|
||||
}
|
||||
|
||||
nonconformity, err := prb.Nonconformities.Update(ctx, &req)
|
||||
@@ -4063,14 +4063,14 @@ func (r *mutationResolver) UpdateObligation(ctx context.Context, input types.Upd
|
||||
|
||||
req := probo.UpdateObligationRequest{
|
||||
ID: input.ID,
|
||||
Area: UnwrapOmittable(input.Area),
|
||||
Source: UnwrapOmittable(input.Source),
|
||||
Requirement: UnwrapOmittable(input.Requirement),
|
||||
ActionsToBeImplemented: UnwrapOmittable(input.ActionsToBeImplemented),
|
||||
Regulator: UnwrapOmittable(input.Regulator),
|
||||
Area: gqlutils.UnwrapOmittable(input.Area),
|
||||
Source: gqlutils.UnwrapOmittable(input.Source),
|
||||
Requirement: gqlutils.UnwrapOmittable(input.Requirement),
|
||||
ActionsToBeImplemented: gqlutils.UnwrapOmittable(input.ActionsToBeImplemented),
|
||||
Regulator: gqlutils.UnwrapOmittable(input.Regulator),
|
||||
OwnerID: input.OwnerID,
|
||||
LastReviewDate: UnwrapOmittable(input.LastReviewDate),
|
||||
DueDate: UnwrapOmittable(input.DueDate),
|
||||
LastReviewDate: gqlutils.UnwrapOmittable(input.LastReviewDate),
|
||||
DueDate: gqlutils.UnwrapOmittable(input.DueDate),
|
||||
Status: input.Status,
|
||||
Type: input.Type,
|
||||
}
|
||||
@@ -4137,10 +4137,10 @@ func (r *mutationResolver) UpdateContinualImprovement(ctx context.Context, input
|
||||
req := probo.UpdateContinualImprovementRequest{
|
||||
ID: input.ID,
|
||||
ReferenceID: input.ReferenceID,
|
||||
Description: UnwrapOmittable(input.Description),
|
||||
Source: UnwrapOmittable(input.Source),
|
||||
Description: gqlutils.UnwrapOmittable(input.Description),
|
||||
Source: gqlutils.UnwrapOmittable(input.Source),
|
||||
OwnerID: input.OwnerID,
|
||||
TargetDate: UnwrapOmittable(input.TargetDate),
|
||||
TargetDate: gqlutils.UnwrapOmittable(input.TargetDate),
|
||||
Status: input.Status,
|
||||
Priority: input.Priority,
|
||||
}
|
||||
@@ -4208,11 +4208,11 @@ func (r *mutationResolver) UpdateRightsRequest(ctx context.Context, input types.
|
||||
ID: input.ID,
|
||||
RequestType: input.RequestType,
|
||||
RequestState: input.RequestState,
|
||||
DataSubject: UnwrapOmittable(input.DataSubject),
|
||||
Contact: UnwrapOmittable(input.Contact),
|
||||
Details: UnwrapOmittable(input.Details),
|
||||
Deadline: UnwrapOmittable(input.Deadline),
|
||||
ActionTaken: UnwrapOmittable(input.ActionTaken),
|
||||
DataSubject: gqlutils.UnwrapOmittable(input.DataSubject),
|
||||
Contact: gqlutils.UnwrapOmittable(input.Contact),
|
||||
Details: gqlutils.UnwrapOmittable(input.Details),
|
||||
Deadline: gqlutils.UnwrapOmittable(input.Deadline),
|
||||
ActionTaken: gqlutils.UnwrapOmittable(input.ActionTaken),
|
||||
}
|
||||
|
||||
rightsRequest, err := prb.RightsRequests.Update(ctx, &req)
|
||||
@@ -4289,23 +4289,23 @@ func (r *mutationResolver) UpdateProcessingActivity(ctx context.Context, input t
|
||||
req := probo.UpdateProcessingActivityRequest{
|
||||
ID: input.ID,
|
||||
Name: input.Name,
|
||||
Purpose: UnwrapOmittable(input.Purpose),
|
||||
DataSubjectCategory: UnwrapOmittable(input.DataSubjectCategory),
|
||||
PersonalDataCategory: UnwrapOmittable(input.PersonalDataCategory),
|
||||
Purpose: gqlutils.UnwrapOmittable(input.Purpose),
|
||||
DataSubjectCategory: gqlutils.UnwrapOmittable(input.DataSubjectCategory),
|
||||
PersonalDataCategory: gqlutils.UnwrapOmittable(input.PersonalDataCategory),
|
||||
SpecialOrCriminalData: input.SpecialOrCriminalData,
|
||||
LawfulBasis: input.LawfulBasis,
|
||||
Recipients: UnwrapOmittable(input.Recipients),
|
||||
Location: UnwrapOmittable(input.Location),
|
||||
Recipients: gqlutils.UnwrapOmittable(input.Recipients),
|
||||
Location: gqlutils.UnwrapOmittable(input.Location),
|
||||
InternationalTransfers: input.InternationalTransfers,
|
||||
TransferSafeguard: UnwrapOmittable(input.TransferSafeguards),
|
||||
RetentionPeriod: UnwrapOmittable(input.RetentionPeriod),
|
||||
SecurityMeasures: UnwrapOmittable(input.SecurityMeasures),
|
||||
TransferSafeguard: gqlutils.UnwrapOmittable(input.TransferSafeguards),
|
||||
RetentionPeriod: gqlutils.UnwrapOmittable(input.RetentionPeriod),
|
||||
SecurityMeasures: gqlutils.UnwrapOmittable(input.SecurityMeasures),
|
||||
DataProtectionImpactAssessmentNeeded: input.DataProtectionImpactAssessmentNeeded,
|
||||
TransferImpactAssessmentNeeded: input.TransferImpactAssessmentNeeded,
|
||||
LastReviewDate: UnwrapOmittable(input.LastReviewDate),
|
||||
NextReviewDate: UnwrapOmittable(input.NextReviewDate),
|
||||
LastReviewDate: gqlutils.UnwrapOmittable(input.LastReviewDate),
|
||||
NextReviewDate: gqlutils.UnwrapOmittable(input.NextReviewDate),
|
||||
Role: input.Role,
|
||||
DataProtectionOfficerID: UnwrapOmittable(input.DataProtectionOfficerID),
|
||||
DataProtectionOfficerID: gqlutils.UnwrapOmittable(input.DataProtectionOfficerID),
|
||||
VendorIDs: &input.VendorIds,
|
||||
}
|
||||
|
||||
@@ -4368,10 +4368,10 @@ func (r *mutationResolver) UpdateDataProtectionImpactAssessment(ctx context.Cont
|
||||
|
||||
req := probo.UpdateDataProtectionImpactAssessmentRequest{
|
||||
ID: input.ID,
|
||||
Description: UnwrapOmittable(input.Description),
|
||||
NecessityAndProportionality: UnwrapOmittable(input.NecessityAndProportionality),
|
||||
PotentialRisk: UnwrapOmittable(input.PotentialRisk),
|
||||
Mitigations: UnwrapOmittable(input.Mitigations),
|
||||
Description: gqlutils.UnwrapOmittable(input.Description),
|
||||
NecessityAndProportionality: gqlutils.UnwrapOmittable(input.NecessityAndProportionality),
|
||||
PotentialRisk: gqlutils.UnwrapOmittable(input.PotentialRisk),
|
||||
Mitigations: gqlutils.UnwrapOmittable(input.Mitigations),
|
||||
ResidualRisk: input.ResidualRisk,
|
||||
}
|
||||
|
||||
@@ -4434,11 +4434,11 @@ func (r *mutationResolver) UpdateTransferImpactAssessment(ctx context.Context, i
|
||||
|
||||
req := probo.UpdateTransferImpactAssessmentRequest{
|
||||
ID: input.ID,
|
||||
DataSubjects: UnwrapOmittable(input.DataSubjects),
|
||||
LegalMechanism: UnwrapOmittable(input.LegalMechanism),
|
||||
Transfer: UnwrapOmittable(input.Transfer),
|
||||
LocalLawRisk: UnwrapOmittable(input.LocalLawRisk),
|
||||
SupplementaryMeasures: UnwrapOmittable(input.SupplementaryMeasures),
|
||||
DataSubjects: gqlutils.UnwrapOmittable(input.DataSubjects),
|
||||
LegalMechanism: gqlutils.UnwrapOmittable(input.LegalMechanism),
|
||||
Transfer: gqlutils.UnwrapOmittable(input.Transfer),
|
||||
LocalLawRisk: gqlutils.UnwrapOmittable(input.LocalLawRisk),
|
||||
SupplementaryMeasures: gqlutils.UnwrapOmittable(input.SupplementaryMeasures),
|
||||
}
|
||||
|
||||
tia, err := prb.TransferImpactAssessments.Update(ctx, &req)
|
||||
|
||||
Reference in New Issue
Block a user