This commit is contained in:
Sofía Aritz 2024-06-30 18:13:29 +02:00
parent ed4ea1db40
commit b9f36b5c2c
Signed by: sofia
GPG key ID: 90B5116E3542B28F
12 changed files with 160 additions and 131 deletions

View file

@ -1,4 +1,8 @@
import globals from "globals";
import pluginJs from "@eslint/js";
export default [{ languageOptions: { globals: globals.node } }, pluginJs.configs.recommended];
export default [
{ ignores: ["**/dist/*"] },
{ languageOptions: { globals: globals.node } },
pluginJs.configs.recommended,
];

View file

@ -19,8 +19,8 @@
},
"scripts": {
"start": "tsc && node dist/index.js",
"lint:fix": "eslint --fix && prettier . --write",
"lint": "eslint && prettier . --check"
"lint:fix": "eslint . --fix && prettier . --write",
"lint": "eslint . && prettier . --check"
},
"devDependencies": {
"@eslint/js": "^9.5.0",

View file

@ -19,7 +19,7 @@ import Fastify from "fastify";
let app = Fastify({
logger: true,
}).withTypeProvider<TypeBoxTypeProvider>()
}).withTypeProvider<TypeBoxTypeProvider>();
export type AppInterface = typeof app;
export default app;

View file

@ -20,36 +20,35 @@ import * as Jose from "jose";
import { JWT_ALG, JWT_SECRET } from "./consts.js";
import { DatabaseInterface, toDBList } from "./database.js";
export type AuthInterface = Awaited<ReturnType<typeof startAuth>>;
export type User = {
id: string,
createdAt: number,
lastConnected: number,
name: string,
email: string,
password: string,
limitID: string,
}
id: string;
createdAt: number;
lastConnected: number;
name: string;
email: string;
password: string;
limitID: string;
};
export type NewUser = {
name: string,
email: string,
password: string,
}
name: string;
email: string;
password: string;
};
export type UpdateUser = {
name?: string,
email?: string,
password?: string,
assets?: string[],
}
name?: string;
email?: string;
password?: string;
assets?: string[];
};
export async function startAuth(database: DatabaseInterface) {
let funcs = {
user: async (uid: string) => await database.user(uid) satisfies User,
findUserByEmail: async (email: string) => await database.findUserByEmail(email) satisfies User,
user: async (uid: string) => (await database.user(uid)) satisfies User,
findUserByEmail: async (email: string) => (await database.findUserByEmail(email)) satisfies User,
findUserBySessionKey: async (sessionKey: string) => {
let key = await database.sessionKey(sessionKey);
return await database.user(key.userID);
@ -61,21 +60,24 @@ export async function startAuth(database: DatabaseInterface) {
return await database.updateUser(uid, user);
},
addUser: async (user: NewUser) => {
let result = await database.insertUser({
id: randomUUID(),
createdAt: Date.now(),
lastConnected: Date.now(),
name: user.name,
email: user.email,
password: await argon2.hash(user.password),
assets: toDBList([]),
// FIXME: This shouldn't be required, the DB interface overwrites it.
limitID: "",
}, {
id: randomUUID(),
currentAssetCount: 0,
maxAssetCount: 5,
});
let result = await database.insertUser(
{
id: randomUUID(),
createdAt: Date.now(),
lastConnected: Date.now(),
name: user.name,
email: user.email,
password: await argon2.hash(user.password),
assets: toDBList([]),
// FIXME: This shouldn't be required, the DB interface overwrites it.
limitID: "",
},
{
id: randomUUID(),
currentAssetCount: 0,
maxAssetCount: 5,
},
);
return result satisfies User;
},
@ -103,9 +105,9 @@ export async function startAuth(database: DatabaseInterface) {
},
verifyJwt: async (jwt) => {
return await Jose.jwtVerify<{
uid: string,
email: string,
name: string,
uid: string;
email: string;
name: string;
}>(jwt, JWT_SECRET);
},
cleanUser: (user: User) => {
@ -114,7 +116,7 @@ export async function startAuth(database: DatabaseInterface) {
clean.limitID = undefined;
return clean;
}
},
};
return funcs;

View file

@ -21,14 +21,14 @@ import { sqliteTable } from "drizzle-orm/sqlite-core";
import { text, integer } from "drizzle-orm/sqlite-core";
import { asc, desc, eq, sql } from "drizzle-orm";
export type DatabaseInterface = Awaited<ReturnType<typeof startDatabase>>
export type DatabaseInterface = Awaited<ReturnType<typeof startDatabase>>;
export function toDBList(input: any[]): string {
return JSON.stringify(input);
return JSON.stringify(input);
}
export function fromDBList<T>(input: string): Array<T> {
return JSON.parse(input)
return JSON.parse(input);
}
export async function startDatabase() {
@ -114,10 +114,10 @@ export async function startDatabase() {
return result[0].id;
},
removeHeir: async (id: string) => {
await database.delete(heirs).where(eq(heirs.id, id));
await database.delete(heirs).where(eq(heirs.id, id));
},
listHeirs: async (userID: string) => {
return await database.select().from(heirs).where(eq(heirs.userID, userID));
return await database.select().from(heirs).where(eq(heirs.userID, userID));
},
insertUser: async (user: typeof users.$inferInsert, limit: typeof limits.$inferInsert) => {
let limitsResult = await database.insert(limits).values(limit).returning({ id: limits.id });
@ -131,14 +131,17 @@ export async function startDatabase() {
return result[0];
},
userLimits: async (limitsID: string) => {
let result = await database.select().from(limits).where(eq(limits.id, limitsID));
return result[0];
let result = await database.select().from(limits).where(eq(limits.id, limitsID));
return result[0];
},
updateUser: async (userID: string, newUser: {
name?: string,
email?: string,
password?: string,
}) => {
updateUser: async (
userID: string,
newUser: {
name?: string;
email?: string;
password?: string;
},
) => {
let result = await database.update(users).set(newUser).where(eq(users.id, userID)).returning();
return result[0];
},
@ -157,7 +160,12 @@ export async function startDatabase() {
findSessionKeyByUserID: async (userID: string) => {
return await database.select().from(session_keys).where(eq(session_keys.userID, userID));
},
insertEntry: async (entry: typeof entries.$inferInsert, musicEntry?: typeof musicEntries.$inferInsert, locationEntry?: typeof locationEntries.$inferInsert, dateEntry?: typeof dateEntries.$inferInsert) => {
insertEntry: async (
entry: typeof entries.$inferInsert,
musicEntry?: typeof musicEntries.$inferInsert,
locationEntry?: typeof locationEntries.$inferInsert,
dateEntry?: typeof dateEntries.$inferInsert,
) => {
if (entry.kind === "album" || entry.kind === "song") {
let result = await database.insert(musicEntries).values(musicEntry).returning({ id: musicEntries.id });
entry.musicEntry = result[0].id;
@ -176,7 +184,7 @@ export async function startDatabase() {
return result[0].id;
},
removeEntry: async (entryID: string) => {
await database.delete(entries).where(eq(entries.id, entryID));
await database.delete(entries).where(eq(entries.id, entryID));
},
entryPage: async (userID: string, offset: number, limit: number) => {
let result = await database
@ -188,46 +196,54 @@ export async function startDatabase() {
.orderBy(desc(entries.createdAt));
for (let key in result) {
if (!result.hasOwnProperty(key)) { continue; }
let entry = structuredClone(result[key]);
let base = {};
if (entry.musicEntry != null) {
let musicDetails = (await database.select().from(musicEntries).where(eq(musicEntries.id, entry.musicEntry)))[0];
(musicDetails["link"] as any) = fromDBList(musicDetails.links);
(musicDetails["id"] as any) = fromDBList(musicDetails.universalIDs);
musicDetails["links"] = undefined;
musicDetails["universalIDs"] = undefined;
base = musicDetails;
} else if (entry.locationEntry != null) {
let locationDetails = (await database.select().from(locationEntries).where(eq(locationEntries.id, entry.locationEntry)))[0];
if (locationDetails.locationCoordinates != null) {
base = { location: JSON.parse(locationDetails.locationCoordinates) }
} else if (locationDetails.locationText != null) {
base = { location: locationDetails.locationText }
if (!result.hasOwnProperty(key)) {
continue;
}
} else if (entry.dateEntry != null) {
let dateDetails = (await database.select().from(dateEntries).where(eq(dateEntries.id, entry.dateEntry)))[0];
base = {
referencedDate: new Date(dateDetails["referencedDate"]).toISOString()
};
}
let entry = structuredClone(result[key]);
let base = {};
if (entry.musicEntry != null) {
let musicDetails = (
await database.select().from(musicEntries).where(eq(musicEntries.id, entry.musicEntry))
)[0];
(musicDetails["link"] as any) = fromDBList(musicDetails.links);
(musicDetails["id"] as any) = fromDBList(musicDetails.universalIDs);
base["kind"] = entry.kind
musicDetails["links"] = undefined;
musicDetails["universalIDs"] = undefined;
base = musicDetails;
} else if (entry.locationEntry != null) {
let locationDetails = (
await database.select().from(locationEntries).where(eq(locationEntries.id, entry.locationEntry))
)[0];
result[key]["creationDate"] = new Date(entry.createdAt).toISOString();
(result[key] as any)["feelings"] = fromDBList(entry.feelings);
(result[key] as any)["assets"] = fromDBList(entry.assets);
result[key]["base"] = base;
if (locationDetails.locationCoordinates != null) {
base = { location: JSON.parse(locationDetails.locationCoordinates) };
} else if (locationDetails.locationText != null) {
base = { location: locationDetails.locationText };
}
} else if (entry.dateEntry != null) {
let dateDetails = (
await database.select().from(dateEntries).where(eq(dateEntries.id, entry.dateEntry))
)[0];
result[key].kind = undefined;
result[key].userID = undefined;
result[key].musicEntry = undefined;
result[key].locationEntry = undefined;
result[key].dateEntry = undefined;
base = {
referencedDate: new Date(dateDetails["referencedDate"]).toISOString(),
};
}
base["kind"] = entry.kind;
result[key]["creationDate"] = new Date(entry.createdAt).toISOString();
(result[key] as any)["feelings"] = fromDBList(entry.feelings);
(result[key] as any)["assets"] = fromDBList(entry.assets);
result[key]["base"] = base;
result[key].kind = undefined;
result[key].userID = undefined;
result[key].musicEntry = undefined;
result[key].locationEntry = undefined;
result[key].dateEntry = undefined;
}
return result;

View file

@ -24,7 +24,7 @@ const Body = Type.Object({
contactMethod: Type.String(),
name: Type.String(),
value: Type.String(),
})
});
type BodyType = Static<typeof Body>;
@ -53,9 +53,9 @@ export default function register(app: AppInterface, auth: AuthInterface, databas
});
return (await database.listHeirs(payload.uid))
.map(v => v["contactMethod"] = "email")
.map(v => v["value"] = v["email"])
.map(v => v["email"] = undefined);
.map((v) => (v["contactMethod"] = "email"))
.map((v) => (v["value"] = v["email"]))
.map((v) => (v["email"] = undefined));
},
schema: {
headers: { $ref: "schema://identity/authorization" },
@ -76,9 +76,9 @@ export default function register(app: AppInterface, auth: AuthInterface, databas
await database.removeHeir(request.body);
return (await database.listHeirs(payload.uid))
.map(v => v["contactMethod"] = "email")
.map(v => v["value"] = v["email"])
.map(v => v["email"] = undefined);
.map((v) => (v["contactMethod"] = "email"))
.map((v) => (v["value"] = v["email"]))
.map((v) => (v["email"] = undefined));
},
schema: {
headers: { $ref: "schema://identity/authorization" },

View file

@ -24,7 +24,7 @@ const Body = Type.Object({
password: Type.String(),
});
type BodyType = Static<typeof Body>;
type BodyType = Static<typeof Body>;
export default function register(app: AppInterface, auth: AuthInterface) {
app.post<{ Body: BodyType }>("/auth/register", {
@ -35,7 +35,7 @@ export default function register(app: AppInterface, auth: AuthInterface) {
password: request.body.password,
name: request.body.name,
});
return { token: await auth.createJwt(user.id) };
}

View file

@ -24,7 +24,7 @@ import { Static, Type } from "@sinclair/typebox";
const EntryIDQuery = Type.Object({
entry_id: Type.String(),
})
});
type EntryIDQueryType = Static<typeof EntryIDQuery>;
@ -43,22 +43,23 @@ const PutEntryBody = Type.Object({
backgroundColor: Type.String(),
textColor: Type.String(),
}),
])
]),
),
base: Type.Union([
Type.Object({
kind: Type.String(),
}),
Type.Object({
kind: Type.String(),
artist: Type.String(),
title: Type.String(),
link: Type.Array(Type.String()),
id: Type.Array(Type.Object({
provider: Type.String(),
id: Type.String(),
})),
id: Type.Array(
Type.Object({
provider: Type.String(),
id: Type.String(),
}),
),
}),
Type.Object({
kind: Type.String(),
@ -75,7 +76,7 @@ const PutEntryBody = Type.Object({
referencedDate: Type.String(),
}),
]),
})
}),
});
type PutEntryBodyType = Static<typeof PutEntryBody>;
@ -105,37 +106,43 @@ export default function registerRoutes(app: AppInterface, auth: AuthInterface, d
let entry = request.body.entry;
let musicEntry, locationEntry, dateEntry;
if ((entry.base.kind === "album" || entry.base.kind === "song") && 'artist' in entry.base) {
if ((entry.base.kind === "album" || entry.base.kind === "song") && "artist" in entry.base) {
musicEntry = {
id: randomUUID(),
title: entry.base.title,
artist: entry.base.artist,
links: toDBList(entry.base.link),
universalIDs: toDBList(entry.base.id),
}
} else if (entry.base.kind === "environment" && 'location' in entry.base) {
};
} else if (entry.base.kind === "environment" && "location" in entry.base) {
locationEntry = {
id: randomUUID(),
locationText: typeof entry.base.location === "string" ? entry.base.location : undefined,
locationCoordinates: typeof entry.base.location === "string" ? undefined : JSON.stringify(entry.base.location),
}
} else if (entry.base.kind === "date" && 'referencedDate' in entry.base) {
locationCoordinates:
typeof entry.base.location === "string" ? undefined : JSON.stringify(entry.base.location),
};
} else if (entry.base.kind === "date" && "referencedDate" in entry.base) {
dateEntry = {
id: randomUUID(),
referencedDate: Date.parse(entry.base.referencedDate),
}
};
}
await database.insertEntry({
id: randomUUID(),
userID: payload.uid,
feelings: toDBList(entry.feelings),
assets: toDBList(entry.assets),
title: entry.title,
description: entry.description,
kind: entry.base.kind,
createdAt: Date.now(),
}, musicEntry, locationEntry, dateEntry);
await database.insertEntry(
{
id: randomUUID(),
userID: payload.uid,
feelings: toDBList(entry.feelings),
assets: toDBList(entry.assets),
title: entry.title,
description: entry.description,
kind: entry.base.kind,
createdAt: Date.now(),
},
musicEntry,
locationEntry,
dateEntry,
);
},
schema: {
headers: { $ref: "schema://identity/authorization" },

View file

@ -22,7 +22,7 @@ import type { DatabaseInterface } from "../../database.js";
const Query = Type.Object({
limit: Type.Number(),
offset: Type.Number(),
})
});
type QueryType = Static<typeof Query>;
@ -37,7 +37,7 @@ export default function register(app: AppInterface, auth: AuthInterface, databas
let jwt = request.headers["authorization"].replace("Bearer", "").trim();
let { payload } = await auth.verifyJwt(jwt);
return await database.entryPage(payload.uid, request.query.offset, request.query.limit)
return await database.entryPage(payload.uid, request.query.offset, request.query.limit);
},
schema: {
headers: { $ref: "schema://identity/authorization" },

View file

@ -29,7 +29,7 @@ export default function register(app: AppInterface, auth: AuthInterface, databas
}
let body = JSON.parse(contentFromSigned(request.body));
let user = await auth.findUserBySessionKey(body.session_key);
let limits = await database.userLimits(user.limitID);
(user.assets as any) = fromDBList(user.assets);

View file

@ -33,7 +33,7 @@ export default function register(app: AppInterface, auth: AuthInterface) {
let user = await auth.findUserBySessionKey(body.session_key);
let assets = fromDBList(user.assets) as string[];
assets.push(body.asset_id);
await auth.updateUser(user.id, {
assets,
});

View file

@ -10,4 +10,4 @@
},
"include": ["./src/**/*"],
"exclude": ["./node_modules/**/*"]
}
}