oai-reverse-proxy/src/admin/api/users.ts

118 lines
3.4 KiB
TypeScript

import { Router } from "express";
import { z } from "zod";
import * as userStore from "../../shared/users/user-store";
import { parseSort, sortBy } from "../../shared/utils";
import { UserPartialSchema, UserSchema } from "../../shared/users/schema";
const router = Router();
/**
* Returns a list of all users, sorted by prompt count and then last used time.
* GET /admin/users
*/
router.get("/", (req, res) => {
const sort = parseSort(req.query.sort) || ["promptCount", "lastUsedAt"];
const users = userStore.getUsers().sort(sortBy(sort, false));
res.json({ users, count: users.length });
});
/**
* Returns the user with the given token.
* GET /admin/users/:token
*/
router.get("/:token", (req, res) => {
const user = userStore.getUser(req.params.token);
if (!user) {
return res.status(404).json({ error: "Not found" });
}
res.json(user);
});
/**
* Creates a new user.
* Optionally accepts a JSON body containing `type`, and for temporary-type
* users, `tokenLimits` and `expiresAt` fields.
* Returns the created user's token.
* POST /admin/users
*/
router.post("/", (req, res) => {
const body = req.body;
const base = z.object({
type: UserSchema.shape.type.exclude(["temporary"]).default("normal"),
});
const tempUser = base
.extend({
type: z.literal("temporary"),
expiresAt: UserSchema.shape.expiresAt,
tokenLimits: UserSchema.shape.tokenLimits,
})
.required();
const schema = z.union([base, tempUser]);
const result = schema.safeParse(body);
if (!result.success) {
return res.status(400).json({ error: result.error });
}
const token = userStore.createUser({ ...result.data });
res.json({ token });
});
/**
* Updates the user with the given token, creating them if they don't exist.
* Accepts a JSON body containing at least one field on the User type.
* Returns the upserted user.
* PUT /admin/users/:token
*/
router.put("/:token", (req, res) => {
const result = UserPartialSchema.safeParse({
...req.body,
token: req.params.token,
});
if (!result.success) {
return res.status(400).json({ error: result.error });
}
userStore.upsertUser(result.data);
res.json(userStore.getUser(req.params.token));
});
/**
* Bulk-upserts users given a list of User updates.
* Accepts a JSON body with the field `users` containing an array of updates.
* Returns an object containing the upserted users and the number of upserts.
* PUT /admin/users
*/
router.put("/", (req, res) => {
const result = z.array(UserPartialSchema).safeParse(req.body.users);
if (!result.success) {
return res.status(400).json({ error: result.error });
}
const upserts = result.data.map((user) => userStore.upsertUser(user));
res.json({ upserted_users: upserts, count: upserts.length });
});
/**
* Disables the user with the given token. Optionally accepts a `disabledReason`
* query parameter.
* Returns the disabled user.
* DELETE /admin/users/:token
*/
router.delete("/:token", (req, res) => {
const user = userStore.getUser(req.params.token);
const disabledReason = z
.string()
.optional()
.safeParse(req.query.disabledReason);
if (!disabledReason.success) {
return res.status(400).json({ error: disabledReason.error });
}
if (!user) {
return res.status(404).json({ error: "Not found" });
}
userStore.disableUser(req.params.token, disabledReason.data);
res.json(userStore.getUser(req.params.token));
});
export { router as usersApiRouter };