@formancy/server 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,12 @@
1
1
  import { createHash, randomBytes, randomUUID } from "node:crypto";
2
- import { AUDIT_ACTIONS, auditedBy, authenticateApiKey, authenticateLocal, can, collectAbandonedFiles, completeBuilderRequest, createApiKey, createLocalUser, createSubmission, decodeSolution, exportCsv, formToFill, formVersion, healthOf, keepExamples, listForms, listSubmissions, listVersions, mintChallenge, offerUpload, publishForm, readExamples, replayDelivery, resolveForm, resumeDraft, saveDraft, screenUpload, setFormAccess, startDraft, verifySolution } from "@formancy/server-core";
2
+ import { AUDIT_ACTIONS, auditedBy, authenticateApiKey, authenticateLocal, can, collectAbandonedFiles, completeBuilderRequest, createApiKey, createLocalUser, createSubmission, decodeSolution, exportCsv, formToFill, formVersion, healthOf, keepExamples, listForms, listSubmissionPage, listSubmissions, listVersions, mintChallenge, offerUpload, publishForm, readExamples, replayDelivery, resolveForm, resumeDraft, saveDraft, screenUpload, setFormAccess, startDraft, summariseResponses, verifySolution } from "@formancy/server-core";
3
3
  import { MODEL_REQUEST_KINDS } from "@formancy/builder-core";
4
4
  import Fastify, { LogController } from "fastify";
5
5
  import rateLimit from "@fastify/rate-limit";
6
6
  import { hash, verify } from "@node-rs/argon2";
7
7
  import { SignJWT, jwtVerify } from "jose";
8
8
  import { METHODS, STATUS_CODES, validateHeaderName, validateHeaderValue } from "node:http";
9
- import { and, desc, eq, inArray, isNull, lt, lte, max, ne, or } from "drizzle-orm";
9
+ import { and, desc, eq, inArray, isNull, lt, lte, max, ne, or, sql } from "drizzle-orm";
10
10
  import { drizzle } from "drizzle-orm/postgres-js";
11
11
  import { integer, jsonb, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
12
12
  import postgres from "postgres";
@@ -1193,6 +1193,26 @@ async function createApp(storage, options) {
1193
1193
  });
1194
1194
  app.get("/f/:path/submissions", { preHandler: requires("submission.read") }, async (request, reply) => {
1195
1195
  const { path } = request.params;
1196
+ const query = request.query ?? {};
1197
+ if (query.limit !== void 0 || query.after !== void 0) {
1198
+ const asked = typeof query.limit === "string" ? Number.parseInt(query.limit, 10) : 100;
1199
+ const after = query.after === void 0 ? void 0 : typeof query.after === "string" ? query.after : "";
1200
+ const page = await listSubmissionPage(deps, path, {
1201
+ limit: Number.isFinite(asked) ? asked : 100,
1202
+ after
1203
+ });
1204
+ if (page === void 0) return reply.code(404).send({ error: "unknown_form" });
1205
+ if (page.kind === "bad_cursor") return reply.code(400).send({ error: "bad_cursor" });
1206
+ await audit(request, {
1207
+ action: "submission.read",
1208
+ subject: path,
1209
+ detail: { count: page.submissions.length }
1210
+ });
1211
+ return reply.send({
1212
+ submissions: page.submissions,
1213
+ next: page.next
1214
+ });
1215
+ }
1196
1216
  const listed = await listSubmissions(deps, path);
1197
1217
  if (listed === void 0) return reply.code(404).send({ error: "unknown_form" });
1198
1218
  await audit(request, {
@@ -1202,6 +1222,21 @@ async function createApp(storage, options) {
1202
1222
  });
1203
1223
  return reply.send({ submissions: listed });
1204
1224
  });
1225
+ app.get("/f/:path/summary", { preHandler: requires("submission.read") }, async (request, reply) => {
1226
+ const { path } = request.params;
1227
+ const versions = await summariseResponses(deps, path);
1228
+ if (versions === void 0) return reply.code(404).send({ error: "unknown_form" });
1229
+ const count = versions.reduce((total, version) => total + version.responses, 0);
1230
+ await audit(request, {
1231
+ action: "submission.read",
1232
+ subject: path,
1233
+ detail: {
1234
+ count,
1235
+ summary: true
1236
+ }
1237
+ });
1238
+ return reply.send({ versions });
1239
+ });
1205
1240
  app.get("/f/:path/submissions/export.csv", { preHandler: requires("submission.export") }, async (request, reply) => {
1206
1241
  const { path } = request.params;
1207
1242
  const csv = await exportCsv(deps, path);
@@ -1387,6 +1422,7 @@ async function bootstrapSchema(sql) {
1387
1422
  data jsonb NOT NULL,
1388
1423
  submitted_at timestamptz NOT NULL
1389
1424
  )`;
1425
+ await sql`CREATE INDEX IF NOT EXISTS submissions_by_form ON submissions (form_id, submitted_at DESC, id DESC)`;
1390
1426
  await sql`
1391
1427
  CREATE TABLE IF NOT EXISTS webhooks (
1392
1428
  id uuid PRIMARY KEY,
@@ -1638,8 +1674,8 @@ function toApiKeyRecord(row) {
1638
1674
  }
1639
1675
  //#endregion
1640
1676
  //#region src/postgres-storage.ts
1641
- function createPostgresStorage(sql) {
1642
- const db = drizzle(sql);
1677
+ function createPostgresStorage(sql$1) {
1678
+ const db = drizzle(sql$1);
1643
1679
  return {
1644
1680
  ...examplesStorage(db),
1645
1681
  ...identitiesStorage(db),
@@ -1873,6 +1909,9 @@ function createPostgresStorage(sql) {
1873
1909
  async listSubmissionsByForm(formId) {
1874
1910
  return (await db.select().from(submissions).where(eq(submissions.formId, formId)).orderBy(desc(submissions.submittedAt), desc(submissions.id))).map(toSubmissionRecord);
1875
1911
  },
1912
+ async pageSubmissionsByForm(formId, limit, before) {
1913
+ return (await db.select().from(submissions).where(and(eq(submissions.formId, formId), before === void 0 ? void 0 : sql`(${submissions.submittedAt}, ${submissions.id}) < (${before.submittedAt}::timestamptz, ${before.id}::uuid)`)).orderBy(desc(submissions.submittedAt), desc(submissions.id)).limit(limit)).map(toSubmissionRecord);
1914
+ },
1876
1915
  async listVersionsByForm(formId) {
1877
1916
  return (await db.select().from(formVersions).where(eq(formVersions.formId, formId)).orderBy(desc(formVersions.version))).map((row) => ({
1878
1917
  id: row.id,
@@ -2380,4 +2419,4 @@ function createCompleter(settings, fetch) {
2380
2419
  //#endregion
2381
2420
  export { SUBMISSION_TOKEN_HEADER as _, createLocalFileStore as a, createPostgresStorage as c, LOG_LEVELS as d, createServerLog as f, SCHEMA_HASH_HEADER as g, maxFileBytesFrom as h, startFileCollector as i, bootstrapSchema as l, workerLog as m, MODEL_PROVIDERS as n, sha256 as o, databaseNotices as p, modelSettings as r, createPostgresRateLimitStore as s, createCompleter as t, createApp as u };
2382
2421
 
2383
- //# sourceMappingURL=completers-CQQDS4qr.mjs.map
2422
+ //# sourceMappingURL=completers-Cjn4y1ZH.mjs.map