wow
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
parent
4c3d96778d
commit
3253e91d95
4 changed files with 89 additions and 27 deletions
|
|
@ -263,6 +263,57 @@ export default new Elysia({ prefix: "/auth" })
|
|||
}),
|
||||
})
|
||||
|
||||
.delete("/user/:id", async ({ params, jwt, cookie: { mizuki }, status }) => {
|
||||
const rawToken = mizuki.value;
|
||||
if (typeof rawToken !== "string" || rawToken.length === 0) {
|
||||
return status(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const payload = await jwt.verify(rawToken);
|
||||
if (!payload || typeof payload !== "object" || !("id" in payload) || typeof payload.id !== "string") {
|
||||
return status(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const requester = await User.findOne({ userId: payload.id });
|
||||
if (!requester || requester.role !== "admin") {
|
||||
return status(403, "Forbidden");
|
||||
}
|
||||
|
||||
const target = await User.findOne({ userId: params.id });
|
||||
if (!target) {
|
||||
return status(404, "User not found");
|
||||
}
|
||||
|
||||
if (hardcodedOwners.has(target.discordId)) {
|
||||
return status(400, "Hardcoded owners cannot be deleted");
|
||||
}
|
||||
|
||||
await User.deleteOne({ userId: params.id });
|
||||
|
||||
await createAuditLog({
|
||||
actor: {
|
||||
userId: requester.userId,
|
||||
discordId: requester.discordId,
|
||||
username: requester.username,
|
||||
role: requester.role,
|
||||
},
|
||||
action: "auth.user.delete",
|
||||
targetType: "user",
|
||||
targetId: params.id,
|
||||
summary: `${requester.username} deleted user ${target.username}`,
|
||||
detail: {
|
||||
userId: target.userId,
|
||||
discordId: target.discordId,
|
||||
},
|
||||
});
|
||||
|
||||
return { ok: true };
|
||||
}, {
|
||||
params: t.Object({
|
||||
id: t.String(),
|
||||
})
|
||||
})
|
||||
|
||||
.get("/users", async ({ jwt, cookie: { mizuki }, status }) => {
|
||||
const rawToken = mizuki.value;
|
||||
if (typeof rawToken !== "string" || rawToken.length === 0) {
|
||||
|
|
|
|||
|
|
@ -217,7 +217,17 @@ export default new Elysia({ prefix: "/post" })
|
|||
})
|
||||
})
|
||||
|
||||
.get("/list", async ({ query }) => {
|
||||
.get("/list", async ({ query, jwt, cookie: { mizuki }, status }) => {
|
||||
const rawToken = mizuki.value;
|
||||
if (typeof rawToken !== "string" || rawToken.length === 0) {
|
||||
return status(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const payload = await jwt.verify(rawToken);
|
||||
if (!payload || typeof payload !== "object" || !("id" in payload) || typeof payload.id !== "string") {
|
||||
return status(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const page = query.page;
|
||||
const pageSize = query.size || 10;
|
||||
const filterTags = normalizeQueryTags(query.tags);
|
||||
|
|
@ -260,22 +270,7 @@ export default new Elysia({ prefix: "/post" })
|
|||
}),
|
||||
})
|
||||
|
||||
.get("/detail/:id", async ({ params, status, jwt, cookie: { mizuki } }) => {
|
||||
const rawToken = mizuki.value;
|
||||
if (typeof rawToken !== "string" || rawToken.length === 0) {
|
||||
return status(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const payload = await jwt.verify(rawToken);
|
||||
if (!payload || typeof payload !== "object" || !("id" in payload) || typeof payload.id !== "string") {
|
||||
return status(401, "Unauthorized");
|
||||
}
|
||||
|
||||
const user = await User.findOne({ userId: payload.id });
|
||||
if (!user) {
|
||||
return status(401, "Unauthorized");
|
||||
}
|
||||
|
||||
.get("/detail/:id", async ({ params, status }) => {
|
||||
const post = await MediaUpload.findById(params.id);
|
||||
if (!post) {
|
||||
return status(404, "포스트를 찾을 수 없습니다.");
|
||||
|
|
|
|||
|
|
@ -416,16 +416,28 @@ export default function DashboardPage() {
|
|||
</select>
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
void updateRole(user);
|
||||
}}
|
||||
disabled={savingUserId === user.id || (pendingRole[user.id] ?? user.role) === user.role}
|
||||
className="border border-border px-3 py-1 disabled:opacity-50"
|
||||
>
|
||||
{savingUserId === user.id ? "저장 중..." : "저장"}
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
void updateRole(user);
|
||||
}}
|
||||
disabled={savingUserId === user.id || (pendingRole[user.id] ?? user.role) === user.role}
|
||||
className="border border-border px-3 py-1 disabled:opacity-50"
|
||||
>
|
||||
{savingUserId === user.id ? "저장 중..." : "저장"}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
void deleteUser(user);
|
||||
}}
|
||||
disabled={savingUserId === user.id}
|
||||
className="border border-red-300 bg-red-50 px-3 py-1 text-xs text-red-700 disabled:opacity-50"
|
||||
>
|
||||
삭제
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -102,6 +102,10 @@
|
|||
transition-timing-function: var(--ease-out-expo);
|
||||
}
|
||||
|
||||
html, body {
|
||||
overscroll-behavior-y: none;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.image-scale:hover {
|
||||
--tw-scale-x: 105%;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue