Skip to content
Draft
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
18 changes: 18 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.git
.github
.vercel
.env
.env.*
**/.env
**/.env.*
**/.next
**/dist
**/node_modules
**/playwright-report
**/test-results
**/*.log
apps/bot/test
apps/web
assets
docs
graphify-out
12 changes: 7 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,22 @@
DISCORD_TOKEN=
DISCORD_CLIENT_ID=
DISCORD_CLIENT_SECRET=
DISCORD_PUBLIC_KEY=
# Optional: register commands instantly in one isolated development server.
DISCORD_TEST_GUILD_ID=
# Optional numeric permission bitfield used by the dashboard's guild-locked install link.
DISCORD_INSTALL_PERMISSIONS=1099914365968

# Dashboard auth
# NEXTAUTH_URL must match the public site for Discord OAuth callbacks.
# NEXTAUTH_URL must be the public site origin only, with no callback path.
NEXTAUTH_URL=http://localhost:3000
# Generate a long random value for signed dashboard sessions.
# Generate at least 32 random bytes. This derives the Discord-token encryption key.
NEXTAUTH_SECRET=

# Data
# Required for live website training to sync with the bot. Without it, the site trainer runs in preview mode.
# Required for Discord login/session storage and all live dashboard data. The app fails closed without it.
DATABASE_URL=postgres://user:password@host:5432/piphacklup

# Bot behavior
PIPHACKLUP_SUPPORT_URL=https://github.com/rupayon123/PipHackLup/issues
PIPHACKLUP_PUBLIC_URL=http://localhost:3000
# Must be exactly true or false. Keep false unless Message Content intent is enabled.
PIPHACKLUP_AMBIENT_QA_ENABLED=false
86 changes: 81 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,52 @@ on:
push:
branches: [main]

permissions:
contents: read

concurrency:
group: piphacklup-ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
database-integration:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:17-alpine
env:
POSTGRES_DB: piphacklup
POSTGRES_PASSWORD: piphacklup_ci_only
POSTGRES_USER: piphacklup
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U piphacklup -d piphacklup"
--health-interval 5s
--health-timeout 5s
--health-retries 10
steps:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
with:
version: 10.25.0
- uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Apply migrations to ephemeral PostgreSQL
run: pnpm --filter @piphacklup/db db:migrate
env:
DATABASE_URL: postgres://piphacklup:piphacklup_ci_only@localhost:5432/piphacklup
- name: Exercise auth and guild-isolation persistence on real SQL
run: >-
pnpm --filter @piphacklup/db exec vitest run
test/accounts.integration.test.ts
test/operations.integration.test.ts
env:
PIPHACKLUP_INTEGRATION_DATABASE_URL: postgres://piphacklup:piphacklup_ci_only@localhost:5432/piphacklup

test:
runs-on: ubuntu-latest
steps:
Expand All @@ -18,11 +63,42 @@ jobs:
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Dependency audit
run: pnpm audit --audit-level moderate
- name: Secret pattern scan
run: |
if git grep -InE '([A-Za-z0-9_-]{24,}\.[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{20,}|github_pat_[A-Za-z0-9_]+|ghp_[A-Za-z0-9]{30,}|sk-[A-Za-z0-9]{32,})' -- ':!*.png' ':!*.jpg' ':!*.webp' ':!*.gif'; then
echo "Potential secret-like token found. Remove it before pushing."
exit 1
fi
run: pnpm scan:secrets
- name: Migration history integrity
run: pnpm check:migrations
- run: pnpm check
- run: pnpm format
- run: pnpm test
- run: pnpm build
- name: Smoke-test production Next.js server
run: pnpm verify:production:web
- name: Build production bot image
run: >-
docker build
--build-arg PIPHACKLUP_RELEASE_SHA=${{ github.sha }}
--file apps/bot/Dockerfile
--tag piphacklup-bot:ci
.
- name: Smoke-test production bot image
run: |
test "$(docker image inspect --format '{{range .Config.Env}}{{println .}}{{end}}' piphacklup-bot:ci | grep '^PIPHACKLUP_RELEASE_SHA=' | cut -d= -f2-)" = "${{ github.sha }}"
docker run --rm --workdir /app/apps/bot --entrypoint node piphacklup-bot:ci --check dist/index.js
docker run --rm --workdir /app/apps/bot --entrypoint node piphacklup-bot:ci --input-type=module --eval "await Promise.all([import('./dist/lib/health.js'), import('@piphacklup/core'), import('@piphacklup/db')])"

browser:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
with:
version: 10.25.0
- uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm --filter @piphacklup/web exec playwright install --with-deps chromium
- run: pnpm --filter @piphacklup/web e2e
4 changes: 4 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
apps/web/public/googleafb8aa8a0befb71d.html
graphify-out/
packages/db/drizzle/meta/
pnpm-lock.yaml
54 changes: 30 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<p align="center">
<a href="https://piphacklup.vercel.app">Website</a>
|
<a href="https://discord.com/oauth2/authorize?client_id=1512918151313231983&scope=bot+applications.commands&permissions=1117094267958">Add to Discord</a>
<a href="https://discord.com/oauth2/authorize?client_id=1512918151313231983&scope=bot+applications.commands&permissions=1099914365968">Add to Discord</a>
|
<a href="docs/discord-setup.md">Discord setup</a>
|
Expand All @@ -22,7 +22,7 @@
<img src="assets/piphacklup-discord-avatar.png" alt="PipHackLup Discord bot avatar" width="144">
</p>

PipHackLup is a hackathon operations Discord bot for 100-500 person events. It helps organizers make Discord feel less chaotic by guiding newcomers, assigning roles, managing mentor queues, forming teams, tracking moderation cases, and giving staff a dashboard for event day.
PipHackLup is a hackathon operations Discord bot for 100-500 person events. It helps organizers make Discord feel less chaotic by guiding newcomers, provisioning event roles and channels, managing mentor queues, suggesting team matches, tracking moderation cases, and giving staff a dashboard for event day.

Brand assets live in `assets/`:

Expand All @@ -36,30 +36,30 @@ Brand assets live in `assets/`:

- Website: https://piphacklup.vercel.app
- Public repo: https://github.com/rupayon123/PipHackLup
- Add to Discord: https://discord.com/oauth2/authorize?client_id=1512918151313231983&scope=bot+applications.commands&permissions=1117094267958
- Add to Discord: https://discord.com/oauth2/authorize?client_id=1512918151313231983&scope=bot+applications.commands&permissions=1099914365968
- Support and bugs: https://github.com/rupayon123/PipHackLup/issues

## What It Does

- Guided server setup with roles, channels, queues, moderation logs, and onboarding mode.
- Idempotent server setup with event roles, channels, onboarding/help/team panels, and durable configuration.
- Staff-trained hackathon Q&A so participants can ask event questions and get instant answers.
- Discord-linked website training where organizers sign in, pick a managed server, add FAQs, import event details, and preview answers.
- Human escalation for uncertain, mentor-needed, safety, conduct, judging, and staff-needed questions.
- Newcomer onboarding for nicknames, roles, hacker profiles, team finding, and help queues.
- Mentor, tech help, staff follow-up, and judging queues with claim, escalation, close, and transcripts.
- Team formation with solo profiles, recruiting teams, join requests, matching, and team channels.
- Moderation reports, staff actions, case history, audit logs, and Discord AutoMod setup guidance.
- Newcomer onboarding with nickname changes, Discord-native rules acknowledgement, participant-role access, hacker profiles, and honest team guidance. Gated servers restrict event channels until that role is present.
- Durable mentor, tech help, staff follow-up, and judging queues with open, claim, escalation, and close transitions.
- Team formation with participant profiles, recruiting teams, staff-run match suggestions, and a shared team-finder channel.
- Moderation reports, Discord timeouts, durable cases/audit events, and Discord AutoMod guidance.
- Organizer dashboard for setup, Q&A training, queues, teams, moderation, settings, and CSV import/export.

## Slash Commands

- `/ask`: ask PipHackLup a staff-trained question about the hackathon.
- `/train`: staff-only training for event details, FAQs, escalation rules, roles, and help channels.
- `/setup`: guided server setup for roles, channels, queues, moderation logs, and onboarding.
- `/onboard`: newcomer checklist for nickname, roles, profile, team, and help.
- `/setup`: idempotent provisioning for event roles, channels, durable panels, and guided or participant-role-gated onboarding.
- `/onboard`: evidence-based checklist plus nickname and profile updates; the onboarding panel grants only the configured participant role after explicit rules acknowledgement.
- `/queue`: mentor, tech help, staff follow-up, and judging/demo queues.
- `/team`: solo profiles, recruiting teams, join requests, matching, and team channels.
- `/mod`: reports, warns, timeouts, case history, and audit logs.
- `/team`: participant profiles, recruiting teams, and staff-run suggestions from opt-in profiles.
- `/mod`: reports, durable warning cases, and Discord timeouts with audit events.

## Workspace

Expand All @@ -76,55 +76,61 @@ assets Public mascot/profile assets
## Quick Start

```bash
corepack enable
pnpm install
npx --yes pnpm@10.25.0 install --frozen-lockfile
cp .env.example .env.local
pnpm test
pnpm dev:web
npx --yes pnpm@10.25.0 test
npx --yes pnpm@10.25.0 dev:web
```

To run the bot locally, create a Discord app in the Developer Portal, add the bot token/client ID to `.env.local`, then run:

```bash
pnpm dev:bot
npx --yes pnpm@10.25.0 dev:bot
```

Register slash commands:

```bash
pnpm --filter @piphacklup/bot deploy:commands
npx --yes pnpm@10.25.0 --filter @piphacklup/bot deploy:commands
```

## Website Q&A Training

Staff can train PipHackLup from `/training` on the website. Discord OAuth links the dashboard to the organizer account, shows servers where that account has Manage Server, and saves Q&A entries/settings for the selected server.

Live training needs these server-side env vars:
The live organizer dashboard needs these server-side env vars:

```bash
DISCORD_CLIENT_ID=
DISCORD_CLIENT_SECRET=
DISCORD_TOKEN=
NEXTAUTH_URL=https://piphacklup.vercel.app
NEXTAUTH_SECRET=
DATABASE_URL=
```

When `DATABASE_URL` is configured, website training and `/train` slash-command training use the same Postgres-backed knowledge source. Without the database, the page stays in preview mode so organizers can test the workflow safely.
Website training and `/train` use the same guild-scoped Postgres knowledge source. Dashboard login and protected data routes fail closed when OAuth or database configuration is missing; production never substitutes sample data.

## Discord Permissions

Required scopes: `bot`, `applications.commands`.

Recommended permissions: View Channels, Send Messages, Embed Links, Attach Files, Read Message History, Manage Roles, Manage Nicknames, Manage Channels, Manage Threads, Moderate Members, Manage Guild, and optional Kick/Ban.
Recommended permissions: View Channels, Send Messages, Embed Links, Read Message History, Manage Roles, Manage Nicknames, Manage Channels, and Moderate Members. PipHackLup does not request Kick or Ban Members, Manage Server, Attach Files, or Manage Threads.

Enable the Guild Members intent. Keep Message Content intent disabled unless you intentionally enable ambient mention Q&A with `PIPHACKLUP_AMBIENT_QA_ENABLED=true`.

## Security Baseline

PipHackLup is built for public hackathon servers, so the codebase includes organizer RBAC, API and bot rate limiting, prompt-injection filtering for staff-trained Q&A, signed Discord dashboard sessions, Dependabot, and a CI secret-pattern scan.
PipHackLup is built for public hackathon servers, so the codebase includes organizer RBAC, shared API and bot rate limiting, prompt-injection filtering for staff-trained Q&A, opaque database-backed Discord sessions with encrypted OAuth tokens, Dependabot, and a CI secret-pattern scan.

See `docs/security-baseline.md` and `SECURITY.md` before adding new public endpoints, bot commands, or AI-assisted workflows.

## Status
## Production readiness

PipHackLup is in public alpha. The website, repo, slash commands, and install link are live; the next major milestone is a hosted always-on bot process plus production database-backed dashboard flows.
PipHackLup is a two-service product: the Next.js organizer dashboard runs on Vercel, while the Discord gateway bot runs as a long-lived Node process. Both share one migrated Postgres database. A deployment is ready only when the environment variables are configured, migrations are applied, the bot health endpoint is healthy, Discord OAuth succeeds, and the isolated-server release checklist in `docs/discord-setup.md` passes.

Before contacting either hosting provider, run the secret-redacting offline contract check:

```bash
npx --yes pnpm@10.25.0 preflight:deployment --target all
```
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ PipHackLup's public code should keep RBAC, rate limiting, prompt-injection filte

## Supported Versions

PipHackLup is currently public alpha. Security fixes should target the `main` branch.
The current production deployment tracks the `main` branch. Security fixes should target `main`; older deployments and unmaintained forks are not supported.
21 changes: 14 additions & 7 deletions apps/bot/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
FROM node:24-slim AS base
WORKDIR /app
RUN corepack enable
RUN npm install --global pnpm@10.25.0

FROM base AS deps
COPY package.json pnpm-workspace.yaml tsconfig.base.json ./
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json ./
COPY apps/bot/package.json apps/bot/package.json
COPY packages/core/package.json packages/core/package.json
COPY packages/db/package.json packages/db/package.json
RUN pnpm install --filter @piphacklup/bot... --prod=false --frozen-lockfile

FROM deps AS build
COPY apps/bot apps/bot
COPY packages/core packages/core
RUN pnpm --filter @piphacklup/core build && pnpm --filter @piphacklup/bot build
COPY packages/db packages/db
RUN pnpm --filter @piphacklup/bot build

FROM node:24-slim AS runner
FROM base AS runner
WORKDIR /app
ARG PIPHACKLUP_RELEASE_SHA=unknown
ENV NODE_ENV=production
RUN corepack enable
COPY --from=build /app/package.json /app/pnpm-workspace.yaml ./
ENV PIPHACKLUP_RELEASE_SHA=${PIPHACKLUP_RELEASE_SHA}
COPY --from=build /app/package.json /app/pnpm-lock.yaml /app/pnpm-workspace.yaml ./
COPY --from=build /app/node_modules node_modules
COPY --from=build /app/apps/bot apps/bot
COPY --from=build /app/packages/core packages/core
COPY --from=build /app/packages/db packages/db
EXPOSE 8787
CMD ["pnpm", "--filter", "@piphacklup/bot", "start"]
USER node
HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \
CMD node -e "fetch('http://127.0.0.1:' + (process.env.PORT || '8787') + '/health').then(r => { if (!r.ok) process.exit(1) }).catch(() => process.exit(1))"
CMD ["node", "apps/bot/dist/index.js"]
6 changes: 3 additions & 3 deletions apps/bot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
"lint": "pnpm typecheck",
"predev": "pnpm --filter @piphacklup/db build",
"start": "node dist/index.js",
"test": "vitest run --passWithNoTests",
"typecheck": "pnpm --filter @piphacklup/db build && tsc -p tsconfig.json --noEmit"
"test": "vitest run",
"typecheck": "pnpm --filter @piphacklup/db build && tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit"
},
"dependencies": {
"@piphacklup/core": "workspace:*",
"@piphacklup/db": "workspace:*",
"discord.js": "^14.26.4",
"discord.js": "^14.27.0",
"dotenv": "^17.2.3"
},
"devDependencies": {
Expand Down
14 changes: 8 additions & 6 deletions apps/bot/src/commands/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ export const commandDefinitions = [
.addBooleanOption((option) =>
option
.setName("private")
.setDescription("Only show the answer to you")
.setDescription(
"Only you see the answer; escalated questions may be shared with authorized staff",
)
.setRequired(false),
),
new SlashCommandBuilder()
Expand Down Expand Up @@ -165,16 +167,16 @@ export const commandDefinitions = [
.addStringOption((option) =>
option
.setName("onboarding")
.setDescription("Newcomer onboarding mode")
.setDescription("Choose guided or participant-role-gated onboarding")
.setRequired(false)
.addChoices(
{ name: "Guided", value: "guided" },
{ name: "Gated", value: "gated" },
{ name: "Guided checklist", value: "guided" },
{ name: "Gated participant access", value: "gated" },
),
),
new SlashCommandBuilder()
.setName("onboard")
.setDescription("Newcomer checklist, nickname, roles, and profile helpers")
.setDescription("Verified checklist, nickname, and profile helpers")
.addSubcommand((subcommand) =>
subcommand
.setName("checklist")
Expand Down Expand Up @@ -298,7 +300,7 @@ export const commandDefinitions = [
),
new SlashCommandBuilder()
.setName("team")
.setDescription("Create teams, recruit members, and run matching")
.setDescription("Save profiles, create recruiting teams, suggest matches")
.addSubcommand((subcommand) =>
subcommand
.setName("create")
Expand Down
Loading
Loading