Add rank to reference

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2025-10-28 16:38:12 +01:00
parent ca008721ba
commit 75f3f92f04
17 changed files with 497 additions and 50 deletions

View File

@@ -22,6 +22,7 @@ const referenceSchema = z.object({
name: z.string().min(1, "Name is required"),
description: z.string(),
websiteUrl: z.string().url("Please enter a valid URL"),
rank: z.number().int().positive().optional(),
});
type ReferenceFormData = z.infer<typeof referenceSchema>;
@@ -33,6 +34,7 @@ export type TrustCenterReferenceDialogRef = {
name: string;
description: string;
websiteUrl: string;
rank: number;
}) => void;
};
@@ -41,6 +43,7 @@ type Reference = {
name: string;
description: string;
websiteUrl: string;
rank: number;
};
export const TrustCenterReferenceDialog = forwardRef<TrustCenterReferenceDialogRef, { children?: ReactNode }>(
@@ -89,6 +92,7 @@ export const TrustCenterReferenceDialog = forwardRef<TrustCenterReferenceDialogR
name: reference.name,
description: reference.description,
websiteUrl: reference.websiteUrl,
rank: reference.rank,
});
dialogRef.current?.open();
},
@@ -133,6 +137,7 @@ export const TrustCenterReferenceDialog = forwardRef<TrustCenterReferenceDialogR
name: string;
description: string;
websiteUrl: string;
rank?: number;
logoFile?: null;
} = {
id: editReference.id,
@@ -141,6 +146,10 @@ export const TrustCenterReferenceDialog = forwardRef<TrustCenterReferenceDialogR
websiteUrl: data.websiteUrl,
};
if (data.rank !== undefined) {
input.rank = data.rank;
}
const uploadables: Record<string, File> = {};
if (uploadedFile) {
@@ -210,6 +219,18 @@ export const TrustCenterReferenceDialog = forwardRef<TrustCenterReferenceDialogR
placeholder={__("https://example.com")}
/>
{mode === 'edit' && (
<Field
{...register("rank", { valueAsNumber: true })}
label={__("Rank")}
type="number"
min={1}
error={errors.rank?.message}
placeholder={__("Display order (1, 2, 3...)")}
help={__("Lower numbers appear first")}
/>
)}
<Field label={__("Logo")}>
<Dropzone
description={__("Upload logo image (PNG, JPG, WEBP up to 5MB)")}

View File

@@ -14,9 +14,10 @@ import {
IconPencil,
IconArrowLink,
} from "@probo/ui";
import { type ReactNode, useRef } from "react";
import { type ReactNode, useRef, useState } from "react";
import {
useTrustCenterReferences,
useUpdateTrustCenterReferenceRankMutation,
} from "/hooks/graph/TrustCenterReferenceGraph";
import { TrustCenterReferenceDialog, type TrustCenterReferenceDialogRef } from "./TrustCenterReferenceDialog";
import { DeleteTrustCenterReferenceDialog } from "./DeleteTrustCenterReferenceDialog";
@@ -32,6 +33,7 @@ type Reference = {
description: string;
websiteUrl: string;
logoUrl: string;
rank: number;
createdAt: string;
updatedAt: string;
};
@@ -39,7 +41,11 @@ type Reference = {
export function TrustCenterReferencesSection({ trustCenterId }: Props) {
const { __ } = useTranslate();
const dialogRef = useRef<TrustCenterReferenceDialogRef>(null);
const data = useTrustCenterReferences(trustCenterId);
const [draggedIndex, setDraggedIndex] = useState<number | null>(null);
const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);
const [refetchKey, setRefetchKey] = useState(0);
const data = useTrustCenterReferences(trustCenterId, refetchKey);
const [updateRank] = useUpdateTrustCenterReferenceRankMutation();
const trustCenterNode = data?.node;
const references = trustCenterNode?.references?.edges?.map((edge) => edge.node) || [];
@@ -55,11 +61,47 @@ export function TrustCenterReferencesSection({ trustCenterId }: Props) {
dialogRef.current?.openEdit(reference);
};
const handleVisitWebsite = (websiteUrl: string) => {
safeOpenUrl(websiteUrl);
};
const handleDragStart = (index: number) => {
setDraggedIndex(index);
};
const handleDragOver = (e: React.DragEvent, index: number) => {
e.preventDefault();
if (draggedIndex !== index) {
setDragOverIndex(index);
}
};
const handleDrop = (targetIndex: number) => {
if (draggedIndex === null || draggedIndex === targetIndex) {
setDraggedIndex(null);
setDragOverIndex(null);
return;
}
const draggedRef = references[draggedIndex];
const targetRank = references[targetIndex].rank;
updateRank({
variables: {
input: {
id: draggedRef.id,
rank: targetRank,
},
},
onCompleted: () => {
setRefetchKey((prev) => prev + 1);
},
});
setDraggedIndex(null);
setDragOverIndex(null);
};
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
@@ -93,18 +135,28 @@ export function TrustCenterReferencesSection({ trustCenterId }: Props) {
</Td>
</Tr>
)}
{references.map((reference: Reference) => (
{references.map((reference: Reference, index: number) => (
<ReferenceRow
key={reference.id}
reference={reference}
index={index}
isDragging={draggedIndex === index}
isDropTarget={dragOverIndex === index && draggedIndex !== index}
onEdit={() => handleEdit(reference)}
connectionId={referencesConnectionId}
onVisitWebsite={() => handleVisitWebsite(reference.websiteUrl)}
onDragStart={() => handleDragStart(index)}
onDragOver={(e) => handleDragOver(e, index)}
onDrop={() => handleDrop(index)}
/>
))}
</Tbody>
</Table>
<p className="text-xs text-txt-tertiary">
{__("Drag and drop references to change their displayed order")}
</p>
<TrustCenterReferenceDialog ref={dialogRef} />
</div>
);
@@ -112,14 +164,48 @@ export function TrustCenterReferencesSection({ trustCenterId }: Props) {
type ReferenceRowProps = {
reference: Reference;
index: number;
isDragging: boolean;
isDropTarget: boolean;
onEdit: () => void;
connectionId: string;
onVisitWebsite: () => void;
onDragStart: () => void;
onDragOver: (e: React.DragEvent) => void;
onDrop: () => void;
};
function ReferenceRow({ reference, onEdit, connectionId, onVisitWebsite }: ReferenceRowProps) {
function ReferenceRow({
reference,
isDragging,
isDropTarget,
onEdit,
connectionId,
onVisitWebsite,
onDragStart,
onDragOver,
onDrop,
}: ReferenceRowProps) {
const [isMouseDown, setIsMouseDown] = useState(false);
const className = [
isDragging && "opacity-50 cursor-grabbing",
!isDragging && !isMouseDown && "cursor-grab",
!isDragging && isMouseDown && "cursor-grabbing",
isDropTarget && "!bg-primary-50 border-y-2 border-primary-500",
].filter(Boolean).join(" ");
return (
<Tr>
<Tr
draggable
onDragStart={onDragStart}
onDragOver={onDragOver}
onDrop={onDrop}
onMouseDown={() => setIsMouseDown(true)}
onMouseUp={() => setIsMouseDown(false)}
onMouseLeave={() => setIsMouseDown(false)}
className={className}
>
<Td>
<div className="flex items-center gap-3">
<Avatar

View File

@@ -7,6 +7,7 @@ import type {
} from "./__generated__/TrustCenterReferenceGraphQuery.graphql";
import type { TrustCenterReferenceGraphCreateMutation } from "./__generated__/TrustCenterReferenceGraphCreateMutation.graphql";
import type { TrustCenterReferenceGraphUpdateMutation } from "./__generated__/TrustCenterReferenceGraphUpdateMutation.graphql";
import type { TrustCenterReferenceGraphUpdateRankMutation } from "./__generated__/TrustCenterReferenceGraphUpdateRankMutation.graphql";
import type { TrustCenterReferenceGraphDeleteMutation } from "./__generated__/TrustCenterReferenceGraphDeleteMutation.graphql";
export const trustCenterReferencesQuery = graphql`
@@ -14,7 +15,7 @@ export const trustCenterReferencesQuery = graphql`
node(id: $trustCenterId) {
... on TrustCenter {
id
references(first: 100, orderBy: { field: CREATED_AT, direction: DESC })
references(first: 100, orderBy: { field: RANK, direction: ASC })
@connection(key: "TrustCenterReferencesSection_references") {
__id
pageInfo {
@@ -31,6 +32,7 @@ export const trustCenterReferencesQuery = graphql`
description
websiteUrl
logoUrl
rank
createdAt
updatedAt
}
@@ -47,7 +49,7 @@ export const createTrustCenterReferenceMutation = graphql`
$connections: [ID!]!
) {
createTrustCenterReference(input: $input) {
trustCenterReferenceEdge @prependEdge(connections: $connections) {
trustCenterReferenceEdge @appendEdge(connections: $connections) {
cursor
node {
id
@@ -55,6 +57,7 @@ export const createTrustCenterReferenceMutation = graphql`
description
websiteUrl
logoUrl
rank
createdAt
updatedAt
}
@@ -74,6 +77,7 @@ export const updateTrustCenterReferenceMutation = graphql`
description
websiteUrl
logoUrl
rank
createdAt
updatedAt
}
@@ -92,11 +96,11 @@ export const deleteTrustCenterReferenceMutation = graphql`
}
`;
export function useTrustCenterReferences(trustCenterId: string): TrustCenterReferenceGraphQuery$data | null {
export function useTrustCenterReferences(trustCenterId: string, refetchKey = 0): TrustCenterReferenceGraphQuery$data | null {
const data = useLazyLoadQuery<TrustCenterReferenceGraphQuery>(
trustCenterReferencesQuery,
{ trustCenterId: trustCenterId || "" },
{ fetchPolicy: 'network-only' }
{ fetchPolicy: 'network-only', fetchKey: refetchKey }
);
return trustCenterId ? data : null;
@@ -122,6 +126,29 @@ export function useUpdateTrustCenterReferenceMutation() {
);
}
export const updateTrustCenterReferenceRankMutation = graphql`
mutation TrustCenterReferenceGraphUpdateRankMutation(
$input: UpdateTrustCenterReferenceInput!
) {
updateTrustCenterReference(input: $input) {
trustCenterReference {
id
rank
}
}
}
`;
export function useUpdateTrustCenterReferenceRankMutation() {
return useMutationWithToasts<TrustCenterReferenceGraphUpdateRankMutation>(
updateTrustCenterReferenceRankMutation,
{
successMessage: "Order updated successfully",
errorMessage: "Failed to update order",
}
);
}
export function useDeleteTrustCenterReferenceMutation() {
return useMutationWithToasts<TrustCenterReferenceGraphDeleteMutation>(
deleteTrustCenterReferenceMutation,

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<216f3e7b4510c3d1127c3910bebc8b95>>
* @generated SignedSource<<9d17f089c36e0567d217fe339b6382fd>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -30,6 +30,7 @@ export type TrustCenterReferenceGraphCreateMutation$data = {
readonly id: string;
readonly logoUrl: string;
readonly name: string;
readonly rank: number;
readonly updatedAt: any;
readonly websiteUrl: string;
};
@@ -117,6 +118,13 @@ v3 = {
"name": "logoUrl",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "rank",
"storageKey": null
},
{
"alias": null,
"args": null,
@@ -185,7 +193,7 @@ return {
"alias": null,
"args": null,
"filters": null,
"handle": "prependEdge",
"handle": "appendEdge",
"key": "",
"kind": "LinkedHandle",
"name": "trustCenterReferenceEdge",
@@ -203,16 +211,16 @@ return {
]
},
"params": {
"cacheID": "17a206ba4702d641ffd7631cb8e3e03b",
"cacheID": "623c9df0fba82887a878addd1ddaf706",
"id": null,
"metadata": {},
"name": "TrustCenterReferenceGraphCreateMutation",
"operationKind": "mutation",
"text": "mutation TrustCenterReferenceGraphCreateMutation(\n $input: CreateTrustCenterReferenceInput!\n) {\n createTrustCenterReference(input: $input) {\n trustCenterReferenceEdge {\n cursor\n node {\n id\n name\n description\n websiteUrl\n logoUrl\n createdAt\n updatedAt\n }\n }\n }\n}\n"
"text": "mutation TrustCenterReferenceGraphCreateMutation(\n $input: CreateTrustCenterReferenceInput!\n) {\n createTrustCenterReference(input: $input) {\n trustCenterReferenceEdge {\n cursor\n node {\n id\n name\n description\n websiteUrl\n logoUrl\n rank\n createdAt\n updatedAt\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "e0a76c3f5582bf1ed5def08db36d9e25";
(node as any).hash = "ee754e6c9bc3aa992e1a40e828b0d07e";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<f53f001d217e4c6f5b353f2243f700f2>>
* @generated SignedSource<<85556d15b776b3e61e312057a994d482>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -25,6 +25,7 @@ export type TrustCenterReferenceGraphQuery$data = {
readonly id: string;
readonly logoUrl: string;
readonly name: string;
readonly rank: number;
readonly updatedAt: any;
readonly websiteUrl: string;
};
@@ -69,8 +70,8 @@ v3 = {
"kind": "Literal",
"name": "orderBy",
"value": {
"direction": "DESC",
"field": "CREATED_AT"
"direction": "ASC",
"field": "RANK"
}
},
v4 = {
@@ -172,6 +173,13 @@ v5 = [
"name": "logoUrl",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "rank",
"storageKey": null
},
{
"alias": null,
"args": null,
@@ -243,7 +251,7 @@ return {
"name": "__TrustCenterReferencesSection_references_connection",
"plural": false,
"selections": (v5/*: any*/),
"storageKey": "__TrustCenterReferencesSection_references_connection(orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
"storageKey": "__TrustCenterReferencesSection_references_connection(orderBy:{\"direction\":\"ASC\",\"field\":\"RANK\"})"
}
],
"type": "TrustCenter",
@@ -283,7 +291,7 @@ return {
"name": "references",
"plural": false,
"selections": (v5/*: any*/),
"storageKey": "references(first:100,orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
"storageKey": "references(first:100,orderBy:{\"direction\":\"ASC\",\"field\":\"RANK\"})"
},
{
"alias": null,
@@ -306,7 +314,7 @@ return {
]
},
"params": {
"cacheID": "8e9fd111488feb14deeedf47bfa4a0f0",
"cacheID": "8fdd97abcaa0d9eb889eb454c85f2e4c",
"id": null,
"metadata": {
"connection": [
@@ -323,11 +331,11 @@ return {
},
"name": "TrustCenterReferenceGraphQuery",
"operationKind": "query",
"text": "query TrustCenterReferenceGraphQuery(\n $trustCenterId: ID!\n) {\n node(id: $trustCenterId) {\n __typename\n ... on TrustCenter {\n id\n references(first: 100, orderBy: {field: CREATED_AT, direction: DESC}) {\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n edges {\n cursor\n node {\n id\n name\n description\n websiteUrl\n logoUrl\n createdAt\n updatedAt\n __typename\n }\n }\n }\n }\n id\n }\n}\n"
"text": "query TrustCenterReferenceGraphQuery(\n $trustCenterId: ID!\n) {\n node(id: $trustCenterId) {\n __typename\n ... on TrustCenter {\n id\n references(first: 100, orderBy: {field: RANK, direction: ASC}) {\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n edges {\n cursor\n node {\n id\n name\n description\n websiteUrl\n logoUrl\n rank\n createdAt\n updatedAt\n __typename\n }\n }\n }\n }\n id\n }\n}\n"
}
};
})();
(node as any).hash = "8b62ed10055cff04e117a31dd598eee4";
(node as any).hash = "cb4b9b8a68249ae8066a3ff2bfc6acd8";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<3b02f7734c467adddc8f8abe1c18c4e5>>
* @generated SignedSource<<9f21158abea504110ef50d4e0114a56a>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -14,6 +14,7 @@ export type UpdateTrustCenterReferenceInput = {
id: string;
logoFile?: any | null | undefined;
name?: string | null | undefined;
rank?: number | null | undefined;
websiteUrl?: string | null | undefined;
};
export type TrustCenterReferenceGraphUpdateMutation$variables = {
@@ -27,6 +28,7 @@ export type TrustCenterReferenceGraphUpdateMutation$data = {
readonly id: string;
readonly logoUrl: string;
readonly name: string;
readonly rank: number;
readonly updatedAt: any;
readonly websiteUrl: string;
};
@@ -103,6 +105,13 @@ v1 = [
"name": "logoUrl",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "rank",
"storageKey": null
},
{
"alias": null,
"args": null,
@@ -142,16 +151,16 @@ return {
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "e5fa3bdb21897523c1491d6cfbf816cf",
"cacheID": "f55510d6d1e0a686d4733f5bd82a605a",
"id": null,
"metadata": {},
"name": "TrustCenterReferenceGraphUpdateMutation",
"operationKind": "mutation",
"text": "mutation TrustCenterReferenceGraphUpdateMutation(\n $input: UpdateTrustCenterReferenceInput!\n) {\n updateTrustCenterReference(input: $input) {\n trustCenterReference {\n id\n name\n description\n websiteUrl\n logoUrl\n createdAt\n updatedAt\n }\n }\n}\n"
"text": "mutation TrustCenterReferenceGraphUpdateMutation(\n $input: UpdateTrustCenterReferenceInput!\n) {\n updateTrustCenterReference(input: $input) {\n trustCenterReference {\n id\n name\n description\n websiteUrl\n logoUrl\n rank\n createdAt\n updatedAt\n }\n }\n}\n"
}
};
})();
(node as any).hash = "2340a9c559d302025df08a5748c18b80";
(node as any).hash = "bed2a3190570cd85d85dd38f20f375da";
export default node;

View File

@@ -0,0 +1,118 @@
/**
* @generated SignedSource<<ef22c5c717241729685db94c9e9c7701>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type UpdateTrustCenterReferenceInput = {
description?: string | null | undefined;
id: string;
logoFile?: any | null | undefined;
name?: string | null | undefined;
rank?: number | null | undefined;
websiteUrl?: string | null | undefined;
};
export type TrustCenterReferenceGraphUpdateRankMutation$variables = {
input: UpdateTrustCenterReferenceInput;
};
export type TrustCenterReferenceGraphUpdateRankMutation$data = {
readonly updateTrustCenterReference: {
readonly trustCenterReference: {
readonly id: string;
readonly rank: number;
};
};
};
export type TrustCenterReferenceGraphUpdateRankMutation = {
response: TrustCenterReferenceGraphUpdateRankMutation$data;
variables: TrustCenterReferenceGraphUpdateRankMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "UpdateTrustCenterReferencePayload",
"kind": "LinkedField",
"name": "updateTrustCenterReference",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterReference",
"kind": "LinkedField",
"name": "trustCenterReference",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "rank",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "TrustCenterReferenceGraphUpdateRankMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "TrustCenterReferenceGraphUpdateRankMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "9e3d186eff2bff3d66482ab88f27aead",
"id": null,
"metadata": {},
"name": "TrustCenterReferenceGraphUpdateRankMutation",
"operationKind": "mutation",
"text": "mutation TrustCenterReferenceGraphUpdateRankMutation(\n $input: UpdateTrustCenterReferenceInput!\n) {\n updateTrustCenterReference(input: $input) {\n trustCenterReference {\n id\n rank\n }\n }\n}\n"
}
};
})();
(node as any).hash = "72d23bac1e404137890be27bf98bc59e";
export default node;

View File

@@ -0,0 +1,19 @@
ALTER TABLE trust_center_references ADD COLUMN rank INTEGER;
WITH ranked_references AS (
SELECT
id,
ROW_NUMBER() OVER (PARTITION BY trust_center_id ORDER BY created_at DESC, id DESC) AS rn
FROM trust_center_references
)
UPDATE trust_center_references tcr
SET rank = rr.rn
FROM ranked_references rr
WHERE tcr.id = rr.id;
ALTER TABLE trust_center_references ALTER COLUMN rank SET NOT NULL;
ALTER TABLE trust_center_references
ADD CONSTRAINT trust_center_references_trust_center_id_rank_key
UNIQUE (trust_center_id, rank)
DEFERRABLE INITIALLY DEFERRED;

View File

@@ -34,15 +34,26 @@ type (
Description string `db:"description"`
WebsiteURL string `db:"website_url"`
LogoFileID gid.GID `db:"logo_file_id"`
Rank int `db:"rank"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
TrustCenterReferences []*TrustCenterReference
ErrTrustCenterReferenceNotFound struct {
ID string
}
)
func (e ErrTrustCenterReferenceNotFound) Error() string {
return fmt.Sprintf("trust center reference not found: %s", e.ID)
}
func (t TrustCenterReference) CursorKey(orderBy TrustCenterReferenceOrderField) page.CursorKey {
switch orderBy {
case TrustCenterReferenceOrderFieldRank:
return page.NewCursorKey(t.ID, t.Rank)
case TrustCenterReferenceOrderFieldName:
return page.NewCursorKey(t.ID, t.Name)
case TrustCenterReferenceOrderFieldCreatedAt:
@@ -67,6 +78,7 @@ SELECT
description,
website_url,
logo_file_id,
rank,
created_at,
updated_at
FROM
@@ -96,7 +108,7 @@ LIMIT 1;
return nil
}
func (t TrustCenterReference) Insert(
func (t *TrustCenterReference) Insert(
ctx context.Context,
conn pg.Conn,
scope Scoper,
@@ -111,6 +123,7 @@ INSERT INTO
description,
website_url,
logo_file_id,
rank,
created_at,
updated_at
)
@@ -122,9 +135,11 @@ VALUES (
@description,
@website_url,
@logo_file_id,
(SELECT COALESCE(MAX(rank), 0) + 1 FROM trust_center_references WHERE trust_center_id = @trust_center_id),
@created_at,
@updated_at
);
)
RETURNING rank;
`
args := pgx.StrictNamedArgs{
@@ -139,7 +154,7 @@ VALUES (
"updated_at": t.UpdatedAt,
}
_, err := conn.Exec(ctx, q, args)
err := conn.QueryRow(ctx, q, args).Scan(&t.Rank)
if err != nil {
return fmt.Errorf("cannot insert trust center reference: %w", err)
}
@@ -162,16 +177,7 @@ SET
updated_at = @updated_at
WHERE
%s
AND id = @id
RETURNING
id,
trust_center_id,
name,
description,
website_url,
logo_file_id,
created_at,
updated_at
AND id = @id;
`
q = fmt.Sprintf(q, scope.SQLFragment())
@@ -186,17 +192,64 @@ RETURNING
}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
result, err := conn.Exec(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot update trust center reference: %w", err)
}
reference, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[TrustCenterReference])
if err != nil {
return fmt.Errorf("cannot collect updated trust center reference: %w", err)
if result.RowsAffected() == 0 {
return ErrTrustCenterReferenceNotFound{ID: t.ID.String()}
}
*t = reference
return nil
}
func (t *TrustCenterReference) UpdateRank(
ctx context.Context,
conn pg.Conn,
scope Scoper,
) error {
q := `
WITH old AS (
SELECT
rank AS old_rank
FROM trust_center_references
WHERE %s AND id = @id AND trust_center_id = @trust_center_id
)
UPDATE trust_center_references
SET
rank = CASE
WHEN id = @id THEN @new_rank
ELSE rank + CASE
WHEN @new_rank < old.old_rank THEN 1
WHEN @new_rank > old.old_rank THEN -1
END
END,
updated_at = @updated_at
FROM old
WHERE %s
AND (
id = @id
OR (rank BETWEEN LEAST(old.old_rank, @new_rank) AND GREATEST(old.old_rank, @new_rank))
);
`
scopeFragment := scope.SQLFragment()
q = fmt.Sprintf(q, scopeFragment, scopeFragment)
args := pgx.StrictNamedArgs{
"id": t.ID,
"new_rank": t.Rank,
"trust_center_id": t.TrustCenterID,
"updated_at": t.UpdatedAt,
}
maps.Copy(args, scope.SQLArguments())
_, err := conn.Exec(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot update trust center reference rank: %w", err)
}
return nil
}
@@ -242,6 +295,7 @@ SELECT
description,
website_url,
logo_file_id,
rank,
created_at,
updated_at
FROM

View File

@@ -19,6 +19,7 @@ type (
)
const (
TrustCenterReferenceOrderFieldRank TrustCenterReferenceOrderField = "RANK"
TrustCenterReferenceOrderFieldName TrustCenterReferenceOrderField = "NAME"
TrustCenterReferenceOrderFieldCreatedAt TrustCenterReferenceOrderField = "CREATED_AT"
TrustCenterReferenceOrderFieldUpdatedAt TrustCenterReferenceOrderField = "UPDATED_AT"
@@ -26,6 +27,8 @@ const (
func (p TrustCenterReferenceOrderField) Column() string {
switch p {
case TrustCenterReferenceOrderFieldRank:
return "rank"
case TrustCenterReferenceOrderFieldName:
return "name"
case TrustCenterReferenceOrderFieldCreatedAt:

View File

@@ -52,6 +52,7 @@ type (
Description *string
WebsiteURL *string
LogoFile *File
Rank *int
}
DeleteTrustCenterReferenceRequest struct {
@@ -229,6 +230,13 @@ func (s TrustCenterReferenceService) Update(
}
reference.UpdatedAt = now
if req.Rank != nil {
reference.Rank = *req.Rank
if err := reference.UpdateRank(ctx, tx, s.svc.scope); err != nil {
return fmt.Errorf("cannot update rank: %w", err)
}
}
if err := reference.Update(ctx, tx, s.svc.scope); err != nil {
return fmt.Errorf("cannot update trust center reference: %w", err)
}

View File

@@ -1173,6 +1173,10 @@ enum TrustCenterReferenceOrderField
@goModel(
model: "github.com/getprobo/probo/pkg/coredata.TrustCenterReferenceOrderField"
) {
RANK
@goEnum(
value: "github.com/getprobo/probo/pkg/coredata.TrustCenterReferenceOrderFieldRank"
)
NAME
@goEnum(
value: "github.com/getprobo/probo/pkg/coredata.TrustCenterReferenceOrderFieldName"
@@ -2461,6 +2465,7 @@ type TrustCenterReference implements Node {
description: String!
websiteUrl: String!
logoUrl: String! @goField(forceResolver: true)
rank: Int!
createdAt: Datetime!
updatedAt: Datetime!
}
@@ -3252,6 +3257,7 @@ input UpdateTrustCenterReferenceInput {
description: String
websiteUrl: String
logoFile: Upload
rank: Int
}
input DeleteTrustCenterReferenceInput {

View File

@@ -1366,6 +1366,7 @@ type ComplexityRoot struct {
ID func(childComplexity int) int
LogoURL func(childComplexity int) int
Name func(childComplexity int) int
Rank func(childComplexity int) int
UpdatedAt func(childComplexity int) int
WebsiteURL func(childComplexity int) int
}
@@ -7937,6 +7938,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.TrustCenterReference.Name(childComplexity), true
case "TrustCenterReference.rank":
if e.complexity.TrustCenterReference.Rank == nil {
break
}
return e.complexity.TrustCenterReference.Rank(childComplexity), true
case "TrustCenterReference.updatedAt":
if e.complexity.TrustCenterReference.UpdatedAt == nil {
break
@@ -10491,6 +10499,10 @@ enum TrustCenterReferenceOrderField
@goModel(
model: "github.com/getprobo/probo/pkg/coredata.TrustCenterReferenceOrderField"
) {
RANK
@goEnum(
value: "github.com/getprobo/probo/pkg/coredata.TrustCenterReferenceOrderFieldRank"
)
NAME
@goEnum(
value: "github.com/getprobo/probo/pkg/coredata.TrustCenterReferenceOrderFieldName"
@@ -11779,6 +11791,7 @@ type TrustCenterReference implements Node {
description: String!
websiteUrl: String!
logoUrl: String! @goField(forceResolver: true)
rank: Int!
createdAt: Datetime!
updatedAt: Datetime!
}
@@ -12570,6 +12583,7 @@ input UpdateTrustCenterReferenceInput {
description: String
websiteUrl: String
logoFile: Upload
rank: Int
}
input DeleteTrustCenterReferenceInput {
@@ -59663,6 +59677,50 @@ func (ec *executionContext) fieldContext_TrustCenterReference_logoUrl(_ context.
return fc, nil
}
func (ec *executionContext) _TrustCenterReference_rank(ctx context.Context, field graphql.CollectedField, obj *types.TrustCenterReference) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_TrustCenterReference_rank(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children
return obj.Rank, 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_TrustCenterReference_rank(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "TrustCenterReference",
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) _TrustCenterReference_createdAt(ctx context.Context, field graphql.CollectedField, obj *types.TrustCenterReference) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_TrustCenterReference_createdAt(ctx, field)
if err != nil {
@@ -59992,6 +60050,8 @@ func (ec *executionContext) fieldContext_TrustCenterReferenceEdge_node(_ context
return ec.fieldContext_TrustCenterReference_websiteUrl(ctx, field)
case "logoUrl":
return ec.fieldContext_TrustCenterReference_logoUrl(ctx, field)
case "rank":
return ec.fieldContext_TrustCenterReference_rank(ctx, field)
case "createdAt":
return ec.fieldContext_TrustCenterReference_createdAt(ctx, field)
case "updatedAt":
@@ -61510,6 +61570,8 @@ func (ec *executionContext) fieldContext_UpdateTrustCenterReferencePayload_trust
return ec.fieldContext_TrustCenterReference_websiteUrl(ctx, field)
case "logoUrl":
return ec.fieldContext_TrustCenterReference_logoUrl(ctx, field)
case "rank":
return ec.fieldContext_TrustCenterReference_rank(ctx, field)
case "createdAt":
return ec.fieldContext_TrustCenterReference_createdAt(ctx, field)
case "updatedAt":
@@ -76457,7 +76519,7 @@ func (ec *executionContext) unmarshalInputUpdateTrustCenterReferenceInput(ctx co
asMap[k] = v
}
fieldsInOrder := [...]string{"id", "name", "description", "websiteUrl", "logoFile"}
fieldsInOrder := [...]string{"id", "name", "description", "websiteUrl", "logoFile", "rank"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
@@ -76499,6 +76561,13 @@ func (ec *executionContext) unmarshalInputUpdateTrustCenterReferenceInput(ctx co
return it, err
}
it.LogoFile = data
case "rank":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("rank"))
data, err := ec.unmarshalOInt2ᚖint(ctx, v)
if err != nil {
return it, err
}
it.Rank = data
}
}
@@ -91027,6 +91096,11 @@ func (ec *executionContext) _TrustCenterReference(ctx context.Context, sel ast.S
}
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
case "rank":
out.Values[i] = ec._TrustCenterReference_rank(ctx, field, obj)
if out.Values[i] == graphql.Null {
atomic.AddUint32(&out.Invalids, 1)
}
case "createdAt":
out.Values[i] = ec._TrustCenterReference_createdAt(ctx, field, obj)
if out.Values[i] == graphql.Null {
@@ -101438,11 +101512,13 @@ func (ec *executionContext) marshalNTrustCenterReferenceOrderField2githubᚗcom
var (
unmarshalNTrustCenterReferenceOrderField2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐTrustCenterReferenceOrderField = map[string]coredata.TrustCenterReferenceOrderField{
"RANK": coredata.TrustCenterReferenceOrderFieldRank,
"NAME": coredata.TrustCenterReferenceOrderFieldName,
"CREATED_AT": coredata.TrustCenterReferenceOrderFieldCreatedAt,
"UPDATED_AT": coredata.TrustCenterReferenceOrderFieldUpdatedAt,
}
marshalNTrustCenterReferenceOrderField2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐTrustCenterReferenceOrderField = map[coredata.TrustCenterReferenceOrderField]string{
coredata.TrustCenterReferenceOrderFieldRank: "RANK",
coredata.TrustCenterReferenceOrderFieldName: "NAME",
coredata.TrustCenterReferenceOrderFieldCreatedAt: "CREATED_AT",
coredata.TrustCenterReferenceOrderFieldUpdatedAt: "UPDATED_AT",

View File

@@ -35,6 +35,7 @@ func NewTrustCenterReference(tcc *coredata.TrustCenterReference) *TrustCenterRef
Name: tcc.Name,
Description: tcc.Description,
WebsiteURL: tcc.WebsiteURL,
Rank: tcc.Rank,
CreatedAt: tcc.CreatedAt,
UpdatedAt: tcc.UpdatedAt,
}

View File

@@ -1734,6 +1734,7 @@ type TrustCenterReference struct {
Description string `json:"description"`
WebsiteURL string `json:"websiteUrl"`
LogoURL string `json:"logoUrl"`
Rank int `json:"rank"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
@@ -2024,6 +2025,7 @@ type UpdateTrustCenterReferenceInput struct {
Description *string `json:"description,omitempty"`
WebsiteURL *string `json:"websiteUrl,omitempty"`
LogoFile *graphql.Upload `json:"logoFile,omitempty"`
Rank *int `json:"rank,omitempty"`
}
type UpdateTrustCenterReferencePayload struct {

View File

@@ -1341,7 +1341,7 @@ func (r *mutationResolver) CreateTrustCenterReference(ctx context.Context, input
}
return &types.CreateTrustCenterReferencePayload{
TrustCenterReferenceEdge: types.NewTrustCenterReferenceEdge(reference, coredata.TrustCenterReferenceOrderFieldCreatedAt),
TrustCenterReferenceEdge: types.NewTrustCenterReferenceEdge(reference, coredata.TrustCenterReferenceOrderFieldRank),
}, nil
}
@@ -1354,6 +1354,7 @@ func (r *mutationResolver) UpdateTrustCenterReference(ctx context.Context, input
Name: input.Name,
Description: input.Description,
WebsiteURL: input.WebsiteURL,
Rank: input.Rank,
}
if input.LogoFile != nil {
@@ -4944,7 +4945,7 @@ func (r *trustCenterResolver) References(ctx context.Context, obj *types.TrustCe
prb := r.ProboService(ctx, obj.ID.TenantID())
pageOrderBy := page.OrderBy[coredata.TrustCenterReferenceOrderField]{
Field: coredata.TrustCenterReferenceOrderFieldName,
Field: coredata.TrustCenterReferenceOrderFieldRank,
Direction: page.OrderDirectionAsc,
}
if orderBy != nil {

View File

@@ -891,8 +891,8 @@ func (r *trustCenterResolver) References(ctx context.Context, obj *types.TrustCe
publicTrustService := r.PublicTrustService(ctx, obj.ID.TenantID())
pageOrderBy := page.OrderBy[coredata.TrustCenterReferenceOrderField]{
Field: coredata.TrustCenterReferenceOrderFieldCreatedAt,
Direction: page.OrderDirectionDesc,
Field: coredata.TrustCenterReferenceOrderFieldRank,
Direction: page.OrderDirectionAsc,
}
cursor := types.NewCursor(first, after, last, before, pageOrderBy)