partial: user routes

This commit is contained in:
DecDuck
2025-08-10 10:19:45 +10:00
parent 80f7757558
commit a0b4381f0b
54 changed files with 545 additions and 410 deletions

View File

@ -1,10 +1,10 @@
import aclManager from "~/server/internal/acls";
/**
* Check if we are an admin/system
* Check if we are a setup token
*/
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, []);
const allowed = await aclManager.allowSystemACL(h3, ["setup"]);
if (!allowed) return false;
return true;
});

View File

@ -1,5 +1,8 @@
import authManager from "~/server/internal/auth";
/**
* Fetch public authentication provider mechanisms
*/
export default defineEventHandler(() => {
return authManager.getEnabledAuthProviders();
});

View File

@ -15,6 +15,9 @@ const signinValidator = type({
"rememberMe?": "boolean | undefined",
});
/**
* Sign in as a session using the "Simple" authentication mechanism. Not recommended for third-party applications.
*/
export default defineEventHandler<{
body: typeof signinValidator.infer;
}>(async (h3) => {

View File

@ -1,8 +1,16 @@
import prisma from "~/server/internal/db/database";
import taskHandler from "~/server/internal/tasks";
import authManager from "~/server/internal/auth";
import { ArkErrors, type } from "arktype";
export default defineEventHandler(async (h3) => {
const Query = type({
id: "string",
});
/**
* Fetch invitation details for pre-filling
*/
export default defineEventHandler<{ query: typeof Query.infer }>(async (h3) => {
const t = await useTranslation(h3);
if (!authManager.getAuthProviders().Simple)
@ -11,13 +19,13 @@ export default defineEventHandler(async (h3) => {
statusMessage: t("errors.auth.method.signinDisabled"),
});
const query = getQuery(h3);
const id = query.id?.toString();
if (!id)
const query = Query(getQuery(h3));
if (query instanceof ArkErrors)
throw createError({
statusCode: 400,
statusMessage: t("errors.auth.inviteIdRequired"),
statusMessage: "Invalid query: " + query.summary,
});
const id = query.id;
taskHandler.runTaskGroupByName("cleanup:invitations");
const invitation = await prisma.invitation.findUnique({ where: { id: id } });

View File

@ -18,6 +18,9 @@ const CreateUserValidator = SharedRegisterValidator.and({
"displayName?": "string | undefined",
}).configure(throwingArktype);
/**
* Create user from invitation
*/
export default defineEventHandler<{
body: typeof CreateUserValidator.infer;
}>(async (h3) => {

View File

@ -1,34 +1,44 @@
import { type } from "arktype";
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import aclManager from "~/server/internal/acls";
import userLibraryManager from "~/server/internal/userlibrary";
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["collections:remove"]);
if (!userId)
throw createError({
statusCode: 403,
});
const RemoveEntry = type({
id: "string",
}).configure(throwingArktype);
const id = getRouterParam(h3, "id");
if (!id)
throw createError({
statusCode: 400,
statusMessage: "ID required in route params",
});
/**
* Remove entry from collection
* @param id Collection ID
*/
export default defineEventHandler<{ body: typeof RemoveEntry.infer }>(
async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["collections:remove"]);
if (!userId)
throw createError({
statusCode: 403,
});
const body = await readBody(h3);
const gameId = body.id;
if (!gameId)
throw createError({ statusCode: 400, statusMessage: "Game ID required" });
const id = getRouterParam(h3, "id");
if (!id)
throw createError({
statusCode: 400,
statusMessage: "ID required in route params",
});
const successful = await userLibraryManager.collectionRemove(
gameId,
id,
userId,
);
if (!successful)
throw createError({
statusCode: 404,
statusMessage: "Collection not found",
});
return {};
});
const body = await readDropValidatedBody(h3, RemoveEntry);
const gameId = body.id;
const successful = await userLibraryManager.collectionRemove(
gameId,
id,
userId,
);
if (!successful)
throw createError({
statusCode: 404,
statusMessage: "Collection not found",
});
return {};
},
);

View File

@ -1,7 +1,17 @@
import { type } from "arktype";
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import aclManager from "~/server/internal/acls";
import userLibraryManager from "~/server/internal/userlibrary";
export default defineEventHandler(async (h3) => {
const AddEntry = type({
id: "string",
}).configure(throwingArktype);
/**
* Add game to collection
* @param id Collection ID
*/
export default defineEventHandler<{ body: typeof AddEntry.infer }>(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["collections:add"]);
if (!userId)
throw createError({
@ -15,10 +25,8 @@ export default defineEventHandler(async (h3) => {
statusMessage: "ID required in route params",
});
const body = await readBody(h3);
const body = await readDropValidatedBody(h3, AddEntry);
const gameId = body.id;
if (!gameId)
throw createError({ statusCode: 400, statusMessage: "Game ID required" });
return await userLibraryManager.collectionAdd(gameId, id, userId);
});

View File

@ -1,6 +1,10 @@
import aclManager from "~/server/internal/acls";
import userLibraryManager from "~/server/internal/userlibrary";
/**
* Delete a collection
* @param id Collection ID
*/
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["collections:delete"]);
if (!userId)

View File

@ -1,6 +1,10 @@
import aclManager from "~/server/internal/acls";
import userLibraryManager from "~/server/internal/userlibrary";
/**
* Fetch collection by ID
* @param id Collection ID
*/
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["collections:read"]);
if (!userId)

View File

@ -1,20 +1,29 @@
import { type } from "arktype";
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import aclManager from "~/server/internal/acls";
import userLibraryManager from "~/server/internal/userlibrary";
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["library:remove"]);
if (!userId)
throw createError({
statusCode: 403,
statusMessage: "Requires authentication",
});
const DeleteEntry = type({
id: "string",
}).configure(throwingArktype);
const body = await readBody(h3);
/**
* Remove game from user library
*/
export default defineEventHandler<{ body: typeof DeleteEntry.infer }>(
async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["library:remove"]);
if (!userId)
throw createError({
statusCode: 403,
statusMessage: "Requires authentication",
});
const gameId = body.id;
if (!gameId)
throw createError({ statusCode: 400, statusMessage: "Game ID required" });
const body = await readDropValidatedBody(h3, DeleteEntry);
await userLibraryManager.libraryRemove(gameId, userId);
return {};
});
const gameId = body.id;
await userLibraryManager.libraryRemove(gameId, userId);
return {};
},
);

View File

@ -1,20 +1,29 @@
import { type } from "arktype";
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import aclManager from "~/server/internal/acls";
import userLibraryManager from "~/server/internal/userlibrary";
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["library:add"]);
if (!userId)
throw createError({
statusCode: 403,
statusMessage: "Requires authentication",
});
const AddGame = type({
id: "string",
}).configure(throwingArktype);
const body = await readBody(h3);
const gameId = body.id;
if (!gameId)
throw createError({ statusCode: 400, statusMessage: "Game ID required" });
/**
* Add game to user library
*/
export default defineEventHandler<{ body: typeof AddGame.infer }>(
async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["library:add"]);
if (!userId)
throw createError({
statusCode: 403,
statusMessage: "Requires authentication",
});
// Add the game to the default collection
await userLibraryManager.libraryAdd(gameId, userId);
return {};
});
const body = await readDropValidatedBody(h3, AddGame);
const gameId = body.id;
// Add the game to the default collection
await userLibraryManager.libraryAdd(gameId, userId);
return {};
},
);

View File

@ -1,6 +1,9 @@
import aclManager from "~/server/internal/acls";
import userLibraryManager from "~/server/internal/userlibrary";
/**
* Fetch user library
*/
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["collections:read"]);
if (!userId)

View File

@ -1,6 +1,9 @@
import aclManager from "~/server/internal/acls";
import userLibraryManager from "~/server/internal/userlibrary";
/**
* Fetch all collections
*/
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["collections:read"]);
if (!userId)

View File

@ -1,20 +1,28 @@
import { type } from "arktype";
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import aclManager from "~/server/internal/acls";
import userLibraryManager from "~/server/internal/userlibrary";
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["collections:read"]);
if (!userId)
throw createError({
statusCode: 403,
});
const CreateCollection = type({
name: "string",
}).configure(throwingArktype);
const body = await readBody(h3);
export default defineEventHandler<{ body: typeof CreateCollection.infer }>(
async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["collections:read"]);
if (!userId)
throw createError({
statusCode: 403,
});
const name = body.name;
if (!name)
throw createError({ statusCode: 400, statusMessage: "Requires name" });
const body = await readDropValidatedBody(h3, CreateCollection);
const name = body.name;
// Create the collection using the manager
const newCollection = await userLibraryManager.collectionCreate(name, userId);
return newCollection;
});
// Create the collection using the manager
const newCollection = await userLibraryManager.collectionCreate(
name,
userId,
);
return newCollection;
},
);

View File

@ -1,16 +1,15 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
/**
* Fetch company by ID
* @param id Company ID
*/
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["store:read"]);
if (!userId) throw createError({ statusCode: 403 });
const companyId = getRouterParam(h3, "id");
if (!companyId)
throw createError({
statusCode: 400,
statusMessage: "Missing gameId in route params (somehow...?)",
});
const companyId = getRouterParam(h3, "id")!;
const company = await prisma.company.findUnique({
where: { id: companyId },

View File

@ -1,16 +1,15 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
/**
* Fetch game by ID
* @param id Game ID
*/
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["store:read"]);
if (!userId) throw createError({ statusCode: 403 });
const gameId = getRouterParam(h3, "id");
if (!gameId)
throw createError({
statusCode: 400,
statusMessage: "Missing gameId in route params (somehow...?)",
});
const gameId = getRouterParam(h3, "id")!;
const game = await prisma.game.findUnique({
where: { id: gameId },

View File

@ -1,5 +1,8 @@
import { systemConfig } from "~/server/internal/config/sys-conf";
/**
* Fetch instance information
*/
export default defineEventHandler(async (_h3) => {
return {
appName: "Drop",

View File

@ -2,6 +2,10 @@ import { defineEventHandler, createError } from "h3";
import aclManager from "~/server/internal/acls";
import newsManager from "~/server/internal/news";
/**
* Fetch news article by ID
* @param id Article ID
*/
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["news:read"]);
if (!userId)
@ -10,12 +14,7 @@ export default defineEventHandler(async (h3) => {
statusMessage: "Requires authentication",
});
const id = h3.context.params?.id;
if (!id)
throw createError({
statusCode: 400,
message: "Missing news ID",
});
const id = getRouterParam(h3, "id")!;
const news = await newsManager.fetchById(id);
if (!news)

View File

@ -1,37 +1,44 @@
import { ArkErrors, type } from "arktype";
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: tags.map((e) => e.toString()) }),
search: query.search as string,
};
const news = await newsManager.fetch(options);
return news;
const NewsFetch = type({
"order?": "'asc' | 'desc'",
"tags?": "string[]",
"limit?": "string.numeric.parse",
"skip?": "string.numeric.parse",
"search?": "string",
});
/**
* Fetch instance news articles
*/
export default defineEventHandler<{ query: typeof NewsFetch.infer }>(
async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["news:read"]);
if (!userId)
throw createError({
statusCode: 403,
statusMessage: "Requires authentication",
});
const query = NewsFetch(getQuery(h3));
if (query instanceof ArkErrors)
throw createError({ statusCode: 400, statusMessage: query.summary });
const orderBy = query.order;
const tags = query.tags;
const options = {
take: Math.min(query.limit ?? 10, 10),
skip: query.skip ?? 0,
orderBy: orderBy,
...(tags && { tags: tags.map((e) => e.toString()) }),
search: query.search,
};
const news = await newsManager.fetch(options);
return news;
},
);

View File

@ -1,6 +1,10 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
/**
* Delete notification.
* @param id Notification ID
*/
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["notifications:delete"]);
if (!userId) throw createError({ statusCode: 403 });

View File

@ -1,6 +1,10 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
/**
* Fetch notification by ID
* @param id Notification ID
*/
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["notifications:read"]);
if (!userId) throw createError({ statusCode: 403 });

View File

@ -1,6 +1,10 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
/**
* Mark notification as read
* @param id Notification ID
*/
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["notifications:mark"]);
if (!userId) throw createError({ statusCode: 403 });

View File

@ -1,6 +1,9 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
/**
* Fetch all notifications for this token
*/
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["notifications:read"]);
if (!userId) throw createError({ statusCode: 403 });

View File

@ -1,6 +1,9 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
/**
* Mark all notifications as read
*/
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["notifications:mark"]);
if (!userId) throw createError({ statusCode: 403 });

View File

@ -6,6 +6,11 @@ import { logger } from "~/server/internal/logging";
// Peer ID to user ID
const socketSessions = new Map<string, string>();
/**
* Connect to a WebSocket to listen for notification pushes.
*
* Sends "unauthenticated" if authentication fails, otherwise JSON notifications.
*/
export default defineWebSocketHandler({
async open(peer) {
const h3 = { headers: peer.request?.headers ?? new Headers() };

View File

@ -2,6 +2,10 @@ import aclManager from "~/server/internal/acls";
import objectHandler from "~/server/internal/objects";
import sanitize from "sanitize-filename";
/**
* Delete object
* @param id Object ID
*/
export default defineEventHandler(async (h3) => {
const unsafeId = getRouterParam(h3, "id");
if (!unsafeId)

View File

@ -2,6 +2,10 @@ import aclManager from "~/server/internal/acls";
import objectHandler from "~/server/internal/objects";
import sanitize from "sanitize-filename";
/**
* Fetch object. Sets a lot of caching headers, recommended to use them.
* @param id Object ID
*/
export default defineEventHandler(async (h3) => {
const unsafeId = getRouterParam(h3, "id");
if (!unsafeId)

View File

@ -2,7 +2,10 @@ import aclManager from "~/server/internal/acls";
import objectHandler from "~/server/internal/objects";
import sanitize from "sanitize-filename";
// this request method is purely used by the browser to check if etag values are still valid
/**
* Check if object has changed (etag/browser caching)
* @param id Object ID
*/
export default defineEventHandler(async (h3) => {
const unsafeId = getRouterParam(h3, "id");
if (!unsafeId)

View File

@ -2,6 +2,10 @@ import aclManager from "~/server/internal/acls";
import objectHandler from "~/server/internal/objects";
import sanitize from "sanitize-filename";
/**
* Upload and overwrite object. Takes raw binary data (`application/octet-stream`)
* @param id Object ID
*/
export default defineEventHandler(async (h3) => {
const unsafeId = getRouterParam(h3, "id");
if (!unsafeId)

View File

@ -1,8 +1,11 @@
// get a specific screenshot
import aclManager from "~/server/internal/acls";
import screenshotManager from "~/server/internal/screenshots";
import sanitize from "sanitize-filename";
/**
* Delete screenshot by ID
* @param id Screenshot ID
*/
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["screenshots:delete"]);
if (!userId) throw createError({ statusCode: 403 });

View File

@ -3,6 +3,10 @@ import aclManager from "~/server/internal/acls";
import screenshotManager from "~/server/internal/screenshots";
import sanitize from "sanitize-filename";
/**
* Fetch screenshot by ID. Use `/api/v1/object/:id` to actually fetch screenshot image data.
* @param id Screenshot ID
*/
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["screenshots:read"]);
if (!userId) throw createError({ statusCode: 403 });

View File

@ -1,7 +1,10 @@
// get all user screenshots by game
import aclManager from "~/server/internal/acls";
import screenshotManager from "~/server/internal/screenshots";
/**
* Fetch all screenshots for game
* @param id Game ID
*/
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["screenshots:read"]);
if (!userId) throw createError({ statusCode: 403 });

View File

@ -3,8 +3,10 @@ import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import screenshotManager from "~/server/internal/screenshots";
// TODO: make defineClientEventHandler instead?
// only clients will be upload screenshots yea??
/**
* Upload screenshot by game. Subject to change, will likely become a client route. Takes raw upload (`application/octet-stream`)
* @param id Game ID
*/
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["screenshots:new"]);
if (!userId) throw createError({ statusCode: 403 });

View File

@ -2,6 +2,9 @@
import aclManager from "~/server/internal/acls";
import screenshotManager from "~/server/internal/screenshots";
/**
* Fetch all screenshots
*/
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["screenshots:read"]);
if (!userId) throw createError({ statusCode: 403 });

View File

@ -1,6 +1,9 @@
import aclManager from "~/server/internal/acls";
import { applicationSettings } from "~/server/internal/config/application-configuration";
/**
* Fetch system settings
*/
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.getUserACL(h3, ["settings:read"]);
if (!allowed) throw createError({ statusCode: 403 });

View File

@ -2,6 +2,9 @@ import { APITokenMode } from "~/prisma/client/enums";
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
/**
* Complete setup, and delete setup token.
*/
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["setup"]);
if (!allowed)

View File

@ -1,6 +1,9 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
/**
* Fetch all featured games. Used for store carousel.
*/
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserACL(h3, ["store:read"]);
if (!userId) throw createError({ statusCode: 403 });

View File

@ -21,102 +21,107 @@ const StoreRead = type({
sort: "'default' | 'newest' | 'recent' = 'default'",
});
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["store:read"]);
if (!userId) throw createError({ statusCode: 403 });
/**
* Store endpoint. Filter games with pagination. Used for all "store views".
*/
export default defineEventHandler<{ query: typeof StoreRead.infer }>(
async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["store:read"]);
if (!userId) throw createError({ statusCode: 403 });
const query = getQuery(h3);
const options = StoreRead(query);
if (options instanceof ArkErrors)
throw createError({ statusCode: 400, statusMessage: options.summary });
const query = getQuery(h3);
const options = StoreRead(query);
if (options instanceof ArkErrors)
throw createError({ statusCode: 400, statusMessage: options.summary });
/**
* Generic filters
*/
const tagFilter = options.tags
? {
tags: {
some: {
id: {
in: options.tags.split(","),
/**
* Generic filters
*/
const tagFilter = options.tags
? {
tags: {
some: {
id: {
in: options.tags.split(","),
},
},
},
},
}
: undefined;
const platformFilter = options.platform
? {
versions: {
some: {
platform: {
in: options.platform
.split(",")
.map(parsePlatform)
.filter((e) => e !== undefined),
}
: undefined;
const platformFilter = options.platform
? {
versions: {
some: {
platform: {
in: options.platform
.split(",")
.map(parsePlatform)
.filter((e) => e !== undefined),
},
},
},
},
}
: undefined;
}
: undefined;
/**
* Company filtering
*/
const companyActions = options.companyActions.split(",");
const developedFilter = companyActions.includes("developed")
? {
developers: {
some: {
id: options.company!,
/**
* Company filtering
*/
const companyActions = options.companyActions.split(",");
const developedFilter = companyActions.includes("developed")
? {
developers: {
some: {
id: options.company!,
},
},
},
}
: undefined;
const publishedFilter = companyActions.includes("published")
? {
publishers: {
some: {
id: options.company!,
}
: undefined;
const publishedFilter = companyActions.includes("published")
? {
publishers: {
some: {
id: options.company!,
},
},
},
}
: undefined;
const companyFilter = options.company
? ({
OR: [developedFilter, publishedFilter].filter((e) => e !== undefined),
} satisfies Prisma.GameWhereInput)
: undefined;
}
: undefined;
const companyFilter = options.company
? ({
OR: [developedFilter, publishedFilter].filter((e) => e !== undefined),
} satisfies Prisma.GameWhereInput)
: undefined;
/**
* Query
*/
/**
* Query
*/
const finalFilter: Prisma.GameWhereInput = {
...tagFilter,
...platformFilter,
...companyFilter,
};
const finalFilter: Prisma.GameWhereInput = {
...tagFilter,
...platformFilter,
...companyFilter,
};
const sort: Prisma.GameOrderByWithRelationInput = {};
switch (options.sort) {
case "default":
case "newest":
sort.mReleased = "desc";
break;
case "recent":
sort.created = "desc";
break;
}
const sort: Prisma.GameOrderByWithRelationInput = {};
switch (options.sort) {
case "default":
case "newest":
sort.mReleased = "desc";
break;
case "recent":
sort.created = "desc";
break;
}
const [results, count] = await prisma.$transaction([
prisma.game.findMany({
skip: options.skip,
take: Math.min(options.take, 50),
where: finalFilter,
orderBy: sort,
}),
prisma.game.count({ where: finalFilter }),
]);
const [results, count] = await prisma.$transaction([
prisma.game.findMany({
skip: options.skip,
take: Math.min(options.take, 50),
where: finalFilter,
orderBy: sort,
}),
prisma.game.count({ where: finalFilter }),
]);
return { results, count };
});
return { results, count };
},
);

View File

@ -1,6 +1,9 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
/**
* Fetch all game tags.
*/
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["store:read"]);
if (!userId) throw createError({ statusCode: 403 });

View File

@ -1,16 +1,15 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
/**
* Fetch tag by ID
* @param id Tag ID
*/
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["store:read"]);
if (!userId) throw createError({ statusCode: 403 });
const tagId = getRouterParam(h3, "id");
if (!tagId)
throw createError({
statusCode: 400,
statusMessage: "Missing gameId in route params (somehow...?)",
});
const tagId = getRouterParam(h3, "id")!;
const tag = await prisma.gameTag.findUnique({
where: { id: tagId },

View File

@ -5,6 +5,15 @@ import type { MinimumRequestObject } from "~/server/h3";
// ID to admin
const socketHeaders = new Map<string, MinimumRequestObject>();
/**
* WebSocket to listen to task updates.
*
* Sends "unauthenticated" if authentication failed.
*
* Use `connect/:taskId` to subscribe to a task.
*
* Sends JSON tasks for all tasks subscribed.
*/
export default defineWebSocketHandler({
async open(peer) {
const request = peer.request;

View File

@ -1,16 +1,15 @@
import aclManager from "~/server/internal/acls";
import clientHandler from "~/server/internal/clients/handler";
/**
* Revoke client
* @param id Client ID
*/
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["clients:revoke"]);
if (!userId) throw createError({ statusCode: 403 });
const clientId = getRouterParam(h3, "id");
if (!clientId)
throw createError({
statusCode: 400,
statusMessage: "Client ID missing in route params",
});
const clientId = getRouterParam(h3, "id")!;
await clientHandler.removeClient(clientId);
});

View File

@ -1,6 +1,9 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
/**
* Fetch all clients connected to this account
*/
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["clients:read"]);
if (!userId) throw createError({ statusCode: 403 });

View File

@ -1,5 +1,8 @@
import aclManager from "~/server/internal/acls";
/**
* Fetch user.
*/
export default defineEventHandler(async (h3) => {
const user = await aclManager.getUserACL(h3, ["read"]);
return user ?? null; // Need to specifically return null

View File

@ -2,16 +2,15 @@ import { APITokenMode } from "~/prisma/client/enums";
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
/**
* Revoke token
* @param id Token ID
*/
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, []); // No ACLs only allows session authentication
if (!userId) throw createError({ statusCode: 403 });
const id = h3.context.params?.id;
if (!id)
throw createError({
statusCode: 400,
statusMessage: "No id in router params",
});
const id = getRouterParam(h3, "id")!;
const deleted = await prisma.aPIToken.delete({
where: { id: id, userId: userId, mode: APITokenMode.User },

View File

@ -1,6 +1,9 @@
import aclManager from "~/server/internal/acls";
import { userACLDescriptions } from "~/server/internal/acls/descriptions";
/**
* Fetch ACL descriptions.
*/
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, []); // No ACLs only allows session authentication
if (!userId) throw createError({ statusCode: 403 });

View File

@ -2,6 +2,9 @@ import { APITokenMode } from "~/prisma/client/enums";
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
/**
* Fetch all API tokens for this account.
*/
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, []); // No ACLs only allows session authentication
if (!userId) throw createError({ statusCode: 403 });

View File

@ -1,46 +1,44 @@
import { type } from "arktype";
import { APITokenMode } from "~/prisma/client/enums";
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import aclManager, { userACLs } from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, []); // No ACLs only allows session authentication
if (!userId) throw createError({ statusCode: 403 });
const CreateToken = type({
name: "string",
acls: "string[] > 0",
}).configure(throwingArktype);
const body = await readBody(h3);
const name: string = body.name;
const acls: string[] = body.acls;
/**
*
*/
export default defineEventHandler<{ body: typeof CreateToken.infer }>(
async (h3) => {
const userId = await aclManager.getUserIdACL(h3, []); // No ACLs only allows session authentication
if (!userId) throw createError({ statusCode: 403 });
if (!name || typeof name !== "string")
throw createError({
statusCode: 400,
statusMessage: "Token name required",
});
if (!acls || !Array.isArray(acls))
throw createError({ statusCode: 400, statusMessage: "ACLs required" });
const body = await readDropValidatedBody(h3, CreateToken);
const name: string = body.name;
const acls: string[] = body.acls;
if (acls.length == 0)
throw createError({
statusCode: 400,
statusMessage: "Token requires more than zero ACLs",
const invalidACLs = acls.filter(
(e) => userACLs.findIndex((v) => e == v) == -1,
);
if (invalidACLs.length > 0)
throw createError({
statusCode: 400,
statusMessage: `Invalid ACLs: ${invalidACLs.join(", ")}`,
});
const token = await prisma.aPIToken.create({
data: {
mode: APITokenMode.User,
name: name,
userId: userId,
acls: acls,
},
});
const invalidACLs = acls.filter(
(e) => userACLs.findIndex((v) => e == v) == -1,
);
if (invalidACLs.length > 0)
throw createError({
statusCode: 400,
statusMessage: `Invalid ACLs: ${invalidACLs.join(", ")}`,
});
const token = await prisma.aPIToken.create({
data: {
mode: APITokenMode.User,
name: name,
userId: userId,
acls: acls,
},
});
return token;
});
return token;
},
);