Files
Xu_BrowserBookmark/apps/server/src/routes/admin.routes.js

148 lines
5.3 KiB
JavaScript
Raw Normal View History

import { computeUrlHash, normalizeUrl } from "@browser-bookmark/shared";
import { httpError } from "../lib/httpErrors.js";
import { requireAdmin, isAdminEmail } from "../lib/admin.js";
import { bookmarkRowToDto, folderRowToDto, userRowToDto } from "../lib/rows.js";
function toUserDtoWithAdminOverride(app, row) {
const dto = userRowToDto(row);
if (isAdminEmail(app, dto.email)) dto.role = "admin";
return dto;
}
export async function adminRoutes(app) {
app.get(
"/admin/users",
{ preHandler: [async (req) => requireAdmin(app, req)] },
async () => {
const res = await app.pg.query(
"select id, email, role, created_at, updated_at from users order by created_at desc limit 500"
);
return res.rows.map((r) => toUserDtoWithAdminOverride(app, r));
}
);
app.get(
"/admin/users/:id/folders",
{ preHandler: [async (req) => requireAdmin(app, req)] },
async (req) => {
const userId = req.params?.id;
if (!userId) throw httpError(400, "user id required");
const orderBy = app.features?.folderSortOrder
? "parent_id nulls first, sort_order asc, name asc"
: "parent_id nulls first, name asc";
const res = await app.pg.query(
`select * from bookmark_folders where user_id=$1 order by ${orderBy} limit 1000`,
[userId]
);
return res.rows.map(folderRowToDto);
}
);
app.get(
"/admin/users/:id/bookmarks",
{ preHandler: [async (req) => requireAdmin(app, req)] },
async (req) => {
const userId = req.params?.id;
if (!userId) throw httpError(400, "user id required");
const q = (req.query?.q || "").trim();
const params = [userId];
let where = "where user_id=$1 and deleted_at is null";
if (q) {
params.push(`%${q}%`);
where += ` and (title ilike $${params.length} or url ilike $${params.length})`;
}
const orderBy = app.features?.bookmarkSortOrder
? "folder_id nulls first, sort_order asc, updated_at desc"
: "updated_at desc";
const res = await app.pg.query(
`select * from bookmarks ${where} order by ${orderBy} limit 500`,
params
);
return res.rows.map(bookmarkRowToDto);
}
);
app.delete(
"/admin/users/:userId/bookmarks/:bookmarkId",
{ preHandler: [async (req) => requireAdmin(app, req)] },
async (req) => {
const userId = req.params?.userId;
const bookmarkId = req.params?.bookmarkId;
if (!userId || !bookmarkId) throw httpError(400, "userId and bookmarkId required");
const res = await app.pg.query(
"update bookmarks set deleted_at=now(), updated_at=now() where id=$1 and user_id=$2 and deleted_at is null returning *",
[bookmarkId, userId]
);
if (!res.rows[0]) throw httpError(404, "bookmark not found");
return bookmarkRowToDto(res.rows[0]);
}
);
app.delete(
"/admin/users/:userId/folders/:folderId",
{ preHandler: [async (req) => requireAdmin(app, req)] },
async (req) => {
const userId = req.params?.userId;
const folderId = req.params?.folderId;
if (!userId || !folderId) throw httpError(400, "userId and folderId required");
const res = await app.pg.query(
"delete from bookmark_folders where id=$1 and user_id=$2 returning *",
[folderId, userId]
);
if (!res.rows[0]) throw httpError(404, "folder not found");
return folderRowToDto(res.rows[0]);
}
);
app.post(
"/admin/users/:userId/bookmarks/:bookmarkId/copy-to-me",
{ preHandler: [async (req) => requireAdmin(app, req)] },
async (req) => {
const sourceUserId = req.params?.userId;
const bookmarkId = req.params?.bookmarkId;
const adminUserId = req.adminUser?.id;
if (!sourceUserId || !bookmarkId) throw httpError(400, "userId and bookmarkId required");
if (!adminUserId) throw httpError(401, "unauthorized");
const srcRes = await app.pg.query(
"select * from bookmarks where id=$1 and user_id=$2 and deleted_at is null",
[bookmarkId, sourceUserId]
);
const src = srcRes.rows[0];
if (!src) throw httpError(404, "bookmark not found");
const urlNormalized = normalizeUrl(src.url);
const urlHash = computeUrlHash(urlNormalized);
const existing = await app.pg.query(
"select * from bookmarks where user_id=$1 and url_hash=$2 and deleted_at is null limit 1",
[adminUserId, urlHash]
);
if (existing.rows[0]) {
const merged = await app.pg.query(
`update bookmarks
set title=$1, url=$2, url_normalized=$3, visibility='private', folder_id=null, source='manual', updated_at=now()
where id=$4
returning *`,
[src.title, src.url, urlNormalized, existing.rows[0].id]
);
return bookmarkRowToDto(merged.rows[0]);
}
const res = await app.pg.query(
`insert into bookmarks (user_id, folder_id, title, url, url_normalized, url_hash, visibility, source)
values ($1, null, $2, $3, $4, $5, 'private', 'manual')
returning *`,
[adminUserId, src.title, src.url, urlNormalized, urlHash]
);
return bookmarkRowToDto(res.rows[0]);
}
);
}