completed game importing; partial work on version importing

This commit is contained in:
DecDuck
2024-10-11 00:37:08 +11:00
parent 718f5ba514
commit a7c33e7d43
42 changed files with 1499 additions and 281 deletions

View File

@ -0,0 +1,9 @@
import libraryManager from "~/server/internal/library";
export default defineEventHandler(async (h3) => {
const user = await h3.context.session.getAdminUser(h3);
if (!user) throw createError({ statusCode: 403 });
const unimportedGames = await libraryManager.fetchAllUnimportedGames();
return { unimportedGames };
});

View File

@ -0,0 +1,36 @@
import libraryManager from "~/server/internal/library";
import {
GameMetadataSearchResult,
GameMetadataSource,
} from "~/server/internal/metadata/types";
export default defineEventHandler(async (h3) => {
const user = await h3.context.session.getAdminUser(h3);
if (!user) throw createError({ statusCode: 403 });
const body = await readBody(h3);
const path = body.path;
const metadata = body.metadata as GameMetadataSearchResult &
GameMetadataSource;
if (!path)
throw createError({
statusCode: 400,
statusMessage: "Path missing from body",
});
if (!metadata.id || !metadata.sourceId)
throw createError({
statusCode: 400,
statusMessage: "Metadata IDs missing from body",
});
const validPath = await libraryManager.checkUnimportedGamePath(path);
if (!validPath)
throw createError({
statusCode: 400,
statusMessage: "Invalid unimported game path",
});
const game = await h3.context.metadataHandler.createGame(metadata, path);
return game;
});

View File

@ -0,0 +1,13 @@
import libraryManager from "~/server/internal/library";
export default defineEventHandler(async (h3) => {
const user = await h3.context.session.getAdminUser(h3);
if (!user) throw createError({ statusCode: 403 });
const query = getQuery(h3);
const search = query.q?.toString();
if (!search)
throw createError({ statusCode: 400, statusMessage: "Invalid search" });
return await h3.context.metadataHandler.search(search);
});

View File

@ -0,0 +1,22 @@
import libraryManager from "~/server/internal/library";
export default defineEventHandler(async (h3) => {
const user = await h3.context.session.getAdminUser(h3);
if (!user) throw createError({ statusCode: 403 });
const query = await getQuery(h3);
const gameId = query.id?.toString();
if (!gameId)
throw createError({
statusCode: 400,
statusMessage: "Missing id in request params",
});
const unimportedVersions = await libraryManager.fetchUnimportedVersions(
gameId
);
if (!unimportedVersions)
throw createError({ statusCode: 400, statusMessage: "Invalid game ID" });
return unimportedVersions;
});

View File

@ -0,0 +1,27 @@
import libraryManager from "~/server/internal/library";
export default defineEventHandler(async (h3) => {
const user = await h3.context.session.getAdminUser(h3);
if (!user) throw createError({ statusCode: 403 });
const query = await getQuery(h3);
const gameId = query.id?.toString();
const versionName = query.version?.toString();
if (!gameId || !versionName)
throw createError({
statusCode: 400,
statusMessage: "Missing id or version in request params",
});
const preload = await libraryManager.fetchUnimportedVersionInformation(
gameId,
versionName
);
if (!preload)
throw createError({
statusCode: 400,
statusMessage: "Invalid game or version id/name",
});
return preload;
});

View File

@ -0,0 +1,6 @@
export default defineEventHandler(async (h3) => {
const user = await h3.context.session.getUser(h3);
if (!user)
throw createError({ statusCode: 403, statusMessage: "Not authenticated" });
return { admin: user.admin };
});

View File

@ -0,0 +1,13 @@
import libraryManager from "~/server/internal/library";
export default defineEventHandler(async (h3) => {
const user = await h3.context.session.getAdminUser(h3);
if (!user) throw createError({ statusCode: 403 });
const unimportedGames = await libraryManager.fetchAllUnimportedGames();
const games = await libraryManager.fetchGamesWithStatus();
// Fetch other library data here
return { unimportedGames, games };
});

View File

@ -24,7 +24,9 @@ export default defineEventHandler(async (h3) => {
const userId = uuidv4();
const profilePictureObject = await h3.context.objects.createFromSource(
const profilePictureId = uuidv4();
await h3.context.objects.createFromSource(
profilePictureId,
() =>
$fetch<Readable>("https://avatars.githubusercontent.com/u/64579723?v=4", {
responseType: "stream",
@ -32,18 +34,12 @@ export default defineEventHandler(async (h3) => {
{},
[`anonymous:read`, `${userId}:write`]
);
if (!profilePictureObject)
throw createError({
statusCode: 500,
statusMessage: "Unable to import profile picture",
});
const user = await prisma.user.create({
data: {
username,
displayName: "DecDuck",
email: "",
profilePicture: profilePictureObject,
profilePicture: profilePictureId,
admin: true,
},
});

View File

@ -1,17 +1,25 @@
import clientHandler from "~/server/internal/clients/handler";
import { parsePlatform } from "~/server/internal/utils/parseplatform";
export default defineEventHandler(async (h3) => {
const body = await readBody(h3);
const name = body.name;
const platform = body.platform;
const platformRaw = body.platform;
if (!name || !platform)
if (!name || !platformRaw)
throw createError({
statusCode: 400,
statusMessage: "Missing name or platform in body",
});
const platform = parsePlatform(platformRaw);
if (!platform)
throw createError({
statusCode: 400,
statusMessage: "Invalid or unsupported platform",
});
const clientId = await clientHandler.initiate({ name, platform });
return `/client/${clientId}/callback`;

View File

@ -0,0 +1,36 @@
import prisma from "~/server/internal/db/database";
export default defineEventHandler(async (h3) => {
const userId = await h3.context.session.getUserId(h3);
if (!userId) throw createError({ statusCode: 403 });
const rawGames = await prisma.game.findMany({
select: {
id: true,
mName: true,
mShortDescription: true,
mBannerId: true,
mDevelopers: {
select: {
id: true,
mName: true,
},
},
mPublishers: {
select: {
id: true,
mName: true,
},
},
versions: {
select: {
platform: true,
},
},
},
});
const games = rawGames.map((e) => ({...e, platforms: e.versions.map((e) => e.platform).filter((e, _, r) => !r.includes(e))}))
return games;
});

View File

@ -1,10 +1,11 @@
import { v4 as uuidv4 } from "uuid";
import { CertificateBundle } from "./ca";
import prisma from "../db/database";
import { Platform } from "@prisma/client";
export interface ClientMetadata {
name: string;
platform: string;
platform: Platform;
}
export class ClientHandler {

View File

@ -10,6 +10,6 @@ declare const globalThis: {
const prisma = globalThis.prismaGlobal ?? prismaClientSingleton()
export default prisma
export default prisma;
if (process.env.NODE_ENV !== 'production') globalThis.prismaGlobal = prisma

View File

@ -0,0 +1,194 @@
/**
* The Library Manager keeps track of games in Drop's library and their various states.
* It uses path relative to the library, so it can moved without issue
*
* It also provides the endpoints with information about unmatched games
*/
import fs from "fs";
import path from "path";
import prisma from "../db/database";
import { GameVersion, Platform } from "@prisma/client";
import { fuzzy } from "fast-fuzzy";
import { recursivelyReaddir } from "../utils/recursivedirs";
class LibraryManager {
private basePath: string;
constructor() {
this.basePath = process.env.LIBRARY ?? "./.data/library";
}
async fetchAllUnimportedGames() {
const dirs = fs.readdirSync(this.basePath).filter((e) => {
const fullDir = path.join(this.basePath, e);
return fs.lstatSync(fullDir).isDirectory();
});
const validGames = await prisma.game.findMany({
where: {
libraryBasePath: { in: dirs },
},
select: {
libraryBasePath: true,
},
});
const validGameDirs = validGames.map((e) => e.libraryBasePath);
const unregisteredGames = dirs.filter((e) => !validGameDirs.includes(e));
return unregisteredGames;
}
async fetchUnimportedGameVersions(
libraryBasePath: string,
versions: Array<GameVersion>
) {
const gameDir = path.join(this.basePath, libraryBasePath);
const versionsDirs = fs.readdirSync(gameDir);
const importedVersionDirs = versions.map((e) => e.versionName);
const unimportedVersions = versionsDirs.filter(
(e) => !importedVersionDirs.includes(e)
);
return unimportedVersions;
}
async fetchGamesWithStatus() {
const games = await prisma.game.findMany({
select: {
id: true,
versions: true,
mName: true,
mShortDescription: true,
metadataSource: true,
mDevelopers: true,
mPublishers: true,
mIconId: true,
libraryBasePath: true,
},
});
return await Promise.all(
games.map(async (e) => ({
game: e,
status: {
noVersions: e.versions.length == 0,
unimportedVersions: await this.fetchUnimportedGameVersions(
e.libraryBasePath,
e.versions
),
},
}))
);
}
async fetchUnimportedVersions(gameId: string) {
const game = await prisma.game.findUnique({
where: { id: gameId },
select: {
versions: {
select: {
versionName: true,
},
},
libraryBasePath: true,
},
});
if (!game) return undefined;
const targetDir = path.join(this.basePath, game.libraryBasePath);
if (!fs.existsSync(targetDir))
throw new Error(
"Game in database, but no physical directory? Something is very very wrong..."
);
const versions = fs.readdirSync(targetDir);
const currentVersions = game.versions.map((e) => e.versionName);
const unimportedVersions = versions.filter(
(e) => !currentVersions.includes(e)
);
return unimportedVersions;
}
async fetchUnimportedVersionInformation(gameId: string, versionName: string) {
const game = await prisma.game.findUnique({
where: { id: gameId },
select: { libraryBasePath: true, mName: true },
});
if (!game) return undefined;
const targetDir = path.join(
this.basePath,
game.libraryBasePath,
versionName
);
if (!fs.existsSync(targetDir)) return undefined;
const fileExts: { [key: string]: string[] } = {
Linux: [
// Ext for Unity games
".x86_64",
// No extension is common for Linux binaries
"",
],
Windows: [
// Pretty much the only one
".exe",
],
};
const options: Array<{
filename: string;
platform: string;
match: number;
}> = [];
const files = recursivelyReaddir(targetDir);
for (const file of files) {
const filename = path.basename(file);
const dotLocation = file.lastIndexOf(".");
const ext = dotLocation == -1 ? "" : file.slice(dotLocation);
for (const [platform, checkExts] of Object.entries(fileExts)) {
for (const checkExt of checkExts) {
if (checkExt != ext) continue;
const fuzzyValue = fuzzy(filename, game.mName);
options.push({
filename: file,
platform: platform,
match: fuzzyValue,
});
}
}
}
const sortedOptions = options.sort((a, b) => b.match - a.match);
let startupGuess = "";
let platformGuess = "";
if (sortedOptions.length > 0) {
const finalChoice = sortedOptions[0];
const finalChoiceRelativePath = path.relative(
targetDir,
finalChoice.filename
);
startupGuess = finalChoiceRelativePath;
platformGuess = finalChoice.platform;
}
return { startupGuess, platformGuess };
}
// Checks are done in least to most expensive order
async checkUnimportedGamePath(targetPath: string) {
const targetDir = path.join(this.basePath, targetPath);
if (!fs.existsSync(targetDir)) return false;
const hasGame =
(await prisma.game.count({ where: { libraryBasePath: targetPath } })) > 0;
if (hasGame) return false;
return true;
}
}
export const libraryManager = new LibraryManager();
export default libraryManager;

View File

@ -50,9 +50,10 @@ interface GameResult {
interface CompanySearchResult {
guid: string,
deck: string,
description: string,
deck: string | null,
description: string | null,
name: string,
website: string | null,
image: {
icon_url: string,
@ -191,8 +192,9 @@ export class GiantBombProvider implements MetadataProvider {
const metadata: PublisherMetadata = {
id: company.guid,
name: company.name,
shortDescription: company.deck,
description: longDescription,
shortDescription: company.deck ?? "",
description: longDescription ?? "",
website: company.website ?? "",
logo: createObject(company.image.icon_url),
banner: createObject(company.image.screen_large_url),

View File

@ -1,162 +1,215 @@
import { Developer, MetadataSource, PrismaClient, Publisher } from "@prisma/client";
import {
Developer,
MetadataSource,
PrismaClient,
Publisher,
} from "@prisma/client";
import prisma from "../db/database";
import { _FetchDeveloperMetadataParams, _FetchGameMetadataParams, _FetchPublisherMetadataParams, DeveloperMetadata, GameMetadata, GameMetadataSearchResult, InternalGameMetadataResult, PublisherMetadata } from "./types";
import {
_FetchDeveloperMetadataParams,
_FetchGameMetadataParams,
_FetchPublisherMetadataParams,
DeveloperMetadata,
GameMetadata,
GameMetadataSearchResult,
InternalGameMetadataResult,
PublisherMetadata,
} from "./types";
import { ObjectTransactionalHandler } from "../objects/transactional";
import { PriorityList, PriorityListIndexed } from "../utils/prioritylist";
export abstract class MetadataProvider {
abstract id(): string;
abstract name(): string;
abstract source(): MetadataSource;
abstract id(): string;
abstract name(): string;
abstract source(): MetadataSource;
abstract search(query: string): Promise<GameMetadataSearchResult[]>;
abstract fetchGame(params: _FetchGameMetadataParams): Promise<GameMetadata>;
abstract fetchPublisher(params: _FetchPublisherMetadataParams): Promise<PublisherMetadata>;
abstract fetchDeveloper(params: _FetchDeveloperMetadataParams): Promise<DeveloperMetadata>;
abstract search(query: string): Promise<GameMetadataSearchResult[]>;
abstract fetchGame(params: _FetchGameMetadataParams): Promise<GameMetadata>;
abstract fetchPublisher(
params: _FetchPublisherMetadataParams
): Promise<PublisherMetadata>;
abstract fetchDeveloper(
params: _FetchDeveloperMetadataParams
): Promise<DeveloperMetadata>;
}
export class MetadataHandler {
// Ordered by priority
private providers: PriorityListIndexed<MetadataProvider> = new PriorityListIndexed("id");
private objectHandler: ObjectTransactionalHandler = new ObjectTransactionalHandler();
// Ordered by priority
private providers: PriorityListIndexed<MetadataProvider> =
new PriorityListIndexed("id");
private objectHandler: ObjectTransactionalHandler =
new ObjectTransactionalHandler();
addProvider(provider: MetadataProvider, priority: number = 0) {
this.providers.push(provider, priority);
}
addProvider(provider: MetadataProvider, priority: number = 0) {
this.providers.push(provider, priority);
}
async search(query: string) {
const promises: Promise<InternalGameMetadataResult[]>[] = [];
for (const provider of this.providers.values()) {
const queryTransformationPromise = new Promise<InternalGameMetadataResult[]>(async (resolve, reject) => {
const results = await provider.search(query);
const mappedResults: InternalGameMetadataResult[] = results.map((result) => Object.assign(
{},
result,
{
sourceId: provider.id(),
sourceName: provider.name()
}
));
resolve(mappedResults);
});
promises.push(queryTransformationPromise);
}
const results = await Promise.allSettled(promises);
const successfulResults = results.filter((result) => result.status === 'fulfilled').map((result) => result.value).flat();
return successfulResults;
}
async fetchGame(result: InternalGameMetadataResult) {
const provider = this.providers.get(result.sourceId);
if (!provider) throw new Error(`Invalid metadata provider for ID "${result.sourceId}"`);
const existing = await prisma.game.findUnique({
where: {
metadataKey: {
metadataSource: provider.source(),
metadataId: provider.id(),
}
}
});
if (existing) return existing;
const [createObject, pullObjects, dumpObjects] = this.objectHandler.new();
let metadata;
try {
metadata = await provider.fetchGame({
id: result.id,
publisher: this.fetchPublisher,
developer: this.fetchDeveloper,
createObject,
async search(query: string) {
const promises: Promise<InternalGameMetadataResult[]>[] = [];
for (const provider of this.providers.values()) {
const queryTransformationPromise = new Promise<
InternalGameMetadataResult[]
>(async (resolve, reject) => {
const results = await provider.search(query);
const mappedResults: InternalGameMetadataResult[] = results.map(
(result) =>
Object.assign({}, result, {
sourceId: provider.id(),
sourceName: provider.name(),
})
} catch (e) {
dumpObjects();
throw e;
}
await pullObjects();
const game = await prisma.game.create({
data: {
metadataSource: provider.source(),
metadataId: metadata.id,
mName: metadata.name,
mShortDescription: metadata.shortDescription,
mDescription: metadata.description,
mDevelopers: {
connect: metadata.developers
},
mPublishers: {
connect: metadata.publishers,
},
mReviewCount: metadata.reviewCount,
mReviewRating: metadata.reviewRating,
mIconId: metadata.icon,
mBannerId: metadata.banner,
mArt: metadata.art,
mScreenshots: metadata.screenshots,
},
});
return game;
);
resolve(mappedResults);
});
promises.push(queryTransformationPromise);
}
async fetchDeveloper(query: string) {
return await this.fetchDeveloperPublisher(query, "fetchDeveloper", "developer") as Developer;
const results = await Promise.allSettled(promises);
const successfulResults = results
.filter((result) => result.status === "fulfilled")
.map((result) => result.value)
.flat();
return successfulResults;
}
async createGame(
result: InternalGameMetadataResult,
libraryBasePath: string
) {
const provider = this.providers.get(result.sourceId);
if (!provider)
throw new Error(`Invalid metadata provider for ID "${result.sourceId}"`);
const existing = await prisma.game.findUnique({
where: {
metadataKey: {
metadataSource: provider.source(),
metadataId: provider.id(),
},
},
});
if (existing) return existing;
const [createObject, pullObjects, dumpObjects] = this.objectHandler.new(
{},
["internal:read"]
);
let metadata;
try {
metadata = await provider.fetchGame({
id: result.id,
// wrap in anonymous functions to keep references to this
publisher: (name: string) => this.fetchPublisher(name),
developer: (name: string) => this.fetchDeveloper(name),
createObject,
});
} catch (e) {
dumpObjects();
throw e;
}
async fetchPublisher(query: string) {
return await this.fetchDeveloperPublisher(query, "fetchPublisher", "publisher") as Publisher;
await pullObjects();
const game = await prisma.game.create({
data: {
metadataSource: provider.source(),
metadataId: metadata.id,
mName: metadata.name,
mShortDescription: metadata.shortDescription,
mDescription: metadata.description,
mDevelopers: {
connect: metadata.developers,
},
mPublishers: {
connect: metadata.publishers,
},
mReviewCount: metadata.reviewCount,
mReviewRating: metadata.reviewRating,
mIconId: metadata.icon,
mBannerId: metadata.banner,
mArt: metadata.art,
mScreenshots: metadata.screenshots,
versionOrder: [],
libraryBasePath,
},
});
return game;
}
async fetchDeveloper(query: string) {
return (await this.fetchDeveloperPublisher(
query,
"fetchDeveloper",
"developer"
)) as Developer;
}
async fetchPublisher(query: string) {
return (await this.fetchDeveloperPublisher(
query,
"fetchPublisher",
"publisher"
)) as Publisher;
}
// Careful with this function, it has no typechecking
// TODO: fix typechecking
private async fetchDeveloperPublisher(
query: string,
functionName: any,
databaseName: any
) {
const existing = await (prisma as any)[databaseName].findFirst({
where: {
metadataOriginalQuery: query,
},
});
if (existing) return existing;
for (const provider of this.providers.values() as any) {
const [createObject, pullObjects, dumpObjects] = this.objectHandler.new(
{},
["internal:read"]
);
let result;
try {
result = await provider[functionName]({ query, createObject });
} catch(e) {
console.warn(e);
dumpObjects();
continue;
}
// If we're successful
await pullObjects();
const object = await (prisma as any)[databaseName].create({
data: {
metadataSource: provider.source(),
metadataId: provider.id(),
metadataOriginalQuery: query,
mName: result.name,
mShortDescription: result.shortDescription,
mDescription: result.description,
mLogo: result.logo,
mBanner: result.banner,
mWebsite: result.website,
},
});
return object;
}
// Careful with this function, it has no typechecking
// TODO: fix typechecking
private async fetchDeveloperPublisher(query: string, functionName: any, databaseName: any) {
const existing = await (prisma as any)[databaseName].findFirst({
where: {
mName: query,
}
});
if (existing) return existing;
for (const provider of this.providers.values() as any) {
const [createObject, pullObjects, dumpObjects] = this.objectHandler.new();
let result;
try {
result = await provider[functionName]({ query, createObject });
} catch {
dumpObjects();
continue;
}
// If we're successful
await pullObjects();
const object = await (prisma as any)[databaseName].create({
data: {
metadataSource: provider.source(),
metadataId: provider.id(),
mName: result.name,
mShortDescription: result.shortDescription,
mDescription: result.description,
mLogo: result.logo,
mBanner: result.banner,
},
})
return object;
}
throw new Error(`No metadata provider found a ${databaseName} for "${query}"`);
}
throw new Error(
`No metadata provider found a ${databaseName} for "${query}"`
);
}
}
export default new MetadataHandler();
export default new MetadataHandler();

View File

@ -45,6 +45,7 @@ export interface PublisherMetadata {
logo: ObjectReference;
banner: ObjectReference;
website: String;
}
export type DeveloperMetadata = PublisherMetadata;

View File

@ -45,10 +45,10 @@ export class FsObjectBackend extends ObjectBackend {
return false;
}
async create(
id: string,
source: Source,
metadata: ObjectMetadata
): Promise<ObjectReference | undefined> {
const id = uuidv4();
const objectPath = path.join(this.baseObjectPath, sanitize(id));
const metadataPath = path.join(
this.baseMetadataPath,

View File

@ -17,6 +17,7 @@
import { parse as getMimeTypeBuffer } from "file-type-mime";
import { Readable } from "stream";
import { getMimeType as getMimeTypeStream } from "stream-mime-type";
import { v4 as uuidv4 } from "uuid";
export type ObjectReference = string;
export type ObjectMetadata = {
@ -46,6 +47,7 @@ export abstract class ObjectBackend {
abstract fetch(id: ObjectReference): Promise<Source | undefined>;
abstract write(id: ObjectReference, source: Source): Promise<boolean>;
abstract create(
id: string,
source: Source,
metadata: ObjectMetadata
): Promise<ObjectReference | undefined>;
@ -59,6 +61,7 @@ export abstract class ObjectBackend {
): Promise<boolean>;
async createFromSource(
id: string,
sourceFetcher: () => Promise<Source>,
metadata: { [key: string]: string },
permissions: Array<string>
@ -83,13 +86,11 @@ export abstract class ObjectBackend {
if (!mime)
throw new Error("Unable to calculate MIME type - is the source empty?");
const objectId = this.create(source, {
await this.create(id, source, {
permissions,
userMetadata: metadata,
mime,
});
return objectId;
}
async fetchWithPermissions(id: ObjectReference, userId?: string) {

View File

@ -2,7 +2,9 @@
The purpose of this class is to hold references to remote objects (like images) until they're actually needed
This is used as a utility in metadata handling, so we only fetch the objects if we're actually creating a database record.
*/
import { v4 as uuidv4 } from 'uuid';
import { Readable } from "stream";
import { v4 as uuidv4 } from "uuid";
import { GlobalObjectHandler } from "~/server/plugins/objects";
type TransactionTable = { [key: string]: string }; // ID to URL
type GlobalTransactionRecord = { [key: string]: TransactionTable }; // Transaction ID to table
@ -12,27 +14,38 @@ type Pull = () => Promise<void>;
type Dump = () => void;
export class ObjectTransactionalHandler {
private record: GlobalTransactionRecord = {};
private record: GlobalTransactionRecord = {};
new(): [Register, Pull, Dump] {
const transactionId = uuidv4();
new(
metadata: { [key: string]: string },
permissions: Array<string>
): [Register, Pull, Dump] {
const transactionId = uuidv4();
const register = (url: string) => {
const objectId = uuidv4();
this.record[transactionId][objectId] = url;
this.record[transactionId] ??= {};
return objectId;
}
const register = (url: string) => {
const objectId = uuidv4();
this.record[transactionId][objectId] = url;
const pull = async () => {
// Dummy function
dump();
}
return objectId;
};
const dump = () => {
delete this.record[transactionId];
}
const pull = async () => {
for (const [id, url] of Object.entries(this.record[transactionId])) {
await GlobalObjectHandler.createFromSource(
id,
() => $fetch<Readable>(url, { responseType: "stream" }),
metadata,
permissions
);
}
};
return [register, pull, dump];
}
}
const dump = () => {
delete this.record[transactionId];
};
return [register, pull, dump];
}
}

View File

@ -13,52 +13,71 @@ const userSessionKey = "_userSession";
const userIdKey = "_userId";
export class SessionHandler {
private sessionProvider: SessionProvider;
private sessionProvider: SessionProvider;
constructor() {
// Create a new provider
this.sessionProvider = createMemorySessionProvider();
constructor() {
// Create a new provider
this.sessionProvider = createMemorySessionProvider();
}
async getSession<T extends Session>(h3: H3Event) {
const data = await this.sessionProvider.getSession<{ [userSessionKey]: T }>(
h3
);
if (!data) return undefined;
return data[userSessionKey];
}
async setSession(h3: H3Event, data: any, expend = false) {
const result = await this.sessionProvider.updateSession(
h3,
userSessionKey,
data
);
if (!result) {
const toCreate = { [userSessionKey]: data };
await this.sessionProvider.setSession(h3, toCreate, expend);
}
}
async clearSession(h3: H3Event) {
await this.sessionProvider.clearSession(h3);
}
async getSession<T extends Session>(h3: H3Event) {
const data = await this.sessionProvider.getSession<{ [userSessionKey]: T }>(h3);
if (!data) return undefined;
async getUserId(h3: H3Event) {
const session = await this.sessionProvider.getSession<{
[userIdKey]: string | undefined;
}>(h3);
if (!session) return undefined;
return data[userSessionKey];
}
async setSession(h3: H3Event, data: any, expend = false) {
const result = await this.sessionProvider.updateSession(h3, userSessionKey, data);
if (!result) {
const toCreate = { [userSessionKey]: data };
await this.sessionProvider.setSession(h3, toCreate, expend);
}
}
async clearSession(h3: H3Event) {
await this.sessionProvider.clearSession(h3);
return session[userIdKey];
}
async getUser(h3: H3Event) {
const userId = await this.getUserId(h3);
if (!userId) return undefined;
const user = await prisma.user.findFirst({ where: { id: userId } });
return user;
}
async setUserId(h3: H3Event, userId: string, extend = false) {
const result = await this.sessionProvider.updateSession(
h3,
userIdKey,
userId
);
if (!result) {
const toCreate = { [userIdKey]: userId };
await this.sessionProvider.setSession(h3, toCreate, extend);
}
}
async getUserId(h3: H3Event) {
const session = await this.sessionProvider.getSession<{ [userIdKey]: string | undefined }>(h3);
if (!session) return undefined;
return session[userIdKey];
}
async getUser(h3: H3Event) {
const userId = await this.getUserId(h3);
if (!userId) return undefined;
const user = await prisma.user.findFirst({ where: { id: userId } });
return user;
}
async setUserId(h3: H3Event, userId: string, extend = false) {
const result = await this.sessionProvider.updateSession(h3, userIdKey, userId);
if (!result) {
const toCreate = { [userIdKey]: userId };
await this.sessionProvider.setSession(h3, toCreate, extend);
}
}
async getAdminUser(h3: H3Event) {
const user = await this.getUser(h3);
if (!user) return undefined;
if (!user.admin) return undefined;
return user;
}
}
export default new SessionHandler();
export default new SessionHandler();

View File

@ -0,0 +1,14 @@
import { Platform } from "@prisma/client";
export function parsePlatform(platform: string) {
switch (platform) {
case "linux":
case "Linux":
return Platform.Linux;
case "windows":
case "Windows":
return Platform.Windows;
}
return undefined;
}

View File

@ -0,0 +1,20 @@
import fs from "fs";
import path from "path";
export function recursivelyReaddir(dir: string) {
const result: Array<string> = [];
const files = fs.readdirSync(dir);
for (const file of files) {
const targetDir = path.join(dir, file);
const stat = fs.lstatSync(targetDir);
if (stat.isDirectory()) {
const subdirs = recursivelyReaddir(targetDir);
const subdirsWithBase = subdirs.map((e) => path.join(dir, e));
result.push(...subdirsWithBase);
continue;
}
result.push(targetDir);
}
return result;
}

View File

@ -1,11 +1,10 @@
import { FsObjectBackend } from "../internal/objects/fsBackend";
// To-do insert logic surrounding deciding what object backend to use
export const GlobalObjectHandler = new FsObjectBackend();
export default defineNitroPlugin((nitro) => {
const currentObjectHandler = new FsObjectBackend();
// To-do insert logic surrounding deciding what object backend to use
nitro.hooks.hook("request", (h3) => {
h3.context.objects = currentObjectHandler;
h3.context.objects = GlobalObjectHandler;
});
});