Functional account summaries
This commit is contained in:
parent
15ff9609c4
commit
d485c2adcf
9
.vscode/launch.json
vendored
Normal file
9
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
28
drizzle/0001_old_leopardon.sql
Normal file
28
drizzle/0001_old_leopardon.sql
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
CREATE TABLE "accounts" (
|
||||||
|
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "accounts_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
|
||||||
|
"owner_id" integer NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"currency" text NOT NULL,
|
||||||
|
"starting_date" timestamp with time zone NOT NULL,
|
||||||
|
"starting_balance" numeric DEFAULT '0' NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "receipt_items" (
|
||||||
|
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "receipt_items_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
|
||||||
|
"receipt_id" integer NOT NULL,
|
||||||
|
"amount" numeric DEFAULT '0' NOT NULL,
|
||||||
|
"category" text NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "receipts" (
|
||||||
|
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "receipts_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
|
||||||
|
"date" timestamp with time zone NOT NULL,
|
||||||
|
"account_from_id" integer NOT NULL,
|
||||||
|
"account_to_id" integer,
|
||||||
|
CONSTRAINT "no_account_loops" CHECK ("receipts"."account_from_id" <> "receipts"."account_to_id")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "accounts" ADD CONSTRAINT "accounts_owner_id_users_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "receipt_items" ADD CONSTRAINT "receipt_items_receipt_id_receipts_id_fk" FOREIGN KEY ("receipt_id") REFERENCES "public"."receipts"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "receipts" ADD CONSTRAINT "receipts_account_from_id_accounts_id_fk" FOREIGN KEY ("account_from_id") REFERENCES "public"."accounts"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "receipts" ADD CONSTRAINT "receipts_account_to_id_accounts_id_fk" FOREIGN KEY ("account_to_id") REFERENCES "public"."accounts"("id") ON DELETE no action ON UPDATE no action;
|
338
drizzle/meta/0001_snapshot.json
Normal file
338
drizzle/meta/0001_snapshot.json
Normal file
@ -0,0 +1,338 @@
|
|||||||
|
{
|
||||||
|
"id": "812f446c-4ff5-43a4-bad1-9e4cbb821b35",
|
||||||
|
"prevId": "9f31c19a-9e3c-41db-a08b-27d9916265cb",
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"tables": {
|
||||||
|
"public.accounts": {
|
||||||
|
"name": "accounts",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"identity": {
|
||||||
|
"type": "always",
|
||||||
|
"name": "accounts_id_seq",
|
||||||
|
"schema": "public",
|
||||||
|
"increment": "1",
|
||||||
|
"startWith": "1",
|
||||||
|
"minValue": "1",
|
||||||
|
"maxValue": "2147483647",
|
||||||
|
"cache": "1",
|
||||||
|
"cycle": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"owner_id": {
|
||||||
|
"name": "owner_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"currency": {
|
||||||
|
"name": "currency",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"starting_date": {
|
||||||
|
"name": "starting_date",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"starting_balance": {
|
||||||
|
"name": "starting_balance",
|
||||||
|
"type": "numeric",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'0'"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"accounts_owner_id_users_id_fk": {
|
||||||
|
"name": "accounts_owner_id_users_id_fk",
|
||||||
|
"tableFrom": "accounts",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"owner_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.receipt_items": {
|
||||||
|
"name": "receipt_items",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"identity": {
|
||||||
|
"type": "always",
|
||||||
|
"name": "receipt_items_id_seq",
|
||||||
|
"schema": "public",
|
||||||
|
"increment": "1",
|
||||||
|
"startWith": "1",
|
||||||
|
"minValue": "1",
|
||||||
|
"maxValue": "2147483647",
|
||||||
|
"cache": "1",
|
||||||
|
"cycle": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"receipt_id": {
|
||||||
|
"name": "receipt_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"amount": {
|
||||||
|
"name": "amount",
|
||||||
|
"type": "numeric",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'0'"
|
||||||
|
},
|
||||||
|
"category": {
|
||||||
|
"name": "category",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"receipt_items_receipt_id_receipts_id_fk": {
|
||||||
|
"name": "receipt_items_receipt_id_receipts_id_fk",
|
||||||
|
"tableFrom": "receipt_items",
|
||||||
|
"tableTo": "receipts",
|
||||||
|
"columnsFrom": [
|
||||||
|
"receipt_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.receipts": {
|
||||||
|
"name": "receipts",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"identity": {
|
||||||
|
"type": "always",
|
||||||
|
"name": "receipts_id_seq",
|
||||||
|
"schema": "public",
|
||||||
|
"increment": "1",
|
||||||
|
"startWith": "1",
|
||||||
|
"minValue": "1",
|
||||||
|
"maxValue": "2147483647",
|
||||||
|
"cache": "1",
|
||||||
|
"cycle": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
"name": "date",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"account_from_id": {
|
||||||
|
"name": "account_from_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"account_to_id": {
|
||||||
|
"name": "account_to_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"receipts_account_from_id_accounts_id_fk": {
|
||||||
|
"name": "receipts_account_from_id_accounts_id_fk",
|
||||||
|
"tableFrom": "receipts",
|
||||||
|
"tableTo": "accounts",
|
||||||
|
"columnsFrom": [
|
||||||
|
"account_from_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"receipts_account_to_id_accounts_id_fk": {
|
||||||
|
"name": "receipts_account_to_id_accounts_id_fk",
|
||||||
|
"tableFrom": "receipts",
|
||||||
|
"tableTo": "accounts",
|
||||||
|
"columnsFrom": [
|
||||||
|
"account_to_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {
|
||||||
|
"no_account_loops": {
|
||||||
|
"name": "no_account_loops",
|
||||||
|
"value": "\"receipts\".\"account_from_id\" <> \"receipts\".\"account_to_id\""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.sessions": {
|
||||||
|
"name": "sessions",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sessions_user_id_users_id_fk": {
|
||||||
|
"name": "sessions_user_id_users_id_fk",
|
||||||
|
"tableFrom": "sessions",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.users": {
|
||||||
|
"name": "users",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"identity": {
|
||||||
|
"type": "always",
|
||||||
|
"name": "users_id_seq",
|
||||||
|
"schema": "public",
|
||||||
|
"increment": "1",
|
||||||
|
"startWith": "1",
|
||||||
|
"minValue": "1",
|
||||||
|
"maxValue": "2147483647",
|
||||||
|
"cache": "1",
|
||||||
|
"cycle": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "varchar(128)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"password_hash": {
|
||||||
|
"name": "password_hash",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"display_name": {
|
||||||
|
"name": "display_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"users_username_unique": {
|
||||||
|
"name": "users_username_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"username"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"sequences": {},
|
||||||
|
"roles": {},
|
||||||
|
"policies": {},
|
||||||
|
"views": {},
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,13 @@
|
|||||||
"when": 1738197417069,
|
"when": 1738197417069,
|
||||||
"tag": "0000_users-and-sessions",
|
"tag": "0000_users-and-sessions",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 1,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1738208377219,
|
||||||
|
"tag": "0001_old_leopardon",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
31
src/lib/server/accounting.ts
Normal file
31
src/lib/server/accounting.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { and, eq, gt, sql } from "drizzle-orm";
|
||||||
|
import { db } from "./db";
|
||||||
|
import { accounts, receiptItems, receipts } from "./schema";
|
||||||
|
|
||||||
|
export interface AccountSummary {
|
||||||
|
id: number,
|
||||||
|
name: string,
|
||||||
|
balance: bigint,
|
||||||
|
currency: string,
|
||||||
|
lastReceipt?: Date,
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUserAccountSummary(userId: number) {
|
||||||
|
return (await db
|
||||||
|
.select({
|
||||||
|
id: accounts.id,
|
||||||
|
name: accounts.name,
|
||||||
|
currency: accounts.currency,
|
||||||
|
balance: sql<string>`${accounts.startingBalance} + COALESCE(sum(${receiptItems.amount}), 0)`,
|
||||||
|
lastReceipt: sql<Date | null>`max(${receipts.date})`,
|
||||||
|
})
|
||||||
|
.from(accounts)
|
||||||
|
.where(eq(accounts.ownerId, userId))
|
||||||
|
.leftJoin(receipts, and(eq(receipts.accountFromId, accounts.id), gt(receipts.date, accounts.startingDate)))
|
||||||
|
.leftJoin(receiptItems, eq(receiptItems.receiptId, receipts.id))
|
||||||
|
.groupBy(accounts.id)).map((row) => ({
|
||||||
|
...row,
|
||||||
|
balance: BigInt(row.balance),
|
||||||
|
lastReceipt: row.lastReceipt ?? undefined,
|
||||||
|
} satisfies AccountSummary))
|
||||||
|
}
|
@ -1,10 +1,12 @@
|
|||||||
import { relations } from "drizzle-orm";
|
import { ne, relations } from "drizzle-orm";
|
||||||
import {
|
import {
|
||||||
|
check,
|
||||||
integer,
|
integer,
|
||||||
|
numeric,
|
||||||
pgTable,
|
pgTable,
|
||||||
varchar,
|
|
||||||
text,
|
text,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
varchar,
|
||||||
} from "drizzle-orm/pg-core";
|
} from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
export const users = pgTable("users", {
|
export const users = pgTable("users", {
|
||||||
@ -14,6 +16,10 @@ export const users = pgTable("users", {
|
|||||||
displayName: text(),
|
displayName: text(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const userRelations = relations(users, ({ many }) => ({
|
||||||
|
accounts: many(accounts),
|
||||||
|
}));
|
||||||
|
|
||||||
export const sessions = pgTable("sessions", {
|
export const sessions = pgTable("sessions", {
|
||||||
id: text().primaryKey(),
|
id: text().primaryKey(),
|
||||||
userId: integer()
|
userId: integer()
|
||||||
@ -23,8 +29,75 @@ export const sessions = pgTable("sessions", {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const sessionRelations = relations(sessions, ({ one }) => ({
|
export const sessionRelations = relations(sessions, ({ one }) => ({
|
||||||
user: one(users, {
|
user: one(users, {
|
||||||
fields: [ sessions.userId ],
|
fields: [sessions.userId],
|
||||||
references: [users.id],
|
references: [users.id],
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
export const accounts = pgTable("accounts", {
|
||||||
|
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||||
|
ownerId: integer()
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id),
|
||||||
|
name: text().notNull(),
|
||||||
|
currency: text().notNull(),
|
||||||
|
startingDate: timestamp({ withTimezone: true }).notNull(),
|
||||||
|
startingBalance: numeric().notNull().default("0"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const accountRelations = relations(accounts, ({ one, many }) => ({
|
||||||
|
owner: one(users, {
|
||||||
|
fields: [accounts.ownerId],
|
||||||
|
references: [users.id],
|
||||||
|
}),
|
||||||
|
credits: many(receipts, { relationName: "account_from" }),
|
||||||
|
debits: many(receipts, { relationName: "account_to" }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const receipts = pgTable(
|
||||||
|
"receipts",
|
||||||
|
{
|
||||||
|
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||||
|
date: timestamp({ withTimezone: true }).notNull(),
|
||||||
|
|
||||||
|
accountFromId: integer()
|
||||||
|
.notNull()
|
||||||
|
.references(() => accounts.id),
|
||||||
|
|
||||||
|
accountToId: integer().references(() => accounts.id),
|
||||||
|
},
|
||||||
|
(table) => [
|
||||||
|
check("no_account_loops", ne(table.accountFromId, table.accountToId)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const receiptRelations = relations(receipts, ({ one, many }) => ({
|
||||||
|
accountFrom: one(accounts, {
|
||||||
|
fields: [receipts.accountFromId],
|
||||||
|
references: [accounts.id],
|
||||||
|
relationName: "account_from",
|
||||||
|
}),
|
||||||
|
accountTo: one(accounts, {
|
||||||
|
fields: [receipts.accountToId],
|
||||||
|
references: [accounts.id],
|
||||||
|
relationName: "account_to",
|
||||||
|
}),
|
||||||
|
receipts: many(receipts),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const receiptItems = pgTable("receipt_items", {
|
||||||
|
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||||
|
receiptId: integer()
|
||||||
|
.notNull()
|
||||||
|
.references(() => receipts.id),
|
||||||
|
amount: numeric().notNull().default("0"),
|
||||||
|
category: text().notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const receiptItemRelations = relations(receiptItems, ({ one }) => ({
|
||||||
|
receipt: one(receipts, {
|
||||||
|
fields: [receiptItems.receiptId],
|
||||||
|
references: [receipts.id],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
@ -1,45 +1,66 @@
|
|||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { db } from "./db";
|
import { db } from "./db";
|
||||||
import { users } from "./schema";
|
import { users } from "./schema";
|
||||||
|
import type { RequestEvent } from "../../routes/$types";
|
||||||
|
import { redirect } from "@sveltejs/kit";
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
id: number;
|
id: number;
|
||||||
username: string;
|
username: string;
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getUserFromUsername(
|
||||||
export async function getUserFromUsername(username: string): Promise<User | null> {
|
username: string,
|
||||||
const user = await db.query.users.findFirst({
|
): Promise<User | null> {
|
||||||
columns: {
|
const user = await db.query.users.findFirst({
|
||||||
id: true,
|
columns: {
|
||||||
username: true,
|
id: true,
|
||||||
displayName: true
|
username: true,
|
||||||
},
|
displayName: true,
|
||||||
where: eq(users.username, username)
|
},
|
||||||
});
|
where: eq(users.username, username),
|
||||||
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...user,
|
...user,
|
||||||
displayName: user.displayName || undefined,
|
displayName: user.displayName || undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUserPasswordHash(userId: number): Promise<string|null> {
|
export async function getUserPasswordHash(
|
||||||
const user = (await db.query.users.findFirst({
|
userId: number,
|
||||||
columns: {
|
): Promise<string | null> {
|
||||||
passwordHash: true,
|
const user = await db.query.users.findFirst({
|
||||||
},
|
columns: {
|
||||||
where: eq(users.id, userId)
|
passwordHash: true,
|
||||||
}));
|
},
|
||||||
|
where: eq(users.id, userId),
|
||||||
|
});
|
||||||
|
|
||||||
if (user === undefined) {
|
if (user === undefined) {
|
||||||
throw new Error("Invalid user ID");
|
throw new Error("Invalid user ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
return user.passwordHash;
|
return user.passwordHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ensureUser(
|
||||||
|
event: RequestEvent,
|
||||||
|
defaultAction: (() => never) | undefined = undefined,
|
||||||
|
): User {
|
||||||
|
const user = event.locals.user;
|
||||||
|
if (user !== null) {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultAction === undefined) {
|
||||||
|
redirect(303, "/login");
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultAction();
|
||||||
|
}
|
||||||
|
12
src/routes/+page.server.ts
Normal file
12
src/routes/+page.server.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { getUserAccountSummary } from "$lib/server/accounting";
|
||||||
|
import { ensureUser } from "$lib/server/user";
|
||||||
|
import type { PageServerLoad } from "./$types";
|
||||||
|
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async (event) => {
|
||||||
|
const user = ensureUser(event);
|
||||||
|
|
||||||
|
return {
|
||||||
|
accountSummaries: await getUserAccountSummary(user.id),
|
||||||
|
}
|
||||||
|
};
|
@ -6,3 +6,11 @@
|
|||||||
|
|
||||||
<h1>Welcome to SvelteKit</h1>
|
<h1>Welcome to SvelteKit</h1>
|
||||||
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
|
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
|
||||||
|
|
||||||
|
Your accounts:
|
||||||
|
<ul>
|
||||||
|
{#each data.accountSummaries as summary }
|
||||||
|
<li>{summary.name} - {summary.lastReceipt ?? "never"} - {summary.balance}{summary.currency}</li>
|
||||||
|
|
||||||
|
{/each}
|
||||||
|
</ul>
|
Loading…
x
Reference in New Issue
Block a user