Merge branch 'AdenMGB-develop' into develop

This commit is contained in:
DecDuck
2025-03-10 11:41:40 +11:00
26 changed files with 1526 additions and 4 deletions

View File

@@ -0,0 +1,23 @@
import { defineEventHandler, createError } from "h3";
import newsManager from "~/server/internal/news";
export default defineEventHandler(async (event) => {
const userId = await event.context.session.getUserId(event);
if (!userId) {
throw createError({
statusCode: 401,
message: "Unauthorized",
});
}
const id = event.context.params?.id;
if (!id) {
throw createError({
statusCode: 400,
message: "Missing news ID",
});
}
await newsManager.delete(id);
return { success: true };
});

View File

@@ -0,0 +1,27 @@
import { defineEventHandler, createError } from "h3";
import aclManager from "~/server/internal/acls";
import newsManager from "~/server/internal/news";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["news:read"]);
if (!allowed)
throw createError({
statusCode: 403,
});
const id = h3.context.params?.id;
if (!id)
throw createError({
statusCode: 400,
message: "Missing news ID",
});
const news = await newsManager.fetchById(id);
if (!news)
throw createError({
statusCode: 404,
message: "News article not found",
});
return news;
});

View File

@@ -0,0 +1,36 @@
import { defineEventHandler, getQuery } from "h3";
import aclManager from "~/server/internal/acls";
import newsManager from "~/server/internal/news";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["news:read"]);
if (!allowed)
throw createError({
statusCode: 403,
});
const query = getQuery(h3);
const orderBy = query.order as "asc" | "desc";
if (orderBy) {
if (typeof orderBy !== "string" || !["asc", "desc"].includes(orderBy))
throw createError({ statusCode: 400, statusMessage: "Invalid order" });
}
const tags = query.tags as string[] | undefined;
if (tags) {
if (typeof tags !== "object" || !Array.isArray(tags))
throw createError({ statusCode: 400, statusMessage: "Invalid tags" });
}
const options = {
take: parseInt(query.limit as string),
skip: parseInt(query.skip as string),
orderBy: orderBy,
tags: tags?.map((e) => e.toString()),
search: query.search as string,
};
const news = await newsManager.fetch(options);
return news;
});

View File

@@ -0,0 +1,23 @@
import { defineEventHandler, createError, readBody } from "h3";
import aclManager from "~/server/internal/acls";
import newsManager from "~/server/internal/news";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["news:create"]);
if (!allowed) throw createError({ statusCode: 403 });
const body = await readBody(h3);
const article = await newsManager.create({
title: body.title,
description: body.description,
content: body.content,
tags: body.tags,
image: body.image,
authorId: body.authorId,
});
return article;
});

View File

@@ -93,7 +93,7 @@ export default defineEventHandler(async (h3) => {
profilePictureId,
async () => jdenticon.toPng(username, 256),
{},
[`anonymous:read`, `${userId}:write`]
[`internal:read`, `${userId}:write`]
);
const user = await prisma.user.create({
data: {

View File

@@ -0,0 +1,30 @@
import { defineEventHandler, createError } from "h3";
import aclManager from "~/server/internal/acls";
import newsManager from "~/server/internal/news";
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["news:read"]);
if (!userId)
throw createError({
statusCode: 403,
statusMessage: "Requires authentication",
});
const id = h3.context.params?.id;
if (!id)
throw createError({
statusCode: 400,
message: "Missing news ID",
});
const news = await newsManager.fetchById(id);
if (!news)
throw createError({
statusCode: 404,
message: "News article not found",
});
return news;
});

View File

@@ -0,0 +1,37 @@
import { defineEventHandler, getQuery } from "h3";
import aclManager from "~/server/internal/acls";
import newsManager from "~/server/internal/news";
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["news:read"]);
if (!userId)
throw createError({
statusCode: 403,
statusMessage: "Requires authentication",
});
const query = getQuery(h3);
const orderBy = query.order as "asc" | "desc";
if (orderBy) {
if (typeof orderBy !== "string" || !["asc", "desc"].includes(orderBy))
throw createError({ statusCode: 400, statusMessage: "Invalid order" });
}
const tags = query.tags as string[] | undefined;
if (tags) {
if (typeof tags !== "object" || !Array.isArray(tags))
throw createError({ statusCode: 400, statusMessage: "Invalid tags" });
}
const options = {
take: parseInt(query.limit as string),
skip: parseInt(query.skip as string),
orderBy: orderBy,
tags: tags?.map((e) => e.toString()),
search: query.search as string,
};
const news = await newsManager.fetch(options);
return news;
});

View File

@@ -30,6 +30,8 @@ export const userACLDescriptions: ObjectFromList<typeof userACLs> = {
"Remove a game from any collection (excluding library).",
"library:add": "Add a game to your library.",
"library:remove": "Remove a game from your library.",
"news:read": "Read the server's news articles.",
};
export const systemACLDescriptions: ObjectFromList<typeof systemACLs> = {
@@ -55,4 +57,8 @@ export const systemACLDescriptions: ObjectFromList<typeof systemACLs> = {
"import:game:new": "Import a game.",
"user:read": "Fetch any user's information.",
"news:read": "Read news articles.",
"news:create": "Create a new news article.",
"news:delete": "Delete a news article."
};

View File

@@ -25,6 +25,11 @@ export const userACLs = [
"collections:remove",
"library:add",
"library:remove",
<<<<<<< HEAD
=======
"news:read",
>>>>>>> AdenMGB-develop
] as const;
const userACLPrefix = "user:";
@@ -51,6 +56,13 @@ export const systemACLs = [
"import:game:new",
"user:read",
<<<<<<< HEAD
=======
"news:read",
"news:create",
"news:delete",
>>>>>>> AdenMGB-develop
] as const;
const systemACLPrefix = "system:";

View File

@@ -0,0 +1,118 @@
import { triggerAsyncId } from "async_hooks";
import prisma from "../db/database";
class NewsManager {
async create(data: {
title: string;
content: string;
description: string;
tags: string[];
authorId: string;
image?: string;
}) {
return await prisma.article.create({
data: {
title: data.title,
description: data.description,
content: data.content,
tags: {
connectOrCreate: data.tags.map((e) => ({
where: { name: e },
create: { name: e },
})),
},
image: data.image,
author: {
connect: {
id: data.authorId,
},
},
},
});
}
async fetch(
options: {
take?: number;
skip?: number;
orderBy?: "asc" | "desc";
tags?: string[];
search?: string;
} = {}
) {
return await prisma.article.findMany({
where: {
AND: [
{
tags: {
some: { OR: options.tags?.map((e) => ({ name: e })) ?? [] },
},
},
{
title: {
search: options.search
},
description: {
search: options.search
},
content: {
search: options.search
}
}
],
},
take: options?.take || 10,
skip: options?.skip || 0,
orderBy: {
publishedAt: options?.orderBy || "desc",
},
include: {
author: {
select: {
id: true,
displayName: true,
},
},
},
});
}
async fetchById(id: string) {
return await prisma.article.findUnique({
where: { id },
include: {
author: {
select: {
id: true,
displayName: true,
},
},
},
});
}
async update(
id: string,
data: {
title?: string;
content?: string;
excerpt?: string;
image?: string;
}
) {
return await prisma.article.update({
where: { id },
data,
});
}
async delete(id: string) {
return await prisma.article.delete({
where: { id },
});
}
}
export default new NewsManager();