diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e69de29 diff --git a/.gitignore b/.gitignore index 6b142b0..eb95056 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ node_modules/ # production /build */dist +**/dist # misc .DS_Store diff --git a/apps/aqua/Dockerfile b/apps/aqua/Dockerfile new file mode 100644 index 0000000..df5aead --- /dev/null +++ b/apps/aqua/Dockerfile @@ -0,0 +1,54 @@ +# Use the official Bun image as base +FROM oven/bun:1.0 AS builder + +# Set working directory +WORKDIR /app + +# Copy root workspace files +COPY package.json bun.lockb ./ + +# Copy turbo.json +COPY turbo.json ./ + +# Copy workspace packages +COPY packages/db/ ./packages/db/ +COPY packages/lexicons/ ./packages/lexicons/ +COPY packages/tsconfig/ ./packages/tsconfig/ + +# Copy the aqua app +COPY apps/aqua/ ./apps/aqua/ + +# Install dependencies +RUN bun install + +# Build workspace packages (if needed) +RUN bun run build --filter=@teal/db +RUN bun run build --filter=@teal/lexicons + +# Build the aqua app +WORKDIR /app/apps/aqua +RUN bun run build + +# Start production image (node lts ideally) +FROM node:bookworm-slim + +WORKDIR /app + +# Copy built assets from builder +COPY --from=builder /app/apps/aqua/dist ./dist +# copy base node modules +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/apps/aqua/node_modules ./node_modules +COPY --from=builder /app/apps/aqua/package.json ./ + +# move public to cwd +RUN mv ./dist/public ./public + +# Set environment variables +ENV NODE_ENV=production + +# Expose the port your app runs on +EXPOSE 3000 + +# Start the application +CMD ["npm", "run", "start"] diff --git a/apps/aqua/package.json b/apps/aqua/package.json index 978f3c7..5f6d7f9 100644 --- a/apps/aqua/package.json +++ b/apps/aqua/package.json @@ -7,8 +7,9 @@ "scripts": { "dev": "tsx watch --clear-screen=false src/index.ts | pino-pretty", "bun:dev": "bun run --hot src/index.ts | pino-pretty", - "build": "tsup", - "start": "node dist/index.js", + "deno:dev": "deno run --allow-net --allow-read --allow-env --allow-run --unstable --watch src/index.ts | pino-pretty", + "build": "tsup && cp -r public dist/", + "start": "node dist/index.cjs | pino-pretty", "lexgen": "lex gen-server ./src/lexicon ./lexicons/*", "clean": "rimraf dist coverage", "check-types": "tsc --noEmit", @@ -21,7 +22,7 @@ "@atproto/common": "^0.4.4", "@atproto/identity": "^0.4.3", "@atproto/lexicon": "^0.4.2", - "@atproto/oauth-client-node": "^0.2.0", + "@atproto/oauth-client-node": "^0.2.1", "@atproto/sync": "^0.1.5", "@atproto/syntax": "^0.3.0", "@atproto/xrpc-server": "^0.6.4", @@ -49,15 +50,5 @@ "tsup": "^8.3.5", "tsx": "^4.19.2", "typescript": "^5.6.3" - }, - "tsup": { - "entry": [ - "src", - "!src/**/__tests__/**", - "!src/**/*.test.*" - ], - "splitting": false, - "sourcemap": true, - "clean": true } -} \ No newline at end of file +} diff --git a/apps/aqua/src/public/fonts/LM-bold-italic.ttf b/apps/aqua/public/fonts/LM-bold-italic.ttf similarity index 100% rename from apps/aqua/src/public/fonts/LM-bold-italic.ttf rename to apps/aqua/public/fonts/LM-bold-italic.ttf diff --git a/apps/aqua/src/public/fonts/LM-bold-italic.woff b/apps/aqua/public/fonts/LM-bold-italic.woff similarity index 100% rename from apps/aqua/src/public/fonts/LM-bold-italic.woff rename to apps/aqua/public/fonts/LM-bold-italic.woff diff --git a/apps/aqua/src/public/fonts/LM-bold-italic.woff2 b/apps/aqua/public/fonts/LM-bold-italic.woff2 similarity index 100% rename from apps/aqua/src/public/fonts/LM-bold-italic.woff2 rename to apps/aqua/public/fonts/LM-bold-italic.woff2 diff --git a/apps/aqua/src/public/fonts/LM-bold.ttf b/apps/aqua/public/fonts/LM-bold.ttf similarity index 100% rename from apps/aqua/src/public/fonts/LM-bold.ttf rename to apps/aqua/public/fonts/LM-bold.ttf diff --git a/apps/aqua/src/public/fonts/LM-bold.woff b/apps/aqua/public/fonts/LM-bold.woff similarity index 100% rename from apps/aqua/src/public/fonts/LM-bold.woff rename to apps/aqua/public/fonts/LM-bold.woff diff --git a/apps/aqua/src/public/fonts/LM-bold.woff2 b/apps/aqua/public/fonts/LM-bold.woff2 similarity index 100% rename from apps/aqua/src/public/fonts/LM-bold.woff2 rename to apps/aqua/public/fonts/LM-bold.woff2 diff --git a/apps/aqua/src/public/fonts/LM-italic.ttf b/apps/aqua/public/fonts/LM-italic.ttf similarity index 100% rename from apps/aqua/src/public/fonts/LM-italic.ttf rename to apps/aqua/public/fonts/LM-italic.ttf diff --git a/apps/aqua/src/public/fonts/LM-italic.woff b/apps/aqua/public/fonts/LM-italic.woff similarity index 100% rename from apps/aqua/src/public/fonts/LM-italic.woff rename to apps/aqua/public/fonts/LM-italic.woff diff --git a/apps/aqua/src/public/fonts/LM-italic.woff2 b/apps/aqua/public/fonts/LM-italic.woff2 similarity index 100% rename from apps/aqua/src/public/fonts/LM-italic.woff2 rename to apps/aqua/public/fonts/LM-italic.woff2 diff --git a/apps/aqua/src/public/fonts/LM-regular.ttf b/apps/aqua/public/fonts/LM-regular.ttf similarity index 100% rename from apps/aqua/src/public/fonts/LM-regular.ttf rename to apps/aqua/public/fonts/LM-regular.ttf diff --git a/apps/aqua/src/public/fonts/LM-regular.woff b/apps/aqua/public/fonts/LM-regular.woff similarity index 100% rename from apps/aqua/src/public/fonts/LM-regular.woff rename to apps/aqua/public/fonts/LM-regular.woff diff --git a/apps/aqua/src/public/fonts/LM-regular.woff2 b/apps/aqua/public/fonts/LM-regular.woff2 similarity index 100% rename from apps/aqua/src/public/fonts/LM-regular.woff2 rename to apps/aqua/public/fonts/LM-regular.woff2 diff --git a/apps/aqua/src/public/fonts/Libertinus-bold-italic.woff2 b/apps/aqua/public/fonts/Libertinus-bold-italic.woff2 similarity index 100% rename from apps/aqua/src/public/fonts/Libertinus-bold-italic.woff2 rename to apps/aqua/public/fonts/Libertinus-bold-italic.woff2 diff --git a/apps/aqua/src/public/fonts/Libertinus-bold.woff2 b/apps/aqua/public/fonts/Libertinus-bold.woff2 similarity index 100% rename from apps/aqua/src/public/fonts/Libertinus-bold.woff2 rename to apps/aqua/public/fonts/Libertinus-bold.woff2 diff --git a/apps/aqua/src/public/fonts/Libertinus-italic.woff2 b/apps/aqua/public/fonts/Libertinus-italic.woff2 similarity index 100% rename from apps/aqua/src/public/fonts/Libertinus-italic.woff2 rename to apps/aqua/public/fonts/Libertinus-italic.woff2 diff --git a/apps/aqua/src/public/fonts/Libertinus-regular.woff2 b/apps/aqua/public/fonts/Libertinus-regular.woff2 similarity index 100% rename from apps/aqua/src/public/fonts/Libertinus-regular.woff2 rename to apps/aqua/public/fonts/Libertinus-regular.woff2 diff --git a/apps/aqua/src/public/fonts/Libertinus-semibold-italic.woff2 b/apps/aqua/public/fonts/Libertinus-semibold-italic.woff2 similarity index 100% rename from apps/aqua/src/public/fonts/Libertinus-semibold-italic.woff2 rename to apps/aqua/public/fonts/Libertinus-semibold-italic.woff2 diff --git a/apps/aqua/src/public/fonts/Libertinus-semibold.woff2 b/apps/aqua/public/fonts/Libertinus-semibold.woff2 similarity index 100% rename from apps/aqua/src/public/fonts/Libertinus-semibold.woff2 rename to apps/aqua/public/fonts/Libertinus-semibold.woff2 diff --git a/apps/aqua/src/public/latex.css b/apps/aqua/public/latex.css similarity index 100% rename from apps/aqua/src/public/latex.css rename to apps/aqua/public/latex.css diff --git a/apps/aqua/src/auth/router.ts b/apps/aqua/src/auth/router.ts index 992d94f..29a022f 100644 --- a/apps/aqua/src/auth/router.ts +++ b/apps/aqua/src/auth/router.ts @@ -1,7 +1,7 @@ import { atclient } from "./client"; import { db } from "@teal/db/connect"; -import {atProtoSession} from "@teal/db/schema" -import { eq } from "drizzle-orm" +import { atProtoSession } from "@teal/db/schema"; +import { eq } from "drizzle-orm"; import { EnvWithCtx, TealContext } from "@/ctx"; import { Hono } from "hono"; import { tealSession } from "@teal/db/schema"; @@ -42,12 +42,12 @@ export async function callback(c: TealContext) { maxAge: 60 * 60 * 24 * 365, }); - if(params.get("spa")) { + if (params.get("spa")) { return c.json({ provider: "atproto", jwt: did, accessToken: did, - }) + }); } return c.redirect("/"); @@ -57,7 +57,6 @@ export async function callback(c: TealContext) { } } - // Refresh an access token from a refresh token. Should be only used in SPAs. // Pass in 'key' and 'refresh_token' query params. export async function refresh(c: TealContext) { @@ -67,17 +66,20 @@ export async function refresh(c: TealContext) { const params = new URLSearchParams(honoParams); let key = params.get("key"); let refresh_token = params.get("refresh_token"); - if(!key || !refresh_token) { - return Response.json({error: "Missing key or refresh_token"}); + if (!key || !refresh_token) { + return Response.json({ error: "Missing key or refresh_token" }); } // check if refresh token is valid - let r_tk_check = await db.select().from(atProtoSession).where(eq(atProtoSession.key, key)).execute() as any; - - if(r_tk_check.tokenSet.refresh_token !== refresh_token) { - return Response.json({error: "Invalid refresh token"}); + let r_tk_check = (await db + .select() + .from(atProtoSession) + .where(eq(atProtoSession.key, key)) + .execute()) as any; + + if (r_tk_check.tokenSet.refresh_token !== refresh_token) { + return Response.json({ error: "Invalid refresh token" }); } - const session = await atclient.restore(key); const did = session.did; @@ -108,10 +110,10 @@ export async function refresh(c: TealContext) { }); return c.json({ - provider:"atproto", + provider: "atproto", jwt: did, accessToken: did, - }) + }); } catch (e) { console.error(e); return Response.json({ error: "Could not authorize user" }); diff --git a/apps/aqua/src/index.ts b/apps/aqua/src/index.ts index 52e5075..70a932d 100644 --- a/apps/aqua/src/index.ts +++ b/apps/aqua/src/index.ts @@ -1,5 +1,5 @@ import { serve } from "@hono/node-server"; -import { serveStatic } from '@hono/node-server/serve-static' +import { serveStatic } from "@hono/node-server/serve-static"; import { Hono } from "hono"; import { db } from "@teal/db/connect"; import { getAuthRouter } from "./auth/router"; @@ -14,7 +14,7 @@ import { sanitizeUrl } from "@braintree/sanitize-url"; const HEAD = ` - ` + `; const logger = pino({ name: "server start" }); @@ -48,11 +48,13 @@ app.get("/", async (c) => { home stamp - logout +
+ +
- +
`, ); @@ -210,12 +212,13 @@ app.post("/stamp", async (c: TealContext) => { const body = await c.req.parseBody(); let { artist, track, link } = body; // shouldn't get a File, escape now - if (artist instanceof File || track instanceof File || link instanceof File) return c.redirect("/stamp"); - + if (artist instanceof File || track instanceof File || link instanceof File) + return c.redirect("/stamp"); + artist = sanitizeUrl(artist); track = sanitizeUrl(track); link = sanitizeUrl(link); - + const agent = await getSessionAgent(c); if (agent) { @@ -245,8 +248,6 @@ app.post("/stamp", async (c: TealContext) => { embed: embed, }); - console.log(`post: ${post}`); - return c.html( ` ${HEAD} @@ -272,10 +273,12 @@ app.post("/stamp", async (c: TealContext) => { `, ); } - return c.html(`

doesn't look like you're logged in... try logging in?

`); + return c.html( + `

doesn't look like you're logged in... try logging in?

`, + ); }); -app.use('/*', serveStatic({ root: '/src/public' })); +app.use("/*", serveStatic({ root: "/public" })); const run = async () => { logger.info("Running in " + navigator.userAgent); diff --git a/apps/aqua/tsconfig.json b/apps/aqua/tsconfig.json index 2f30ce2..c76b141 100644 --- a/apps/aqua/tsconfig.json +++ b/apps/aqua/tsconfig.json @@ -1,9 +1,11 @@ { "extends": "@teal/tsconfig/base.json", + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"], "compilerOptions": { + "baseUrl": ".", "paths": { - "@/*": ["./src/*"], - "+lexicons/*": ["../../lexicons/*"] + "@/*": ["src/*"] } } } diff --git a/apps/aqua/tsup.config.ts b/apps/aqua/tsup.config.ts new file mode 100644 index 0000000..ab343b2 --- /dev/null +++ b/apps/aqua/tsup.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + format: ["cjs"], + entry: ["./src/index.ts"], + dts: false, + shims: true, + skipNodeModulesBundle: false, + clean: true, + minify: false, + bundle: true, + // https://github.com/egoist/tsup/issues/619 + noExternal: [/(.*)/, "@teal/db", "@teal/lexicons"], + splitting: false, +}); diff --git a/apps/viridian/go.mod b/apps/viridian/go.mod deleted file mode 100644 index e84049b..0000000 --- a/apps/viridian/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module viridian - -go 1.21.5 - -require github.com/pebbe/zmq4 v1.2.11 diff --git a/apps/viridian/go.sum b/apps/viridian/go.sum deleted file mode 100644 index a607dbc..0000000 --- a/apps/viridian/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -github.com/pebbe/zmq4 v1.2.11 h1:Ua5mgIaZeabUGnH7tqswkUcjkL7JYGai5e8v4hpEU9Q= -github.com/pebbe/zmq4 v1.2.11/go.mod h1:nqnPueOapVhE2wItZ0uOErngczsJdLOGkebMxaO8r48= diff --git a/apps/viridian/main.go b/apps/viridian/main.go deleted file mode 100644 index dd28bb0..0000000 --- a/apps/viridian/main.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -import ( - "fmt" - "sync" - - server "viridian/server" - worker "viridian/worker" -) - -func main() { - var wg sync.WaitGroup - - // Run the server in a goroutine - go server.RunServer() - - // Simulate workers, each running in a separate goroutine - workerCount := worker.GetCoreCount() - fmt.Printf("Starting %d workers\n", workerCount) - for i := 1; i <= workerCount; i++ { - fmt.Printf("Starting worker %d\n", i) - workerID := fmt.Sprintf("worker-%d", i) - wg.Add(1) - go worker.RunWorker(workerID, &wg) - } - - // Wait for all workers to complete (this won't happen in this example since workers run indefinitely) - wg.Wait() -} diff --git a/apps/viridian/package.json b/apps/viridian/package.json deleted file mode 100644 index 7718996..0000000 --- a/apps/viridian/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@teal/viridian", - "version": "0.0.0", - "private": true, - "scripts": { - "install": "go mod download", - "build": "go build main.go", - "dev": "go run main.go", - "start": "go run main.go" - } -} diff --git a/apps/viridian/server/server.go b/apps/viridian/server/server.go deleted file mode 100644 index e9d19ff..0000000 --- a/apps/viridian/server/server.go +++ /dev/null @@ -1,62 +0,0 @@ -package server - -import ( - "fmt" - "time" - - "viridian/types" - - zmq "github.com/pebbe/zmq4" -) - -// RunServer launches the server that distributes jobs to workers -func RunServer() { - server := &types.Server{ - Jobs: []types.Job{{"job1", "pending"}, {"job2", "pending"}, {"job3", "pending"}}, - WorkerJobs: make(map[string]string), - JobStatus: make(map[string]string), - } - - // Initialize job status - for _, job := range server.Jobs { - server.JobStatus[job.ID] = "pending" - } - - // Create a ZeroMQ ROUTER socket for distributing jobs - socket, _ := zmq.NewSocket(zmq.ROUTER) - defer socket.Close() - socket.Bind("tcp://*:5555") - - for len(server.Jobs) > 0 { - // Wait for a worker to send a ready message - workerAddr, _ := socket.RecvMessage(0) - - fmt.Printf("Received ready message from worker %s\n", workerAddr) - - fmt.Print(workerAddr[2]) - - // Lock jobs to pick the next one safely - server.Mu.Lock() - if len(server.Jobs) == 0 { - server.Mu.Unlock() - continue - } - job := server.Jobs[0] - server.Jobs = server.Jobs[1:] - server.Mu.Unlock() - - // Track the job for this worker - server.TrackJob(workerAddr[0], job.ID) - - // Send job to the worker - fmt.Printf("Sending job %s to worker %s\n", job.ID, workerAddr[0]) - socket.SendMessage(workerAddr[0], "", job.ID) - - time.Sleep(1 * time.Second) // Simulate a delay - - // In a real-world scenario, this would be based on a worker response - server.MarkJobCompleted(workerAddr[0]) - } - - fmt.Println("All jobs processed") -} diff --git a/apps/viridian/types/types.go b/apps/viridian/types/types.go deleted file mode 100644 index de715a6..0000000 --- a/apps/viridian/types/types.go +++ /dev/null @@ -1,46 +0,0 @@ -package types - -import ( - "strconv" - "sync" -) - -// Job represents a job with an ID and status -type Job struct { - ID string - Status string -} - -// Server represents the state of the server, including jobs, worker-job associations, and job status -type Server struct { - Jobs []Job // Job queue - WorkerJobs map[string]string // Mapping worker ID -> job ID - JobStatus map[string]string // Mapping job ID -> status - Mu sync.Mutex // Mutex to handle concurrency -} - -func (s *Server) AddJob() Job { - s.Mu.Lock() - defer s.Mu.Unlock() - job := Job{"job" + strconv.Itoa(len(s.Jobs)), "pending"} - s.Jobs = append(s.Jobs, job) - - return job -} - -// TrackJob assigns a job to a worker and marks it as in-progress -func (s *Server) TrackJob(workerID, jobID string) { - s.Mu.Lock() - defer s.Mu.Unlock() - s.WorkerJobs[workerID] = jobID - s.JobStatus[jobID] = "in-progress" -} - -// MarkJobCompleted marks a job as completed and removes the worker-job association -func (s *Server) MarkJobCompleted(workerID string) { - s.Mu.Lock() - defer s.Mu.Unlock() - jobID := s.WorkerJobs[workerID] - s.JobStatus[jobID] = "completed" - delete(s.WorkerJobs, workerID) // Remove worker-job association -} diff --git a/apps/viridian/worker/ffmpeg.go b/apps/viridian/worker/ffmpeg.go deleted file mode 100644 index 4df0094..0000000 --- a/apps/viridian/worker/ffmpeg.go +++ /dev/null @@ -1 +0,0 @@ -package worker diff --git a/apps/viridian/worker/worker.go b/apps/viridian/worker/worker.go deleted file mode 100644 index c8c216e..0000000 --- a/apps/viridian/worker/worker.go +++ /dev/null @@ -1,47 +0,0 @@ -package worker - -import ( - "fmt" - "runtime" - "sync" - "time" - - zmq "github.com/pebbe/zmq4" -) - -// RunWorker simulates a worker that connects to the server and processes jobs -func RunWorker(id string, wg *sync.WaitGroup) { - defer wg.Done() - - // Create a ZeroMQ REQ socket for receiving jobs - socket, _ := zmq.NewSocket(zmq.REQ) - defer socket.Close() - socket.Connect("tcp://localhost:5555") - - for { - // Send a ready signal to the server - socket.SendMessage(fmt.Sprintf("READY %s", id), 0) - - // Receive the job - msg, err := socket.RecvMessage(0) - if err != nil { - fmt.Println("Error receiving job:", err) - continue - } - job := msg[0] - - // Process the job - fmt.Printf("Worker %s received job: %s\n", id, job) - socket.SendMessage(fmt.Sprintf("RECEIVED %s", job), 0) - - // Simulate processing the job - fmt.Printf("Worker %s processing job: %s\n", id, job) - socket.SendMessage(fmt.Sprintf("PROCESSING %s", job), 0) - time.Sleep(2 * time.Second) // Simulate job processing delay - } -} - -// GetCoreCount returns the number of CPU cores on the current machine -func GetCoreCount() int { - return runtime.NumCPU() -} diff --git a/bun.lockb b/bun.lockb index ae31aba49034a8612ca3369a7136c48f8a88a532..80581009a4d8906424630ecd48ef4ef6a6ffdb69 100755 GIT binary patch delta 31417 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 KO=rBsG#>!tFZcEU delta 32119 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 diff --git a/compose.dev.yml b/compose.dev.yml index 201f531..c01804d 100644 --- a/compose.dev.yml +++ b/compose.dev.yml @@ -1,46 +1,60 @@ services: - traefik: - image: traefik:v2.10 - container_name: traefik - command: - - "--api.insecure=true" - - "--providers.file.directory=/etc/traefik/dynamic" - - "--providers.file.watch=true" - - "--entrypoints.web.address=:80" + aqua-api: + build: + context: . + dockerfile: apps/aqua/Dockerfile + container_name: aqua-app ports: - - "80:80" # HTTP - - "8080:8080" # Dashboard - volumes: - - ./traefik/dynamic:/etc/traefik/dynamic:ro + - "3000:3000" networks: - app_network - extra_hosts: - - "host.docker.internal:host-gateway" # This allows reaching host machine + depends_on: + - postgres + - redis + env_file: + - .env + # traefik: + # image: traefik:v2.10 + # container_name: traefik + # command: + # - "--api.insecure=true" + # - "--providers.file.directory=/etc/traefik/dynamic" + # - "--providers.file.watch=true" + # - "--entrypoints.web.address=:80" + # ports: + # - "80:80" # HTTP + # - "8080:8080" # Dashboard + # volumes: + # - ./traefik/dynamic:/etc/traefik/dynamic:ro + # networks: + # - app_network + # extra_hosts: + # - "host.docker.internal:host-gateway" # This allows reaching host machine - postgres: - image: postgres:latest - container_name: postgres_db - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: yourpassword - POSTGRES_DB: yourdatabase - ports: - - "5432:5432" - volumes: - - postgres_data:/var/lib/postgresql/data - networks: - - app_network + # postgres: + # image: postgres:latest + # container_name: postgres_db + # environment: + # POSTGRES_USER: postgres + # POSTGRES_PASSWORD: yourpassword + # POSTGRES_DB: yourdatabase + # ports: + # - "5432:5432" + # volumes: + # - postgres_data:/var/lib/postgresql/data + # networks: + # - app_network - redis: - image: redis:latest - container_name: redis_cache - ports: - - "6379:6379" - volumes: - - redis_data:/data - command: redis-server --appendonly yes - networks: - - app_network + # redis: + # image: redis:latest + # container_name: redis_cache + # ports: + # - "6379:6379" + # volumes: + # - redis_data:/data + # command: redis-server --appendonly yes + # networks: + # - app_network networks: app_network: diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..5ad0e58 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,17 @@ +services: + aqua-api: + build: + context: . + dockerfile: apps/aqua/Dockerfile + container_name: aqua-api + # pass through db.sqlite + volumes: + - ./db.sqlite:/app/db.sqlite + ports: + - "3000:3000" + env_file: + - .env + +volumes: + postgres_data: + redis_data: diff --git a/packages/db/connect.ts b/packages/db/connect.ts index 990b09e..56f206c 100644 --- a/packages/db/connect.ts +++ b/packages/db/connect.ts @@ -3,16 +3,13 @@ import * as schema from "@teal/db/schema"; import process from "node:process"; import path from "node:path"; -console.log( - "Loading SQLite file at", - path.join(process.cwd(), "../../db.sqlite"), -); +console.log("Loading SQLite file at", path.join(process.cwd(), "./db.sqlite")); export const db = drizzle({ connection: // default is in project root / db.sqlite process.env.DATABASE_URL ?? - "file:" + path.join(process.cwd(), "../../db.sqlite"), + "file:" + path.join(process.cwd(), "./db.sqlite"), // doesn't seem to work? //casing: "snake_case", schema: schema, diff --git a/packages/jetstring/src/index.ts b/packages/jetstring/src/index.ts index abecc4d..516c561 100644 --- a/packages/jetstring/src/index.ts +++ b/packages/jetstring/src/index.ts @@ -2,8 +2,6 @@ import type { Database } from "@teal/db/connect"; import { db } from "@teal/db/connect"; import { status } from "@teal/db/schema"; import { CommitCreateEvent, Jetstream } from "@skyware/jetstream"; -import { server } from "@teal/lexicons/generated/server/types"; -import ws from "ws"; class Handler { private static instance: Handler; diff --git a/packages/tsconfig/base.json b/packages/tsconfig/base.json index 0b817f7..6313223 100644 --- a/packages/tsconfig/base.json +++ b/packages/tsconfig/base.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/tsconfig", "compilerOptions": { - "target": "ES2021", + "target": "ESNext", "module": "ESNext", "moduleResolution": "node", "esModuleInterop": true,