fix: eslint errors, switch to using maps

This commit is contained in:
Huskydog9988
2025-04-15 20:04:45 -04:00
parent e362f732e7
commit 8f429e1e56
21 changed files with 158 additions and 159 deletions

View File

@ -91,6 +91,4 @@ const navigation: NavigationItem[] = [
prefix: "",
},
].filter((e) => e !== undefined);
const router = useRouter();
</script>

View File

@ -31,6 +31,7 @@ export const fetchNews = async (options?: {
const news = useNews();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore forget why this ignor exists
const newValue = await $dropFetch(`/api/v1/news?${query.toString()}`);

View File

@ -17,6 +17,8 @@ interface DropFetch<
request: R,
opts?: O,
): Promise<
// sometimes there is an error, other times there isn't
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
TypedInternalResponse<
R,
@ -28,7 +30,7 @@ interface DropFetch<
export const $dropFetch: DropFetch = async (request, opts) => {
if (!getCurrentInstance()?.proxy) {
return (await $fetch(request, opts)) as any;
return await $fetch(request, opts);
}
const id = request.toString();
@ -45,7 +47,7 @@ export const $dropFetch: DropFetch = async (request, opts) => {
const data = await $fetch(request, {
...opts,
headers: { ...opts?.headers, ...headers },
} as any);
});
if (import.meta.server) state.value = data;
return data as any;
return data;
};

View File

@ -7,33 +7,35 @@ import { randomUUID } from "node:crypto";
import objectHandler from ".";
export type TransactionDataType = string | Readable | Buffer;
type TransactionTable = { [key: string]: TransactionDataType }; // ID to data
type GlobalTransactionRecord = { [key: string]: TransactionTable }; // Transaction ID to table
type TransactionTable = Map<string, TransactionDataType>; // ID to data
type GlobalTransactionRecord = Map<string, TransactionTable>; // Transaction ID to table
export type Register = (url: TransactionDataType) => string;
export type Pull = () => Promise<void>;
export type Dump = () => void;
export class ObjectTransactionalHandler {
private record: GlobalTransactionRecord = {};
private record: GlobalTransactionRecord = new Map();
new(
metadata: { [key: string]: string },
permissions: Array<string>
permissions: Array<string>,
): [Register, Pull, Dump] {
const transactionId = randomUUID();
this.record[transactionId] ??= {};
this.record.set(transactionId, new Map());
const register = (data: TransactionDataType) => {
const objectId = randomUUID();
this.record[transactionId][objectId] = data;
this.record.get(transactionId)?.set(objectId, data);
return objectId;
};
const pull = async () => {
for (const [id, data] of Object.entries(this.record[transactionId])) {
const transaction = this.record.get(transactionId);
if (!transaction) return;
for (const [id, data] of transaction) {
await objectHandler.createFromSource(
id,
() => {
@ -43,13 +45,13 @@ export class ObjectTransactionalHandler {
return (async () => data)();
},
metadata,
permissions
permissions,
);
}
};
const dump = () => {
delete this.record[transactionId];
this.record.delete(transactionId);
};
return [register, pull, dump];

View File

@ -1,4 +1,4 @@
import Stream, { Readable } from "stream";
import Stream from "stream";
import prisma from "../db/database";
import { applicationSettings } from "../config/application-configuration";
import objectHandler from "../objects";
@ -10,7 +10,7 @@ class SaveManager {
gameId: string,
userId: string,
index: number,
objectId: string
objectId: string,
) {
await objectHandler.deleteWithPermission(objectId, userId);
}
@ -20,7 +20,7 @@ class SaveManager {
userId: string,
index: number,
stream: IncomingMessage,
clientId: string | undefined = undefined
clientId: string | undefined = undefined,
) {
const save = await prisma.saveSlot.findUnique({
where: {
@ -38,7 +38,7 @@ class SaveManager {
const newSaveStream = await objectHandler.createWithStream(
newSaveObjectId,
{ saveSlot: JSON.stringify({ userId, gameId, index }) },
[]
[],
);
if (!newSaveStream)
throw createError({
@ -51,10 +51,9 @@ class SaveManager {
stream,
createHash("sha256").setEncoding("hex"),
async function (source) {
// Not sure how to get this to be typed
// @ts-expect-error
// @ts-expect-error Not sure how to get this to be typed
hash = (await source.toArray())[0];
}
},
);
const uploadStream = Stream.promises.pipeline(stream, newSaveStream);

View File

@ -1,6 +1,5 @@
import bcrypt from "bcryptjs";
import * as argon2 from "argon2";
import { type } from "arktype";
export async function checkHashBcrypt(password: string, hash: string) {
return await bcrypt.compare(password, hash);

View File

@ -1,7 +1,6 @@
import { LRUCache } from "lru-cache";
import prisma from "../db/database";
import type { Session, SessionProvider } from "./types";
import { Prisma } from "@prisma/client";
export default function createDBSessionHandler(): SessionProvider {
const cache = new LRUCache<string, Session>({

View File

@ -1,5 +1,4 @@
import type { H3Event } from "h3";
import createMemorySessionProvider from "./memory";
import type { Session, SessionProvider } from "./types";
import { randomUUID } from "node:crypto";
import { parse as parseCookies } from "cookie-es";
@ -90,7 +89,7 @@ export class SessionHandler {
* @returns session token
*/
private getSessionToken(
request: MinimumRequestObject | undefined
request: MinimumRequestObject | undefined,
): string | undefined {
if (!request) throw new Error("Native web request not available");
const cookieHeader = request.headers.get("Cookie");

View File

@ -1,29 +1,29 @@
import type { Session, SessionProvider } from "./types";
export default function createMemorySessionHandler() {
const sessions: { [key: string]: Session } = {};
const sessions = new Map<string, Session>();
const memoryProvider: SessionProvider = {
async setSession(token, data) {
sessions[token] = data;
sessions.set(token, data);
return true;
},
async getSession<T extends Session>(token: string): Promise<T | undefined> {
const session = sessions[token];
const session = sessions.get(token);
return session ? (session as T) : undefined; // Ensure undefined is returned if session is not found
},
async updateSession(token, data) {
return this.setSession(token, data);
},
async removeSession(token) {
delete sessions[token];
sessions.delete(token);
return true;
},
async cleanupSessions() {
const now = new Date();
for (const token in sessions) {
for (const [token, session] of sessions) {
// if expires at time is before now, the session is expired
if (sessions[token].expiresAt < now) await this.removeSession(token);
if (session.expiresAt < now) await this.removeSession(token);
}
},
};

View File

@ -1,9 +1,8 @@
import { H3Event } from "h3";
export type Session = {
userId: string;
expiresAt: Date;
data: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
};
};

View File

@ -13,18 +13,18 @@ type TaskRegistryEntry = {
progress: number;
log: string[];
error: { title: string; description: string } | undefined;
clients: { [key: string]: boolean };
clients: Map<string, boolean>;
name: string;
acls: string[];
};
class TaskHandler {
private taskRegistry: { [key: string]: TaskRegistryEntry } = {};
private clientRegistry: { [key: string]: PeerImpl } = {};
// TODO: make these maps, using objects like this has performance impacts
// https://typescript-eslint.io/rules/no-dynamic-delete/
private taskRegistry = new Map<string, TaskRegistryEntry>();
private clientRegistry = new Map<string, PeerImpl>();
startTasks: (() => void)[] = [];
constructor() {}
create(task: Task) {
let updateCollectTimeout: NodeJS.Timeout | undefined;
let updateCollectResolves: Array<(value: unknown) => void> = [];
@ -37,7 +37,7 @@ class TaskHandler {
return;
}
updateCollectTimeout = setTimeout(() => {
const taskEntry = this.taskRegistry[task.id];
const taskEntry = this.taskRegistry.get(task.id);
if (!taskEntry) return;
const taskMessage: TaskMessage = {
@ -51,9 +51,10 @@ class TaskHandler {
};
logOffset = taskEntry.log.length;
for (const client of Object.keys(taskEntry.clients)) {
if (!this.clientRegistry[client]) continue;
this.clientRegistry[client].send(JSON.stringify(taskMessage));
for (const clientId of Object.keys(taskEntry.clients)) {
const client = this.clientRegistry.get(clientId);
if (!client) continue;
client.send(JSON.stringify(taskMessage));
}
updateCollectTimeout = undefined;
@ -66,65 +67,65 @@ class TaskHandler {
});
const progress = (progress: number) => {
const taskEntry = this.taskRegistry[task.id];
const taskEntry = this.taskRegistry.get(task.id);
if (!taskEntry) return;
this.taskRegistry[task.id].progress = progress;
taskEntry.progress = progress;
updateAllClients();
};
const log = (entry: string) => {
const taskEntry = this.taskRegistry[task.id];
const taskEntry = this.taskRegistry.get(task.id);
if (!taskEntry) return;
this.taskRegistry[task.id].log.push(entry);
taskEntry.log.push(entry);
updateAllClients();
};
this.taskRegistry[task.id] = {
this.taskRegistry.set(task.id, {
name: task.name,
success: false,
progress: 0,
error: undefined,
log: [],
clients: {},
clients: new Map(),
acls: task.acls,
};
});
updateAllClients(true);
droplet.callAltThreadFunc(async () => {
const taskEntry = this.taskRegistry[task.id];
const taskEntry = this.taskRegistry.get(task.id);
if (!taskEntry) throw new Error("No task entry");
try {
await task.run({ progress, log });
this.taskRegistry[task.id].success = true;
taskEntry.success = true;
} catch (error: unknown) {
this.taskRegistry[task.id].success = false;
this.taskRegistry[task.id].error = {
taskEntry.success = false;
taskEntry.error = {
title: "An error occurred",
description: (error as string).toString(),
};
}
await updateAllClients();
for (const client of Object.keys(taskEntry.clients)) {
if (!this.clientRegistry[client]) continue;
this.disconnect(client, task.id);
for (const clientId of Object.keys(taskEntry.clients)) {
if (!this.clientRegistry.get(clientId)) continue;
this.disconnect(clientId, task.id);
}
delete this.taskRegistry[task.id];
this.taskRegistry.delete(task.id);
});
}
async connect(
id: string,
clientId: string,
taskId: string,
peer: PeerImpl,
request: MinimumRequestObject
request: MinimumRequestObject,
) {
const task = this.taskRegistry[taskId];
const task = this.taskRegistry.get(taskId);
if (!task) {
peer.send(
`error/${taskId}/Unknown task/Drop couldn't find the task you're looking for.`
`error/${taskId}/Unknown task/Drop couldn't find the task you're looking for.`,
);
return;
}
@ -133,13 +134,13 @@ class TaskHandler {
if (!allowed) {
console.warn("user does not have necessary ACLs");
peer.send(
`error/${taskId}/Unknown task/Drop couldn't find the task you're looking for.`
`error/${taskId}/Unknown task/Drop couldn't find the task you're looking for.`,
);
return;
}
this.clientRegistry[id] = peer;
this.taskRegistry[taskId].clients[id] = true; // Uniquely insert client to avoid sending duplicate traffic
this.clientRegistry.set(clientId, peer);
task.clients.set(clientId, true); // Uniquely insert client to avoid sending duplicate traffic
const catchupMessage: TaskMessage = {
id: taskId,
@ -153,24 +154,25 @@ class TaskHandler {
}
sendDisconnectEvent(id: string, taskId: string) {
const client = this.clientRegistry[id];
const client = this.clientRegistry.get(id);
if (!client) return;
client.send(`disconnect/${taskId}`);
}
disconnectAll(id: string) {
for (const taskId of Object.keys(this.taskRegistry)) {
delete this.taskRegistry[taskId].clients[id];
this.taskRegistry.get(taskId)?.clients.delete(id);
this.sendDisconnectEvent(id, taskId);
}
delete this.clientRegistry[id];
this.clientRegistry.delete(id);
}
disconnect(id: string, taskId: string) {
if (!this.taskRegistry[taskId]) return false;
const task = this.taskRegistry.get(taskId);
if (!task) return false;
delete this.taskRegistry[taskId].clients[id];
task.clients.delete(id);
this.sendDisconnectEvent(id, taskId);
const allClientIds = Object.values(this.taskRegistry)
@ -178,7 +180,7 @@ class TaskHandler {
.flat();
if (!allClientIds.includes(id)) {
delete this.clientRegistry[id];
this.clientRegistry.delete(id);
}
return true;

View File

@ -8,8 +8,6 @@ class UserLibraryManager {
// Caches the user's core library
private userCoreLibraryCache: { [key: string]: string } = {};
constructor() {}
private async fetchUserLibrary(userId: string) {
if (this.userCoreLibraryCache[userId])
return this.userCoreLibraryCache[userId];

View File

@ -1,89 +1,93 @@
import type { FilterConditionally } from "./types";
interface PriorityTagged<T> {
object: T,
priority: number, // Higher takes priority
addedIndex: number, // Lower takes priority
object: T;
priority: number; // Higher takes priority
addedIndex: number; // Lower takes priority
}
export class PriorityList<T> {
private source: Array<PriorityTagged<T>> = [];
private cachedSorted: Array<T> | undefined;
private source: Array<PriorityTagged<T>> = [];
private cachedSorted: Array<T> | undefined;
push(item: T, priority: number = 0) {
this.source.push({
object: item,
priority,
addedIndex: this.source.length,
});
this.cachedSorted = undefined;
push(item: T, priority: number = 0) {
this.source.push({
object: item,
priority,
addedIndex: this.source.length,
});
this.cachedSorted = undefined;
}
pop(index: number = 0) {
this.cachedSorted = undefined;
return this.source.splice(index, 1)[0];
}
values() {
if (this.cachedSorted !== undefined) {
return this.cachedSorted;
}
pop(index: number = 0) {
this.cachedSorted = undefined;
return this.source.splice(index, 1)[0];
}
values() {
if (this.cachedSorted !== undefined) {
return this.cachedSorted;
const sorted = this.source
.sort((a, b) => {
if (a.priority == a.priority) {
return a.addedIndex - b.addedIndex;
}
const sorted = this.source.sort((a, b) => {
if (a.priority == a.priority) {
return a.addedIndex - b.addedIndex;
}
return b.priority - a.priority;
})
.map((e) => e.object);
this.cachedSorted = sorted;
return b.priority - a.priority;
}).map((e) => e.object);
this.cachedSorted = sorted;
return this.cachedSorted;
}
return this.cachedSorted;
}
find(predicate: (value: T, index: number, obj: T[]) => boolean) {
return this.source.map((e) => e.object).find(predicate);
}
find(predicate: (value: T, index: number, obj: T[]) => boolean) {
return this.source.map((e) => e.object).find(predicate);
}
}
type IndexableProperty<T> = keyof FilterConditionally<T, (() => string) | string>;
type IndexableProperty<T> = keyof FilterConditionally<
T,
(() => string) | string
>;
export class PriorityListIndexed<T> extends PriorityList<T> {
private indexName: IndexableProperty<T>;
private indexMap: { [key: string]: T } = {};
private indexName: IndexableProperty<T>;
private indexMap = new Map<string, T>();
constructor(indexName: IndexableProperty<T>) {
super();
this.indexName = indexName;
constructor(indexName: IndexableProperty<T>) {
super();
this.indexName = indexName;
}
private getIndex(object: T): string {
const index = object[this.indexName];
if (typeof index === "function") {
return index();
}
private getIndex(object: T): string {
const index = object[this.indexName];
return index as string;
}
if (typeof index === 'function') {
return index();
}
override push(item: T, priority?: number): void {
const index = this.getIndex(item);
this.indexMap.set(index, item);
return index as string;
}
super.push(item, priority);
}
override push(item: T, priority?: number): void {
const index = this.getIndex(item);
this.indexMap[index] = item;
override pop(position?: number): PriorityTagged<T> {
const value = super.pop(position);
super.push(item, priority);
}
const index = this.getIndex(value.object);
this.indexMap.delete(index);
override pop(position?: number): PriorityTagged<T> {
const value = super.pop(position);
return value;
}
const index = this.getIndex(value.object);
delete this.indexMap[index];
return value;
}
get(index: string) {
return this.indexMap[index];
}
}
get(index: string) {
return this.indexMap.get(index);
}
}

View File

@ -3,6 +3,8 @@ export type FilterConditionally<Source, Condition> = Pick<
{ [K in keyof Source]: Source[K] extends Condition ? K : never }[keyof Source]
>;
export type KeyOfType<T, V> = keyof {
// TODO: should switch to unknown??
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[P in keyof T as T[P] extends V ? P : never]: any;
};

View File

@ -1,7 +1,6 @@
import { applicationSettings } from "../internal/config/application-configuration";
import prisma from "../internal/db/database";
export default defineNitroPlugin(async (nitro) => {
export default defineNitroPlugin(async () => {
// Ensure system user exists
// The system user owns any user-based code
// that we want to re-use for the app

View File

@ -1,6 +1,6 @@
import prisma from "../internal/db/database";
export default defineNitroPlugin(async (nitro) => {
export default defineNitroPlugin(async () => {
const userCount = await prisma.user.count({
where: { id: { not: "system" } },
});

View File

@ -6,20 +6,20 @@ import { IGDBProvider } from "../internal/metadata/igdb";
import { ManualMetadataProvider } from "../internal/metadata/manual";
import { PCGamingWikiProvider } from "../internal/metadata/pcgamingwiki";
export default defineNitroPlugin(async (nitro) => {
export default defineNitroPlugin(async () => {
const metadataProviders = [
GiantBombProvider,
PCGamingWikiProvider,
IGDBProvider,
];
const providers: { [key: string]: MetadataProvider } = {};
const providers = new Map<string, MetadataProvider>();
for (const provider of metadataProviders) {
try {
const prov = new provider();
const id = prov.id();
providers[id] = prov;
providers.set(id, prov);
console.log(`enabled metadata provider: ${prov.name()}`);
} catch (e) {
@ -28,23 +28,22 @@ export default defineNitroPlugin(async (nitro) => {
}
// Add providers based on their position in the application settings
const configuredProviderList = await applicationSettings.get(
"metadataProviders"
);
const configuredProviderList =
await applicationSettings.get("metadataProviders");
const max = configuredProviderList.length;
for (const [index, providerId] of configuredProviderList.entries()) {
const priority = max * 2 - index; // Offset by the length --- (max - index) + max
const provider = providers[providerId];
const provider = providers.get(providerId);
if (!provider) {
console.warn(`failed to add existing metadata provider: ${providerId}`);
continue;
}
metadataHandler.addProvider(provider, priority);
delete providers[providerId];
providers.delete(providerId);
}
// Add the rest with no position
for (const [providerId, provider] of Object.entries(providers)) {
for (const [, provider] of Object.entries(providers)) {
metadataHandler.addProvider(provider);
}
@ -53,6 +52,6 @@ export default defineNitroPlugin(async (nitro) => {
// Update the applicatonConfig
await applicationSettings.set(
"metadataProviders",
metadataHandler.fetchProviderIdsInOrder()
metadataHandler.fetchProviderIdsInOrder(),
);
});

View File

@ -1,9 +1,5 @@
import { CertificateAuthority } from "../internal/clients/ca";
import fs from "fs";
import {
dbCertificateStore,
fsCertificateStore,
} from "../internal/clients/ca-store";
import { dbCertificateStore } from "../internal/clients/ca-store";
let ca: CertificateAuthority | undefined;
@ -12,7 +8,7 @@ export const useCertificateAuthority = () => {
return ca;
};
export default defineNitroPlugin(async (nitro) => {
export default defineNitroPlugin(async () => {
// const basePath = process.env.CLIENT_CERTIFICATES ?? "./certs";
// fs.mkdirSync(basePath, { recursive: true });
// const store = fsCertificateStore(basePath);

View File

@ -13,13 +13,14 @@ export default defineNitroPlugin((nitro) => {
switch (error.statusCode) {
case 401:
case 403:
case 403: {
const user = await sessionHandler.getSession(event);
if (user) break;
return sendRedirect(
event,
`/auth/signin?redirect=${encodeURIComponent(event.path)}`
`/auth/signin?redirect=${encodeURIComponent(event.path)}`,
);
}
}
});
});

View File

@ -4,7 +4,7 @@ export default defineTask({
meta: {
name: "cleanup:invitations",
},
async run({}) {
async run() {
const now = new Date();
await prisma.invitation.deleteMany({

View File

@ -4,7 +4,7 @@ export default defineTask({
meta: {
name: "cleanup:invitations",
},
async run({}) {
async run() {
await sessionHandler.cleanupSessions();
return { result: true };