Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.git
.gitignore
node_modules
dist
src/ui/dist
.env
.env.dev
.env.test
.env.age
facebook_posts
reels
shorts
tts_previews
post_media
tmp_voice.wav
*.wav
Binary file added .env.age
Binary file not shown.
7 changes: 5 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
OPENAI_API_KEY=your_openai_api_key
NODE_ENV=production
UI_PORT=3333

# Use .env.dev for development and .env.test for tests (see package.json scripts)
# Scheduler cron expression (default every 4 hours)
CRON=0 */4 * * *

# Postgres connection string
# Postgres connection string (host-based dev default; prod compose overrides to postgres service)
DATABASE_URL=postgres://ai_farm_brain:ai_farm_brain_password@localhost:5432/funrang_dev

# Redis (BullMQ)
# Redis (host-based dev default; prod compose overrides to redis service)
REDIS_URL=redis://localhost:6379

# RSS ingest concurrency
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ post_media
.env
.env.dev
.env.test
reels
/reels/
dist
shorts
*.wav
Expand Down
34 changes: 34 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
FROM node:20-bookworm-slim AS builder

WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci

COPY . .
RUN npm run build

FROM node:20-bookworm-slim AS runtime

RUN apt-get update \
&& apt-get install -y --no-install-recommends ffmpeg python3 python3-pip ca-certificates \
&& rm -rf /var/lib/apt/lists/*

# Local STT runtime for REEL_STT_PROVIDER=local.
RUN python3 -m pip install --no-cache-dir faster-whisper

WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci --omit=dev

COPY --from=builder /app/dist ./dist
COPY --from=builder /app/migrations ./migrations
COPY --from=builder /app/src/ui ./src/ui
COPY --from=builder /app/scripts/faster_whisper_words.py ./scripts/faster_whisper_words.py

RUN mkdir -p facebook_posts reels shorts tts_previews post_media

EXPOSE 3333

CMD ["sh", "-lc", "NODE_ENV=production node dist/scripts/migrate.js && NODE_ENV=production node dist/src/server.js"]
93 changes: 90 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,63 @@ Funrang is a modern social content engine for social media managers. It discover
## Status
Local MVP with working UI, job system, post review, and Facebook publishing.

## One-command setup (Docker, production-style)
### Prereqs
- Docker + Docker Compose plugin (`docker compose`)

### 1) Create env
```bash
cp .env.example .env
```
- Set at least `OPENAI_API_KEY` in `.env`.
- Keep `DATABASE_URL`/`REDIS_URL` as-is for Compose; `docker-compose.prod.yml` overrides them to internal service URLs.

### 2) Start everything
```bash
docker compose -f docker-compose.prod.yml up --build -d
```
- App: `http://localhost:3333`
- The production image compiles TypeScript during build and runs emitted JavaScript (`node`), not `tsx`.

### 3) Stop everything
```bash
docker compose -f docker-compose.prod.yml down
```

### Optional ops tooling
- Start pgAdmin too:
```bash
docker compose -f docker-compose.prod.yml --profile ops up -d
```
- pgAdmin: `http://localhost:5050`

## Get up and running (dev)
### Prereqs
- Node 18+
- Docker (for Postgres + pgAdmin + Redis)
- Node.js 18+ (Node 20+ recommended)
- Docker + Docker Compose plugin (`docker compose`)
- `ffmpeg` and `ffprobe` on your PATH (required for reels/shorts generation)
- OpenAI API key

### OS-level dependencies
- Ubuntu/Debian:
```bash
sudo apt-get update
sudo apt-get install -y ffmpeg python3 python3-pip
```
- macOS (Homebrew):
```bash
brew install ffmpeg python
```
- Windows:
1. Install FFmpeg (make sure `ffmpeg`/`ffprobe` are on PATH).
2. Install Python 3 (with `pip` and PATH enabled).
3. Install Docker Desktop.

### Optional (only if `REEL_STT_PROVIDER=local`)
- Install local Whisper runtime:
```bash
python3 -m pip install --user faster-whisper
```

### 1) Install + env
```bash
Expand All @@ -34,6 +87,7 @@ cp .env.example .env
cp .env.example .env.dev
cp .env.example .env.test
```
- Set at least `OPENAI_API_KEY` in `.env.dev`.

### 2) Start local services
```bash
Expand All @@ -49,13 +103,46 @@ npm run migrate:dev
```bash
npm run ui
```
- Open `http://localhost:3333`

### 5) Tests
```bash
npm test
```

### Quick checks (recommended)
```bash
ffmpeg -version
ffprobe -version
docker compose ps
```

### Notes
- `.env.dev` is used by `npm run ui`
- `.env.test` is used by `npm test`
- `docker-compose.yml` brings up Postgres (`funrang_dev`), pgAdmin, and Redis
- `.env` is used by `docker-compose.prod.yml` for app secrets/config.
- `docker-compose.yml` is the development infra stack (Postgres + pgAdmin + Redis, DB: `funrang_dev`).
- `docker-compose.prod.yml` is the production-style stack (app + Postgres + Redis, DB: `funrang_prod`, and pgAdmin when `--profile ops` is used).
- In prod compose, `DATABASE_URL`/`REDIS_URL` are set to `postgres`/`redis` service hosts automatically.
- If OAuth callbacks are needed from real providers, use a public callback URL (for example via ngrok) and set `FACEBOOK_REDIRECT_URI` / `YOUTUBE_REDIRECT_URI`.

### Share `.env` securely with `age`
- Install `age`:
```bash
# Ubuntu/Debian
sudo apt-get install -y age

# macOS
brew install age
```
- Encrypt `.env` before sending:
```bash
age -p -o .env.age .env
```
- Decrypt on receiver side:
```bash
age -d -o .env .env.age
```
- Safety: send `.env.age` and passphrase in different channels.
- Safety: use a one-time strong passphrase.
- Safety: never commit `.env` or decrypted secrets to git.
94 changes: 94 additions & 0 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
services:
app:
build:
context: .
dockerfile: Dockerfile
container_name: ai-farm-brain-app
restart: unless-stopped
ports:
- "3333:3333"
env_file:
- .env
environment:
NODE_ENV: production
UI_PORT: 3333
DATABASE_URL: postgres://ai_farm_brain:ai_farm_brain_password@postgres:5432/funrang_prod
REDIS_URL: redis://redis:6379
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test:
[
"CMD-SHELL",
"node -e \"fetch('http://127.0.0.1:3333').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""
]
interval: 10s
timeout: 5s
retries: 20
volumes:
- facebook_posts_data:/app/facebook_posts
- reels_data:/app/reels
- shorts_data:/app/shorts
- tts_previews_data:/app/tts_previews
- post_media_data:/app/post_media

postgres:
image: postgres:16
container_name: ai-farm-brain-postgres
restart: unless-stopped
environment:
POSTGRES_USER: ai_farm_brain
POSTGRES_PASSWORD: ai_farm_brain_password
POSTGRES_DB: funrang_prod
volumes:
- pgdata:/var/lib/postgresql/data
- ./pg-init.sql:/docker-entrypoint-initdb.d/00-init.sql:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ai_farm_brain -d funrang_prod"]
interval: 5s
timeout: 5s
retries: 10

redis:
image: redis:7-alpine
container_name: ai-farm-brain-redis
restart: unless-stopped
command: ["redis-server", "--save", "60", "1"]
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 20

pgadmin:
image: dpage/pgadmin4:8
container_name: ai-farm-brain-pgadmin
profiles: ["ops"]
restart: unless-stopped
ports:
- "5050:80"
environment:
PGADMIN_DEFAULT_EMAIL: admin@local.dev
PGADMIN_DEFAULT_PASSWORD: admin123
PGADMIN_CONFIG_SERVER_MODE: "False"
depends_on:
postgres:
condition: service_healthy
volumes:
- pgadmin_data:/var/lib/pgadmin
- ./pgadmin/servers.prod.json:/pgadmin4/servers.json:ro

volumes:
pgdata:
pgadmin_data:
redis_data:
facebook_posts_data:
reels_data:
shorts_data:
tts_previews_data:
post_media_data:
1 change: 1 addition & 0 deletions docs/CNAME
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
docs.funrang.com
2 changes: 1 addition & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ <h1>Funrang</h1>
<h2>Contact</h2>
<p>
Support: <a href="mailto:fardin4work@gmail.com">fardin4work@gmail.com</a><br />
Website: <a href="https://funrang.com">https://funrang.app</a>
Website: <a href="https://funrang.com">https://funrang.com</a>
</p>

<p class="meta">Last updated: February 13, 2026</p>
Expand Down
5 changes: 5 additions & 0 deletions migrations/0048_posts_job_id/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
DROP INDEX IF EXISTS idx_posts_org_job_id_created_at;

ALTER TABLE posts
DROP CONSTRAINT IF EXISTS posts_job_id_fkey,
DROP COLUMN IF EXISTS job_id;
19 changes: 19 additions & 0 deletions migrations/0048_posts_job_id/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
ALTER TABLE posts
ADD COLUMN IF NOT EXISTS job_id UUID;

DO $$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM pg_constraint
WHERE conname = 'posts_job_id_fkey'
) THEN
ALTER TABLE posts
ADD CONSTRAINT posts_job_id_fkey
FOREIGN KEY (job_id) REFERENCES jobs(id)
ON DELETE SET NULL;
END IF;
END $$;

CREATE INDEX IF NOT EXISTS idx_posts_org_job_id_created_at
ON posts (organization_id, job_id, created_at DESC);
2 changes: 2 additions & 0 deletions migrations/0049_post_ai_generated_flag/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE posts
DROP COLUMN IF EXISTS is_ai_generated;
6 changes: 6 additions & 0 deletions migrations/0049_post_ai_generated_flag/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ALTER TABLE posts
ADD COLUMN IF NOT EXISTS is_ai_generated BOOLEAN NOT NULL DEFAULT TRUE;

UPDATE posts
SET is_ai_generated = TRUE
WHERE is_ai_generated IS NULL;
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@
"scripts": {
"ui": "npm run ui:styles:watch & npm run ui:js:watch & DOTENV_CONFIG_PATH=.env.dev NODE_ENV=development tsx watch src/server.ts",
"prod": "DOTENV_CONFIG_PATH=.env NODE_ENV=production tsx src/runner.ts",
"build:server": "tsc -p tsconfig.json --noCheck",
"build": "npm run ui:styles && npm run ui:js:build && npm run build:server",
"reset-db:dev": "DOTENV_CONFIG_PATH=.env.dev NODE_ENV=development tsx scripts/reset-db.ts",
"reset-db:prod": "DOTENV_CONFIG_PATH=.env NODE_ENV=production tsx scripts/reset-db.ts",
"migrate": "tsx scripts/migrate.ts",
"migrate:dev": "DOTENV_CONFIG_PATH=.env.dev NODE_ENV=development tsx scripts/migrate.ts",
"migrate:prod": "DOTENV_CONFIG_PATH=.env NODE_ENV=production tsx scripts/migrate.ts",
"migrate:compiled": "NODE_ENV=production node dist/scripts/migrate.js",
"start:compiled": "NODE_ENV=production node dist/src/server.js",
"ui:js:build": "vite build",
"ui:js:watch": "vite build --watch",
"ui:styles": "tailwindcss -i src/ui/styles/input.css -o src/ui/styles/tailwind.css",
Expand Down
13 changes: 13 additions & 0 deletions pgadmin/servers.prod.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"Servers": {
"1": {
"Name": "funrang-prod",
"Group": "Servers",
"Host": "postgres",
"Port": 5432,
"MaintenanceDB": "funrang_prod",
"Username": "ai_farm_brain",
"SSLMode": "prefer"
}
}
}
Loading