+6
.dockerignore
+6
.dockerignore
+19
Dockerfile
+19
Dockerfile
···
1
+
FROM node:24-alpine AS base
2
+
ENV PNPM_HOME="/pnpm"
3
+
ENV PATH="$PNPM_HOME:$PATH"
4
+
ENV NODE_ENV=production
5
+
RUN corepack enable
6
+
7
+
FROM base AS build
8
+
COPY . /app
9
+
WORKDIR /app
10
+
11
+
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
12
+
RUN pnpm run -r build
13
+
RUN pnpm deploy --filter=./apps/web --prod /prod/web
14
+
15
+
FROM base AS web
16
+
COPY --from=build /prod/web /prod/web
17
+
WORKDIR /prod/web
18
+
EXPOSE 8002
19
+
CMD ["pnpm", "start"]
+1
-1
apps/bot/commands/auth.ts
+1
-1
apps/bot/commands/auth.ts
+1
-1
apps/bot/commands/ping.ts
+1
-1
apps/bot/commands/ping.ts
+1
-1
apps/bot/commands/top.ts
+1
-1
apps/bot/commands/top.ts
+1
-1
apps/bot/deploy-commands.ts
+1
-1
apps/bot/deploy-commands.ts
+2
-2
apps/bot/main.ts
+2
-2
apps/bot/main.ts
···
1
-
import { DISCORD_BOT_TOKEN } from "@tealfmbot/common/constants.js";
2
-
import { logger } from "@tealfmbot/common/logger.js";
1
+
import { DISCORD_BOT_TOKEN } from "@tealfmbot/common/constants";
2
+
import { logger } from "@tealfmbot/common/logger";
3
3
import { Client, Collection, Events, GatewayIntentBits, MessageFlags } from "discord.js";
4
4
import fs from "node:fs";
5
5
import path from "node:path";
+1
-1
apps/tapper/index.ts
+1
-1
apps/tapper/index.ts
···
1
1
import { SimpleIndexer, Tap } from "@atproto/tap";
2
-
import { TAP_ADMIN_PASSWORD } from "@tealfmbot/common/constants.js";
2
+
import { TAP_ADMIN_PASSWORD } from "@tealfmbot/common/constants";
3
3
// import { db } from "./kysely/db.ts"
4
4
5
5
const tap = new Tap("https://tap.xero.systems", {
apps/web/Dockerfile
apps/web/Dockerfile
This is a binary file and will not be displayed.
+3
-3
apps/web/client.ts
+3
-3
apps/web/client.ts
···
5
5
NodeOAuthClient,
6
6
type OAuthClientMetadataInput,
7
7
} from "@atproto/oauth-client-node";
8
-
import { PUBLIC_URL, PRIVATE_KEYS } from "@tealfmbot/common/constants.js";
9
-
import { db } from "@tealfmbot/database/db.ts";
8
+
import { PUBLIC_URL, PRIVATE_KEYS } from "@tealfmbot/common/constants";
9
+
import { db } from "@tealfmbot/database/db";
10
10
import assert from "node:assert";
11
11
12
-
import { SessionStore, StateStore } from "./storage";
12
+
import { SessionStore, StateStore } from "./storage.js";
13
13
14
14
const keyset =
15
15
PUBLIC_URL && PRIVATE_KEYS
+4
-4
apps/web/index.ts
+4
-4
apps/web/index.ts
···
1
1
import { serve, type HttpBindings } from "@hono/node-server";
2
-
import { COOKIE_SECRET } from "@tealfmbot/common/constants.js";
3
-
import { logger } from "@tealfmbot/common/logger.js";
2
+
import { COOKIE_SECRET } from "@tealfmbot/common/constants";
3
+
import { logger } from "@tealfmbot/common/logger";
4
4
import { Hono } from "hono";
5
5
import { deleteCookie, getSignedCookie } from "hono/cookie";
6
6
import { html } from "hono/html";
7
7
import { validator } from "hono/validator";
8
8
import pinoHttpLogger from "pino-http";
9
9
10
-
import { client } from "./client";
11
-
import { createSession, getSessionAgent, MAX_AGE, validateIdentifier } from "./utils";
10
+
import { client } from "./client.js";
11
+
import { createSession, getSessionAgent, MAX_AGE, validateIdentifier } from "./utils.js";
12
12
13
13
type Variables = {
14
14
logger: typeof logger;
+1
-1
apps/web/package.json
+1
-1
apps/web/package.json
+1
-1
apps/web/storage.ts
+1
-1
apps/web/storage.ts
···
4
4
NodeSavedState,
5
5
NodeSavedStateStore,
6
6
} from "@atproto/oauth-client-node";
7
-
import type { Database } from "@tealfmbot/database/types.ts";
7
+
import type { Database } from "@tealfmbot/database/types";
8
8
9
9
export class StateStore implements NodeSavedStateStore {
10
10
constructor(private db: Database) {}
-3
apps/web/tsconfig.json
-3
apps/web/tsconfig.json
+3
-3
apps/web/utils.ts
+3
-3
apps/web/utils.ts
···
3
3
import { Agent } from "@atproto/api";
4
4
import { isAtprotoDid, isAtprotoDidWeb } from "@atproto/did";
5
5
import { isValidHandle } from "@atproto/syntax";
6
-
import { COOKIE_SECRET } from "@tealfmbot/common/constants.js";
7
-
import { logger } from "@tealfmbot/common/logger.js";
6
+
import { COOKIE_SECRET } from "@tealfmbot/common/constants";
7
+
import { logger } from "@tealfmbot/common/logger";
8
8
import { deleteCookie, generateSignedCookie, getSignedCookie } from "hono/cookie";
9
9
10
-
import { client } from "./client";
10
+
import { client } from "./client.js";
11
11
12
12
export const MAX_AGE = process.env.NODE_ENV === "production" ? 60 : 0;
13
13
+1
package.json
+1
package.json
···
15
15
"bot": "pnpm --filter bot dev",
16
16
"tap": "pnpm --filter tapper dev",
17
17
"web": "pnpm --filter web dev",
18
+
"docker:web": "docker build . --target web --tag web:latest",
18
19
"dev:all": "pnpm --filter './apps/**' dev",
19
20
"typecheck": "pnpm --filter './apps/**' typecheck",
20
21
"lint": "oxlint",
+3
-1
packages/common/constants.ts
+3
-1
packages/common/constants.ts
···
1
1
import path from "node:path";
2
2
import { loadEnvFile } from "node:process";
3
3
4
-
loadEnvFile(path.join(import.meta.dirname, "../../.env"));
4
+
if (process.env.NODE_ENV !== "production") {
5
+
loadEnvFile(path.join(import.meta.dirname, "../../.env"));
6
+
}
5
7
6
8
export const DISCORD_BOT_TOKEN = process.env.DISCORD_BOT_TOKEN as string;
7
9
export const DISCORD_APPLICATION_ID = process.env.DISCORD_APPLICATION_ID as string;
+2
packages/common/index.ts
+2
packages/common/index.ts
+11
-8
packages/common/logger.ts
+11
-8
packages/common/logger.ts
···
1
1
import pino from "pino";
2
2
3
-
export const logger = pino({
4
-
transport: {
5
-
target: "pino-pretty",
6
-
options: {
7
-
colorize: true,
8
-
},
9
-
},
10
-
});
3
+
export const logger =
4
+
process.env.NODE_ENV === "production"
5
+
? pino()
6
+
: pino({
7
+
transport: {
8
+
target: "pino-pretty",
9
+
options: {
10
+
colorize: true,
11
+
},
12
+
},
13
+
});
+10
-2
packages/common/package.json
+10
-2
packages/common/package.json
···
4
4
"private": true,
5
5
"type": "module",
6
6
"exports": {
7
-
"./*": "./*"
8
-
},
7
+
"./constants": {
8
+
"types": "./dist/constants.d.ts",
9
+
"import": "./dist/constants.js"
10
+
},
11
+
"./logger": {
12
+
"types": "./dist/logger.d.ts",
13
+
"import": "./dist/logger.js"
14
+
}
15
+
},
9
16
"scripts": {
17
+
"build": "tsc",
10
18
"typecheck": "tsc --noEmit"
11
19
},
12
20
"dependencies": {
+8
-1
packages/common/tsconfig.json
+8
-1
packages/common/tsconfig.json
+51
packages/database/database.d.ts
+51
packages/database/database.d.ts
···
1
+
/**
2
+
* This file was generated by kysely-codegen.
3
+
* Please do not edit it manually.
4
+
*/
5
+
6
+
import type { ColumnType } from "kysely";
7
+
8
+
export type Generated<T> =
9
+
T extends ColumnType<infer S, infer I, infer U>
10
+
? ColumnType<S, I | undefined, U>
11
+
: ColumnType<T, T | undefined, T>;
12
+
13
+
export type Timestamp = ColumnType<Date, Date | string, Date | string>;
14
+
15
+
export interface Artists {
16
+
artist_name: string;
17
+
play_id: number;
18
+
}
19
+
20
+
export interface AuthSession {
21
+
key: string;
22
+
session: string;
23
+
}
24
+
25
+
export interface AuthState {
26
+
key: string;
27
+
state: string;
28
+
}
29
+
30
+
export interface Plays {
31
+
id: Generated<number>;
32
+
indexed_at: Generated<Timestamp>;
33
+
played_time: Timestamp;
34
+
release_name: string;
35
+
track_name: string;
36
+
user_id: number;
37
+
}
38
+
39
+
export interface Users {
40
+
created_at: Generated<Timestamp>;
41
+
did: string;
42
+
id: Generated<number>;
43
+
}
44
+
45
+
export interface DB {
46
+
artists: Artists;
47
+
auth_session: AuthSession;
48
+
auth_state: AuthState;
49
+
plays: Plays;
50
+
users: Users;
51
+
}
+3
-3
packages/database/db.ts
+3
-3
packages/database/db.ts
···
1
-
import type { DB } from "kysely-codegen";
2
-
3
-
import { DATABASE_URL } from "@tealfmbot/common/constants.js";
1
+
import { DATABASE_URL } from "@tealfmbot/common/constants";
4
2
import { Kysely, PostgresDialect } from "kysely";
5
3
import { Pool } from "pg";
4
+
5
+
import type { DB } from "./database.js";
6
6
7
7
const dialect = new PostgresDialect({
8
8
pool: new Pool({
+3
-3
packages/database/migrate.ts
+3
-3
packages/database/migrate.ts
···
1
-
import type { DB } from "kysely-codegen";
2
-
3
-
import { DATABASE_URL } from "@tealfmbot/common/constants.ts";
1
+
import { DATABASE_URL } from "@tealfmbot/common/constants.js";
4
2
import { Kysely, Migrator, PostgresDialect, FileMigrationProvider } from "kysely";
5
3
import { run } from "kysely-migration-cli";
6
4
import fs from "node:fs/promises";
7
5
import path from "node:path";
8
6
import { Pool } from "pg";
7
+
8
+
import type { DB } from "./database.js";
9
9
10
10
async function migrateToLatest() {
11
11
const migrationFolder = new URL("../migrations", import.meta.url).pathname;
+12
-4
packages/database/package.json
+12
-4
packages/database/package.json
···
4
4
"private": true,
5
5
"type": "module",
6
6
"exports": {
7
-
"./db.ts": "./db.ts",
8
-
"./types.ts": "./types.ts"
9
-
},
7
+
"./db": {
8
+
"types": "./dist/db.d.ts",
9
+
"import": "./dist/db.js"
10
+
},
11
+
"./types": {
12
+
"types": "./dist/types.d.ts",
13
+
"import": "./dist/types.js"
14
+
}
15
+
},
16
+
"types": "./database.d.ts",
10
17
"scripts": {
18
+
"build": "tsc",
11
19
"migrate": "kysely migrate latest",
12
-
"codegen": "kysely-codegen --dialect postgres --env-file='../../.env'",
20
+
"codegen": "kysely-codegen --dialect postgres --env-file='../../.env' --out-file ./database.d.ts",
13
21
"seed": "tsx seed.ts",
14
22
"typecheck": "tsc --noEmit"
15
23
},
+9
-1
packages/database/tsconfig.json
+9
-1
packages/database/tsconfig.json
···
1
1
{
2
2
"extends": "@tealfmbot/tsconfig/tsconfig.node.json",
3
-
"exclude": ["node_modules"]
3
+
"exclude": ["node_modules"],
4
+
"compilerOptions": {
5
+
"rootDir": ".",
6
+
"declaration": true,
7
+
"declarationMap": false,
8
+
"emitDeclarationOnly": false,
9
+
"outDir": "./dist"
10
+
},
11
+
"include": ["db.ts", "database.d.ts", "types.ts"]
4
12
}
+3
-7
packages/database/types.ts
+3
-7
packages/database/types.ts
···
1
-
// @ts-nocheck
2
-
import type { Artists, Plays } from "kysely-codegen";
1
+
import type { Kysely } from "kysely";
3
2
4
-
import { db } from "./db.ts";
3
+
import type { DB } from "./database.js";
5
4
6
-
export type Database = typeof db;
7
-
export interface TealFMPlay extends Plays {
8
-
artists: Artists;
9
-
}
5
+
export type Database = Kysely<DB>;
+2
packages/tsconfig/tsconfig.node.json
+2
packages/tsconfig/tsconfig.node.json
+1
pnpm-lock.yaml
+1
pnpm-lock.yaml