118 lines
3.4 KiB
TypeScript
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 };
|