diff --git a/.gitignore b/.gitignore index 77a530a..bc9b0e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ _fresh/ traq/ + +.env diff --git a/compose.yaml b/compose.yaml index a678821..732188f 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,15 +1,18 @@ services: mariadb: image: mariadb:10.11.16-jammy + restart: unless-stopped ports: - 3306:3306 environment: - MARIADB_DATABASE: qonstellation MARIADB_ROOT_PASSWORD: password volumes: - type: volume source: mariadb_data target: /var/lib/mysql + - type: bind + source: ./dev/mariadb/ + target: /docker-entrypoint-initdb.d/ healthcheck: test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] start_period: 10s @@ -17,8 +20,46 @@ services: timeout: 10s retries: 10 + traq_server: + image: ghcr.io/traptitech/traq:3.28.1 + restart: unless-stopped + environment: + TRAQ_MARIADB_HOST: mariadb + TRAQ_MARIADB_USER: root + TRAQ_MARIADB_PASSWORD: password + TRAQ_STORAGE_LOCAL_DIR: /app/storage + volumes: + - type: volume + source: traq_storage + target: /app/storage + depends_on: + mariadb: + condition: service_healthy + + traq_ui: + image: ghcr.io/traptitech/traq-ui:3.31.3 + restart: unless-stopped + volumes: + - type: volume + source: traq_override + target: /app/override + + caddy: + image: caddy:2.11.2-alpine + restart: unless-stopped + ports: + - 3000:3000 + volumes: + - type: bind + source: ./dev/caddy/ + target: /etc/caddy/ + depends_on: + - traq_server + - traq_ui + adminer: image: adminer:5.4.2 + restart: unless-stopped ports: - 8080:8080 environment: @@ -29,3 +70,5 @@ services: volumes: mariadb_data: + traq_storage: + traq_override: diff --git a/database/db.ts b/database/db.ts index 5db6105..875947b 100644 --- a/database/db.ts +++ b/database/db.ts @@ -1,5 +1,6 @@ import { Kysely, MysqlDialect } from "@kysely/kysely" import { createPool } from "mysql2" +import { config } from "../lib/config.ts" import type { PostsTable } from "./posts.ts" import type { SystemStatesTable } from "./systemStates.ts" import type { UsersTable } from "./users.ts" @@ -16,11 +17,11 @@ export interface Database { const dialect = new MysqlDialect({ pool: createPool({ - host: Deno.env.get("DB_HOST") ?? "localhost", - port: Number(Deno.env.get("DB_PORT") ?? 3306), - user: Deno.env.get("DB_USER") ?? "root", - password: Deno.env.get("DB_PASSWORD") ?? "password", - database: Deno.env.get("DB_NAME") ?? "qonstellation", + host: config.dbHost, + port: config.dbPort, + user: config.dbUser, + password: config.dbPassword, + database: config.dbName, }), }) diff --git a/deno.jsonc b/deno.jsonc index 71b77a4..1717ed9 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -34,7 +34,8 @@ }, "tasks": { "build": "deno run -A vite build", - "dev": "deno run -A vite" + "create-local-client": "deno run -A scripts/createOauthClient.ts >> .env", + "dev": "deno run --env-file -A vite" }, "unstable": [ "sloppy-imports" // Required for generated traQ client code diff --git a/dev/caddy/Caddyfile b/dev/caddy/Caddyfile new file mode 100644 index 0000000..e266cd4 --- /dev/null +++ b/dev/caddy/Caddyfile @@ -0,0 +1,9 @@ +:3000 { + handle /api/* { + reverse_proxy traq_server:3000 + } + + handle { + reverse_proxy traq_ui:80 + } +} diff --git a/dev/mariadb/init.sql b/dev/mariadb/init.sql new file mode 100644 index 0000000..1c6c137 --- /dev/null +++ b/dev/mariadb/init.sql @@ -0,0 +1,2 @@ +CREATE DATABASE IF NOT EXISTS qonstellation; +CREATE DATABASE IF NOT EXISTS traq; diff --git a/lib/config.ts b/lib/config.ts new file mode 100644 index 0000000..c5cfeb0 --- /dev/null +++ b/lib/config.ts @@ -0,0 +1,29 @@ +const qonstellationJwtSecret = Deno.env.get("QONSTELLATION_JWT_SECRET") + +if (!qonstellationJwtSecret) { + throw new Error("QONSTELLATION_JWT_SECRET is not set") +} + +const traqClientId = Deno.env.get("TRAQ_CLIENT_ID") + +if (!traqClientId) { + throw new Error("TRAQ_CLIENT_ID is not set") +} + +const traqClientSecret = Deno.env.get("TRAQ_CLIENT_SECRET") + +if (!traqClientSecret) { + throw new Error("TRAQ_CLIENT_SECRET is not set") +} + +export const config = { + qonstellationJwtSecret, + dbHost: Deno.env.get("DB_HOST") ?? "localhost", + dbPort: Number(Deno.env.get("DB_PORT") ?? 3306), + dbUser: Deno.env.get("DB_USER") ?? "root", + dbPassword: Deno.env.get("DB_PASSWORD") ?? "password", + dbName: Deno.env.get("DB_NAME") ?? "qonstellation", + traqBaseUrl: Deno.env.get("TRAQ_BASE_URL") ?? "http://localhost:3000", + traqClientId, + traqClientSecret, +} as const diff --git a/lib/oauth2.ts b/lib/oauth2.ts index 2bed6a5..c3113d1 100644 --- a/lib/oauth2.ts +++ b/lib/oauth2.ts @@ -1,9 +1,10 @@ import { OAuth2Client } from "@badgateway/oauth2-client" +import { config } from "./config.ts" export const oauth2Client = new OAuth2Client({ - server: "https://q.trap.jp", - clientId: Deno.env.get("TRAQ_CLIENT_ID")!, - clientSecret: Deno.env.get("TRAQ_CLIENT_SECRET")!, + server: config.traqBaseUrl, + clientId: config.traqClientId, + clientSecret: config.traqClientSecret, tokenEndpoint: "/api/v3/oauth2/token", authorizationEndpoint: "/api/v3/oauth2/authorize", }) diff --git a/lib/session.ts b/lib/session.ts index 91d8e80..e4a8821 100644 --- a/lib/session.ts +++ b/lib/session.ts @@ -1,10 +1,9 @@ import { decodeBase64 } from "@std/encoding" import { getCookies, setCookie } from "@std/http" import { jwtVerify, SignJWT } from "jose" +import { config } from "./config.ts" -const secret = decodeBase64( - Deno.env.get("QONSTELLATION_JWT_SECRET")!, -) +const secret = decodeBase64(config.qonstellationJwtSecret) const COOKIE_NAME = "qonstellation_session" export const createSessionToken = (userId: string) => diff --git a/scripts/createOauthClient.ts b/scripts/createOauthClient.ts new file mode 100644 index 0000000..c83f4d1 --- /dev/null +++ b/scripts/createOauthClient.ts @@ -0,0 +1,38 @@ +import { client } from "../traq/client.gen.ts" +import { createClient, login } from "../traq/index.ts" + +client.setConfig({ + baseUrl: "http://localhost:3000/api/v3", +}) + +const loginRes = await login({ + body: { + name: "traq", + password: "traq", + }, +}) + +if (!loginRes.response?.ok) { + console.log(loginRes.response) + + throw new Error("Failed to login") +} + +const clientRes = await createClient({ + body: { + name: "Qonstellation", + description: "Test Client", + callbackUrl: "http://localhost:5173/callback", + scopes: ["read", "write"], + }, + headers: { + Cookie: loginRes.response?.headers.get("set-cookie"), + }, +}) + +if (!clientRes.data) { + throw new Error(`Failed to create client: ${clientRes.error}`) +} + +console.log(`TRAQ_CLIENT_ID=${clientRes.data.id}`) +console.log(`TRAQ_CLIENT_SECRET=${clientRes.data.secret}`) diff --git a/service/jestream.ts b/service/jestream.ts index a0405fc..a9de463 100644 --- a/service/jestream.ts +++ b/service/jestream.ts @@ -1,13 +1,19 @@ import { Jetstream } from "@skyware/jetstream" import { buildAtProtoUri } from "../lib/atProto.ts" +import { config } from "../lib/config.ts" import { getTraqMessageIdByAtProtoUri, savePostMetadata, } from "../repository/post.ts" import { saveJetstreamCursor } from "../repository/systemState.ts" import { getUserAccessToken, getUserSettingByDid } from "../repository/user.ts" +import { client } from "../traq/client.gen.ts" import { postMessage } from "../traq/index.ts" +client.setConfig({ + baseUrl: `${config.traqBaseUrl}/api/v3`, +}) + export class JetstreamService { private jetstream: Jetstream private cursor?: number