72 lines
2.1 KiB
TypeScript
72 lines
2.1 KiB
TypeScript
import { z } from "zod";
|
|
|
|
export const developerScopes = [
|
|
"maps:read",
|
|
"account:read",
|
|
"account:maps:read",
|
|
"account:games:read",
|
|
] as const;
|
|
export type DeveloperScope = (typeof developerScopes)[number];
|
|
export type DeveloperKey = {
|
|
id: string;
|
|
name: string;
|
|
prefix: string;
|
|
scopes: DeveloperScope[];
|
|
createdAt: string;
|
|
expiresAt: string;
|
|
revokedAt: string | null;
|
|
lastUsedAt: string | null;
|
|
};
|
|
export const developerKeyInput = z
|
|
.object({
|
|
name: z.string().trim().min(1).max(60),
|
|
scopes: z
|
|
.array(z.enum(developerScopes))
|
|
.min(1)
|
|
.max(4)
|
|
.refine(
|
|
(values) => new Set(values).size === values.length,
|
|
"Duplicate permissions",
|
|
),
|
|
expiresDays: z.number().int().min(1).max(365).default(90),
|
|
})
|
|
.strict();
|
|
|
|
export function developerPagination(url: URL, search = false) {
|
|
const allowed = new Set(
|
|
search ? ["limit", "offset", "q"] : ["limit", "offset"],
|
|
);
|
|
for (const key of url.searchParams.keys()) {
|
|
if (!allowed.has(key) || url.searchParams.getAll(key).length !== 1)
|
|
throw new Error("Unsupported or duplicate query parameter");
|
|
}
|
|
const integer = (key: string, fallback: number, min: number, max: number) => {
|
|
const raw = url.searchParams.get(key);
|
|
if (raw === null) return fallback;
|
|
if (!/^\d+$/.test(raw)) throw new Error(`Invalid ${key}`);
|
|
const value = Number(raw);
|
|
if (!Number.isSafeInteger(value) || value < min || value > max)
|
|
throw new Error(`Invalid ${key}`);
|
|
return value;
|
|
};
|
|
const q = (url.searchParams.get("q") ?? "").trim();
|
|
if (q.length > 80) throw new Error("Search must be 80 characters or fewer");
|
|
return {
|
|
limit: integer("limit", 20, 1, 50),
|
|
offset: integer("offset", 0, 0, 10000),
|
|
q,
|
|
};
|
|
}
|
|
export type DeveloperPagination = ReturnType<typeof developerPagination>;
|
|
export function developerPage(total: number, page: DeveloperPagination) {
|
|
return {
|
|
limit: page.limit,
|
|
offset: page.offset,
|
|
total,
|
|
nextOffset:
|
|
page.offset + page.limit < total && page.offset + page.limit <= 10000
|
|
? page.offset + page.limit
|
|
: null,
|
|
};
|
|
}
|