Skip to content

Commit

Permalink
feat: Added table for assets
Browse files Browse the repository at this point in the history
  • Loading branch information
matvp91 committed Oct 31, 2024
1 parent 5d0d374 commit 11f5023
Show file tree
Hide file tree
Showing 23 changed files with 405 additions and 282 deletions.
6 changes: 5 additions & 1 deletion packages/api/src/db/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Kysely, PostgresDialect } from "kysely";
import { Pool } from "pg";
import { Pool, types } from "pg";
import { env } from "../env";
import type { KyselyDatabase } from "./types.ts";

Expand All @@ -8,3 +8,7 @@ export const db = new Kysely<KyselyDatabase>({
pool: new Pool({ connectionString: env.DATABASE }),
}),
});

types.setTypeParser(/* INT8_TYPE_ID= */ 20, (val) => {
return parseInt(val, 10);
});
32 changes: 23 additions & 9 deletions packages/api/src/db/migrations/2024_10_26_init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,6 @@ export async function up(db: Kysely<any>) {
.addColumn("autoRefresh", "boolean", (col) => col.notNull().defaultTo(true))
.execute();

await db
.insertInto("users")
.values({
username: "admin",
password: await Bun.password.hash("admin"),
})
.execute();

await db.schema
.createTable("groups")
.addColumn("id", "serial", (col) => col.primaryKey())
Expand All @@ -37,9 +29,31 @@ export async function up(db: Kysely<any>) {
col.notNull().defaultTo(sql`now()`),
)
.execute();

await db.schema
.createTable("playables")
.addColumn("assetId", "uuid", (col) =>
col.references("assets.id").onDelete("cascade"),
)
.addColumn("name", "text", (col) => col.notNull())
.addColumn("createdAt", "timestamp", (col) =>
col.notNull().defaultTo(sql`now()`),
)
.addPrimaryKeyConstraint("id", ["assetId", "name"])
.execute();

await db
.insertInto("users")
.values({
username: "admin",
password: await Bun.password.hash("admin"),
})
.execute();
}

export async function down(db: Kysely<any>) {
await db.schema.dropTable("users").execute();
await db.schema.dropTable("assets");
await db.schema.dropTable("groups").execute();
await db.schema.dropTable("assets").execute();
await db.schema.dropTable("playables").execute();
}
9 changes: 9 additions & 0 deletions packages/api/src/db/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export interface KyselyDatabase {
users: UsersTable;
groups: GroupsTable;
assets: AssetsTable;
playables: PlayablesTable;
}

export interface UsersTable {
Expand All @@ -29,3 +30,11 @@ export interface AssetsTable {
}

export type AssetInsert = Insertable<AssetsTable>;

export interface PlayablesTable {
assetId: string;
name: string;
createdAt: ColumnType<Date, never, never>;
}

export type PlayableInsert = Insertable<PlayablesTable>;
2 changes: 2 additions & 0 deletions packages/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
StorageFolderSchema,
StorageFileSchema,
AssetSchema,
GroupSchema,
} from "./types";

// Import workers and they'll start running immediately.
Expand Down Expand Up @@ -96,6 +97,7 @@ const app = new Elysia()
StorageFolder: StorageFolderSchema,
StorageFile: StorageFileSchema,
Asset: AssetSchema,
Group: GroupSchema,
})
.use(token)
.use(user)
Expand Down
23 changes: 16 additions & 7 deletions packages/api/src/repositories/assets.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
import { db } from "../db";
import { executeWithPagination } from "../utils/query-paginate";
import { executeAsTable } from "../utils/query-table";
import type { AssetInsert } from "../db/types";
import type { TableQuery } from "../utils/query-table";

export async function createAsset(fields: AssetInsert) {
return await db.insertInto("assets").values(fields).executeTakeFirstOrThrow();
}

export async function getAssets(page: number, perPage: number) {
const query = db.selectFrom("assets").select(["id", "groupId", "createdAt"]);
return await executeWithPagination(query, {
page,
perPage,
});
export async function getAssetsTable(query: TableQuery) {
return await executeAsTable(
query,
db
.selectFrom("assets")
.leftJoin("playables", "playables.assetId", "assets.id")
.select(({ fn }) => [
"assets.id",
"assets.groupId",
"assets.createdAt",
fn.count<number>("playables.assetId").as("playablesCount"),
])
.groupBy("assets.id"),
);
}
9 changes: 9 additions & 0 deletions packages/api/src/repositories/playables.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { db } from "../db";
import type { PlayableInsert } from "../db/types";

export async function createPlayable(fields: PlayableInsert) {
return await db
.insertInto("playables")
.values(fields)
.executeTakeFirstOrThrow();
}
22 changes: 6 additions & 16 deletions packages/api/src/routes/assets.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,25 @@
import { Elysia, t } from "elysia";
import { Elysia } from "elysia";
import { authUser } from "./token";
import { getAssets } from "../repositories/assets";
import { getAssetsTable } from "../repositories/assets";
import { getGroups } from "../repositories/groups";
import { AssetSchema } from "../types";
import { tableQuery, getTableObject } from "../utils/query-table";

export const assets = new Elysia()
.use(authUser)
.get(
"/assets",
async ({ query }) => {
const PER_PAGE = 30;
const assets = await getAssets(query.page, PER_PAGE);
return {
page: query.page,
...assets,
};
return await getAssetsTable(query);
},
{
detail: {
summary: "Get all assets",
tags: ["Assets"],
},
query: t.Object({
page: t.Number({ default: 1 }),
}),
query: tableQuery,
response: {
200: t.Object({
page: t.Number(),
totalPages: t.Number(),
rows: t.Array(AssetSchema),
}),
200: getTableObject(AssetSchema),
},
},
)
Expand Down
13 changes: 13 additions & 0 deletions packages/api/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,23 @@ export const AssetSchema = t.Object(
id: t.String({ format: "uuid" }),
groupId: t.Nullable(t.Number()),
createdAt: t.Date(),
playablesCount: t.Number(),
},
{
$id: "#/components/schemas/Asset",
},
);

export type Asset = Static<typeof AssetSchema>;

export const GroupSchema = t.Object(
{
id: t.Number(),
name: t.String(),
},
{
$id: "#/components/schemas/Group",
},
);

export type Group = Static<typeof GroupSchema>;
41 changes: 0 additions & 41 deletions packages/api/src/utils/query-paginate.ts

This file was deleted.

55 changes: 55 additions & 0 deletions packages/api/src/utils/query-table.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { t } from "elysia";
import type { SelectQueryBuilder, TableNode } from "kysely";
import type { Static, TSchema } from "elysia";
import { db } from "../db";

export type TableResult<O> = {
rows: O[];
totalPages: number;
};

export const tableQuery = t.Object({
page: t.Number(),
perPage: t.Number(),
sort: t.String(),
});

export type TableQuery = Static<typeof tableQuery>;

export function getTableObject<T extends TSchema>(schema: T) {
return t.Object({
page: t.Number(),
totalPages: t.Number(),
rows: t.Array(schema),
});
}

export async function executeAsTable<O, DB, TB extends keyof DB>(
query: TableQuery,
qb: SelectQueryBuilder<DB, TB, O>,
) {
const from = qb.toOperationNode().from?.froms[0] as TableNode;

const { count } = await db
// @ts-expect-error Table string
.selectFrom(from.table.identifier.name)
.select((eb) => eb.fn.count<number>("id").as("count"))
.executeTakeFirstOrThrow();

const [sortKey, sortMode] = query.sort.split(":");

const rows = await qb
.limit(query.perPage)
.offset((query.page - 1) * query.perPage)
// @ts-expect-error map sortKey
.orderBy(sortKey, sortMode)
.execute();

const totalPages = Math.ceil(count / query.perPage);

return {
page: query.page,
totalPages,
rows,
};
}
Loading

0 comments on commit 11f5023

Please sign in to comment.