129 lines
3.8 KiB
TypeScript
129 lines
3.8 KiB
TypeScript
import { db } from "@/db";
|
|
import { submissions } from "@/db/schema";
|
|
import {
|
|
and,
|
|
asc,
|
|
count,
|
|
desc,
|
|
eq,
|
|
gte,
|
|
ilike,
|
|
lt,
|
|
or,
|
|
sql,
|
|
type SQL,
|
|
} from "drizzle-orm";
|
|
|
|
export const RESULTS_PAGE_SIZE = 25;
|
|
|
|
export type ResultFilters = {
|
|
q: string;
|
|
page: number;
|
|
sort: "submittedAt" | "respondent";
|
|
direction: "asc" | "desc";
|
|
edited: "all" | "edited" | "unedited";
|
|
dateFrom: string;
|
|
dateTo: string;
|
|
};
|
|
|
|
export function parseResultFilters(
|
|
params: Record<string, string | string[] | undefined>
|
|
): ResultFilters {
|
|
const value = (key: string) => {
|
|
const entry = params[key];
|
|
return Array.isArray(entry) ? entry[0] ?? "" : entry ?? "";
|
|
};
|
|
const page = Number.parseInt(value("page"), 10);
|
|
const sort = value("sort");
|
|
const direction = value("direction");
|
|
const edited = value("edited");
|
|
|
|
return {
|
|
q: value("q").trim().slice(0, 100),
|
|
page: Number.isFinite(page) && page > 0 ? page : 1,
|
|
sort: sort === "respondent" ? "respondent" : "submittedAt",
|
|
direction: direction === "asc" ? "asc" : "desc",
|
|
edited: edited === "edited" || edited === "unedited" ? edited : "all",
|
|
dateFrom: /^\d{4}-\d{2}-\d{2}$/.test(value("dateFrom")) ? value("dateFrom") : "",
|
|
dateTo: /^\d{4}-\d{2}-\d{2}$/.test(value("dateTo")) ? value("dateTo") : "",
|
|
};
|
|
}
|
|
|
|
function bangkokBoundary(date: string, end: boolean) {
|
|
if (!date) return null;
|
|
const value = new Date(`${date}T00:00:00+07:00`);
|
|
if (end) value.setUTCDate(value.getUTCDate() + 1);
|
|
return value;
|
|
}
|
|
|
|
function buildWhere(formId: string, filters: ResultFilters) {
|
|
const conditions: SQL[] = [eq(submissions.formId, formId)];
|
|
if (filters.q) {
|
|
const query = `%${filters.q}%`;
|
|
conditions.push(
|
|
or(
|
|
ilike(submissions.userName, query),
|
|
ilike(submissions.userDiscordId, query)
|
|
)!
|
|
);
|
|
}
|
|
if (filters.edited === "edited") {
|
|
conditions.push(sql`jsonb_array_length(coalesce(${submissions.editHistory}, '[]'::jsonb)) > 0`);
|
|
}
|
|
if (filters.edited === "unedited") {
|
|
conditions.push(sql`jsonb_array_length(coalesce(${submissions.editHistory}, '[]'::jsonb)) = 0`);
|
|
}
|
|
const from = bangkokBoundary(filters.dateFrom, false);
|
|
const to = bangkokBoundary(filters.dateTo, true);
|
|
if (from) conditions.push(gte(submissions.submittedAt, from));
|
|
if (to) conditions.push(lt(submissions.submittedAt, to));
|
|
return and(...conditions)!;
|
|
}
|
|
|
|
export async function getFilteredSubmissions(
|
|
formId: string,
|
|
filters: ResultFilters,
|
|
options: { paginate?: boolean } = {}
|
|
) {
|
|
const where = buildWhere(formId, filters);
|
|
const orderDirection = filters.direction === "asc" ? asc : desc;
|
|
const order =
|
|
filters.sort === "respondent"
|
|
? orderDirection(submissions.userName)
|
|
: orderDirection(submissions.submittedAt);
|
|
const [{ total }] = await db
|
|
.select({ total: count() })
|
|
.from(submissions)
|
|
.where(where);
|
|
|
|
const baseQuery = db
|
|
.select()
|
|
.from(submissions)
|
|
.where(where)
|
|
.orderBy(order, orderDirection(submissions.id));
|
|
const rows =
|
|
options.paginate === false
|
|
? await baseQuery
|
|
: await baseQuery
|
|
.limit(RESULTS_PAGE_SIZE)
|
|
.offset((filters.page - 1) * RESULTS_PAGE_SIZE);
|
|
|
|
return { rows, total };
|
|
}
|
|
|
|
export function resultFiltersToSearchParams(
|
|
filters: ResultFilters,
|
|
overrides: Partial<ResultFilters> = {}
|
|
) {
|
|
const next = { ...filters, ...overrides };
|
|
const params = new URLSearchParams();
|
|
if (next.q) params.set("q", next.q);
|
|
if (next.page > 1) params.set("page", String(next.page));
|
|
if (next.sort !== "submittedAt") params.set("sort", next.sort);
|
|
if (next.direction !== "desc") params.set("direction", next.direction);
|
|
if (next.edited !== "all") params.set("edited", next.edited);
|
|
if (next.dateFrom) params.set("dateFrom", next.dateFrom);
|
|
if (next.dateTo) params.set("dateTo", next.dateTo);
|
|
return params.toString();
|
|
}
|