{"id":50695737,"url":"https://github.com/hngprojects/open-profile-be","last_synced_at":"2026-06-09T06:08:30.049Z","repository":{"id":357519411,"uuid":"1237238362","full_name":"hngprojects/open-profile-be","owner":"hngprojects","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-04T13:31:41.000Z","size":818,"stargazers_count":1,"open_issues_count":1,"forks_count":2,"subscribers_count":0,"default_branch":"dev","last_synced_at":"2026-06-04T14:05:10.666Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hngprojects.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-13T02:14:48.000Z","updated_at":"2026-06-04T13:33:29.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/hngprojects/open-profile-be","commit_stats":null,"previous_names":["hngprojects/open-profile-be"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/hngprojects/open-profile-be","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hngprojects%2Fopen-profile-be","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hngprojects%2Fopen-profile-be/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hngprojects%2Fopen-profile-be/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hngprojects%2Fopen-profile-be/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hngprojects","download_url":"https://codeload.github.com/hngprojects/open-profile-be/tar.gz/refs/heads/dev","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hngprojects%2Fopen-profile-be/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34093840,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-09T02:00:06.510Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-09T06:08:29.334Z","updated_at":"2026-06-09T06:08:30.030Z","avatar_url":"https://github.com/hngprojects.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Open Profile Backend\n\nREST API for the Open Profile platform — a social link-in-bio / portfolio builder.\n\n## Tech Stack\n\n| Concern       | Stack                                                    |\n| ------------- | -------------------------------------------------------- |\n| Runtime       | Node.js \u003e= 20, TypeScript                                |\n| Framework     | NestJS v11                                               |\n| Database      | PostgreSQL 15 + TypeORM                                  |\n| Cache / Queue | Redis (ioredis + BullMQ)                                 |\n| Auth          | JWT (access + refresh tokens), Google OAuth 2.0          |\n| Email         | Brevo SMTP (primary) + Resend API (fallback)             |\n| Validation    | class-validator + class-transformer (runtime), Zod (env) |\n| Image Proc    | Sharp                                                    |\n| API Docs      | Swagger (`/docs`)                                        |\n\n## Prerequisites\n\n- **Node.js** \u003e= 20\n- **pnpm** \u003e= 9\n- **PostgreSQL** 15+\n- **Redis** 7+\n\n## Getting Started\n\n```bash\n# Install dependencies\npnpm install\n\n# Copy and fill in environment variables\ncp .env.example .env\n\n# Run migrations\npnpm migration:run\n\n# (Optional) seed data\npnpm seed\n\n# Start dev server with hot reload\npnpm start:dev\n```\n\nServer starts at `http://localhost:3000`. Swagger docs at `http://localhost:3000/docs`.\n\n## Scripts\n\n| Script                    | Description                   |\n| ------------------------- | ----------------------------- |\n| `pnpm build`              | Compile to `dist/`            |\n| `pnpm start:dev`          | Watch mode development        |\n| `pnpm start:prod`         | Run compiled production build |\n| `pnpm lint`               | ESLint with auto-fix          |\n| `pnpm typecheck`          | TypeScript type checking      |\n| `pnpm test`               | Unit tests (Jest)             |\n| `pnpm test:e2e`           | End-to-end tests              |\n| `pnpm migration:run`      | Run pending migrations        |\n| `pnpm migration:generate` | Generate a new migration      |\n| `pnpm db:reset`           | Drop schema + migrate + seed  |\n\n## Environment Variables\n\nKey variables (see `.env.example` for all):\n\n| Variable                      | Description                                     |\n| ----------------------------- | ----------------------------------------------- |\n| `DATABASE_*`                  | PostgreSQL connection                           |\n| `REDIS_URL`                   | Redis connection string                         |\n| `JWT_ACCESS_SECRET`           | Access token signing key (min 32 chars)         |\n| `JWT_REFRESH_SECRET`          | Refresh token signing key (min 32 chars)        |\n| `JWT_RESET_SECRET`            | Password reset token signing key (min 32 chars) |\n| `RESEND_API_KEY`              | Resend transactional email API key              |\n| `BREVO_SMTP_*`                | Brevo SMTP credentials (primary email)          |\n| `CLIENT_ID` / `CLIENT_SECRET` | Google OAuth 2.0 credentials                    |\n| `FRONTEND_URL`                | Frontend URL for OAuth redirects                |\n| `CORS_ORIGINS`                | Comma-separated allowed origins                 |\n\n## Architecture\n\n```text\nsrc/\n├── main.ts                  # Entry point\n├── app.module.ts            # Root module\n├── config/                  # Env validation \u0026 app config\n├── common/                  # Shared decorators, filters, interceptors, Redis, upload\n├── database/                # Migrations, seeds, DataSource\n└── modules/\n    ├── auth/                # Register, login, JWT, OAuth, OTP, password reset\n    ├── users/               # User CRUD, stale user cleanup (cron)\n    ├── profile/             # Profile onboarding, components, publish\n    ├── portfolio/           # Portfolio items CRUD\n    ├── analytics/           # Profile view tracking \u0026 stats\n    ├── search/              # Public profile search\n    ├── contact/             # Contact form submissions\n    ├── waitlist/            # Waitlist signup\n    ├── usernames/           # Username availability \u0026 reserved keywords\n    ├── upload/              # Image upload with Sharp processing\n    ├── mail/                # Email templates \u0026 BullMQ processor\n    ├── queue/               # BullMQ config \u0026 service\n    ├── health/              # Liveness probe\n    └── rate-limiter/        # Redis-based rate limiter\n```\n\n## Authentication\n\n- **Default**: All routes require authentication via a global `JwtAuthGuard`.\n- **Public routes** are marked with `@Public()`.\n- **Access tokens** (15 min) and **refresh tokens** (7 days) are stored in httpOnly cookies.\n- **Silent refresh**: The guard proactively refreshes tokens when the access token is close to expiry (\u003c 3 min).\n- **Token rotation**: Each refresh invalidates the previous refresh token (replay protection).\n- **Rate limiting**: IP-based (login), email-based (forgot password, resend OTP), and brute-force lockout (5 attempts → 30 min).\n\n## API Overview\n\nAll endpoints under `/api/v1/`.\n\n| Module    | Key Endpoints                                                                                               |\n| --------- | ----------------------------------------------------------------------------------------------------------- |\n| Auth      | register, login, logout, refresh, me, forgot-password, reset-password, verify-otp, resend-otp, Google OAuth |\n| Users     | CRUD, onboarding-complete                                                                                   |\n| Profiles  | Create (onboarding), publish, update, get public (cached), dashboard, component management                  |\n| Portfolio | Add / update portfolio items                                                                                |\n| Analytics | Record profile view (IP-deduplicated), get stats                                                            |\n| Search    | Full-text search on published profiles                                                                      |\n| Contact   | Submit contact form                                                                                         |\n| Waitlist  | Signup + list                                                                                               |\n| Usernames | Check availability                                                                                          |\n| Uploads   | Image upload (profiles, projects, portfolio categories)                                                     |\n\n## Database\n\n7 tables: `users`, `profiles`, `components`, `portfolio_items`, `refresh_tokens`, `reset_password`, `profile_views`.\n\n- **User 1:1 Profile** — each user has one profile\n- **Profile 1:N Components** — ordered sections with JSONB metadata\n- **Profile 1:N ProfileViews** — IP-deduplicated view tracking\n- **Soft deletes** on `users` and `profiles`\n\n## CI/CD\n\nGitHub Actions workflows:\n\n- **CI** (`ci.yml`): Lint → migration check → test → build\n- **CD** (`deploy.yml`): SCP + SSH deploy to staging/production via PM2\n\n## Cron Jobs\n\n- **Daily midnight**: Deletes unverified accounts older than 30 days (`StaleUsersCleanupService`).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhngprojects%2Fopen-profile-be","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhngprojects%2Fopen-profile-be","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhngprojects%2Fopen-profile-be/lists"}