Skip to content

jacking7/G2B-BID-REPORT

Repository files navigation

G2B Bid Report

Release v0.1.0 License MIT Node >=20 Live example online

Collect Korea G2B bid notices, review matches, export Excel files, and send email reports from one self-hosted console.

Live Site · Features · Quick Start · Deployment

G2B Bid Report is a self-hosted web console for collecting Korea G2B public procurement notices by user-defined keywords, reviewing matched results, exporting Excel files, and sending email reports.

The project is built as a small operational MVP: one app, one database, explicit environment variables, and simple deployment primitives.

Live Site

Production is published at:

https://bca.ai.kr/g2breport/

The root route redirects to /g2breport/login. Public trust and legal surfaces are available without exposing runtime secrets, local database files, or deployment credentials.

Public URL Purpose
/g2breport/login Operator login and first account bootstrap
/g2breport/privacy Korean privacy notice for the internal console
/g2breport/terms Service notice, contact path, license, and responsibility limits
/g2breport/api/health Basic database health check

Current production smoke target:

curl https://bca.ai.kr/g2breport/api/health

Last verified production deployment:

  • Event date: 2026-06-01 KST
  • Git commit: 7e627c099a16df740c270ce95f0af442f52de0ad
  • Runtime: AWS EC2 + PM2 process g2b-bid-report
  • OAuth start routes: Google, Naver, and Kakao return 302 redirects from production
  • Runtime base URL: https://bca.ai.kr/g2breport

Features

  • Next.js App Router web console
  • Email/password authentication
  • Email verification and Google, Naver, Kakao social login
  • User-specific include and exclude keyword rules
  • User-specific recipients and collection/send schedules
  • User-level automation on/off plus a server-wide scheduler switch
  • Official G2B bid notice Open API collection
  • Result filtering by date, mail status, keyword, and free-text query
  • Excel export for collected results
  • SMTP daily email report sending with send history
  • Password reset, password change, and account withdrawal
  • SQLite + Prisma data layer
  • Light and Dracula-style dark themes with icon-only toggle controls
  • In-app operator manual at /g2breport/manual
  • Legal footer with privacy, service notice, MIT license, and GitHub contact links
  • Security defaults for rate-limited auth, explicit role checks, hardened cookies, crawler blocking, noindex headers, and spreadsheet formula injection defense

Public-Safe Repository Notes

This repository is intended to be safe to share publicly when runtime files stay out of git.

Do not commit:

  • .env or any real environment file
  • Real API keys, SMTP credentials, auth secrets, or job tokens
  • Local SQLite databases such as dev.db
  • Private SSH keys, PEM files, or deployment credentials
  • Production hostnames, IP addresses, or server-specific paths unless they are intentionally public

Use .env.example as the public template and keep real values only in the runtime environment.

Tech Stack

  • Next.js 16
  • React 19
  • TypeScript
  • Prisma 7
  • SQLite via better-sqlite3
  • bcryptjs for password hashing
  • jose for signed session cookies
  • nodemailer for email delivery
  • Built-in XLSX generator for spreadsheet export
  • node-cron for optional in-app scheduling

App Routes

Route Purpose
/g2breport/login Login, first account creation, password reset request
/g2breport/reset-password Password reset by token
/g2breport/settings Keywords, recipients, schedule, and account management
/g2breport/results Manual collection, filters, exports, mail send, status overview
/g2breport/manual Operator workflow manual
/g2breport/privacy Privacy notice
/g2breport/terms Service notice, contact, and license information
/g2breport/api/health Database health check
/g2breport/api/collection/start Authenticated manual collection start endpoint
/g2breport/api/collection/status Authenticated manual collection progress endpoint
/g2breport/api/collection/cancel Authenticated manual collection cancel endpoint
/g2breport/api/jobs/collect Authenticated external collection job endpoint
/g2breport/api/jobs/send Authenticated external mail job endpoint
/g2breport/api/mobile/auth/login Mobile app email/password login endpoint
/g2breport/api/mobile/dashboard Mobile app dashboard summary endpoint
/g2breport/api/mobile/collection/start Mobile app manual collection start endpoint
/g2breport/api/mobile/reports/send Mobile app daily report send endpoint

The native mobile app lives in the separate G2B-BID-REPORT-MOBILE repository, but these /g2breport/api/mobile/* routes are owned and deployed by this server app. The mobile dashboard follows the same result visibility policy as the web console: repeat-matched existing notices refresh their confirmation time, appear in the todayConfirmed metric, and expose confirmedAt while keeping collectedAt for compatibility.

Security Baseline

  • Login, registration, OAuth start, email verification, account lookup, password reset, password change, and withdrawal attempts are rate-limited.
  • User sessions are signed cookies with HttpOnly, production Secure, and SameSite=Lax.
  • Application roles are explicitly constrained to admin and user before protected actions proceed.
  • Passwords are stored only as bcrypt salted hashes; new or changed passwords must satisfy the strong password policy.
  • Sensitive session/auth data is not stored in localStorage; only UI preferences such as theme/sidebar state use browser storage.
  • Database access uses Prisma ORM bindings; do not add concatenated raw SQL.
  • Excel export neutralizes spreadsheet formula injection for values starting with =, +, -, @, or control line prefixes.
  • Public crawler exposure is blocked with /robots.txt, X-Robots-Tag: noindex, nofollow, noarchive, nosnippet, noimageindex, and known search/AI crawler UA blocking in the proxy.
  • Production health/error responses avoid stack traces, debug payloads, framework banners, and exact server version disclosure.
  • nginx deployments must keep server_tokens off; and custom error pages that preserve the original status code.

Quick Start

cp .env.example .env
npm install
npm run db:migrate
npm run dev

Open the local app:

http://localhost:3000/g2breport

On a fresh database, the login page prompts for the first operator account.

Environment Variables

Copy .env.example to .env and fill in local or production values.

Variable Description
DATABASE_URL SQLite database URL, usually file:./dev.db for local development
AUTH_SECRET Long random secret used to sign session cookies
AUTH_COOKIE_SECURE Set true for HTTPS deployments, false for plain HTTP local/dev use
SMTP_HOST SMTP server host
SMTP_PORT SMTP server port
SMTP_USER SMTP username
SMTP_PASS SMTP password or app password
MAIL_FROM Email sender address, optionally with display name such as G2B-Report <bot@example.com>
GOOGLE_OAUTH_CLIENT_ID Google OAuth web client ID for Google login
GOOGLE_OAUTH_CLIENT_SECRET Google OAuth web client secret
NAVER_OAUTH_CLIENT_ID Naver Developers client ID for Naver login
NAVER_OAUTH_CLIENT_SECRET Naver Developers client secret
KAKAO_REST_API_KEY Kakao Developers REST API key for Kakao login
KAKAO_CLIENT_SECRET Optional Kakao client secret, only when enabled in Kakao Developers
G2B_API_SERVICE_KEY Official G2B Open API service key
G2B_API_LOOKBACK_DAYS Number of registration days to search backwards
G2B_API_NUM_ROWS Rows per API page
G2B_API_MAX_PAGES_PER_ENDPOINT Maximum pages to request per endpoint
G2B_API_CONCURRENCY Concurrent API request limit
ENABLE_INTERNAL_SCHEDULER Server-wide in-app scheduler switch
INTERNAL_JOB_TOKEN Bearer token for external job endpoints
APP_BASE_URL Public base URL used by OAuth callbacks, job scripts, and password reset links

If SMTP variables are empty, email sending is skipped and recorded as a skipped mail history entry.

For Gmail SMTP, use an app password instead of the Google account password:

SMTP_HOST="smtp.gmail.com"
SMTP_PORT="465"
SMTP_USER="your-account@gmail.com"
SMTP_PASS="your-16-character-app-password"
MAIL_FROM="G2B-Report <your-account@gmail.com>"

Social login redirect URLs:

https://bca.ai.kr/g2breport/api/auth/oauth/google/callback
https://bca.ai.kr/g2breport/api/auth/oauth/naver/callback
https://bca.ai.kr/g2breport/api/auth/oauth/kakao/callback

Production OAuth callbacks are pinned to the public site URL. If a production runtime accidentally receives a localhost base URL, the auth flow falls back to https://bca.ai.kr/g2breport instead of generating localhost redirects.

Social login requests and stores only the email address needed to identify the account. Do not enable profile/name/nickname permissions in provider consoles unless the product explicitly needs them later.

Collection Rules

Collection runs per user.

  1. At least one active include keyword is required.
  2. The app queries the official G2B bid notice Open API.
  3. A notice must include today's KST date between its notice date and close date.
  4. Include keywords are matched against notice title and organization fields.
  5. Exclude keywords remove otherwise matched notices.
  6. BidNotice is upserted by notice number and order.
  7. CollectedResult is unique per user and notice.
  8. If an existing user result is matched again, its confirmation time and matched keyword are refreshed so it remains visible in today's default result list.
  9. API failures do not create partial result rows.

KST date bounds:

noticeDate <= today 23:59:59.999 KST
closeDate >= today 00:00:00.000 KST

Automation Model

Automation has two layers:

  • ENABLE_INTERNAL_SCHEDULER: server-wide on/off switch
  • ScheduleSetting.active: per-user on/off switch managed in the UI

The effective status is active only when both are on. External job endpoints also process active schedules only. Send jobs use each user's saved ScheduleSetting.sendTime and timezone to build the daily report window from the previous send time to the current send time. The report includes every notice confirmed in that window, including existing notices that matched again and were refreshed into the current result list. Daily report duplicate checks are recipient-scoped. A recipient that already has a successful send history for the same report window is skipped, while failed, skipped, or newly activated recipients remain eligible for retry.

Scripts

npm run dev          # Start local development server
npm run test         # Run ESLint and TypeScript checks
npm run build        # Generate Prisma Client and build Next.js
npm run start        # Start production Next.js server
npm run db:generate  # Generate Prisma Client
npm run db:migrate   # Apply Prisma migrations
npm run db:push      # Push schema directly to the database
npm run job:collect  # Call the external collect job endpoint
npm run job:send     # Call the external send job endpoint

External Job API

External schedulers can trigger collection or sending:

POST /g2breport/api/jobs/collect
POST /g2breport/api/jobs/send

Required headers:

Authorization: Bearer <INTERNAL_JOB_TOKEN>
Content-Type: application/json

Run for all active users:

{}

Run for one user:

{
  "userId": "user-id"
}

Health Check

Basic database check:

curl http://localhost:3000/g2breport/api/health

Example response:

{
  "ok": true,
  "database": "connected",
  "checkedAt": "2026-05-20T00:00:00.000Z"
}

Detailed health data requires the internal job token:

curl "http://localhost:3000/g2breport/api/health?detailed=1" \
  -H "Authorization: Bearer <INTERNAL_JOB_TOKEN>"

Deployment Checklist

  1. Prepare runtime environment variables outside git.
  2. Install dependencies.
  3. Apply migrations.
  4. Run tests.
  5. Build the app.
  6. Start or restart the process manager.
  7. Check /g2breport/api/health.

Example:

npm install
npm run db:migrate
npm run test
npm run build
npm run start

GitHub-Based Production Deploy

The current production server deploys from the GitHub main branch.

git fetch origin
git pull --ff-only origin main
npm run build
pm2 restart g2b-bid-report --update-env
curl http://localhost:3000/g2breport/api/health

Public post-deploy smoke check:

curl https://bca.ai.kr/g2breport/api/health
curl -I https://bca.ai.kr/g2breport/
curl -I https://bca.ai.kr/g2breport/api/auth/oauth/google/start
curl -I https://bca.ai.kr/g2breport/api/auth/oauth/naver/start
curl -I https://bca.ai.kr/g2breport/api/auth/oauth/kakao/start

Expected behavior:

  • /g2breport/api/health returns {"ok":true,"database":"connected",...}
  • / redirects to /g2breport/login
  • OAuth start routes return 302 to each provider with the production callback URL
  • The PM2 process g2b-bid-report is online

Recent production events:

Date (KST) Commit Event Verification
2026-06-01 12:56 f5404c9 Deployed the mobile auth/API routes needed by the native app, including mobile registration, social login start/callback, collection status, and uncapped dashboard results. Local lint/typecheck/build passed, localhost mobile social API smoke passed for Google/Naver/Kakao, production build passed, PM2 online, /api/health OK, production mobile social API smoke passed for Google/Naver/Kakao, Android Samsung/iPhone/iPad Maestro smoke passed.
2026-06-01 05:27 945ce86 Aligned the mobile dashboard API with web repeat-collection visibility by exposing todayConfirmed, confirmedAt, and collection policy metadata while preserving collectedAt. Local lint/typecheck/build passed, production build passed, PM2 online, /api/health OK, /login HTTP 200, unauthenticated mobile dashboard returned 401, authenticated dashboard smoke returned todayConfirmed=322 and confirmedAt.
2026-06-01 05:17 55f7a4f Refreshed already-saved matching notices during repeat collection so open notices remain visible in today's default result list without duplicate rows or duplicate mail. Local lint/typecheck/build passed, production build passed, PM2 online, /api/health OK, /login HTTP 200.
2026-06-01 05:05 03c712c Restored functional section boundaries for metrics, manual actions, automation status, and nested operational groups. Local lint/build passed, Playwright web screenshot confirmed section borders, production build passed, PM2 online, /api/health OK, /login HTTP 200.
2026-06-01 04:59 64c7a35 Restored visible boundaries for bid result tables while keeping non-data header chrome flat. Local lint/build passed, Playwright web screenshot captured with 306 result rows, production build passed, PM2 online, /api/health OK, /login HTTP 200.
2026-06-01 04:49 9ba5403 Flattened the web UI by removing unnecessary card, box, and shadow treatment from operational screens. Local lint/build passed, production build passed, PM2 online, /api/health OK, /login HTTP 200.

Troubleshooting

No Collection Results

  • Confirm G2B_API_SERVICE_KEY is configured.
  • Confirm at least one include keyword is active.
  • Confirm the target notice is within the configured lookback window.
  • Confirm today's KST date is between the notice date and close date.
  • Check whether an exclude keyword filtered the result.

Login or Session Issues

  • Use a stable AUTH_SECRET.
  • Use AUTH_COOKIE_SECURE=true behind HTTPS.
  • Use AUTH_COOKIE_SECURE=false only for local/plain HTTP environments.

Mail Is Not Sent

  • Confirm SMTP variables are configured.
  • Confirm the sender account allows SMTP login.
  • For Gmail, confirm a Google app password is used and SMTP_PASS contains the compact 16-character password.
  • Confirm MAIL_FROM is either a plain address or a valid display-name format such as G2B-Report <account@gmail.com>.
  • Empty SMTP settings intentionally create skipped mail history instead of sending.
  • Daily reports are skipped when there are no notices confirmed between the previous configured send time and the current configured send time.
  • A successfully sent recipient is not sent again for the same report date, but failed, skipped, or newly activated recipients can receive the same daily report on retry.

License

MIT

About

Self-hosted Korea G2B bid notice collector with keyword filters, Excel export, and email reports.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages