diff --git a/apps/console/src/pages/PeopleOverviewPage.tsx b/apps/console/src/pages/PeopleOverviewPage.tsx
index db9cb98fb..a77a4ccc2 100644
--- a/apps/console/src/pages/PeopleOverviewPage.tsx
+++ b/apps/console/src/pages/PeopleOverviewPage.tsx
@@ -1,17 +1,24 @@
"use client";
import { Card } from "@/components/ui/card";
+import { Label } from "@/components/ui/label";
+import { Input } from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
+import { useToast } from "@/hooks/use-toast";
+import { HelpCircle } from "lucide-react";
import {
graphql,
PreloadedQuery,
usePreloadedQuery,
useQueryLoader,
+ useMutation,
} from "react-relay";
-import { Suspense, useEffect } from "react";
+import { Suspense, useEffect, useState, useCallback } from "react";
import type { PeopleOverviewPageQuery as PeopleOverviewPageQueryType } from "./__generated__/PeopleOverviewPageQuery.graphql";
import { useParams } from "react-router";
import { Helmet } from "react-helmet-async";
import { useBreadcrumb } from "@/contexts/BreadcrumbContext";
+import { cn } from "@/lib/utils";
const peopleOverviewPageQuery = graphql`
query PeopleOverviewPageQuery($peopleId: ID!) {
@@ -20,13 +27,61 @@ const peopleOverviewPageQuery = graphql`
id
fullName
primaryEmailAddress
+ additionalEmailAddresses
+ kind
createdAt
updatedAt
+ version
}
}
}
`;
+const updatePeopleMutation = graphql`
+ mutation PeopleOverviewPageUpdatePeopleMutation($input: UpdatePeopleInput!) {
+ updatePeople(input: $input) {
+ id
+ fullName
+ primaryEmailAddress
+ additionalEmailAddresses
+ kind
+ updatedAt
+ version
+ }
+ }
+`;
+
+function EditableField({
+ label,
+ value,
+ onChange,
+ type = "text",
+ helpText,
+}: {
+ label: string;
+ value: string;
+ onChange: (value: string) => void;
+ type?: string;
+ helpText?: string;
+}) {
+ return (
+
+
+
+
+
+
+
onChange(e.target.value)}
+ />
+ {helpText &&
{helpText}
}
+
+
+ );
+}
+
function PeopleOverviewPageContent({
queryRef,
}: {
@@ -34,80 +89,198 @@ function PeopleOverviewPageContent({
}) {
const data = usePreloadedQuery(peopleOverviewPageQuery, queryRef);
const { setBreadcrumbSegment } = useBreadcrumb();
+ const [editedFields, setEditedFields] = useState>(new Set());
+ const [formData, setFormData] = useState({
+ fullName: data.node.fullName || '',
+ primaryEmailAddress: data.node.primaryEmailAddress || '',
+ additionalEmailAddresses: data.node.additionalEmailAddresses || [],
+ kind: data.node.kind,
+ });
+ const [commit] = useMutation(updatePeopleMutation);
+ const [_, loadQuery] = useQueryLoader(peopleOverviewPageQuery);
+ const { toast } = useToast();
+
+ const hasChanges = editedFields.size > 0;
+
+ const handleSave = useCallback(() => {
+ commit({
+ variables: {
+ input: {
+ id: data.node.id,
+ expectedVersion: data.node.version,
+ ...formData,
+ },
+ },
+ onCompleted: () => {
+ toast({
+ title: "Success",
+ description: "Changes saved successfully",
+ variant: "default",
+ });
+ setEditedFields(new Set());
+ },
+ onError: (error) => {
+ if (error.message?.includes('concurrent modification')) {
+ toast({
+ title: "Error",
+ description: "Someone else modified this person. Reloading latest data.",
+ variant: "destructive",
+ });
+ loadQuery({ peopleId: data.node.id! });
+ } else {
+ toast({
+ title: "Error",
+ description: error.message || "Failed to save changes",
+ variant: "destructive",
+ });
+ }
+ },
+ });
+ }, [commit, data.node.id, data.node.version, formData, loadQuery, toast]);
+
+ const handleFieldChange = (field: keyof typeof formData, value: any) => {
+ setFormData(prev => ({
+ ...prev,
+ [field]: value,
+ }));
+ setEditedFields(prev => new Set(prev).add(field));
+ };
+
+ const handleCancel = () => {
+ setFormData({
+ fullName: data.node.fullName || '',
+ primaryEmailAddress: data.node.primaryEmailAddress || '',
+ additionalEmailAddresses: data.node.additionalEmailAddresses || [],
+ kind: data.node.kind,
+ });
+ setEditedFields(new Set());
+ };
useEffect(() => {
- if (data.node?.primaryEmailAddress) {
- setBreadcrumbSegment("peoples/:id", data.node.primaryEmailAddress);
+ if (data.node?.fullName) {
+ setBreadcrumbSegment("peoples/:id", data.node.fullName);
}
- }, [data.node?.primaryEmailAddress, setBreadcrumbSegment]);
+ }, [data.node?.fullName, setBreadcrumbSegment]);
return (
-
-
-
-
- {data.node?.fullName}
-
-
View and manage person details
+ <>
+
+
+
handleFieldChange('fullName', value)}
+ />
+
+ handleFieldChange('primaryEmailAddress', value)}
+ />
+
+
+
+
+
+
+
+ {formData.additionalEmailAddresses.map((email, index) => (
+
+ {
+ const newEmails = [...formData.additionalEmailAddresses];
+ newEmails[index] = e.target.value;
+ handleFieldChange('additionalEmailAddresses', newEmails);
+ }}
+ />
+
+
+ ))}
+
+
+
+
+
+
+
+
Additional Information
+
+ Additional details about the person
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
Personal Information
-
- Basic information about the person
-
-
-
-
-
-
-
{data.node?.fullName}
-
-
-
-
{data.node?.primaryEmailAddress}
-
-
-
-
-
-
-
-
-
System Information
-
- System-related information about the person
-
-
-
-
-
-
-
- {new Date(data.node?.createdAt).toLocaleString()}
-
-
-
-
-
- {new Date(data.node?.updatedAt).toLocaleString()}
-
-
-
-
-
-
+
+ {hasChanges && (
+
+
+
+
+ )}
+ >
);
}
diff --git a/apps/console/src/pages/__generated__/PeopleOverviewPageQuery.graphql.ts b/apps/console/src/pages/__generated__/PeopleOverviewPageQuery.graphql.ts
index 500a79255..28a2200e9 100644
--- a/apps/console/src/pages/__generated__/PeopleOverviewPageQuery.graphql.ts
+++ b/apps/console/src/pages/__generated__/PeopleOverviewPageQuery.graphql.ts
@@ -1,5 +1,5 @@
/**
- * @generated SignedSource<
>
+ * @generated SignedSource<<528ad6b0a104964c9d947ef1e9c41b46>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -9,16 +9,20 @@
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
+export type PeopleKind = "CONTRACTOR" | "EMPLOYEE";
export type PeopleOverviewPageQuery$variables = {
peopleId: string;
};
export type PeopleOverviewPageQuery$data = {
readonly node: {
+ readonly additionalEmailAddresses?: ReadonlyArray;
readonly createdAt?: any;
readonly fullName?: string;
readonly id?: string;
+ readonly kind?: PeopleKind;
readonly primaryEmailAddress?: string;
readonly updatedAt?: any;
+ readonly version?: number;
};
};
export type PeopleOverviewPageQuery = {
@@ -66,15 +70,36 @@ v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
- "name": "createdAt",
+ "name": "additionalEmailAddresses",
"storageKey": null
},
v6 = {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "kind",
+ "storageKey": null
+},
+v7 = {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "createdAt",
+ "storageKey": null
+},
+v8 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
+},
+v9 = {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "version",
+ "storageKey": null
};
return {
"fragment": {
@@ -98,7 +123,10 @@ return {
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
- (v6/*: any*/)
+ (v6/*: any*/),
+ (v7/*: any*/),
+ (v8/*: any*/),
+ (v9/*: any*/)
],
"type": "People",
"abstractKey": null
@@ -138,7 +166,10 @@ return {
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
- (v6/*: any*/)
+ (v6/*: any*/),
+ (v7/*: any*/),
+ (v8/*: any*/),
+ (v9/*: any*/)
],
"type": "People",
"abstractKey": null
@@ -149,16 +180,16 @@ return {
]
},
"params": {
- "cacheID": "0cd0dd96cb7a84c2016bfe1f088f8245",
+ "cacheID": "1383ca6e8c8b205082cc68223bec1833",
"id": null,
"metadata": {},
"name": "PeopleOverviewPageQuery",
"operationKind": "query",
- "text": "query PeopleOverviewPageQuery(\n $peopleId: ID!\n) {\n node(id: $peopleId) {\n __typename\n ... on People {\n id\n fullName\n primaryEmailAddress\n createdAt\n updatedAt\n }\n id\n }\n}\n"
+ "text": "query PeopleOverviewPageQuery(\n $peopleId: ID!\n) {\n node(id: $peopleId) {\n __typename\n ... on People {\n id\n fullName\n primaryEmailAddress\n additionalEmailAddresses\n kind\n createdAt\n updatedAt\n version\n }\n id\n }\n}\n"
}
};
})();
-(node as any).hash = "16b3f0915f69d8475d745b0c7f13b029";
+(node as any).hash = "fa229186ad9e51f3a60b23fa88a882aa";
export default node;
diff --git a/apps/console/src/pages/__generated__/PeopleOverviewPageUpdatePeopleMutation.graphql.ts b/apps/console/src/pages/__generated__/PeopleOverviewPageUpdatePeopleMutation.graphql.ts
new file mode 100644
index 000000000..f4ad1427f
--- /dev/null
+++ b/apps/console/src/pages/__generated__/PeopleOverviewPageUpdatePeopleMutation.graphql.ts
@@ -0,0 +1,146 @@
+/**
+ * @generated SignedSource<>
+ * @lightSyntaxTransform
+ * @nogrep
+ */
+
+/* tslint:disable */
+/* eslint-disable */
+// @ts-nocheck
+
+import { ConcreteRequest } from 'relay-runtime';
+export type PeopleKind = "CONTRACTOR" | "EMPLOYEE";
+export type UpdatePeopleInput = {
+ additionalEmailAddresses?: ReadonlyArray | null | undefined;
+ expectedVersion: number;
+ fullName?: string | null | undefined;
+ id: string;
+ kind?: PeopleKind | null | undefined;
+ primaryEmailAddress?: string | null | undefined;
+};
+export type PeopleOverviewPageUpdatePeopleMutation$variables = {
+ input: UpdatePeopleInput;
+};
+export type PeopleOverviewPageUpdatePeopleMutation$data = {
+ readonly updatePeople: {
+ readonly additionalEmailAddresses: ReadonlyArray;
+ readonly fullName: string;
+ readonly id: string;
+ readonly kind: PeopleKind;
+ readonly primaryEmailAddress: string;
+ readonly updatedAt: any;
+ readonly version: number;
+ };
+};
+export type PeopleOverviewPageUpdatePeopleMutation = {
+ response: PeopleOverviewPageUpdatePeopleMutation$data;
+ variables: PeopleOverviewPageUpdatePeopleMutation$variables;
+};
+
+const node: ConcreteRequest = (function(){
+var v0 = [
+ {
+ "defaultValue": null,
+ "kind": "LocalArgument",
+ "name": "input"
+ }
+],
+v1 = [
+ {
+ "alias": null,
+ "args": [
+ {
+ "kind": "Variable",
+ "name": "input",
+ "variableName": "input"
+ }
+ ],
+ "concreteType": "People",
+ "kind": "LinkedField",
+ "name": "updatePeople",
+ "plural": false,
+ "selections": [
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "id",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "fullName",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "primaryEmailAddress",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "additionalEmailAddresses",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "kind",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "updatedAt",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "version",
+ "storageKey": null
+ }
+ ],
+ "storageKey": null
+ }
+];
+return {
+ "fragment": {
+ "argumentDefinitions": (v0/*: any*/),
+ "kind": "Fragment",
+ "metadata": null,
+ "name": "PeopleOverviewPageUpdatePeopleMutation",
+ "selections": (v1/*: any*/),
+ "type": "Mutation",
+ "abstractKey": null
+ },
+ "kind": "Request",
+ "operation": {
+ "argumentDefinitions": (v0/*: any*/),
+ "kind": "Operation",
+ "name": "PeopleOverviewPageUpdatePeopleMutation",
+ "selections": (v1/*: any*/)
+ },
+ "params": {
+ "cacheID": "85b62bd3c79a01fc437649a9c8a217b3",
+ "id": null,
+ "metadata": {},
+ "name": "PeopleOverviewPageUpdatePeopleMutation",
+ "operationKind": "mutation",
+ "text": "mutation PeopleOverviewPageUpdatePeopleMutation(\n $input: UpdatePeopleInput!\n) {\n updatePeople(input: $input) {\n id\n fullName\n primaryEmailAddress\n additionalEmailAddresses\n kind\n updatedAt\n version\n }\n}\n"
+ }
+};
+})();
+
+(node as any).hash = "3fba4023eb9fa1520ed88bb9b4501dc7";
+
+export default node;
diff --git a/pkg/api/console/v1/schema.graphql b/pkg/api/console/v1/schema.graphql
index fb42949c6..99ca27a9d 100644
--- a/pkg/api/console/v1/schema.graphql
+++ b/pkg/api/console/v1/schema.graphql
@@ -100,6 +100,7 @@ type People implements Node {
kind: PeopleKind!
createdAt: Datetime!
updatedAt: Datetime!
+ version: Int!
}
type VendorConnection {
@@ -316,8 +317,9 @@ type Mutation {
createVendor(input: CreateVendorInput!): Vendor!
updateVendor(input: UpdateVendorInput!): Vendor!
deleteVendor(input: DeleteVendorInput!): Void!
- deletePeople(input: DeletePeopleInput!): Void!
createPeople(input: CreatePeopleInput!): People!
+ updatePeople(input: UpdatePeopleInput!): People!
+ deletePeople(input: DeletePeopleInput!): Void!
}
input CreateVendorInput {
@@ -341,6 +343,15 @@ input CreatePeopleInput {
kind: PeopleKind!
}
+input UpdatePeopleInput {
+ id: ID!
+ expectedVersion: Int!
+ fullName: String
+ primaryEmailAddress: String
+ additionalEmailAddresses: [String!]
+ kind: PeopleKind
+}
+
enum ServiceCriticality @goModel(model: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticality") {
LOW @goEnum(value: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticalityLow")
MEDIUM @goEnum(value: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticalityMedium")
diff --git a/pkg/api/console/v1/schema/schema.go b/pkg/api/console/v1/schema/schema.go
index 9be4c9079..f508c3820 100644
--- a/pkg/api/console/v1/schema/schema.go
+++ b/pkg/api/console/v1/schema/schema.go
@@ -160,6 +160,7 @@ type ComplexityRoot struct {
CreateVendor func(childComplexity int, input types.CreateVendorInput) int
DeletePeople func(childComplexity int, input types.DeletePeopleInput) int
DeleteVendor func(childComplexity int, input types.DeleteVendorInput) int
+ UpdatePeople func(childComplexity int, input types.UpdatePeopleInput) int
UpdateVendor func(childComplexity int, input types.UpdateVendorInput) int
}
@@ -189,6 +190,7 @@ type ComplexityRoot struct {
Kind func(childComplexity int) int
PrimaryEmailAddress func(childComplexity int) int
UpdatedAt func(childComplexity int) int
+ Version func(childComplexity int) int
}
PeopleConnection struct {
@@ -286,8 +288,9 @@ type MutationResolver interface {
CreateVendor(ctx context.Context, input types.CreateVendorInput) (*types.Vendor, error)
UpdateVendor(ctx context.Context, input types.UpdateVendorInput) (*types.Vendor, error)
DeleteVendor(ctx context.Context, input types.DeleteVendorInput) (string, error)
- DeletePeople(ctx context.Context, input types.DeletePeopleInput) (string, error)
CreatePeople(ctx context.Context, input types.CreatePeopleInput) (*types.People, error)
+ UpdatePeople(ctx context.Context, input types.UpdatePeopleInput) (*types.People, error)
+ DeletePeople(ctx context.Context, input types.DeletePeopleInput) (string, error)
}
type OrganizationResolver interface {
Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.FrameworkConnection, error)
@@ -774,6 +777,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.DeleteVendor(childComplexity, args["input"].(types.DeleteVendorInput)), true
+ case "Mutation.updatePeople":
+ if e.complexity.Mutation.UpdatePeople == nil {
+ break
+ }
+
+ args, err := ec.field_Mutation_updatePeople_args(context.TODO(), rawArgs)
+ if err != nil {
+ return 0, false
+ }
+
+ return e.complexity.Mutation.UpdatePeople(childComplexity, args["input"].(types.UpdatePeopleInput)), true
+
case "Mutation.updateVendor":
if e.complexity.Mutation.UpdateVendor == nil {
break
@@ -934,6 +949,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.People.UpdatedAt(childComplexity), true
+ case "People.version":
+ if e.complexity.People.Version == nil {
+ break
+ }
+
+ return e.complexity.People.Version(childComplexity), true
+
case "PeopleConnection.edges":
if e.complexity.PeopleConnection.Edges == nil {
break
@@ -1269,6 +1291,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
ec.unmarshalInputCreateVendorInput,
ec.unmarshalInputDeletePeopleInput,
ec.unmarshalInputDeleteVendorInput,
+ ec.unmarshalInputUpdatePeopleInput,
ec.unmarshalInputUpdateVendorInput,
)
first := true
@@ -1469,6 +1492,7 @@ type People implements Node {
kind: PeopleKind!
createdAt: Datetime!
updatedAt: Datetime!
+ version: Int!
}
type VendorConnection {
@@ -1685,8 +1709,9 @@ type Mutation {
createVendor(input: CreateVendorInput!): Vendor!
updateVendor(input: UpdateVendorInput!): Vendor!
deleteVendor(input: DeleteVendorInput!): Void!
- deletePeople(input: DeletePeopleInput!): Void!
createPeople(input: CreatePeopleInput!): People!
+ updatePeople(input: UpdatePeopleInput!): People!
+ deletePeople(input: DeletePeopleInput!): Void!
}
input CreateVendorInput {
@@ -1710,6 +1735,15 @@ input CreatePeopleInput {
kind: PeopleKind!
}
+input UpdatePeopleInput {
+ id: ID!
+ expectedVersion: Int!
+ fullName: String
+ primaryEmailAddress: String
+ additionalEmailAddresses: [String!]
+ kind: PeopleKind
+}
+
enum ServiceCriticality @goModel(model: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticality") {
LOW @goEnum(value: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticalityLow")
MEDIUM @goEnum(value: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticalityMedium")
@@ -2143,6 +2177,29 @@ func (ec *executionContext) field_Mutation_deleteVendor_argsInput(
return zeroVal, nil
}
+func (ec *executionContext) field_Mutation_updatePeople_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
+ var err error
+ args := map[string]any{}
+ arg0, err := ec.field_Mutation_updatePeople_argsInput(ctx, rawArgs)
+ if err != nil {
+ return nil, err
+ }
+ args["input"] = arg0
+ return args, nil
+}
+func (ec *executionContext) field_Mutation_updatePeople_argsInput(
+ ctx context.Context,
+ rawArgs map[string]any,
+) (types.UpdatePeopleInput, error) {
+ ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input"))
+ if tmp, ok := rawArgs["input"]; ok {
+ return ec.unmarshalNUpdatePeopleInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐUpdatePeopleInput(ctx, tmp)
+ }
+
+ var zeroVal types.UpdatePeopleInput
+ return zeroVal, nil
+}
+
func (ec *executionContext) field_Mutation_updateVendor_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
@@ -5118,49 +5175,6 @@ func (ec *executionContext) fieldContext_Mutation_deleteVendor(ctx context.Conte
return fc, nil
}
-func (ec *executionContext) _Mutation_deletePeople(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
- fc, err := ec.fieldContext_Mutation_deletePeople(ctx, field)
- if err != nil {
- return graphql.Null
- }
- ctx = graphql.WithFieldContext(ctx, fc)
- resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
- ctx = rctx // use context from middleware stack in children
- return ec.resolvers.Mutation().DeletePeople(rctx, fc.Args["input"].(types.DeletePeopleInput))
- })
- if err != nil {
- ec.Error(ctx, err)
- return graphql.Null
- }
- if resTmp == nil {
- if !graphql.HasFieldError(ctx, fc) {
- ec.Errorf(ctx, "must not be null")
- }
- return graphql.Null
- }
- res := resTmp.(string)
- fc.Result = res
- return ec.marshalNVoid2string(ctx, field.Selections, res)
-}
-
-func (ec *executionContext) fieldContext_Mutation_deletePeople(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
- fc = &graphql.FieldContext{
- Object: "Mutation",
- Field: field,
- IsMethod: true,
- IsResolver: true,
- Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
- return nil, errors.New("field of type Void does not have child fields")
- },
- }
- ctx = graphql.WithFieldContext(ctx, fc)
- if fc.Args, err = ec.field_Mutation_deletePeople_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
- ec.Error(ctx, err)
- return fc, err
- }
- return fc, nil
-}
-
func (ec *executionContext) _Mutation_createPeople(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Mutation_createPeople(ctx, field)
if err != nil {
@@ -5208,6 +5222,8 @@ func (ec *executionContext) fieldContext_Mutation_createPeople(ctx context.Conte
return ec.fieldContext_People_createdAt(ctx, field)
case "updatedAt":
return ec.fieldContext_People_updatedAt(ctx, field)
+ case "version":
+ return ec.fieldContext_People_version(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type People", field.Name)
},
@@ -5220,6 +5236,110 @@ func (ec *executionContext) fieldContext_Mutation_createPeople(ctx context.Conte
return fc, nil
}
+func (ec *executionContext) _Mutation_updatePeople(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_Mutation_updatePeople(ctx, field)
+ if err != nil {
+ return graphql.Null
+ }
+ ctx = graphql.WithFieldContext(ctx, fc)
+ resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
+ ctx = rctx // use context from middleware stack in children
+ return ec.resolvers.Mutation().UpdatePeople(rctx, fc.Args["input"].(types.UpdatePeopleInput))
+ })
+ if err != nil {
+ ec.Error(ctx, err)
+ return graphql.Null
+ }
+ if resTmp == nil {
+ if !graphql.HasFieldError(ctx, fc) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+ res := resTmp.(*types.People)
+ fc.Result = res
+ return ec.marshalNPeople2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐPeople(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_Mutation_updatePeople(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "Mutation",
+ Field: field,
+ IsMethod: true,
+ IsResolver: true,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ switch field.Name {
+ case "id":
+ return ec.fieldContext_People_id(ctx, field)
+ case "fullName":
+ return ec.fieldContext_People_fullName(ctx, field)
+ case "primaryEmailAddress":
+ return ec.fieldContext_People_primaryEmailAddress(ctx, field)
+ case "additionalEmailAddresses":
+ return ec.fieldContext_People_additionalEmailAddresses(ctx, field)
+ case "kind":
+ return ec.fieldContext_People_kind(ctx, field)
+ case "createdAt":
+ return ec.fieldContext_People_createdAt(ctx, field)
+ case "updatedAt":
+ return ec.fieldContext_People_updatedAt(ctx, field)
+ case "version":
+ return ec.fieldContext_People_version(ctx, field)
+ }
+ return nil, fmt.Errorf("no field named %q was found under type People", field.Name)
+ },
+ }
+ ctx = graphql.WithFieldContext(ctx, fc)
+ if fc.Args, err = ec.field_Mutation_updatePeople_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
+ ec.Error(ctx, err)
+ return fc, err
+ }
+ return fc, nil
+}
+
+func (ec *executionContext) _Mutation_deletePeople(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_Mutation_deletePeople(ctx, field)
+ if err != nil {
+ return graphql.Null
+ }
+ ctx = graphql.WithFieldContext(ctx, fc)
+ resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
+ ctx = rctx // use context from middleware stack in children
+ return ec.resolvers.Mutation().DeletePeople(rctx, fc.Args["input"].(types.DeletePeopleInput))
+ })
+ if err != nil {
+ ec.Error(ctx, err)
+ return graphql.Null
+ }
+ if resTmp == nil {
+ if !graphql.HasFieldError(ctx, fc) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+ res := resTmp.(string)
+ fc.Result = res
+ return ec.marshalNVoid2string(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_Mutation_deletePeople(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "Mutation",
+ Field: field,
+ IsMethod: true,
+ IsResolver: true,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ return nil, errors.New("field of type Void does not have child fields")
+ },
+ }
+ ctx = graphql.WithFieldContext(ctx, fc)
+ if fc.Args, err = ec.field_Mutation_deletePeople_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
+ ec.Error(ctx, err)
+ return fc, err
+ }
+ return fc, nil
+}
+
func (ec *executionContext) _Organization_id(ctx context.Context, field graphql.CollectedField, obj *types.Organization) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Organization_id(ctx, field)
if err != nil {
@@ -5969,6 +6089,44 @@ func (ec *executionContext) fieldContext_People_updatedAt(_ context.Context, fie
return fc, nil
}
+func (ec *executionContext) _People_version(ctx context.Context, field graphql.CollectedField, obj *types.People) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_People_version(ctx, field)
+ if err != nil {
+ return graphql.Null
+ }
+ ctx = graphql.WithFieldContext(ctx, fc)
+ resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
+ ctx = rctx // use context from middleware stack in children
+ return obj.Version, nil
+ })
+ if err != nil {
+ ec.Error(ctx, err)
+ return graphql.Null
+ }
+ if resTmp == nil {
+ if !graphql.HasFieldError(ctx, fc) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+ res := resTmp.(int)
+ fc.Result = res
+ return ec.marshalNInt2int(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_People_version(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "People",
+ Field: field,
+ IsMethod: false,
+ IsResolver: false,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ return nil, errors.New("field of type Int does not have child fields")
+ },
+ }
+ return fc, nil
+}
+
func (ec *executionContext) _PeopleConnection_edges(ctx context.Context, field graphql.CollectedField, obj *types.PeopleConnection) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_PeopleConnection_edges(ctx, field)
if err != nil {
@@ -6146,6 +6304,8 @@ func (ec *executionContext) fieldContext_PeopleEdge_node(_ context.Context, fiel
return ec.fieldContext_People_createdAt(ctx, field)
case "updatedAt":
return ec.fieldContext_People_updatedAt(ctx, field)
+ case "version":
+ return ec.fieldContext_People_version(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type People", field.Name)
},
@@ -9595,6 +9755,68 @@ func (ec *executionContext) unmarshalInputDeleteVendorInput(ctx context.Context,
return it, nil
}
+func (ec *executionContext) unmarshalInputUpdatePeopleInput(ctx context.Context, obj any) (types.UpdatePeopleInput, error) {
+ var it types.UpdatePeopleInput
+ asMap := map[string]any{}
+ for k, v := range obj.(map[string]any) {
+ asMap[k] = v
+ }
+
+ fieldsInOrder := [...]string{"id", "expectedVersion", "fullName", "primaryEmailAddress", "additionalEmailAddresses", "kind"}
+ for _, k := range fieldsInOrder {
+ v, ok := asMap[k]
+ if !ok {
+ continue
+ }
+ switch k {
+ case "id":
+ ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id"))
+ data, err := ec.unmarshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, v)
+ if err != nil {
+ return it, err
+ }
+ it.ID = data
+ case "expectedVersion":
+ ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("expectedVersion"))
+ data, err := ec.unmarshalNInt2int(ctx, v)
+ if err != nil {
+ return it, err
+ }
+ it.ExpectedVersion = data
+ case "fullName":
+ ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("fullName"))
+ data, err := ec.unmarshalOString2ᚖstring(ctx, v)
+ if err != nil {
+ return it, err
+ }
+ it.FullName = data
+ case "primaryEmailAddress":
+ ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("primaryEmailAddress"))
+ data, err := ec.unmarshalOString2ᚖstring(ctx, v)
+ if err != nil {
+ return it, err
+ }
+ it.PrimaryEmailAddress = data
+ case "additionalEmailAddresses":
+ ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("additionalEmailAddresses"))
+ data, err := ec.unmarshalOString2ᚕstringᚄ(ctx, v)
+ if err != nil {
+ return it, err
+ }
+ it.AdditionalEmailAddresses = data
+ case "kind":
+ ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("kind"))
+ data, err := ec.unmarshalOPeopleKind2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐPeopleKind(ctx, v)
+ if err != nil {
+ return it, err
+ }
+ it.Kind = data
+ }
+ }
+
+ return it, nil
+}
+
func (ec *executionContext) unmarshalInputUpdateVendorInput(ctx context.Context, obj any) (types.UpdateVendorInput, error) {
var it types.UpdateVendorInput
asMap := map[string]any{}
@@ -10675,16 +10897,23 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null {
out.Invalids++
}
- case "deletePeople":
+ case "createPeople":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
- return ec._Mutation_deletePeople(ctx, field)
+ return ec._Mutation_createPeople(ctx, field)
})
if out.Values[i] == graphql.Null {
out.Invalids++
}
- case "createPeople":
+ case "updatePeople":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
- return ec._Mutation_createPeople(ctx, field)
+ return ec._Mutation_updatePeople(ctx, field)
+ })
+ if out.Values[i] == graphql.Null {
+ out.Invalids++
+ }
+ case "deletePeople":
+ out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
+ return ec._Mutation_deletePeople(ctx, field)
})
if out.Values[i] == graphql.Null {
out.Invalids++
@@ -10958,6 +11187,11 @@ func (ec *executionContext) _People(ctx context.Context, sel ast.SelectionSet, o
if out.Values[i] == graphql.Null {
out.Invalids++
}
+ case "version":
+ out.Values[i] = ec._People_version(ctx, field, obj)
+ if out.Values[i] == graphql.Null {
+ out.Invalids++
+ }
default:
panic("unknown field " + strconv.Quote(field.Name))
}
@@ -12911,6 +13145,11 @@ func (ec *executionContext) marshalNTaskStateTransitionEdge2ᚖgithubᚗcomᚋge
return ec._TaskStateTransitionEdge(ctx, sel, v)
}
+func (ec *executionContext) unmarshalNUpdatePeopleInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐUpdatePeopleInput(ctx context.Context, v any) (types.UpdatePeopleInput, error) {
+ res, err := ec.unmarshalInputUpdatePeopleInput(ctx, v)
+ return res, graphql.ErrorOnPath(ctx, err)
+}
+
func (ec *executionContext) unmarshalNUpdateVendorInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐUpdateVendorInput(ctx context.Context, v any) (types.UpdateVendorInput, error) {
res, err := ec.unmarshalInputUpdateVendorInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
@@ -13372,6 +13611,34 @@ func (ec *executionContext) marshalOInt2ᚖint(ctx context.Context, sel ast.Sele
return res
}
+func (ec *executionContext) unmarshalOPeopleKind2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐPeopleKind(ctx context.Context, v any) (*coredata.PeopleKind, error) {
+ if v == nil {
+ return nil, nil
+ }
+ tmp, err := graphql.UnmarshalString(v)
+ res := unmarshalOPeopleKind2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐPeopleKind[tmp]
+ return &res, graphql.ErrorOnPath(ctx, err)
+}
+
+func (ec *executionContext) marshalOPeopleKind2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐPeopleKind(ctx context.Context, sel ast.SelectionSet, v *coredata.PeopleKind) graphql.Marshaler {
+ if v == nil {
+ return graphql.Null
+ }
+ res := graphql.MarshalString(marshalOPeopleKind2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐPeopleKind[*v])
+ return res
+}
+
+var (
+ unmarshalOPeopleKind2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐPeopleKind = map[string]coredata.PeopleKind{
+ "EMPLOYEE": coredata.PeopleKindEmployee,
+ "CONTRACTOR": coredata.PeopleKindContractor,
+ }
+ marshalOPeopleKind2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐPeopleKind = map[coredata.PeopleKind]string{
+ coredata.PeopleKindEmployee: "EMPLOYEE",
+ coredata.PeopleKindContractor: "CONTRACTOR",
+ }
+)
+
func (ec *executionContext) unmarshalORiskTier2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐRiskTier(ctx context.Context, v any) (*coredata.RiskTier, error) {
if v == nil {
return nil, nil
diff --git a/pkg/api/console/v1/types/people.go b/pkg/api/console/v1/types/people.go
index 94a0623fa..7da75591a 100644
--- a/pkg/api/console/v1/types/people.go
+++ b/pkg/api/console/v1/types/people.go
@@ -48,5 +48,6 @@ func NewPeople(p *coredata.People) *People {
Kind: p.Kind,
CreatedAt: p.CreatedAt,
UpdatedAt: p.UpdatedAt,
+ Version: p.Version,
}
}
diff --git a/pkg/api/console/v1/types/types.go b/pkg/api/console/v1/types/types.go
index 7b056c9d6..e4455d848 100644
--- a/pkg/api/console/v1/types/types.go
+++ b/pkg/api/console/v1/types/types.go
@@ -177,6 +177,7 @@ type People struct {
Kind coredata.PeopleKind `json:"kind"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
+ Version int `json:"version"`
}
func (People) IsNode() {}
@@ -238,6 +239,15 @@ type TaskStateTransitionEdge struct {
Node *TaskStateTransition `json:"node"`
}
+type UpdatePeopleInput struct {
+ ID gid.GID `json:"id"`
+ ExpectedVersion int `json:"expectedVersion"`
+ FullName *string `json:"fullName,omitempty"`
+ PrimaryEmailAddress *string `json:"primaryEmailAddress,omitempty"`
+ AdditionalEmailAddresses []string `json:"additionalEmailAddresses,omitempty"`
+ Kind *coredata.PeopleKind `json:"kind,omitempty"`
+}
+
type UpdateVendorInput struct {
ID gid.GID `json:"id"`
ExpectedVersion int `json:"expectedVersion"`
diff --git a/pkg/api/console/v1/v1_resolver.go b/pkg/api/console/v1/v1_resolver.go
index 9f3472822..a6b12c733 100644
--- a/pkg/api/console/v1/v1_resolver.go
+++ b/pkg/api/console/v1/v1_resolver.go
@@ -110,16 +110,6 @@ func (r *mutationResolver) DeleteVendor(ctx context.Context, input types.DeleteV
return "", nil
}
-// DeletePeople is the resolver for the deletePeople field.
-func (r *mutationResolver) DeletePeople(ctx context.Context, input types.DeletePeopleInput) (string, error) {
- err := r.svc.DeletePeople(ctx, input.PeopleID)
- if err != nil {
- return "", fmt.Errorf("cannot delete people: %w", err)
- }
-
- return "", nil
-}
-
// CreatePeople is the resolver for the createPeople field.
func (r *mutationResolver) CreatePeople(ctx context.Context, input types.CreatePeopleInput) (*types.People, error) {
people, err := r.svc.CreatePeople(ctx, probo.CreatePeopleRequest{
@@ -137,6 +127,33 @@ func (r *mutationResolver) CreatePeople(ctx context.Context, input types.CreateP
return types.NewPeople(people), nil
}
+// UpdatePeople is the resolver for the updatePeople field.
+func (r *mutationResolver) UpdatePeople(ctx context.Context, input types.UpdatePeopleInput) (*types.People, error) {
+ people, err := r.svc.UpdatePeople(ctx, probo.UpdatePeopleRequest{
+ ID: input.ID,
+ ExpectedVersion: input.ExpectedVersion,
+ FullName: input.FullName,
+ PrimaryEmailAddress: input.PrimaryEmailAddress,
+ AdditionalEmailAddresses: &input.AdditionalEmailAddresses,
+ Kind: input.Kind,
+ })
+ if err != nil {
+ return nil, fmt.Errorf("cannot update people: %w", err)
+ }
+
+ return types.NewPeople(people), nil
+}
+
+// DeletePeople is the resolver for the deletePeople field.
+func (r *mutationResolver) DeletePeople(ctx context.Context, input types.DeletePeopleInput) (string, error) {
+ err := r.svc.DeletePeople(ctx, input.PeopleID)
+ if err != nil {
+ return "", fmt.Errorf("cannot delete people: %w", err)
+ }
+
+ return "", nil
+}
+
// Frameworks is the resolver for the frameworks field.
func (r *organizationResolver) Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.FrameworkConnection, error) {
cursor := types.NewCursor(first, after, last, before)
diff --git a/pkg/probo/coredata/migrations/20250218T171000Z.sql b/pkg/probo/coredata/migrations/20250218T171000Z.sql
new file mode 100644
index 000000000..236195d03
--- /dev/null
+++ b/pkg/probo/coredata/migrations/20250218T171000Z.sql
@@ -0,0 +1 @@
+ALTER TABLE peoples ADD COLUMN version INTEGER NOT NULL DEFAULT 1;
\ No newline at end of file
diff --git a/pkg/probo/coredata/people.go b/pkg/probo/coredata/people.go
index 7301fa646..291627738 100644
--- a/pkg/probo/coredata/people.go
+++ b/pkg/probo/coredata/people.go
@@ -16,6 +16,7 @@ package coredata
import (
"context"
+ "errors"
"fmt"
"maps"
"time"
@@ -36,9 +37,18 @@ type (
AdditionalEmailAddresses []string
CreatedAt time.Time
UpdatedAt time.Time
+ Version int
}
Peoples []*People
+
+ UpdatePeopleParams struct {
+ ExpectedVersion int
+ FullName *string
+ PrimaryEmailAddress *string
+ AdditionalEmailAddresses *[]string
+ Kind *PeopleKind
+ }
)
func (p People) CursorKey() page.CursorKey {
@@ -55,6 +65,7 @@ func (p *People) scan(r pgx.Row) error {
&p.AdditionalEmailAddresses,
&p.CreatedAt,
&p.UpdatedAt,
+ &p.Version,
)
}
@@ -73,7 +84,8 @@ SELECT
primary_email_address,
additional_email_addresses,
created_at,
- updated_at
+ updated_at,
+ version
FROM
peoples
WHERE
@@ -113,7 +125,8 @@ INSERT INTO
primary_email_address,
additional_email_addresses,
created_at,
- updated_at
+ updated_at,
+ version
)
VALUES (
@people_id,
@@ -123,7 +136,8 @@ VALUES (
@primary_email_address,
@additional_email_addresses,
@created_at,
- @updated_at
+ @updated_at,
+ @version
)
`
@@ -136,6 +150,7 @@ VALUES (
"additional_email_addresses": p.AdditionalEmailAddresses,
"created_at": p.CreatedAt,
"updated_at": p.UpdatedAt,
+ "version": p.Version,
}
_, err := conn.Exec(ctx, q, args)
return err
@@ -175,7 +190,8 @@ SELECT
primary_email_address,
additional_email_addresses,
created_at,
- updated_at
+ updated_at,
+ version
FROM
peoples
WHERE
@@ -214,3 +230,68 @@ WHERE
return nil
}
+
+func (p *People) Update(
+ ctx context.Context,
+ conn pg.Conn,
+ scope *Scope,
+ params UpdatePeopleParams,
+) error {
+ q := `
+UPDATE peoples SET
+ full_name = COALESCE(@full_name, full_name),
+ primary_email_address = COALESCE(@primary_email_address, primary_email_address),
+ additional_email_addresses = COALESCE(@additional_email_addresses, additional_email_addresses),
+ kind = COALESCE(@kind, kind),
+ updated_at = @updated_at,
+ version = version + 1
+WHERE %s
+ AND id = @people_id
+ AND version = @expected_version
+RETURNING
+ id,
+ organization_id,
+ kind,
+ full_name,
+ primary_email_address,
+ additional_email_addresses,
+ created_at,
+ updated_at,
+ version
+`
+ q = fmt.Sprintf(q, scope.SQLFragment())
+
+ args := pgx.NamedArgs{
+ "people_id": p.ID,
+ "expected_version": params.ExpectedVersion,
+ "updated_at": time.Now(),
+ }
+
+ if params.FullName != nil {
+ args["full_name"] = *params.FullName
+ }
+ if params.PrimaryEmailAddress != nil {
+ args["primary_email_address"] = *params.PrimaryEmailAddress
+ }
+ if params.AdditionalEmailAddresses != nil {
+ args["additional_email_addresses"] = *params.AdditionalEmailAddresses
+ }
+ if params.Kind != nil {
+ args["kind"] = *params.Kind
+ }
+
+ maps.Copy(args, scope.SQLArguments())
+
+ r := conn.QueryRow(ctx, q, args)
+
+ p2 := People{}
+ if err := p2.scan(r); err != nil {
+ if errors.Is(err, pgx.ErrNoRows) {
+ return ErrConcurrentModification
+ }
+ return err
+ }
+
+ *p = p2
+ return nil
+}
diff --git a/pkg/probo/update_people.go b/pkg/probo/update_people.go
new file mode 100644
index 000000000..7009f1c9e
--- /dev/null
+++ b/pkg/probo/update_people.go
@@ -0,0 +1,44 @@
+package probo
+
+import (
+ "context"
+
+ "github.com/getprobo/probo/pkg/gid"
+ "github.com/getprobo/probo/pkg/probo/coredata"
+ "go.gearno.de/kit/pg"
+)
+
+type UpdatePeopleRequest struct {
+ ID gid.GID
+ ExpectedVersion int
+ Kind *coredata.PeopleKind
+ FullName *string
+ PrimaryEmailAddress *string
+ AdditionalEmailAddresses *[]string
+}
+
+func (s Service) UpdatePeople(
+ ctx context.Context,
+ req UpdatePeopleRequest,
+) (*coredata.People, error) {
+ params := coredata.UpdatePeopleParams{
+ ExpectedVersion: req.ExpectedVersion,
+ Kind: req.Kind,
+ FullName: req.FullName,
+ PrimaryEmailAddress: req.PrimaryEmailAddress,
+ AdditionalEmailAddresses: req.AdditionalEmailAddresses,
+ }
+
+ people := &coredata.People{ID: req.ID}
+
+ err := s.pg.WithTx(
+ ctx,
+ func(conn pg.Conn) error {
+ return people.Update(ctx, conn, s.scope, params)
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return people, nil
+}