Add update people

Signed-off-by: gearnode <bryan@frimin.fr>
This commit is contained in:
gearnode
2025-02-18 17:26:46 +01:00
parent 4e6f711b47
commit db5cefffe0
11 changed files with 921 additions and 139 deletions

View File

@@ -1,17 +1,24 @@
"use client"; "use client";
import { Card } from "@/components/ui/card"; 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 { import {
graphql, graphql,
PreloadedQuery, PreloadedQuery,
usePreloadedQuery, usePreloadedQuery,
useQueryLoader, useQueryLoader,
useMutation,
} from "react-relay"; } 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 type { PeopleOverviewPageQuery as PeopleOverviewPageQueryType } from "./__generated__/PeopleOverviewPageQuery.graphql";
import { useParams } from "react-router"; import { useParams } from "react-router";
import { Helmet } from "react-helmet-async"; import { Helmet } from "react-helmet-async";
import { useBreadcrumb } from "@/contexts/BreadcrumbContext"; import { useBreadcrumb } from "@/contexts/BreadcrumbContext";
import { cn } from "@/lib/utils";
const peopleOverviewPageQuery = graphql` const peopleOverviewPageQuery = graphql`
query PeopleOverviewPageQuery($peopleId: ID!) { query PeopleOverviewPageQuery($peopleId: ID!) {
@@ -20,13 +27,61 @@ const peopleOverviewPageQuery = graphql`
id id
fullName fullName
primaryEmailAddress primaryEmailAddress
additionalEmailAddresses
kind
createdAt createdAt
updatedAt 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 (
<div className="space-y-2">
<div className="flex items-center gap-2">
<HelpCircle className="h-4 w-4 text-gray-400" />
<Label className="text-sm">{label}</Label>
</div>
<div className="space-y-2">
<Input
type={type}
value={value}
onChange={(e) => onChange(e.target.value)}
/>
{helpText && <p className="text-sm text-gray-500">{helpText}</p>}
</div>
</div>
);
}
function PeopleOverviewPageContent({ function PeopleOverviewPageContent({
queryRef, queryRef,
}: { }: {
@@ -34,80 +89,198 @@ function PeopleOverviewPageContent({
}) { }) {
const data = usePreloadedQuery(peopleOverviewPageQuery, queryRef); const data = usePreloadedQuery(peopleOverviewPageQuery, queryRef);
const { setBreadcrumbSegment } = useBreadcrumb(); const { setBreadcrumbSegment } = useBreadcrumb();
const [editedFields, setEditedFields] = useState<Set<string>>(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<PeopleOverviewPageQueryType>(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(() => { useEffect(() => {
if (data.node?.primaryEmailAddress) { if (data.node?.fullName) {
setBreadcrumbSegment("peoples/:id", data.node.primaryEmailAddress); setBreadcrumbSegment("peoples/:id", data.node.fullName);
} }
}, [data.node?.primaryEmailAddress, setBreadcrumbSegment]); }, [data.node?.fullName, setBreadcrumbSegment]);
return ( return (
<div className="space-y-6 p-4 md:p-6 lg:p-8"> <>
<div className="mx-auto max-w-4xl space-y-6"> <div className="space-y-6 p-4 md:p-6 lg:p-8">
<div className="space-y-2"> <div className="mx-auto max-w-4xl space-y-6">
<h1 className="text-xl font-semibold text-gray-900"> <EditableField
{data.node?.fullName} label="Full Name"
</h1> value={formData.fullName}
<p className="text-gray-600">View and manage person details</p> onChange={(value) => handleFieldChange('fullName', value)}
/>
<EditableField
label="Primary Email"
value={formData.primaryEmailAddress}
type="email"
onChange={(value) => handleFieldChange('primaryEmailAddress', value)}
/>
<div className="space-y-2">
<div className="flex items-center gap-2">
<HelpCircle className="h-4 w-4 text-gray-400" />
<Label className="text-sm">Additional Email Addresses</Label>
</div>
<div className="space-y-2">
{formData.additionalEmailAddresses.map((email, index) => (
<div key={index} className="flex gap-2">
<Input
type="email"
value={email}
onChange={(e) => {
const newEmails = [...formData.additionalEmailAddresses];
newEmails[index] = e.target.value;
handleFieldChange('additionalEmailAddresses', newEmails);
}}
/>
<Button
variant="outline"
onClick={() => {
const newEmails = formData.additionalEmailAddresses.filter((_, i) => i !== index);
handleFieldChange('additionalEmailAddresses', newEmails);
}}
>
Remove
</Button>
</div>
))}
<Button
variant="outline"
onClick={() => {
handleFieldChange('additionalEmailAddresses', [...formData.additionalEmailAddresses, '']);
}}
>
Add Email
</Button>
</div>
</div>
<Card className="p-6">
<div className="space-y-4">
<div className="space-y-2">
<h2 className="text-lg font-medium">Additional Information</h2>
<p className="text-sm text-gray-500">
Additional details about the person
</p>
</div>
<div className="space-y-4">
<div className="space-y-2">
<div className="flex items-center gap-2">
<HelpCircle className="h-4 w-4 text-gray-400" />
<Label className="text-sm">Kind</Label>
</div>
<div className="flex gap-2">
<button
onClick={() => handleFieldChange('kind', 'EMPLOYEE')}
className={cn(
"rounded-full px-4 py-1 text-sm transition-colors",
formData.kind === 'EMPLOYEE'
? "bg-blue-100 text-blue-900 ring-2 ring-blue-600 ring-offset-2"
: "bg-gray-100 text-gray-900 hover:bg-gray-200"
)}
>
Employee
</button>
<button
onClick={() => handleFieldChange('kind', 'CONTRACTOR')}
className={cn(
"rounded-full px-4 py-1 text-sm transition-colors",
formData.kind === 'CONTRACTOR'
? "bg-purple-100 text-purple-900 ring-2 ring-purple-600 ring-offset-2"
: "bg-gray-100 text-gray-900 hover:bg-gray-200"
)}
>
Contractor
</button>
</div>
</div>
</div>
</div>
</Card>
</div> </div>
<Card className="p-6">
<div className="space-y-4">
<div className="space-y-2">
<h2 className="text-lg font-medium">Personal Information</h2>
<p className="text-sm text-gray-500">
Basic information about the person
</p>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="col-span-2">
<label className="text-sm font-medium text-gray-700">
Full Name
</label>
<p className="mt-1">{data.node?.fullName}</p>
</div>
<div className="col-span-2">
<label className="text-sm font-medium text-gray-700">
Email
</label>
<p className="mt-1">{data.node?.primaryEmailAddress}</p>
</div>
</div>
</div>
</Card>
<Card className="p-6">
<div className="space-y-4">
<div className="space-y-2">
<h2 className="text-lg font-medium">System Information</h2>
<p className="text-sm text-gray-500">
System-related information about the person
</p>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-sm font-medium text-gray-700">
Created At
</label>
<p className="mt-1">
{new Date(data.node?.createdAt).toLocaleString()}
</p>
</div>
<div>
<label className="text-sm font-medium text-gray-700">
Last Updated
</label>
<p className="mt-1">
{new Date(data.node?.updatedAt).toLocaleString()}
</p>
</div>
</div>
</div>
</Card>
</div> </div>
</div>
{hasChanges && (
<div className="fixed bottom-6 right-6 flex gap-2">
<Button
variant="outline"
onClick={handleCancel}
>
Cancel
</Button>
<Button
onClick={handleSave}
className="bg-primary text-primary-foreground hover:bg-primary/90"
>
Save Changes
</Button>
</div>
)}
</>
); );
} }

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<ee2731bf27a12fb704f79cf4058e2d80>> * @generated SignedSource<<528ad6b0a104964c9d947ef1e9c41b46>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -9,16 +9,20 @@
// @ts-nocheck // @ts-nocheck
import { ConcreteRequest } from 'relay-runtime'; import { ConcreteRequest } from 'relay-runtime';
export type PeopleKind = "CONTRACTOR" | "EMPLOYEE";
export type PeopleOverviewPageQuery$variables = { export type PeopleOverviewPageQuery$variables = {
peopleId: string; peopleId: string;
}; };
export type PeopleOverviewPageQuery$data = { export type PeopleOverviewPageQuery$data = {
readonly node: { readonly node: {
readonly additionalEmailAddresses?: ReadonlyArray<string>;
readonly createdAt?: any; readonly createdAt?: any;
readonly fullName?: string; readonly fullName?: string;
readonly id?: string; readonly id?: string;
readonly kind?: PeopleKind;
readonly primaryEmailAddress?: string; readonly primaryEmailAddress?: string;
readonly updatedAt?: any; readonly updatedAt?: any;
readonly version?: number;
}; };
}; };
export type PeopleOverviewPageQuery = { export type PeopleOverviewPageQuery = {
@@ -66,15 +70,36 @@ v5 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "createdAt", "name": "additionalEmailAddresses",
"storageKey": null "storageKey": null
}, },
v6 = { 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, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "updatedAt", "name": "updatedAt",
"storageKey": null "storageKey": null
},
v9 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "version",
"storageKey": null
}; };
return { return {
"fragment": { "fragment": {
@@ -98,7 +123,10 @@ return {
(v3/*: any*/), (v3/*: any*/),
(v4/*: any*/), (v4/*: any*/),
(v5/*: any*/), (v5/*: any*/),
(v6/*: any*/) (v6/*: any*/),
(v7/*: any*/),
(v8/*: any*/),
(v9/*: any*/)
], ],
"type": "People", "type": "People",
"abstractKey": null "abstractKey": null
@@ -138,7 +166,10 @@ return {
(v3/*: any*/), (v3/*: any*/),
(v4/*: any*/), (v4/*: any*/),
(v5/*: any*/), (v5/*: any*/),
(v6/*: any*/) (v6/*: any*/),
(v7/*: any*/),
(v8/*: any*/),
(v9/*: any*/)
], ],
"type": "People", "type": "People",
"abstractKey": null "abstractKey": null
@@ -149,16 +180,16 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "0cd0dd96cb7a84c2016bfe1f088f8245", "cacheID": "1383ca6e8c8b205082cc68223bec1833",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "PeopleOverviewPageQuery", "name": "PeopleOverviewPageQuery",
"operationKind": "query", "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; export default node;

View File

@@ -0,0 +1,146 @@
/**
* @generated SignedSource<<b950e6a3b56e0545050b3bbe076c2f65>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type PeopleKind = "CONTRACTOR" | "EMPLOYEE";
export type UpdatePeopleInput = {
additionalEmailAddresses?: ReadonlyArray<string> | 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<string>;
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;

View File

@@ -100,6 +100,7 @@ type People implements Node {
kind: PeopleKind! kind: PeopleKind!
createdAt: Datetime! createdAt: Datetime!
updatedAt: Datetime! updatedAt: Datetime!
version: Int!
} }
type VendorConnection { type VendorConnection {
@@ -316,8 +317,9 @@ type Mutation {
createVendor(input: CreateVendorInput!): Vendor! createVendor(input: CreateVendorInput!): Vendor!
updateVendor(input: UpdateVendorInput!): Vendor! updateVendor(input: UpdateVendorInput!): Vendor!
deleteVendor(input: DeleteVendorInput!): Void! deleteVendor(input: DeleteVendorInput!): Void!
deletePeople(input: DeletePeopleInput!): Void!
createPeople(input: CreatePeopleInput!): People! createPeople(input: CreatePeopleInput!): People!
updatePeople(input: UpdatePeopleInput!): People!
deletePeople(input: DeletePeopleInput!): Void!
} }
input CreateVendorInput { input CreateVendorInput {
@@ -341,6 +343,15 @@ input CreatePeopleInput {
kind: PeopleKind! 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") { enum ServiceCriticality @goModel(model: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticality") {
LOW @goEnum(value: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticalityLow") LOW @goEnum(value: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticalityLow")
MEDIUM @goEnum(value: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticalityMedium") MEDIUM @goEnum(value: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticalityMedium")

View File

@@ -160,6 +160,7 @@ type ComplexityRoot struct {
CreateVendor func(childComplexity int, input types.CreateVendorInput) int CreateVendor func(childComplexity int, input types.CreateVendorInput) int
DeletePeople func(childComplexity int, input types.DeletePeopleInput) int DeletePeople func(childComplexity int, input types.DeletePeopleInput) int
DeleteVendor func(childComplexity int, input types.DeleteVendorInput) 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 UpdateVendor func(childComplexity int, input types.UpdateVendorInput) int
} }
@@ -189,6 +190,7 @@ type ComplexityRoot struct {
Kind func(childComplexity int) int Kind func(childComplexity int) int
PrimaryEmailAddress func(childComplexity int) int PrimaryEmailAddress func(childComplexity int) int
UpdatedAt func(childComplexity int) int UpdatedAt func(childComplexity int) int
Version func(childComplexity int) int
} }
PeopleConnection struct { PeopleConnection struct {
@@ -286,8 +288,9 @@ type MutationResolver interface {
CreateVendor(ctx context.Context, input types.CreateVendorInput) (*types.Vendor, error) CreateVendor(ctx context.Context, input types.CreateVendorInput) (*types.Vendor, error)
UpdateVendor(ctx context.Context, input types.UpdateVendorInput) (*types.Vendor, error) UpdateVendor(ctx context.Context, input types.UpdateVendorInput) (*types.Vendor, error)
DeleteVendor(ctx context.Context, input types.DeleteVendorInput) (string, 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) 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 { type OrganizationResolver interface {
Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.FrameworkConnection, error) 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 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": case "Mutation.updateVendor":
if e.complexity.Mutation.UpdateVendor == nil { if e.complexity.Mutation.UpdateVendor == nil {
break break
@@ -934,6 +949,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.People.UpdatedAt(childComplexity), true 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": case "PeopleConnection.edges":
if e.complexity.PeopleConnection.Edges == nil { if e.complexity.PeopleConnection.Edges == nil {
break break
@@ -1269,6 +1291,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
ec.unmarshalInputCreateVendorInput, ec.unmarshalInputCreateVendorInput,
ec.unmarshalInputDeletePeopleInput, ec.unmarshalInputDeletePeopleInput,
ec.unmarshalInputDeleteVendorInput, ec.unmarshalInputDeleteVendorInput,
ec.unmarshalInputUpdatePeopleInput,
ec.unmarshalInputUpdateVendorInput, ec.unmarshalInputUpdateVendorInput,
) )
first := true first := true
@@ -1469,6 +1492,7 @@ type People implements Node {
kind: PeopleKind! kind: PeopleKind!
createdAt: Datetime! createdAt: Datetime!
updatedAt: Datetime! updatedAt: Datetime!
version: Int!
} }
type VendorConnection { type VendorConnection {
@@ -1685,8 +1709,9 @@ type Mutation {
createVendor(input: CreateVendorInput!): Vendor! createVendor(input: CreateVendorInput!): Vendor!
updateVendor(input: UpdateVendorInput!): Vendor! updateVendor(input: UpdateVendorInput!): Vendor!
deleteVendor(input: DeleteVendorInput!): Void! deleteVendor(input: DeleteVendorInput!): Void!
deletePeople(input: DeletePeopleInput!): Void!
createPeople(input: CreatePeopleInput!): People! createPeople(input: CreatePeopleInput!): People!
updatePeople(input: UpdatePeopleInput!): People!
deletePeople(input: DeletePeopleInput!): Void!
} }
input CreateVendorInput { input CreateVendorInput {
@@ -1710,6 +1735,15 @@ input CreatePeopleInput {
kind: PeopleKind! 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") { enum ServiceCriticality @goModel(model: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticality") {
LOW @goEnum(value: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticalityLow") LOW @goEnum(value: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticalityLow")
MEDIUM @goEnum(value: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticalityMedium") 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 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) { func (ec *executionContext) field_Mutation_updateVendor_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error var err error
args := map[string]any{} args := map[string]any{}
@@ -5118,49 +5175,6 @@ func (ec *executionContext) fieldContext_Mutation_deleteVendor(ctx context.Conte
return fc, nil 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) { func (ec *executionContext) _Mutation_createPeople(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Mutation_createPeople(ctx, field) fc, err := ec.fieldContext_Mutation_createPeople(ctx, field)
if err != nil { if err != nil {
@@ -5208,6 +5222,8 @@ func (ec *executionContext) fieldContext_Mutation_createPeople(ctx context.Conte
return ec.fieldContext_People_createdAt(ctx, field) return ec.fieldContext_People_createdAt(ctx, field)
case "updatedAt": case "updatedAt":
return ec.fieldContext_People_updatedAt(ctx, field) 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) 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 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) { 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) fc, err := ec.fieldContext_Organization_id(ctx, field)
if err != nil { if err != nil {
@@ -5969,6 +6089,44 @@ func (ec *executionContext) fieldContext_People_updatedAt(_ context.Context, fie
return fc, nil 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) { 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) fc, err := ec.fieldContext_PeopleConnection_edges(ctx, field)
if err != nil { if err != nil {
@@ -6146,6 +6304,8 @@ func (ec *executionContext) fieldContext_PeopleEdge_node(_ context.Context, fiel
return ec.fieldContext_People_createdAt(ctx, field) return ec.fieldContext_People_createdAt(ctx, field)
case "updatedAt": case "updatedAt":
return ec.fieldContext_People_updatedAt(ctx, field) 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) 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 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) { func (ec *executionContext) unmarshalInputUpdateVendorInput(ctx context.Context, obj any) (types.UpdateVendorInput, error) {
var it types.UpdateVendorInput var it types.UpdateVendorInput
asMap := map[string]any{} asMap := map[string]any{}
@@ -10675,16 +10897,23 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ out.Invalids++
} }
case "deletePeople": case "createPeople":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { 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 { if out.Values[i] == graphql.Null {
out.Invalids++ out.Invalids++
} }
case "createPeople": case "updatePeople":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { 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 { if out.Values[i] == graphql.Null {
out.Invalids++ out.Invalids++
@@ -10958,6 +11187,11 @@ func (ec *executionContext) _People(ctx context.Context, sel ast.SelectionSet, o
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ out.Invalids++
} }
case "version":
out.Values[i] = ec._People_version(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
}
default: default:
panic("unknown field " + strconv.Quote(field.Name)) panic("unknown field " + strconv.Quote(field.Name))
} }
@@ -12911,6 +13145,11 @@ func (ec *executionContext) marshalNTaskStateTransitionEdge2ᚖgithubᚗcomᚋge
return ec._TaskStateTransitionEdge(ctx, sel, v) 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) { 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) res, err := ec.unmarshalInputUpdateVendorInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err) return res, graphql.ErrorOnPath(ctx, err)
@@ -13372,6 +13611,34 @@ func (ec *executionContext) marshalOInt2ᚖint(ctx context.Context, sel ast.Sele
return res 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) { func (ec *executionContext) unmarshalORiskTier2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐRiskTier(ctx context.Context, v any) (*coredata.RiskTier, error) {
if v == nil { if v == nil {
return nil, nil return nil, nil

View File

@@ -48,5 +48,6 @@ func NewPeople(p *coredata.People) *People {
Kind: p.Kind, Kind: p.Kind,
CreatedAt: p.CreatedAt, CreatedAt: p.CreatedAt,
UpdatedAt: p.UpdatedAt, UpdatedAt: p.UpdatedAt,
Version: p.Version,
} }
} }

View File

@@ -177,6 +177,7 @@ type People struct {
Kind coredata.PeopleKind `json:"kind"` Kind coredata.PeopleKind `json:"kind"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"` UpdatedAt time.Time `json:"updatedAt"`
Version int `json:"version"`
} }
func (People) IsNode() {} func (People) IsNode() {}
@@ -238,6 +239,15 @@ type TaskStateTransitionEdge struct {
Node *TaskStateTransition `json:"node"` 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 { type UpdateVendorInput struct {
ID gid.GID `json:"id"` ID gid.GID `json:"id"`
ExpectedVersion int `json:"expectedVersion"` ExpectedVersion int `json:"expectedVersion"`

View File

@@ -110,16 +110,6 @@ func (r *mutationResolver) DeleteVendor(ctx context.Context, input types.DeleteV
return "", nil 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. // CreatePeople is the resolver for the createPeople field.
func (r *mutationResolver) CreatePeople(ctx context.Context, input types.CreatePeopleInput) (*types.People, error) { func (r *mutationResolver) CreatePeople(ctx context.Context, input types.CreatePeopleInput) (*types.People, error) {
people, err := r.svc.CreatePeople(ctx, probo.CreatePeopleRequest{ 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 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. // 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) { 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) cursor := types.NewCursor(first, after, last, before)

View File

@@ -0,0 +1 @@
ALTER TABLE peoples ADD COLUMN version INTEGER NOT NULL DEFAULT 1;

View File

@@ -16,6 +16,7 @@ package coredata
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"maps" "maps"
"time" "time"
@@ -36,9 +37,18 @@ type (
AdditionalEmailAddresses []string AdditionalEmailAddresses []string
CreatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time UpdatedAt time.Time
Version int
} }
Peoples []*People Peoples []*People
UpdatePeopleParams struct {
ExpectedVersion int
FullName *string
PrimaryEmailAddress *string
AdditionalEmailAddresses *[]string
Kind *PeopleKind
}
) )
func (p People) CursorKey() page.CursorKey { func (p People) CursorKey() page.CursorKey {
@@ -55,6 +65,7 @@ func (p *People) scan(r pgx.Row) error {
&p.AdditionalEmailAddresses, &p.AdditionalEmailAddresses,
&p.CreatedAt, &p.CreatedAt,
&p.UpdatedAt, &p.UpdatedAt,
&p.Version,
) )
} }
@@ -73,7 +84,8 @@ SELECT
primary_email_address, primary_email_address,
additional_email_addresses, additional_email_addresses,
created_at, created_at,
updated_at updated_at,
version
FROM FROM
peoples peoples
WHERE WHERE
@@ -113,7 +125,8 @@ INSERT INTO
primary_email_address, primary_email_address,
additional_email_addresses, additional_email_addresses,
created_at, created_at,
updated_at updated_at,
version
) )
VALUES ( VALUES (
@people_id, @people_id,
@@ -123,7 +136,8 @@ VALUES (
@primary_email_address, @primary_email_address,
@additional_email_addresses, @additional_email_addresses,
@created_at, @created_at,
@updated_at @updated_at,
@version
) )
` `
@@ -136,6 +150,7 @@ VALUES (
"additional_email_addresses": p.AdditionalEmailAddresses, "additional_email_addresses": p.AdditionalEmailAddresses,
"created_at": p.CreatedAt, "created_at": p.CreatedAt,
"updated_at": p.UpdatedAt, "updated_at": p.UpdatedAt,
"version": p.Version,
} }
_, err := conn.Exec(ctx, q, args) _, err := conn.Exec(ctx, q, args)
return err return err
@@ -175,7 +190,8 @@ SELECT
primary_email_address, primary_email_address,
additional_email_addresses, additional_email_addresses,
created_at, created_at,
updated_at updated_at,
version
FROM FROM
peoples peoples
WHERE WHERE
@@ -214,3 +230,68 @@ WHERE
return nil 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
}

View File

@@ -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
}