Skip to content

Commit

Permalink
Merge pull request #9 from PotLock/feat/frontend
Browse files Browse the repository at this point in the history
Updates frontend
  • Loading branch information
elliotBraem authored Jan 16, 2025
2 parents a4393a1 + 79e75f5 commit 7f6bcf0
Show file tree
Hide file tree
Showing 71 changed files with 2,186 additions and 829 deletions.
5 changes: 5 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,8 @@ frontend/**/*.sw?
# flyctl launch added from node_modules/tailwindcss/stubs/.gitignore
!node_modules/tailwindcss/stubs/**/*
fly.toml

# workspace
landing-page
docs
.github
18 changes: 18 additions & 0 deletions .github/workflows/fly-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# See https://fly.io/docs/app-guides/continuous-deployment-with-github-actions/

name: Fly Deploy
on:
push:
branches:
- main
jobs:
deploy:
name: Deploy app
runs-on: ubuntu-latest
concurrency: deploy-group # optional: ensure only one action runs at a time
steps:
- uses: actions/checkout@v4
- uses: superfly/flyctl-actions/setup-flyctl@master
- run: flyctl deploy --remote-only
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
77 changes: 49 additions & 28 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,52 +1,73 @@
FROM oven/bun as deps
## NOTE
# This Dockerfile builds the frontend and backend separately,
# frontend uses npm and backend requires bun.
# This separation is a temporary solution for a Bun issue with rsbuild,
# see: https://github.com/oven-sh/bun/issues/11628

# Frontend deps & build stage
FROM node:20 as frontend-builder
WORKDIR /app

# Copy package files for all workspaces
COPY package.json bun.lockb turbo.json ./
# Copy frontend package files
COPY frontend/package.json ./frontend/
COPY backend/package.json ./backend/

# Install dependencies
RUN bun install
# Install frontend dependencies
RUN cd frontend && npm install

# Copy frontend source code
COPY frontend ./frontend

# Build frontend
RUN cd frontend && npm run build

# Build stage
FROM oven/bun as builder
# Backend deps & build stage
FROM oven/bun as backend-builder
WORKDIR /app

# Set NODE_ENV for build process
ENV NODE_ENV="production"
# Copy backend package files
COPY package.json ./
COPY backend/package.json ./backend/

# Copy all files from deps stage including node_modules
COPY --from=deps /app ./
# Install backend dependencies
RUN cd backend && bun install

# Copy source code
COPY . .
# Copy backend source code
COPY backend ./backend

# Build both frontend and backend
RUN bun run build
# Build backend
RUN cd backend && bun run build

# Production stage
FROM oven/bun as production
WORKDIR /app

# Create directory for mount with correct permissions
RUN mkdir -p /.data/db /.data/cache && \
chown -R bun:bun /.data
# Install LiteFS dependencies
RUN apt-get update -y && apt-get install -y ca-certificates fuse3 sqlite3

# Copy only necessary files from builder
COPY --from=builder --chown=bun:bun /app/package.json /app/bun.lockb /app/turbo.json ./
COPY --from=builder --chown=bun:bun /app/node_modules ./node_modules
COPY --from=builder --chown=bun:bun /app/frontend/dist ./frontend/dist
COPY --from=builder --chown=bun:bun /app/backend/dist ./backend/dist
# Copy LiteFS binary
COPY --from=flyio/litefs:0.5 /usr/local/bin/litefs /usr/local/bin/litefs

# Create directories for mounts with correct permissions
RUN mkdir -p /litefs /var/lib/litefs && \
chown -R bun:bun /litefs /var/lib/litefs

# Create volume mount points
# Copy only necessary files from builders
COPY --from=backend-builder --chown=bun:bun /app/package.json ./
COPY --chown=bun:bun curate.config.json ./

COPY --from=frontend-builder --chown=bun:bun /app/frontend/dist ./frontend/dist
COPY --from=backend-builder --chown=bun:bun /app/backend/dist ./backend/dist

# Set environment variables
ENV DATABASE_URL="file:/.data/db/sqlite.db"
ENV CACHE_DIR="/.data/cache"
ENV DATABASE_URL="file:/litefs/db"
ENV NODE_ENV="production"

# Expose the port
EXPOSE 3000

# Start the application using the production start script
CMD ["bun", "run", "start"]
# Copy LiteFS configuration
COPY --chown=bun:bun litefs.yml /etc/litefs.yml

# Start LiteFS (runs app with distributed file system for SQLite)
ENTRYPOINT ["litefs", "mount"]
13 changes: 4 additions & 9 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,13 @@
TWITTER_USERNAME=your_twitter_username
TWITTER_PASSWORD=your_twitter_password
TWITTER_EMAIL=your_twitter_email
TWITTER_2FA_SECRET=your_twitter_2fa

# Environment
NODE_ENV=development

# Telegram Export Configuration
TELEGRAM_ENABLED=false
#PLUGINS

# Telegram Distributor Configuration
TELEGRAM_BOT_TOKEN=your_bot_token
TELEGRAM_CHANNEL_ID=your_channel_id

# RSS Export Configuration
RSS_ENABLED=false
RSS_TITLE=Public Goods News
RSS_DESCRIPTION=Latest approved public goods submissions
RSS_FEED_PATH=public/feed.xml
RSS_MAX_ITEMS=100
5 changes: 1 addition & 4 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,8 @@
]
},
"devDependencies": {
"@types/express": "^4.17.17",
"@types/jest": "^29.5.11",
"@types/node": "^20.10.6",
"@types/ora": "^3.2.0",
"bun-types": "^1.1.40",
"bun-types": "^1.1.43",
"drizzle-kit": "^0.30.1",
"jest": "^29.7.0",
"jest-mock-extended": "^4.0.0-beta1",
Expand Down
110 changes: 0 additions & 110 deletions backend/src/external/rss.ts

This file was deleted.

113 changes: 113 additions & 0 deletions backend/src/external/rss/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { writeFile, mkdir } from "fs/promises";
import path from "path";
import { DistributorPlugin } from "types/plugin";
import { RssService } from "./rss.service";
import type { RssItem } from "../../services/rss/queries";
import type { DBOperations } from "../../services/db/operations";

export default class RssPlugin implements DistributorPlugin {
name = "@curatedotfun/rss";
private services: Map<string, RssService> = new Map();
private dbOps?: DBOperations;

getServices(): Map<string, RssService> {
return this.services;
}

constructor(dbOperations?: DBOperations) {
this.dbOps = dbOperations;
}

async initialize(
feedId: string,
config: Record<string, string>,
): Promise<void> {
if (!config.title) {
throw new Error("RSS plugin requires title");
}

const maxItems = config.maxItems ? parseInt(config.maxItems) : 100;

// Create a new RSS service for this feed
const service = new RssService(
feedId,
config.title,
maxItems,
config.path,
this.dbOps,
);

this.services.set(feedId, service);
}

async distribute(feedId: string, content: string): Promise<void> {
const service = this.services.get(feedId);
if (!service) {
throw new Error("RSS plugin not initialized for this feed");
}

const item: RssItem = {
title: "New Update",
content,
link: "https://twitter.com/", // TODO: Update with actual link
publishedAt: new Date().toISOString(),
guid: Date.now().toString(),
};

// Save to database
service.saveItem(item);

// Write to file if path is provided (backward compatibility)
const path = service.getPath();
if (path) {
await this.writeToFile(service, path);
}
}

private async writeToFile(
service: RssService,
filePath: string,
): Promise<void> {
const items = service.getItems();

const feed = `<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title>${service.getTitle()}</title>
<link>https://twitter.com/</link>
<lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
${items
.map(
(item) => `
<item>
<title>${this.escapeXml(item.title || "")}</title>
<description>${this.escapeXml(item.content)}</description>
<link>${item.link || ""}</link>
<pubDate>${new Date(item.publishedAt).toUTCString()}</pubDate>
<guid>${item.guid || ""}</guid>
</item>`,
)
.join("\n")}
</channel>
</rss>`;

// Ensure directory exists
const dir = path.dirname(filePath);
await mkdir(dir, { recursive: true });
await writeFile(filePath, feed, "utf-8");
}

private escapeXml(unsafe: string): string {
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&apos;");
}

async shutdown(): Promise<void> {
// Clear all services when plugin shuts down
this.services.clear();
}
}
Loading

0 comments on commit 7f6bcf0

Please sign in to comment.