Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions core/lib/server/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { type NextApiRequest, type NextApiResponse } from "next/types";
import { ErrorHttpStatusCode } from "@ts-rest/core";
import { NextRequest, NextResponse } from "next/server";

export class HTTPStatusError <Status extends ErrorHttpStatusCode> extends Error {
export class HTTPStatusError<Status extends ErrorHttpStatusCode> extends Error {
readonly status: ErrorHttpStatusCode;

constructor(status: Status, message?: string) {
super(`HTTP Error ${status}${message ? ': ' + message : ''}`);
super(`HTTP Error ${status}${message ? ": " + message : ""}`);
this.status = status;
}
}
Expand Down Expand Up @@ -38,18 +38,17 @@ export class NotFoundError extends HTTPStatusError<404> {
// For use in app router API routes
export const handleErrors = async (routeHandler) => {
try {
return await routeHandler()
} catch(error) {
return await routeHandler();
} catch (error) {
if (error instanceof HTTPStatusError) {
return NextResponse.json({ message: error.message }, { status: error.status })
return NextResponse.json({ message: error.message }, { status: error.status });
}
if (error instanceof Error) {
console.error(error.message);
}
return NextResponse.json({ message: "Internal Server Error"}, { status: 500 })
return NextResponse.json({ message: "Internal Server Error" }, { status: 500 });
}

}
};

export const tsRestHandleErrors = (error: unknown, req: NextApiRequest, res: NextApiResponse) => {
if (error instanceof HTTPStatusError) {
Expand All @@ -59,4 +58,6 @@ export const tsRestHandleErrors = (error: unknown, req: NextApiRequest, res: Nex
console.error(error.message);
}
return res.status(500).json({ message: "Internal Server Error" });
};
};

export const InstanceNotFoundError = new NotFoundError("Integration instance not found");
23 changes: 20 additions & 3 deletions core/lib/server/integrations.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
import prisma from "~/prisma/db";
import { InstanceNotFoundError } from "./errors";

export async function setIntegrationInstanceConfig(instanceId: string, config: object) {
return await prisma.integrationInstance.update({
export async function updateIntegrationInstanceConfig(instanceId: string, config: object) {
const instance = await prisma.integrationInstance.findUnique({
where: {
id: instanceId,
},
data: {
});

if (!instance) {
throw InstanceNotFoundError;
}
return await prisma.integrationInstance.upsert({
where: {
id: instanceId,
},
update: {
config,
},
create: {
id: instanceId,
config,
integrationId: instance.integrationId,
communityId: instance.communityId,
name: instance.name,
},
});
}
Expand Down
3 changes: 1 addition & 2 deletions core/lib/server/pub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
} from "contracts";
import prisma from "~/prisma/db";
import { RecursiveInclude, makeRecursiveInclude } from "../types";
import { NotFoundError } from "./errors";
import { InstanceNotFoundError, NotFoundError } from "./errors";
import { Prisma } from "@prisma/client";
import { expect } from "utils";

Expand Down Expand Up @@ -42,7 +42,6 @@ export const getPub = async (pubId: string, depth = 0): Promise<GetPubResponseBo
return recursivelyDenormalizePubValues(pub);
};

const InstanceNotFoundError = new NotFoundError("Integration instance not found");
const PubNotFoundError = new NotFoundError("Pub not found");
const PubFieldSlugsNotFoundError = new NotFoundError("Pub fields not found");

Expand Down
4 changes: 2 additions & 2 deletions core/pages/api/v0/[...ts-rest].ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
getPub,
getPubType,
getSuggestedMembers,
setIntegrationInstanceConfig,
updateIntegrationInstanceConfig,
setIntegrationInstanceState,
tsRestHandleErrors,
updatePub,
Expand Down Expand Up @@ -131,7 +131,7 @@ const integrationsRouter = createNextRoute(api.integrations, {
},
setInstanceConfig: async ({ headers, params, body }) => {
checkAuthentication(headers.authorization);
const config = await setIntegrationInstanceConfig(params.instanceId, { ...body });
const config = await updateIntegrationInstanceConfig(params.instanceId, { ...body });
return { status: 200, body: config };
},
getInstanceConfig: async ({ headers, params }) => {
Expand Down
7 changes: 7 additions & 0 deletions core/prisma/exampleCommunitySeeds/unjournal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,13 @@ export default async function main(prisma: PrismaClient, communityUUID: string)
name: "Unjournal Evaluation Manager",
integrationId: evaluationsIntegration.id,
stageId: stageIds[3],
config: {
pubTypeId: submissionTypeId,
template: {
subject: "You've been invited to review a submission on PubPub",
message: `Please reach out if you have any questions.`,
},
},
},
];

Expand Down
2 changes: 1 addition & 1 deletion integrations/evaluations/app/actions/evaluate/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const evaluate = async (instanceId: string, pubId: string, values: PubVal
}
try {
const pub = await client.createPub(instanceId, {
pubTypeId: instance.pubTypeId,
pubTypeId: instance.config.pubTypeId,
parentId: pubId,
values: values,
});
Expand Down
2 changes: 1 addition & 1 deletion integrations/evaluations/app/actions/evaluate/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ export default async function Page(props: Props) {

const instance = await getInstanceConfig(instanceId);
//dangerously assert instance exists
const pubType = await client.getPubType(instanceId, instance!.pubTypeId);
const pubType = await client.getPubType(instanceId, instance!.config.pubTypeId);
return <Evaluate instanceId={instanceId} pub={pub} pubType={pubType} />;
}
12 changes: 6 additions & 6 deletions integrations/evaluations/app/actions/manage/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const save = async (
}
const pub = await client.getPub(instanceId, pubId);
const evaluations = pub.children.filter(
(child) => child.pubTypeId === instanceConfig.pubTypeId
(child) => child.pubTypeId === instanceConfig.config.pubTypeId
);
const evaluationsByEvaluator = evaluations.reduce((acc, evaluation) => {
acc[evaluation.values["unjournal:evaluator"] as string] = evaluation;
Expand All @@ -45,7 +45,7 @@ export const save = async (
// New evaluator added. Make the corresponding evaluation pub.
await client.createPub(instanceId, {
parentId: pubId,
pubTypeId: instanceConfig.pubTypeId,
pubTypeId: instanceConfig.config.pubTypeId,
values: {
"unjournal:title": `Evaluation of ${pubTitle} by ${invite.firstName} ${invite.lastName}`,
"unjournal:evaluator": invite.userId,
Expand Down Expand Up @@ -76,12 +76,12 @@ export const save = async (
}
);
// Save updated email template and job run time
instanceState[invite.userId] = {
instanceState.state[invite.userId] = {
inviteTemplate: invite.template,
inviteTime: instanceState[invite.userId]?.inviteTime ?? runAt.toString(),
inviteTime: instanceState.state[invite.userId]?.inviteTime ?? runAt.toString(),
};
}
await setInstanceState(instanceId, pubId, instanceState);
await setInstanceState(instanceId, pubId, instanceState.state);
revalidatePath("/");
return { success: true };
} catch (error) {
Expand Down Expand Up @@ -109,7 +109,7 @@ export const remove = async (instanceId: string, pubId: string, userId: string)
await client.deletePub(instanceId, evaluation.id);
}
if (instanceState !== undefined) {
const { [userId]: _, ...instanceStateWithoutEvaluator } = instanceState;
const { [userId]: _, ...instanceStateWithoutEvaluator } = instanceState.state;
setInstanceState(instanceId, pubId, instanceStateWithoutEvaluator);
}
await client.unscheduleEmail(instanceId, makeInviteJobKey(instanceId, pubId, userId));
Expand Down
11 changes: 6 additions & 5 deletions integrations/evaluations/app/actions/manage/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export default async function Page(props: Props) {
const { instanceId, pubId } = props.searchParams;
const instanceConfig = expect(await getInstanceConfig(instanceId));
const instanceState = (await getInstanceState(instanceId, pubId)) ?? {};
console.log(instanceState);
// Fetch the pub and its children
const pub = await client.getPub(instanceId, pubId);
// Load user info for each of the child evaluations
Expand All @@ -23,25 +24,25 @@ export default async function Page(props: Props) {
instanceId,
pub.children
// Only consider the children that are evaluations
.filter((child) => child.pubTypeId === instanceConfig.pubTypeId)
.filter((child) => child.pubTypeId === instanceConfig.config.pubTypeId)
// Extract the evaluator user id
.map((evaluation) => evaluation.values["unjournal:evaluator"] as string)
)
: [];

evaluators.sort(
(a, b) =>
new Date(instanceState[a.id]?.inviteTime).getTime() -
new Date(instanceState[b.id]?.inviteTime).getTime()
new Date(instanceState.state[a.id]?.inviteTime).getTime() -
new Date(instanceState.state[b.id]?.inviteTime).getTime()
);

return (
<EvaluatorInviteForm
instanceId={instanceId}
pub={pub}
evaluators={evaluators}
instanceConfig={instanceConfig}
instanceState={instanceState}
instanceConfig={instanceConfig.config}
instanceState={instanceState.state}
/>
);
}
4 changes: 2 additions & 2 deletions integrations/evaluations/app/configure/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export default async function Page(props: Props) {
return (
<Configure
instanceId={instanceId}
pubTypeId={instance?.pubTypeId}
template={instance?.template}
pubTypeId={instance?.config.pubTypeId}
template={instance?.config.template}
/>
);
}
37 changes: 10 additions & 27 deletions integrations/evaluations/lib/instance.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as redis from "redis";
import { client } from "~/lib/pubpub";

export type EmailTemplate = { subject: string; message: string };

Expand All @@ -14,46 +14,29 @@ export type InstanceState = {
};
};

let client: redis.RedisClientType;
let clientConnect: Promise<void>;

const db = async () => {
if (!client) {
client = redis.createClient({ url: process.env.REDIS_CONNECTION_STRING });
clientConnect = client.connect();
}
await clientConnect;
return client;
};

export const makeInstanceConfig = (): InstanceConfig => ({
pubTypeId: "",
template: { subject: "", message: "" },
});

export const getInstanceConfig = async (instanceId: string) => {
const instance = await (await db()).get(instanceId);
return instance ? (JSON.parse(instance) as InstanceConfig) : undefined;
const instanceConfig = await client.getInstanceConfig(instanceId);
return instanceConfig ? instanceConfig as any : undefined;
};

export const setInstanceConfig = async (
instanceId: string,
instance: InstanceConfig
): Promise<InstanceConfig> => {
(await db()).set(instanceId, JSON.stringify(instance));
return instance;
export const setInstanceConfig = async (instanceId: string, instance: any): Promise<any> => {
return await client.setInstanceConfig(instanceId, instance);
};

export const getInstanceState = async (instanceId: string, pubId: string) => {
const state = await (await db()).get(`${instanceId}:${pubId}`);
return state ? (JSON.parse(state) as InstanceState) : undefined;
const instanceState = await client.getInstanceState(instanceId, pubId);
return instanceState ? instanceState as any : undefined;
};

export const setInstanceState = async (
instanceId: string,
pubId: string,
state: InstanceState
): Promise<InstanceState> => {
(await db()).set(`${instanceId}:${pubId}`, JSON.stringify(state));
return state;
state: any
): Promise<any> => {
return await client.setInstanceState(instanceId, pubId, state);
};