mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-03-26 18:41:12 -04:00
Update docs
This commit is contained in:
@@ -520,7 +520,7 @@ What testing is not
|
||||
|
||||
How we apply it here
|
||||
|
||||
- Unit and integration tests live in each package and run with Jest (see `jest.config.js`).
|
||||
- Unit and integration tests live in each package and run with Jest (see `jest.config.ts`).
|
||||
- Critical user journeys are covered by Playwright E2E tests under `tests/e2e` (see `playwright.config.ts`).
|
||||
|
||||
### Test types at a glance
|
||||
@@ -528,18 +528,20 @@ How we apply it here
|
||||
This project uses three complementary test types. Use the right level for the job:
|
||||
|
||||
- Unit tests
|
||||
|
||||
- Purpose: Verify a single function/module in isolation; fast, deterministic.
|
||||
- Where: Each package under `tests/unit` (e.g., `backend/api/tests/unit`, `web/tests/unit`, `common/tests/unit`,
|
||||
etc.).
|
||||
- Runner: Jest (configured via root `jest.config.js`).
|
||||
- Runner: Jest (configured via root `jest.config.ts`).
|
||||
- Naming: `*.unit.test.ts` (or `.tsx` for React in `web`).
|
||||
- When to use: Pure logic, utilities, hooks, reducers, small components with mocked dependencies.
|
||||
|
||||
- Integration tests
|
||||
|
||||
- Purpose: Verify multiple units working together (e.g., function + DB/client, component + context/provider) without
|
||||
spinning up the full app.
|
||||
- Where: Each package under `tests/integration` (e.g., `backend/shared/tests/integration`, `web/tests/integration`).
|
||||
- Runner: Jest (configured via root `jest.config.js`).
|
||||
- Runner: Jest (configured via root `jest.config.ts`).
|
||||
- Naming: `*.integration.test.ts` (or `.tsx` for React in `web`).
|
||||
- When to use: Boundaries between modules, real serialization/parsing, API handlers with mocked network/DB,
|
||||
component trees with providers.
|
||||
@@ -565,7 +567,7 @@ yarn test:e2e
|
||||
|
||||
```filetree
|
||||
# Config
|
||||
jest.config.js (for unit and integration tests)
|
||||
jest.config.ts (for unit and integration tests)
|
||||
playwright.config.ts (for e2e tests)
|
||||
|
||||
# Top-level End-to-End (Playwright)
|
||||
@@ -617,7 +619,7 @@ web/
|
||||
- End-to-End tests live under `tests/e2e` and are executed by Playwright. The root `playwright.config.ts` sets `testDir`
|
||||
to `./tests/e2e`.
|
||||
- Unit and integration tests live in each package’s `tests` folder and are executed by Jest via the root
|
||||
`jest.config.js` projects array.
|
||||
`jest.config.ts` projects array.
|
||||
- Naming:
|
||||
- Unit: `*.unit.test.ts` (or `.tsx` for React in `web`)
|
||||
- Integration: `*.integration.test.ts`
|
||||
|
||||
12
.github/copilot-instructions.md
vendored
12
.github/copilot-instructions.md
vendored
@@ -514,7 +514,7 @@ What testing is not
|
||||
|
||||
How we apply it here
|
||||
|
||||
- Unit and integration tests live in each package and run with Jest (see `jest.config.js`).
|
||||
- Unit and integration tests live in each package and run with Jest (see `jest.config.ts`).
|
||||
- Critical user journeys are covered by Playwright E2E tests under `tests/e2e` (see `playwright.config.ts`).
|
||||
|
||||
### Test types at a glance
|
||||
@@ -522,18 +522,20 @@ How we apply it here
|
||||
This project uses three complementary test types. Use the right level for the job:
|
||||
|
||||
- Unit tests
|
||||
|
||||
- Purpose: Verify a single function/module in isolation; fast, deterministic.
|
||||
- Where: Each package under `tests/unit` (e.g., `backend/api/tests/unit`, `web/tests/unit`, `common/tests/unit`,
|
||||
etc.).
|
||||
- Runner: Jest (configured via root `jest.config.js`).
|
||||
- Runner: Jest (configured via root `jest.config.ts`).
|
||||
- Naming: `*.unit.test.ts` (or `.tsx` for React in `web`).
|
||||
- When to use: Pure logic, utilities, hooks, reducers, small components with mocked dependencies.
|
||||
|
||||
- Integration tests
|
||||
|
||||
- Purpose: Verify multiple units working together (e.g., function + DB/client, component + context/provider) without
|
||||
spinning up the full app.
|
||||
- Where: Each package under `tests/integration` (e.g., `backend/shared/tests/integration`, `web/tests/integration`).
|
||||
- Runner: Jest (configured via root `jest.config.js`).
|
||||
- Runner: Jest (configured via root `jest.config.ts`).
|
||||
- Naming: `*.integration.test.ts` (or `.tsx` for React in `web`).
|
||||
- When to use: Boundaries between modules, real serialization/parsing, API handlers with mocked network/DB,
|
||||
component trees with providers.
|
||||
@@ -559,7 +561,7 @@ yarn test:e2e
|
||||
|
||||
```filetree
|
||||
# Config
|
||||
jest.config.js (for unit and integration tests)
|
||||
jest.config.ts (for unit and integration tests)
|
||||
playwright.config.ts (for e2e tests)
|
||||
|
||||
# Top-level End-to-End (Playwright)
|
||||
@@ -611,7 +613,7 @@ web/
|
||||
- End-to-End tests live under `tests/e2e` and are executed by Playwright. The root `playwright.config.ts` sets `testDir`
|
||||
to `./tests/e2e`.
|
||||
- Unit and integration tests live in each package’s `tests` folder and are executed by Jest via the root
|
||||
`jest.config.js` projects array.
|
||||
`jest.config.ts` projects array.
|
||||
- Naming:
|
||||
- Unit: `*.unit.test.ts` (or `.tsx` for React in `web`)
|
||||
- Integration: `*.integration.test.ts`
|
||||
|
||||
@@ -514,7 +514,7 @@ What testing is not
|
||||
|
||||
How we apply it here
|
||||
|
||||
- Unit and integration tests live in each package and run with Jest (see `jest.config.js`).
|
||||
- Unit and integration tests live in each package and run with Jest (see `jest.config.ts`).
|
||||
- Critical user journeys are covered by Playwright E2E tests under `tests/e2e` (see `playwright.config.ts`).
|
||||
|
||||
### Test types at a glance
|
||||
@@ -522,18 +522,20 @@ How we apply it here
|
||||
This project uses three complementary test types. Use the right level for the job:
|
||||
|
||||
- Unit tests
|
||||
|
||||
- Purpose: Verify a single function/module in isolation; fast, deterministic.
|
||||
- Where: Each package under `tests/unit` (e.g., `backend/api/tests/unit`, `web/tests/unit`, `common/tests/unit`,
|
||||
etc.).
|
||||
- Runner: Jest (configured via root `jest.config.js`).
|
||||
- Runner: Jest (configured via root `jest.config.ts`).
|
||||
- Naming: `*.unit.test.ts` (or `.tsx` for React in `web`).
|
||||
- When to use: Pure logic, utilities, hooks, reducers, small components with mocked dependencies.
|
||||
|
||||
- Integration tests
|
||||
|
||||
- Purpose: Verify multiple units working together (e.g., function + DB/client, component + context/provider) without
|
||||
spinning up the full app.
|
||||
- Where: Each package under `tests/integration` (e.g., `backend/shared/tests/integration`, `web/tests/integration`).
|
||||
- Runner: Jest (configured via root `jest.config.js`).
|
||||
- Runner: Jest (configured via root `jest.config.ts`).
|
||||
- Naming: `*.integration.test.ts` (or `.tsx` for React in `web`).
|
||||
- When to use: Boundaries between modules, real serialization/parsing, API handlers with mocked network/DB,
|
||||
component trees with providers.
|
||||
@@ -559,7 +561,7 @@ yarn test:e2e
|
||||
|
||||
```filetree
|
||||
# Config
|
||||
jest.config.js (for unit and integration tests)
|
||||
jest.config.ts (for unit and integration tests)
|
||||
playwright.config.ts (for e2e tests)
|
||||
|
||||
# Top-level End-to-End (Playwright)
|
||||
@@ -611,7 +613,7 @@ web/
|
||||
- End-to-End tests live under `tests/e2e` and are executed by Playwright. The root `playwright.config.ts` sets `testDir`
|
||||
to `./tests/e2e`.
|
||||
- Unit and integration tests live in each package’s `tests` folder and are executed by Jest via the root
|
||||
`jest.config.js` projects array.
|
||||
`jest.config.ts` projects array.
|
||||
- Naming:
|
||||
- Unit: `*.unit.test.ts` (or `.tsx` for React in `web`)
|
||||
- Integration: `*.integration.test.ts`
|
||||
|
||||
531
CONTRIBUTING.md
531
CONTRIBUTING.md
@@ -1,128 +1,491 @@
|
||||
# Contributing to This Repository
|
||||
# Contributing to Compass
|
||||
|
||||
We welcome pull requests, but only if they meet the project's quality and design standards. Follow the process below precisely to avoid wasting time—yours or ours.
|
||||
Thank you for your interest in contributing to Compass! This document provides comprehensive guidelines for contributing to this open-source project.
|
||||
|
||||
## Prerequisites
|
||||
## Table of Contents
|
||||
|
||||
- Familiarity with Git and GitHub (basic commands, branching, forking, etc.)
|
||||
- A functioning development environment
|
||||
- Node.js, Python, or other relevant runtime/tools installed (check the `README.md`)
|
||||
- Read the [Development Documentation](docs/development.md) for project-specific setup and guidelines (adding languages,
|
||||
profile fields, etc.)
|
||||
- [Code of Conduct](#code-of-conduct)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Development Environment](#development-environment)
|
||||
- [Project Structure](#project-structure)
|
||||
- [Coding Standards](#coding-standards)
|
||||
- [Making Changes](#making-changes)
|
||||
- [Testing](#testing)
|
||||
- [Pull Request Guidelines](#pull-request-guidelines)
|
||||
- [Commit Message Guidelines](#commit-message-guidelines)
|
||||
- [Documentation](#documentation)
|
||||
- [Questions and Support](#questions-and-support)
|
||||
|
||||
## Fork & Clone
|
||||
## Code of Conduct
|
||||
|
||||
1. **Fork the repository** using the GitHub UI.
|
||||
2. **Clone your fork** locally:
|
||||
Please read and follow our [Code of Conduct](./CODE_OF_CONDUCT.md). We are committed to providing a welcoming and inclusive environment for all contributors.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before contributing, ensure you have the following installed:
|
||||
|
||||
- **Node.js** 20.x or later
|
||||
- **Yarn** 1.x (classic)
|
||||
- **Git**
|
||||
- **Docker** (optional, for isolated development)
|
||||
|
||||
### Fork and Clone
|
||||
|
||||
1. Fork the [repository](https://github.com/CompassConnections/Compass) on GitHub
|
||||
2. Clone your fork:
|
||||
```bash
|
||||
git clone https://github.com/your-username/Compass.git
|
||||
cd your-fork
|
||||
|
||||
git clone https://github.com/<your-username>/Compass.git
|
||||
cd Compass
|
||||
```
|
||||
|
||||
3. **Add the upstream remote**:
|
||||
|
||||
3. Add the upstream remote:
|
||||
```bash
|
||||
git remote add upstream https://github.com/CompassConnections/Compass.git
|
||||
```
|
||||
|
||||
## Create a New Branch
|
||||
|
||||
Never work on `main` or `master`.
|
||||
### Install Dependencies
|
||||
|
||||
```bash
|
||||
git checkout -b fix/brief-but-specific-description
|
||||
yarn install --frozen-lockfile
|
||||
```
|
||||
|
||||
Use a clear, descriptive branch name. Avoid vague names like `patch-1`.
|
||||
## Development Environment
|
||||
|
||||
## Stay Updated
|
||||
|
||||
Before you start, make sure your fork is up to date:
|
||||
### Running the Development Server
|
||||
|
||||
```bash
|
||||
git fetch upstream
|
||||
git checkout main
|
||||
git merge upstream/main
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Then rebase your feature branch if needed:
|
||||
Visit http://localhost:3000 to see the application.
|
||||
|
||||
### Isolated Development (Recommended)
|
||||
|
||||
For full isolation with local Supabase and Firebase emulators:
|
||||
|
||||
```bash
|
||||
git checkout fix/your-feature
|
||||
git rebase main
|
||||
yarn dev:isolated
|
||||
```
|
||||
|
||||
## Make Atomic Commits
|
||||
Benefits:
|
||||
|
||||
Each commit should represent a single logical change. Follow this format:
|
||||
- No conflicts with other contributors
|
||||
- Works offline
|
||||
- Faster database queries
|
||||
- Free to reset and reseed data
|
||||
|
||||
```text
|
||||
type(scope): concise description
|
||||
Requirements:
|
||||
|
||||
body explaining what and why, if necessary
|
||||
- Docker (~500MB)
|
||||
- Supabase CLI
|
||||
- Java 21+ (for Firebase emulators)
|
||||
- Firebase CLI
|
||||
|
||||
See the [README](./README.md) for detailed setup instructions.
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
yarn test
|
||||
|
||||
# Run tests with coverage
|
||||
yarn test:coverage
|
||||
|
||||
# Run tests in watch mode
|
||||
yarn test:watch
|
||||
|
||||
# Run E2E tests
|
||||
yarn test:e2e
|
||||
```
|
||||
|
||||
### Linting and Type Checking
|
||||
|
||||
```bash
|
||||
# Lint all packages
|
||||
yarn lint
|
||||
|
||||
# Fix linting issues
|
||||
yarn lint-fix
|
||||
|
||||
# Type check all packages
|
||||
yarn typecheck
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
This is a Yarn workspaces monorepo with the following packages:
|
||||
|
||||
```
|
||||
Compass/
|
||||
├── web/ # Next.js web application
|
||||
│ ├── components/ # React components
|
||||
│ ├── hooks/ # Custom React hooks
|
||||
│ ├── lib/ # Utilities and services
|
||||
│ ├── pages/ # Next.js pages
|
||||
│ └── messages/ # Internationalization files
|
||||
├── backend/
|
||||
│ ├── api/ # Express API server
|
||||
│ ├── shared/ # Shared backend utilities
|
||||
│ ├── email/ # React email templates
|
||||
│ └── scripts/ # Database migration scripts
|
||||
├── common/ # Shared TypeScript types and utilities
|
||||
├── supabase/ # Database migrations and config
|
||||
├── android/ # Capacitor Android app
|
||||
└── docs/ # Project documentation
|
||||
```
|
||||
|
||||
### Key Technologies
|
||||
|
||||
| Layer | Technology |
|
||||
| -------- | -------------------------------- |
|
||||
| Frontend | Next.js 14, React 18, TypeScript |
|
||||
| Styling | Tailwind CSS |
|
||||
| Backend | Express.js, Node.js |
|
||||
| Database | PostgreSQL (Supabase) |
|
||||
| Auth | Firebase Auth |
|
||||
| Storage | Firebase Storage |
|
||||
| Mobile | Capacitor (Android) |
|
||||
| Testing | Jest, Playwright |
|
||||
|
||||
## Coding Standards
|
||||
|
||||
### TypeScript
|
||||
|
||||
- Use strict TypeScript typing
|
||||
- Avoid `any` type; use `unknown` when necessary
|
||||
- Prefer interfaces over types for object shapes
|
||||
- Use `const` assertions where appropriate
|
||||
|
||||
### React Components
|
||||
|
||||
- Use functional components with hooks
|
||||
- Name components after their file name
|
||||
- Export primary component at the top of the file
|
||||
- Use composition over inheritance
|
||||
- Keep components small and focused
|
||||
|
||||
Example component structure:
|
||||
|
||||
```tsx
|
||||
import clsx from 'clsx'
|
||||
import {useState} from 'react'
|
||||
|
||||
interface ProfileCardProps {
|
||||
name: string
|
||||
age: number
|
||||
onSelect?: (id: string) => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function ProfileCard({name, age, onSelect, className}: ProfileCardProps) {
|
||||
const [selected, setSelected] = useState(false)
|
||||
|
||||
const handleClick = () => {
|
||||
setSelected(!selected)
|
||||
onSelect?.(name)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={clsx('card', selected && 'selected', className)}>
|
||||
<h3>
|
||||
{name}, {age}
|
||||
</h3>
|
||||
<button onClick={handleClick}>Select</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
- **Files**: kebab-case (`profile-card.tsx`)
|
||||
- **Components**: PascalCase (`ProfileCard`)
|
||||
- **Hooks**: camelCase with `use` prefix (`useUserProfile`)
|
||||
- **Constants**: SCREAMING_SNAKE_CASE
|
||||
- **Types/Interfaces**: PascalCase
|
||||
|
||||
### Import Order
|
||||
|
||||
Run `yarn lint-fix` to automatically sort imports:
|
||||
|
||||
1. External libraries (React, Next.js, etc.)
|
||||
2. Internal packages (`common/`, `shared/`)
|
||||
3. Relative imports (`../`, `./`)
|
||||
4. Type imports
|
||||
|
||||
### Error Handling
|
||||
|
||||
- Use try-catch for async operations
|
||||
- Create custom error types for API errors
|
||||
- Implement error boundaries for React components
|
||||
- Log errors with appropriate context
|
||||
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
import {APIError} from './errors'
|
||||
|
||||
try {
|
||||
const result = await api('endpoint', params)
|
||||
return result
|
||||
} catch (err) {
|
||||
if (err instanceof APIError) {
|
||||
logger.error('API error', {status: err.status, message: err.message})
|
||||
} else {
|
||||
logger.error('Unexpected error', err)
|
||||
}
|
||||
throw err
|
||||
}
|
||||
```
|
||||
|
||||
### Accessibility
|
||||
|
||||
- Use semantic HTML elements
|
||||
- Include ARIA labels where appropriate
|
||||
- Ensure keyboard navigation works
|
||||
- Use the `SkipLink` component for main content
|
||||
- Announce dynamic content changes with `useLiveRegion`
|
||||
|
||||
```tsx
|
||||
import {useLiveRegion} from 'web/components/live-region'
|
||||
|
||||
function MyComponent() {
|
||||
const {announce} = useLiveRegion()
|
||||
|
||||
const handleAction = () => {
|
||||
// Action completed
|
||||
announce('Action successful', 'polite')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Making Changes
|
||||
|
||||
### Creating a Branch
|
||||
|
||||
Never work directly on `main`. Create a new branch:
|
||||
|
||||
```bash
|
||||
git checkout -b type/short-description
|
||||
```
|
||||
|
||||
Branch types:
|
||||
|
||||
- `feat/` - New features
|
||||
- `fix/` - Bug fixes
|
||||
- `docs/` - Documentation
|
||||
- `refactor/` - Code refactoring
|
||||
- `test/` - Adding/updating tests
|
||||
- `chore/` - Maintenance tasks
|
||||
|
||||
### Making Commits
|
||||
|
||||
Keep commits atomic and descriptive:
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "feat(profiles): add compatibility score display
|
||||
|
||||
- Added compatibility score calculation
|
||||
- Display score on profile cards
|
||||
- Added tests for scoring algorithm"
|
||||
```
|
||||
|
||||
See [Commit Message Guidelines](#commit-message-guidelines) for details.
|
||||
|
||||
### Keeping Your Fork Updated
|
||||
|
||||
```bash
|
||||
# Fetch latest from upstream
|
||||
git fetch upstream
|
||||
|
||||
# Update main branch
|
||||
git checkout main
|
||||
git merge upstream/main
|
||||
|
||||
# Rebase your feature branch
|
||||
git checkout feat/your-feature
|
||||
git rebase main
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Writing Tests
|
||||
|
||||
#### Unit Tests
|
||||
|
||||
Place tests in `tests/unit/` within each package:
|
||||
|
||||
```typescript
|
||||
// web/tests/unit/my-function.test.ts
|
||||
import {myFunction} from '../my-function'
|
||||
|
||||
describe('myFunction', () => {
|
||||
it('should return correct output', () => {
|
||||
expect(myFunction('input')).toBe('expected')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
#### Integration Tests
|
||||
|
||||
Place in `tests/integration/`:
|
||||
|
||||
```typescript
|
||||
// web/tests/integration/api.test.ts
|
||||
import {render, screen} from '@testing-library/react'
|
||||
import {MyComponent} from '../MyComponent'
|
||||
|
||||
describe('MyComponent', () => {
|
||||
it('renders correctly', () => {
|
||||
render(<MyComponent />)
|
||||
expect(screen.getByText('Hello')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
#### E2E Tests
|
||||
|
||||
Place in `tests/e2e/` at the root:
|
||||
|
||||
```typescript
|
||||
// tests/e2e/web/specs/onboarding.spec.ts
|
||||
import {test, expect} from '@playwright/test'
|
||||
|
||||
test('onboarding flow', async ({page}) => {
|
||||
await page.goto('/signup')
|
||||
await page.fill('[name="email"]', 'test@example.com')
|
||||
await page.click('button[type="submit"]')
|
||||
await expect(page).toHaveURL('/onboarding')
|
||||
})
|
||||
```
|
||||
|
||||
### Running Specific Tests
|
||||
|
||||
```bash
|
||||
# Run unit tests for web
|
||||
yarn workspace web test
|
||||
|
||||
# Run tests matching pattern
|
||||
yarn test --testPathPattern="profile"
|
||||
|
||||
# Run E2E tests
|
||||
yarn test:e2e
|
||||
```
|
||||
|
||||
### Test Coverage
|
||||
|
||||
Aim for meaningful test coverage. Focus on:
|
||||
|
||||
- Business logic
|
||||
- User interactions
|
||||
- Error handling
|
||||
- Edge cases
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
### Before Submitting
|
||||
|
||||
1. **Run all tests**: `yarn test`
|
||||
2. **Run linter**: `yarn lint`
|
||||
3. **Run type check**: `yarn typecheck`
|
||||
4. **Update documentation** if needed
|
||||
5. **Rebase on main** if necessary
|
||||
|
||||
### Pull Request Format
|
||||
|
||||
**Title**: Clear, descriptive title
|
||||
|
||||
**Description**:
|
||||
|
||||
```markdown
|
||||
## Summary
|
||||
|
||||
Brief description of changes
|
||||
|
||||
## Changes
|
||||
|
||||
- Added compatibility score to profile cards
|
||||
- Updated search algorithm for better results
|
||||
|
||||
## Testing
|
||||
|
||||
- Added unit tests for scoring algorithm
|
||||
- Tested manually with synthetic data
|
||||
|
||||
## Screenshots (if UI changes)
|
||||
```
|
||||
|
||||
### PR Checklist
|
||||
|
||||
- [ ] Code follows style guidelines
|
||||
- [ ] Tests added/updated and passing
|
||||
- [ ] Documentation updated
|
||||
- [ ] No console.log statements (except debugging)
|
||||
- [ ] No debug code left behind
|
||||
|
||||
### Review Process
|
||||
|
||||
1. Maintainers review within 48 hours
|
||||
2. Address feedback promptly
|
||||
3. Do not open new PRs for changes - update existing one
|
||||
4. Squash commits before merging
|
||||
|
||||
## Commit Message Guidelines
|
||||
|
||||
Follow [Conventional Commits](https://www.conventionalcommits.org/):
|
||||
|
||||
```
|
||||
<type>(<scope>): <description>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer]
|
||||
```
|
||||
|
||||
### Types
|
||||
|
||||
- `feat`: New feature
|
||||
- `fix`: Bug fix
|
||||
- `docs`: Documentation
|
||||
- `style`: Formatting
|
||||
- `refactor`: Code restructuring
|
||||
- `test`: Tests
|
||||
- `chore`: Maintenance
|
||||
|
||||
### Examples
|
||||
|
||||
```text
|
||||
fix(api): handle 500 error on invalid payload
|
||||
feat(profiles): add compatibility scoring algorithm
|
||||
fix(api): handle rate limiting gracefully
|
||||
docs(readme): update installation instructions
|
||||
refactor(auth): simplify token refresh logic
|
||||
test(profiles): add unit tests for scoring
|
||||
```
|
||||
|
||||
Types include: `feat`, `fix`, `docs`, `refactor`, `test`, `chore`.
|
||||
## Documentation
|
||||
|
||||
## Test Everything
|
||||
### Updating Documentation
|
||||
|
||||
If the project has tests, run them. If it doesn’t, write some. Do **not** submit code that hasn't been tested.
|
||||
- Update relevant README files
|
||||
- Add JSDoc comments to complex functions
|
||||
- Update the `/docs` folder for architectural changes
|
||||
|
||||
```bash
|
||||
# Example for Node.js
|
||||
npm test
|
||||
```
|
||||
### API Documentation
|
||||
|
||||
No exceptions. If you don't validate your changes, your PR will be closed.
|
||||
API docs are auto-generated and available at:
|
||||
|
||||
## Lint & Format
|
||||
- Production: https://api.compassmeet.com
|
||||
- Local: http://localhost:3001 (when running locally)
|
||||
|
||||
Ensure code matches the project style. If the repo uses a linter or formatter, run them:
|
||||
## Questions and Support
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
npm run format
|
||||
```
|
||||
- **Discord**: https://discord.gg/8Vd7jzqjun
|
||||
- **Email**: hello@compassmeet.com
|
||||
- **GitHub Issues**: For bug reports and feature requests
|
||||
|
||||
Or whatever command is defined in the repo.
|
||||
---
|
||||
|
||||
## Write a Good Pull Request
|
||||
|
||||
When opening a pull request:
|
||||
|
||||
- **Title**: Describe what the PR does, clearly and specifically.
|
||||
- **Description**: Explain the context. Link related issues (use `Fixes #123` if applicable).
|
||||
- **Checklist**:
|
||||
- [ ] My code is clean and follows the style guide
|
||||
- [ ] I’ve added or updated tests
|
||||
- [ ] I’ve run all tests and they pass
|
||||
- [ ] I’ve documented my changes (if necessary)
|
||||
|
||||
## Code Review Process
|
||||
|
||||
- PRs are reviewed by maintainers or core contributors.
|
||||
- If feedback is given, respond and push updates. Do **not** open new PRs for changes to an existing one.
|
||||
- PRs that are incomplete, sloppy, or violate the above will be closed.
|
||||
|
||||
## Don't Do This
|
||||
|
||||
- Don’t commit directly to `main`
|
||||
- Don’t submit multiple unrelated changes in a single PR
|
||||
- Don’t ignore CI/test failures
|
||||
- Don’t expect hand-holding—read the docs and the source first
|
||||
|
||||
## Security Issues
|
||||
|
||||
Do **not** open public issues for security vulnerabilities. Email the development team instead.
|
||||
|
||||
## License
|
||||
|
||||
By contributing, you agree that your code will be licensed under the same license as the rest of the project.
|
||||
Thank you for contributing to Compass! Together we're building a platform for meaningful connections.
|
||||
|
||||
@@ -1,11 +1,67 @@
|
||||
# Backend API
|
||||
|
||||
This is the code for the API running at https://api.compassmeet.com.
|
||||
It runs in a docker inside a Google Cloud virtual machine.
|
||||
Express.js REST API for Compass, running at https://api.compassmeet.com.
|
||||
|
||||
### Requirements
|
||||
## Overview
|
||||
|
||||
You must have the `gcloud` CLI.
|
||||
The API handles:
|
||||
|
||||
- User authentication and management
|
||||
- Profile CRUD operations
|
||||
- Search and filtering
|
||||
- Messaging
|
||||
- Notifications
|
||||
- Compatibility scoring
|
||||
- Events management
|
||||
- WebSocket connections for real-time features
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Runtime**: Node.js 20+
|
||||
- **Framework**: Express.js 5.0
|
||||
- **Language**: TypeScript
|
||||
- **Database**: PostgreSQL (via Supabase)
|
||||
- **ORM**: pg-promise
|
||||
- **Validation**: Zod
|
||||
- **WebSocket**: ws library
|
||||
- **API Docs**: Swagger/OpenAPI
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
backend/api/
|
||||
├── src/
|
||||
│ ├── app.ts # Express app setup
|
||||
│ ├── routes.ts # Route definitions
|
||||
│ ├── test.ts # Test utilities
|
||||
│ ├── get-*.ts # GET endpoints
|
||||
│ ├── create-*.ts # POST endpoints
|
||||
│ ├── update-*.ts # PUT/PATCH endpoints
|
||||
│ ├── delete-*.ts # DELETE endpoints
|
||||
│ └── helpers/ # Shared utilities
|
||||
├── tests/
|
||||
│ └── unit/ # Unit tests
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js 20.x or later
|
||||
- Yarn
|
||||
- Access to Supabase project (for database)
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# From root directory
|
||||
yarn install
|
||||
```
|
||||
|
||||
You must also have the `gcloud` CLI.
|
||||
|
||||
On macOS:
|
||||
|
||||
@@ -35,6 +91,356 @@ You also need `opentofu` and `docker`. Try running this (from root) on Linux or
|
||||
|
||||
If it doesn't work, you can install them manually (google how to install `opentofu` and `docker` for your OS).
|
||||
|
||||
### Running Locally
|
||||
|
||||
```bash
|
||||
# Run all services (web + API)
|
||||
yarn dev
|
||||
|
||||
# Run API only (from backend/api)
|
||||
cd backend/api
|
||||
yarn serve
|
||||
```
|
||||
|
||||
The API runs on http://localhost:3001 when running locally with the full stack.
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
# Run unit tests
|
||||
yarn test
|
||||
|
||||
# Run with coverage
|
||||
yarn test --coverage
|
||||
```
|
||||
|
||||
### Linting
|
||||
|
||||
```bash
|
||||
# Check lint
|
||||
yarn lint
|
||||
|
||||
# Fix issues
|
||||
yarn lint-fix
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Authentication
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
| ------ | ------------------- | --------------- |
|
||||
| POST | `/auth/google` | Google Sign-In |
|
||||
| POST | `/auth/create-user` | Create new user |
|
||||
|
||||
### Users
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
| ------ | ------------ | ------------------- |
|
||||
| GET | `/get-me` | Get current user |
|
||||
| PUT | `/update-me` | Update current user |
|
||||
| DELETE | `/delete-me` | Delete account |
|
||||
|
||||
### Profiles
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
| ------ | ----------------- | ------------------ |
|
||||
| GET | `/get-profiles` | List profiles |
|
||||
| GET | `/get-profile` | Get single profile |
|
||||
| POST | `/create-profile` | Create profile |
|
||||
| PUT | `/update-profile` | Update profile |
|
||||
| DELETE | `/delete-profile` | Delete profile |
|
||||
|
||||
### Messaging
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
| ------ | ------------------------------ | -------------- |
|
||||
| GET | `/get-private-messages` | Get messages |
|
||||
| POST | `/create-private-user-message` | Send message |
|
||||
| PUT | `/edit-message` | Edit message |
|
||||
| DELETE | `/delete-message` | Delete message |
|
||||
|
||||
### Notifications
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
| ------ | ----------------------- | ------------------ |
|
||||
| GET | `/get-notifications` | List notifications |
|
||||
| PUT | `/update-notif-setting` | Update settings |
|
||||
|
||||
### Search
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
| ------ | ------------------ | ------------------ |
|
||||
| GET | `/search-users` | Search users |
|
||||
| GET | `/search-location` | Search by location |
|
||||
|
||||
### Compatibility
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
| ------ | ------------------------------ | ----------------------- |
|
||||
| GET | `/get-compatibility-questions` | List questions |
|
||||
| POST | `/set-compatibility-answers` | Submit answers |
|
||||
| GET | `/compatible-profiles` | Get compatible profiles |
|
||||
|
||||
## Writing Endpoints
|
||||
|
||||
### 1. Define Schema
|
||||
|
||||
Add endpoint definition in `common/src/api/schema.ts`:
|
||||
|
||||
```typescript
|
||||
myEndpoint: {
|
||||
method: 'POST',
|
||||
authed
|
||||
:
|
||||
true,
|
||||
returns
|
||||
:
|
||||
z.object({
|
||||
success: z.boolean(),
|
||||
data: z.any()
|
||||
}),
|
||||
props
|
||||
:
|
||||
z.object({
|
||||
userId: z.string(),
|
||||
option: z.string().optional()
|
||||
}).strict()
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Implement Handler
|
||||
|
||||
Create handler file in `backend/api/src/`:
|
||||
|
||||
```typescript
|
||||
import {z} from 'zod'
|
||||
import {APIHandler} from './helpers/endpoint'
|
||||
|
||||
export const myEndpoint: APIHandler<'myEndpoint'> = async (props, auth) => {
|
||||
const {userId, option} = props
|
||||
|
||||
// Implementation
|
||||
return {
|
||||
success: true,
|
||||
data: {userId},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Register Route
|
||||
|
||||
Add to `routes.ts`:
|
||||
|
||||
```typescript
|
||||
import {myEndpoint} from './my-endpoint'
|
||||
|
||||
const handlers = {
|
||||
myEndpoint,
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
### Authenticated Endpoints
|
||||
|
||||
Use the `authed: true` schema property. The auth object is passed to the handler:
|
||||
|
||||
```typescript
|
||||
export const getProfile: APIHandler<'get-profile'> = async (props, auth) => {
|
||||
// auth.uid - user ID
|
||||
// auth.creds - credentials type
|
||||
}
|
||||
```
|
||||
|
||||
### Auth Types
|
||||
|
||||
- `firebase` - Firebase Auth token
|
||||
- `session` - Session-based auth
|
||||
|
||||
## Database Access
|
||||
|
||||
### Using pg-promise
|
||||
|
||||
```typescript
|
||||
import {createSupabaseDirectClient} from 'shared/supabase/init'
|
||||
|
||||
const pg = createSupabaseDirectClient()
|
||||
|
||||
const result = await pg.oneOrNone<User>('SELECT * FROM users WHERE id = $1', [userId])
|
||||
```
|
||||
|
||||
### Using Supabase Client
|
||||
|
||||
```typescript
|
||||
import {db} from 'web/lib/supabase/db'
|
||||
|
||||
const {data, error} = await db.from('profiles').select('*').eq('user_id', userId)
|
||||
```
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
The API includes built-in rate limiting:
|
||||
|
||||
```typescript
|
||||
export const myEndpoint: APIHandler<'myEndpoint'> = withRateLimit(
|
||||
async (props, auth) => {
|
||||
// Handler implementation
|
||||
},
|
||||
{
|
||||
name: 'my-endpoint',
|
||||
limit: 100,
|
||||
windowMs: 60 * 1000, // 1 minute
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
Use `APIError` for consistent error responses:
|
||||
|
||||
```typescript
|
||||
import {APIError} from './helpers/endpoint'
|
||||
|
||||
throw APIError(404, 'User not found')
|
||||
throw APIError(400, 'Invalid input', {field: 'email'})
|
||||
```
|
||||
|
||||
Error codes:
|
||||
|
||||
- `400` - Bad Request
|
||||
- `401` - Unauthorized
|
||||
- `403` - Forbidden
|
||||
- `404` - Not Found
|
||||
- `429` - Too Many Requests
|
||||
- `500` - Internal Server Error
|
||||
|
||||
## WebSocket
|
||||
|
||||
WebSocket connections are handled for real-time features:
|
||||
|
||||
```typescript
|
||||
// Subscribe to updates
|
||||
ws.subscribe('user/123', (data) => {
|
||||
console.log('User updated:', data)
|
||||
})
|
||||
|
||||
// Unsubscribe
|
||||
ws.unsubscribe('user/123', callback)
|
||||
```
|
||||
|
||||
Available topics:
|
||||
|
||||
- `user/{userId}` - User updates
|
||||
- `private-user/{userId}` - Private user updates
|
||||
- `message/{channelId}` - New messages
|
||||
|
||||
## Logging
|
||||
|
||||
Use the shared logger:
|
||||
|
||||
```typescript
|
||||
import {log} from 'shared/monitoring/log'
|
||||
|
||||
log.info('Processing request', {userId: auth.uid})
|
||||
log.error('Failed to process', error)
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
### Production Deployment
|
||||
|
||||
Deployments are automated via GitHub Actions. Push to main triggers deployment:
|
||||
|
||||
```bash
|
||||
# Increment version
|
||||
# Update package.json version
|
||||
git add package.json
|
||||
git commit -m "chore: bump version"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
### Manual Deployment
|
||||
|
||||
```bash
|
||||
cd backend/api
|
||||
./deploy-api.sh prod
|
||||
```
|
||||
|
||||
### Server Access
|
||||
|
||||
Run in this directory to connect to the API server running as virtual machine in Google Cloud. You can access logs,
|
||||
files, debug, etc.
|
||||
|
||||
```bash
|
||||
# SSH into production server
|
||||
cd backend/api
|
||||
./ssh-api.sh prod
|
||||
```
|
||||
|
||||
Useful commands on server:
|
||||
|
||||
```bash
|
||||
sudo journalctl -u konlet-startup --no-pager -ef # View logs
|
||||
sudo docker logs -f $(sudo docker ps -alq) # Container logs
|
||||
docker exec -it $(sudo docker ps -alq) sh # Shell access
|
||||
docker run -it --rm $(docker images -q | head -n 1) sh
|
||||
docker rmi -f $(docker images -aq)
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Required secrets (set in Google Cloud Secrets Manager):
|
||||
|
||||
| Variable | Description |
|
||||
| ---------------------- | ---------------------------- |
|
||||
| `DATABASE_URL` | PostgreSQL connection string |
|
||||
| `FIREBASE_PROJECT_ID` | Firebase project ID |
|
||||
| `FIREBASE_PRIVATE_KEY` | Firebase private key |
|
||||
| `SUPABASE_SERVICE_KEY` | Supabase service role key |
|
||||
| `JWT_SECRET` | JWT signing secret |
|
||||
|
||||
## Testing
|
||||
|
||||
### Writing Unit Tests
|
||||
|
||||
```typescript
|
||||
// tests/unit/my-endpoint.unit.test.ts
|
||||
import {myEndpoint} from '../my-endpoint'
|
||||
|
||||
describe('myEndpoint', () => {
|
||||
it('should return success', async () => {
|
||||
const result = await myEndpoint({userId: '123'}, mockAuth)
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Mocking Database
|
||||
|
||||
```typescript
|
||||
const mockPg = {
|
||||
oneOrNone: jest.fn().mockResolvedValue({id: '123'}),
|
||||
}
|
||||
```
|
||||
|
||||
## API Documentation
|
||||
|
||||
Full API docs available at:
|
||||
|
||||
- Production: https://api.compassmeet.com
|
||||
- Local: http://localhost:8088 (when running)
|
||||
|
||||
Docs are generated from route definitions in `app.ts`.
|
||||
|
||||
## See Also
|
||||
|
||||
- [Main README](../../README.md)
|
||||
- [Contributing Guide](../../CONTRIBUTING.md)
|
||||
- [Shared Backend Utils](../shared/README.md)
|
||||
- [Database Migrations](../../supabase/migrations)
|
||||
|
||||
### Setup
|
||||
|
||||
This section is only for the people who are creating a server from scratch, for instance for a forked project.
|
||||
@@ -155,48 +561,3 @@ in [Google Cloud Secrets manager](https://console.cloud.google.com/security/secr
|
||||
can access them.
|
||||
|
||||
For Compass, the name of the secrets are in [secrets.ts](../../common/src/secrets.ts).
|
||||
|
||||
### Run Locally
|
||||
|
||||
In root directory, run the local api with hot reload, along with all the other backend and web code.
|
||||
|
||||
```bash
|
||||
./run_local.sh prod
|
||||
```
|
||||
|
||||
### Deploy
|
||||
|
||||
To deploy the backend code, simply increment the version number in [package.json](package.json) and push to the `main` branch.
|
||||
|
||||
Or if you have access to the project on google cloud, run in this directory:
|
||||
|
||||
```bash
|
||||
./deploy-api.sh prod
|
||||
```
|
||||
|
||||
### Connect to the server
|
||||
|
||||
Run in this directory to connect to the API server running as virtual machine in Google Cloud. You can access logs,
|
||||
files, debug, etc.
|
||||
|
||||
```bash
|
||||
./ssh-api.sh prod
|
||||
```
|
||||
|
||||
Useful commands once inside the server:
|
||||
|
||||
```bash
|
||||
sudo journalctl -u konlet-startup --no-pager -efb
|
||||
sudo docker logs -f $(sudo docker ps -alq)
|
||||
docker exec -it $(sudo docker ps -alq) sh
|
||||
docker run -it --rm $(docker images -q | head -n 1) sh
|
||||
docker rmi -f $(docker images -aq)
|
||||
```
|
||||
|
||||
### Documentation
|
||||
|
||||
The API doc is available at https://api.compassmeet.com. It's dynamically prepared in [app.ts](src/app.ts).
|
||||
|
||||
### Todo (Tests)
|
||||
|
||||
- [ ] Finish get-supabase-token unit test when endpoint is implemented
|
||||
|
||||
@@ -17,7 +17,7 @@ What testing is not
|
||||
|
||||
How we apply it here
|
||||
|
||||
- Unit and integration tests live in each package and run with Jest (see `jest.config.js`).
|
||||
- Unit and integration tests live in each package and run with Jest (see `jest.config.ts`).
|
||||
- Critical user journeys are covered by Playwright E2E tests under `tests/e2e` (see `playwright.config.ts`).
|
||||
|
||||
### Test types at a glance
|
||||
@@ -25,18 +25,20 @@ How we apply it here
|
||||
This project uses three complementary test types. Use the right level for the job:
|
||||
|
||||
- Unit tests
|
||||
|
||||
- Purpose: Verify a single function/module in isolation; fast, deterministic.
|
||||
- Where: Each package under `tests/unit` (e.g., `backend/api/tests/unit`, `web/tests/unit`, `common/tests/unit`,
|
||||
etc.).
|
||||
- Runner: Jest (configured via root `jest.config.js`).
|
||||
- Runner: Jest (configured via root `jest.config.ts`).
|
||||
- Naming: `*.unit.test.ts` (or `.tsx` for React in `web`).
|
||||
- When to use: Pure logic, utilities, hooks, reducers, small components with mocked dependencies.
|
||||
|
||||
- Integration tests
|
||||
|
||||
- Purpose: Verify multiple units working together (e.g., function + DB/client, component + context/provider) without
|
||||
spinning up the full app.
|
||||
- Where: Each package under `tests/integration` (e.g., `backend/shared/tests/integration`, `web/tests/integration`).
|
||||
- Runner: Jest (configured via root `jest.config.js`).
|
||||
- Runner: Jest (configured via root `jest.config.ts`).
|
||||
- Naming: `*.integration.test.ts` (or `.tsx` for React in `web`).
|
||||
- When to use: Boundaries between modules, real serialization/parsing, API handlers with mocked network/DB,
|
||||
component trees with providers.
|
||||
@@ -62,7 +64,7 @@ yarn test:e2e
|
||||
|
||||
```filetree
|
||||
# Config
|
||||
jest.config.js (for unit and integration tests)
|
||||
jest.config.ts (for unit and integration tests)
|
||||
playwright.config.ts (for e2e tests)
|
||||
|
||||
# Top-level End-to-End (Playwright)
|
||||
@@ -114,7 +116,7 @@ web/
|
||||
- End-to-End tests live under `tests/e2e` and are executed by Playwright. The root `playwright.config.ts` sets `testDir`
|
||||
to `./tests/e2e`.
|
||||
- Unit and integration tests live in each package’s `tests` folder and are executed by Jest via the root
|
||||
`jest.config.js` projects array.
|
||||
`jest.config.ts` projects array.
|
||||
- Naming:
|
||||
- Unit: `*.unit.test.ts` (or `.tsx` for React in `web`)
|
||||
- Integration: `*.integration.test.ts`
|
||||
@@ -348,7 +350,6 @@ jest.mock('path/to/module')
|
||||
* This creates an object containing all named exports from ./path/to/module
|
||||
*/
|
||||
import * as mockModule from 'path/to/module'
|
||||
|
||||
;(mockModule.module as jest.Mock).mockResolvedValue(mockReturnValue)
|
||||
```
|
||||
|
||||
@@ -756,7 +757,7 @@ Firebase emulator supports multiple projects. You can spin up isolated project I
|
||||
too slow):
|
||||
|
||||
```js
|
||||
// jest.config.js - per worker
|
||||
// jest.config.ts - per worker
|
||||
projectId: `test-${process.env.JEST_WORKER_ID}`
|
||||
```
|
||||
|
||||
|
||||
385
docs/architecture.md
Normal file
385
docs/architecture.md
Normal file
@@ -0,0 +1,385 @@
|
||||
# Architecture Documentation
|
||||
|
||||
> [!WARNING]
|
||||
> This document is a work in progress. Please help us improve it!
|
||||
|
||||
## System Overview
|
||||
|
||||
Compass is a monorepo containing a Next.js web application, Express API server, Capacitor Android app, and shared packages. The platform is designed for forming deep, authentic 1-on-1 connections.
|
||||
|
||||
## High-Level Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Users │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Vercel (Frontend) │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
|
||||
│ │ Next.js │ │ Static │ │ API Routes │ │
|
||||
│ │ Web App │ │ Assets │ │ (Serverless) │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ HTTPS
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Google Cloud Platform (Backend) │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
|
||||
│ │ Express │ │ WebSocket │ │ Docker Container │ │
|
||||
│ │ API │ │ Server │ │ │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ Connection
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Supabase (PostgreSQL) │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
|
||||
│ │ Database │ │ Edge │ │ Realtime │ │
|
||||
│ │ (Postgres)│ │ Functions │ │ Subscriptions │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Firebase │
|
||||
│ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ Auth │ │ Storage │ │
|
||||
│ └─────────────┘ └─────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Monorepo Structure
|
||||
|
||||
```
|
||||
Compass/
|
||||
├── web/ # Next.js web application
|
||||
│ ├── components/ # React components
|
||||
│ ├── hooks/ # Custom React hooks
|
||||
│ ├── lib/ # Utilities & services
|
||||
│ ├── pages/ # Next.js pages
|
||||
│ ├── messages/ # i18n translations
|
||||
│ └── tests/ # Unit & integration tests
|
||||
│
|
||||
├── backend/
|
||||
│ ├── api/ # Express REST API
|
||||
│ │ ├── src/ # Handler implementations
|
||||
│ │ └── tests/ # Unit tests
|
||||
│ ├── shared/ # Shared backend utilities
|
||||
│ │ ├── src/
|
||||
│ │ │ ├── supabase/ # Database utilities
|
||||
│ │ │ ├── monitoring/ # Logging
|
||||
│ │ │ └── mobile/ # Push notifications
|
||||
│ │ └── tests/
|
||||
│ ├── email/ # React email templates
|
||||
│ └── scripts/ # Database migrations
|
||||
│
|
||||
├── common/ # Shared types & utilities
|
||||
│ └── src/
|
||||
│ ├── types/ # TypeScript definitions
|
||||
│ ├── api/ # API schema definitions
|
||||
│ └── constants/ # App constants
|
||||
│
|
||||
├── supabase/ # Database migrations
|
||||
│ └── migrations/
|
||||
│
|
||||
├── android/ # Capacitor Android app
|
||||
│
|
||||
└── tests/ # E2E tests (Playwright)
|
||||
└── e2e/
|
||||
```
|
||||
|
||||
## Technology Stack
|
||||
|
||||
### Frontend (web)
|
||||
|
||||
| Category | Technology | Version |
|
||||
| ---------- | --------------------- | ------- |
|
||||
| Framework | Next.js | 14.1.0 |
|
||||
| UI Library | React | 18.2.0 |
|
||||
| Language | TypeScript | 5.5.4 |
|
||||
| Styling | Tailwind CSS | 3.3.3 |
|
||||
| State | React Context + Hooks | - |
|
||||
| Forms | React Hook Form | 7.65.0 |
|
||||
| Rich Text | TipTap | 2.10.4 |
|
||||
| i18n | Custom JSON | - |
|
||||
| Testing | Jest | 29.3.1 |
|
||||
| E2E | Playwright | 1.58.2 |
|
||||
|
||||
### Backend (api)
|
||||
|
||||
| Category | Technology | Version |
|
||||
| ---------- | ---------- | ------- |
|
||||
| Runtime | Node.js | 20+ |
|
||||
| Framework | Express | 5.0.0 |
|
||||
| Language | TypeScript | 5.5.4 |
|
||||
| Database | PostgreSQL | - |
|
||||
| ORM | pg-promise | - |
|
||||
| Validation | Zod | - |
|
||||
| WebSocket | ws | - |
|
||||
| Testing | Jest | 29.3.1 |
|
||||
|
||||
### Infrastructure
|
||||
|
||||
| Service | Purpose |
|
||||
| ---------------- | ------------------- |
|
||||
| Vercel | Frontend hosting |
|
||||
| Google Cloud | Backend hosting |
|
||||
| Supabase | PostgreSQL database |
|
||||
| Firebase Auth | User authentication |
|
||||
| Firebase Storage | Media storage |
|
||||
| PostHog | Analytics |
|
||||
|
||||
## Data Flow
|
||||
|
||||
### User Request Flow
|
||||
|
||||
```
|
||||
1. User clicks button
|
||||
│
|
||||
▼
|
||||
2. React component handles event
|
||||
│
|
||||
▼
|
||||
3. useAPIGetter/useMutation hook called
|
||||
│
|
||||
▼
|
||||
4. API client sends HTTP request
|
||||
│
|
||||
▼
|
||||
5. Express API receives request
|
||||
│
|
||||
▼
|
||||
6. Auth middleware validates token
|
||||
│
|
||||
▼
|
||||
7. Handler processes request
|
||||
│
|
||||
▼
|
||||
8. Database query executed
|
||||
│
|
||||
▼
|
||||
9. Response returned to client
|
||||
│
|
||||
▼
|
||||
10. React state updated, UI re-renders
|
||||
```
|
||||
|
||||
### Authentication Flow
|
||||
|
||||
```
|
||||
User Sign-In:
|
||||
┌──────────┐ ┌──────────┐ ┌──────────────┐ ┌────────────┐
|
||||
│ Client │───▶│ Firebase │───▶│ Auth Token │───▶│ Backend │
|
||||
└──────────┘ └──────────┘ │ (JWT) │ │ Validates │
|
||||
└──────────────┘ └────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────┐
|
||||
│ Session │
|
||||
│ Created │
|
||||
└────────────┘
|
||||
```
|
||||
|
||||
## Database Schema
|
||||
|
||||
### Key Tables
|
||||
|
||||
```sql
|
||||
-- Users table
|
||||
users (
|
||||
id UUID PRIMARY KEY,
|
||||
username TEXT UNIQUE,
|
||||
email TEXT,
|
||||
created_at TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
)
|
||||
-- Profile information
|
||||
profiles (
|
||||
id UUID PRIMARY KEY,
|
||||
user_id UUID REFERENCES users,
|
||||
name TEXT,
|
||||
age INTEGER,
|
||||
bio TEXT,
|
||||
-- many more fields
|
||||
)
|
||||
-- Private user data
|
||||
private_users (
|
||||
id UUID PRIMARY KEY,
|
||||
user_id UUID REFERENCES users,
|
||||
email TEXT,
|
||||
notification_settings JSONB
|
||||
)
|
||||
-- Messages
|
||||
private_user_messages (
|
||||
id UUID PRIMARY KEY,
|
||||
from_user_id UUID,
|
||||
to_user_id UUID,
|
||||
content TEXT,
|
||||
created_at TIMESTAMP
|
||||
)
|
||||
```
|
||||
|
||||
See `supabase/migrations/` for full schema.
|
||||
|
||||
## API Design
|
||||
|
||||
### REST Principles
|
||||
|
||||
- Resource-based URLs: `/get-profiles`, `/create-profile`
|
||||
- HTTP methods: GET (read), POST (create), PUT (update), DELETE (delete)
|
||||
- JSON request/response format
|
||||
- Authentication via Bearer tokens
|
||||
|
||||
### Endpoint Structure
|
||||
|
||||
```
|
||||
/{action} POST - Perform action
|
||||
```
|
||||
|
||||
### Request Format
|
||||
|
||||
```typescript
|
||||
{
|
||||
// Endpoint-specific parameters
|
||||
}
|
||||
```
|
||||
|
||||
### Response Format
|
||||
|
||||
```typescript
|
||||
// Success
|
||||
{
|
||||
// Response data
|
||||
}
|
||||
|
||||
// Error
|
||||
{
|
||||
error: {
|
||||
status: 400,
|
||||
message: "Error description"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## State Management
|
||||
|
||||
### Frontend State
|
||||
|
||||
1. **Server State**: React Query-style hooks (`useAPIGetter`, `useMutation`)
|
||||
2. **Client State**: React `useState`, `useReducer`
|
||||
3. **Persisted State**: `usePersistentLocalState`, `usePersistentInMemoryState`
|
||||
4. **Global State**: React Context (`AuthProvider`, `I18nProvider`)
|
||||
|
||||
### State Persistence
|
||||
|
||||
```typescript
|
||||
// In-memory (lost on refresh)
|
||||
const [state, setState] = useState(initialValue)
|
||||
|
||||
// Local storage (persists)
|
||||
const [state, setState] = usePersistentLocalState(initialValue, 'key')
|
||||
|
||||
// Session storage (persists until tab closed)
|
||||
const [state, setState] = usePersistentInMemoryState(initialValue, 'key')
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
### Authentication
|
||||
|
||||
- Firebase Auth for user authentication
|
||||
- JWT tokens for API requests
|
||||
- Session-based auth for web
|
||||
|
||||
### Authorization
|
||||
|
||||
- Role-based access (user, moderator, admin)
|
||||
- User ID verification on protected endpoints
|
||||
|
||||
### Data Protection
|
||||
|
||||
- Row-level security in PostgreSQL
|
||||
- Environment-based secrets
|
||||
- Input validation with Zod
|
||||
|
||||
## Performance Optimizations
|
||||
|
||||
### Frontend
|
||||
|
||||
- Next.js static generation for public pages
|
||||
- Image optimization with `next/image`
|
||||
- Code splitting per route
|
||||
- Memoization with `useMemo`, `useCallback`
|
||||
- Virtualized lists for large datasets
|
||||
|
||||
### Backend
|
||||
|
||||
- Database connection pooling
|
||||
- Query optimization (indexes)
|
||||
- Caching strategies
|
||||
- Rate limiting
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Logging
|
||||
|
||||
- Structured logging with context
|
||||
- Different log levels: debug, info, warn, error
|
||||
- Environment-aware (development vs production)
|
||||
|
||||
### Error Tracking
|
||||
|
||||
- Error boundaries in React
|
||||
- API error handling
|
||||
- Server-side error logging
|
||||
|
||||
### Analytics
|
||||
|
||||
- PostHog for user analytics
|
||||
- Custom event tracking
|
||||
|
||||
## Deployment
|
||||
|
||||
### CI/CD Pipeline
|
||||
|
||||
```
|
||||
GitHub Push
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ CI Tests │ ──▶ Lint, TypeCheck, Unit Tests
|
||||
└─────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ CD Deploy │ ──▶ Vercel (Web), GCP (API)
|
||||
└─────────────┘
|
||||
```
|
||||
|
||||
### Environments
|
||||
|
||||
| Environment | URL | Purpose |
|
||||
| ----------- | --------------- | ----------------- |
|
||||
| Development | localhost | Local development |
|
||||
| Staging | - | Testing changes |
|
||||
| Production | compassmeet.com | Live users |
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. **Create branch**: `feat/description`
|
||||
2. **Make changes**: Implement feature/fix
|
||||
3. **Test locally**: `yarn dev`
|
||||
4. **Run tests**: `yarn test`
|
||||
5. **Submit PR**: Code review
|
||||
6. **Merge**: Automated deployment
|
||||
|
||||
## See Also
|
||||
|
||||
- [Knowledge Base](knowledge.md)
|
||||
- [Development Guide](development.md)
|
||||
- [Testing Guide](TESTING.md)
|
||||
- [Next.js Documentation](Next.js.md)
|
||||
356
web/README.md
356
web/README.md
@@ -1,12 +1,353 @@
|
||||
# Web
|
||||
# Web Application
|
||||
|
||||
This is the folder for the web application.
|
||||
The Compass web application built with Next.js, React, and TypeScript.
|
||||
|
||||
### Information
|
||||
## Overview
|
||||
|
||||
##### Setup
|
||||
This is the frontend of the Compass platform, a transparent platform for forming deep, authentic 1-on-1 connections.
|
||||
|
||||
This is the setup for deployment on Vercel, which you only need to do if you create a new platform from scratch, not if you are contributing to Compass
|
||||
## Tech Stack
|
||||
|
||||
- **Framework**: Next.js 14.1.0
|
||||
- **Language**: TypeScript
|
||||
- **UI Library**: React 18.2.0
|
||||
- **Styling**: Tailwind CSS 3.3.3
|
||||
- **State Management**: React Context + Custom Hooks
|
||||
- **Forms**: React Hook Form
|
||||
- **Rich Text**: TipTap (ProseMirror)
|
||||
- **Charts**: Recharts
|
||||
- **i18n**: Custom solution with JSON message files
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
web/
|
||||
├── components/ # React components
|
||||
│ ├── auth-context.tsx # Authentication state
|
||||
│ ├── buttons/ # Button components
|
||||
│ ├── chat/ # Chat/messaging components
|
||||
│ ├── comments/ # Comment components
|
||||
│ ├── editor/ # Rich text editor
|
||||
│ ├── events/ # Event components
|
||||
│ ├── filters/ # Search filters
|
||||
│ ├── matches/ # Match components
|
||||
│ ├── nav/ # Navigation components
|
||||
│ ├── profile/ # Profile components
|
||||
│ └── widgets/ # Reusable widgets
|
||||
├── hooks/ # Custom React hooks (50+)
|
||||
├── lib/ # Utilities and services
|
||||
│ ├── api.ts # API client
|
||||
│ ├── firebase/ # Firebase configuration
|
||||
│ ├── locale/ # Internationalization
|
||||
│ ├── service/ # Analytics, push notifications
|
||||
│ ├── supabase/ # Supabase client
|
||||
│ └── logger.ts # Structured logging
|
||||
├── pages/ # Next.js pages
|
||||
│ ├── api/ # API routes
|
||||
│ ├── _app.tsx # App wrapper
|
||||
│ ├── _document.tsx # Document setup
|
||||
│ └── [username].tsx # Dynamic routes
|
||||
├── messages/ # Translation JSON files
|
||||
├── public/ # Static assets
|
||||
├── styles/ # Global CSS
|
||||
├── tests/
|
||||
│ ├── unit/ # Unit tests
|
||||
│ └── integration/ # Integration tests
|
||||
└── types/ # TypeScript type definitions
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js 20.x or later
|
||||
- Yarn 1.x
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# From root directory
|
||||
yarn install
|
||||
```
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
# Run web app with hot reload
|
||||
yarn dev
|
||||
|
||||
# Or from web directory
|
||||
cd web
|
||||
yarn serve
|
||||
```
|
||||
|
||||
Visit http://localhost:3000
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
# Production build
|
||||
yarn build
|
||||
|
||||
# Start production server
|
||||
yarn start
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
# Run tests
|
||||
yarn test
|
||||
|
||||
# Run with coverage
|
||||
yarn test --coverage
|
||||
|
||||
# Watch mode
|
||||
yarn test --watch
|
||||
```
|
||||
|
||||
### Linting
|
||||
|
||||
```bash
|
||||
# Check lint
|
||||
yarn lint
|
||||
|
||||
# Fix lint issues
|
||||
yarn lint-fix
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Components
|
||||
|
||||
Components are organized by feature in `/components`. Reusable widgets are in `/components/widgets`.
|
||||
|
||||
Example component:
|
||||
|
||||
```tsx
|
||||
// components/profile/profile-card.tsx
|
||||
import {User} from 'common/src/user'
|
||||
|
||||
interface ProfileCardProps {
|
||||
user: User
|
||||
onLike?: (userId: string) => void
|
||||
}
|
||||
|
||||
export function ProfileCard({user, onLike}: ProfileCardProps) {
|
||||
return (
|
||||
<div className="profile-card">
|
||||
<img src={user.avatarUrl} alt={user.name} />
|
||||
<h3>{user.name}</h3>
|
||||
<button onClick={() => onLike?.(user.id)}>Like</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Hooks
|
||||
|
||||
Use custom hooks for stateful logic. Common hooks:
|
||||
|
||||
- `useUser()` - Get current user
|
||||
- `useAPIGetter()` - Fetch API data with caching
|
||||
- `useMutation()` - Handle form submissions
|
||||
- `usePersistentInMemoryState()` - Cache state across pages
|
||||
|
||||
```tsx
|
||||
import {useAPIGetter} from 'web/hooks/use-api-getter'
|
||||
|
||||
function ProfileList() {
|
||||
const {data, refresh} = useAPIGetter('get-profiles', {})
|
||||
|
||||
if (!data) return <Loading />
|
||||
|
||||
return (
|
||||
<div>
|
||||
{data.profiles.map((profile) => (
|
||||
<ProfileCard key={profile.id} user={profile} />
|
||||
))}
|
||||
<button onClick={refresh}>Refresh</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### API Calls
|
||||
|
||||
Backend API is called through the `api` helper:
|
||||
|
||||
```tsx
|
||||
import {api} from 'web/lib/api'
|
||||
|
||||
// Server-side (getStaticProps, getServerSideProps)
|
||||
const profiles = await api('get-profiles', {})
|
||||
|
||||
// Client-side - use hooks
|
||||
const {data} = useAPIGetter('get-profiles', {})
|
||||
```
|
||||
|
||||
### Internationalization
|
||||
|
||||
Translation files are in `/messages` (e.g., `en.json`, `fr.json`).
|
||||
|
||||
```tsx
|
||||
import {useT} from 'web/lib/locale'
|
||||
|
||||
function MyComponent() {
|
||||
const t = useT()
|
||||
|
||||
return <h1>{t('welcome', 'Welcome to Compass')}</h1>
|
||||
}
|
||||
```
|
||||
|
||||
### Styling
|
||||
|
||||
Tailwind CSS is used for styling. Use utility classes:
|
||||
|
||||
```tsx
|
||||
<div className="flex items-center justify-between p-4 bg-canvas-50 rounded-lg">
|
||||
<span className="text-ink-900 font-medium">Content</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
The app includes several accessibility features:
|
||||
|
||||
### Error Boundary
|
||||
|
||||
Catches React errors and shows user-friendly message:
|
||||
|
||||
```tsx
|
||||
import {ErrorBoundary} from 'web/components/error-boundary'
|
||||
|
||||
;<ErrorBoundary>
|
||||
<MyComponent />
|
||||
</ErrorBoundary>
|
||||
```
|
||||
|
||||
### Live Region
|
||||
|
||||
Announces dynamic content changes to screen readers:
|
||||
|
||||
```tsx
|
||||
import {useLiveRegion} from 'web/components/live-region'
|
||||
|
||||
const {announce} = useLiveRegion()
|
||||
|
||||
// Announce status changes
|
||||
announce('Profile liked', 'polite')
|
||||
```
|
||||
|
||||
Priority levels:
|
||||
|
||||
- `polite` - Waits for screen reader to finish (default)
|
||||
- `assertive` - Interrupts immediately
|
||||
|
||||
### Skip Links
|
||||
|
||||
Keyboard users can skip to main content:
|
||||
|
||||
```tsx
|
||||
import {SkipLink, MainContent} from 'web/components/skip-link'
|
||||
|
||||
;<>
|
||||
<SkipLink />
|
||||
<MainContent>...</MainContent>
|
||||
</>
|
||||
```
|
||||
|
||||
## Logging
|
||||
|
||||
Use the structured logger for debug logging that's filtered out in production:
|
||||
|
||||
```tsx
|
||||
import {debug, logApiError} from 'common/logger'
|
||||
|
||||
// Simple logging
|
||||
debug('User logged in', {userId: '123'})
|
||||
|
||||
// API errors with context
|
||||
try {
|
||||
await api('endpoint', {})
|
||||
} catch (err) {
|
||||
logApiError('get-profiles', err, {userId: '123'})
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Key environment variables for the web app:
|
||||
|
||||
| Variable | Description |
|
||||
| ------------------------------- | --------------------- |
|
||||
| `NEXT_PUBLIC_SUPABASE_URL` | Supabase project URL |
|
||||
| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Supabase anon key |
|
||||
| `NEXT_PUBLIC_FIREBASE_API_KEY` | Firebase API key |
|
||||
| `NEXT_PUBLIC_POSTHOG_KEY` | PostHog analytics key |
|
||||
| `NEXT_PUBLIC_VERCEL_ENV` | Vercel environment |
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Adding a New Page
|
||||
|
||||
1. Create file in `/pages/`:
|
||||
|
||||
```tsx
|
||||
// pages/new-page.tsx
|
||||
import {Page} from 'web/components/page-base'
|
||||
|
||||
export default function NewPage() {
|
||||
return (
|
||||
<Page>
|
||||
<h1>New Page</h1>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Adding a Component
|
||||
|
||||
1. Create file in appropriate `/components` subdirectory
|
||||
2. Export the component
|
||||
3. Add to parent component
|
||||
|
||||
### Adding a Hook
|
||||
|
||||
1. Create file in `/hooks/`
|
||||
2. Follow naming convention:\*.ts`
|
||||
|
||||
### `use- Adding Translations
|
||||
|
||||
1. Add key to `/messages/en.json`
|
||||
2. Add translations to other locale files
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Slow local development
|
||||
|
||||
Running `yarn dev:isolated` uses local emulators and is faster.
|
||||
|
||||
### Type errors
|
||||
|
||||
Run `yarn typecheck` to see all type errors.
|
||||
|
||||
### Build failures
|
||||
|
||||
Check `yarn lint` first, as linting issues can cause build failures.
|
||||
|
||||
## See Also
|
||||
|
||||
- [Main README](../README.md)
|
||||
- [Contributing Guide](../CONTRIBUTING.md)
|
||||
- [Development Docs](../docs/development.md)
|
||||
- [Testing Guide](../docs/TESTING.md)
|
||||
- [Architecture Docs](../docs/knowledge.md)
|
||||
|
||||
## Setup
|
||||
|
||||
This is the setup for deployment on Vercel, which you only need to do if you create a new platform from scratch, not if
|
||||
you are contributing to Compass
|
||||
|
||||
Set up a Vercel account and link it to your GitHub repository.
|
||||
|
||||
@@ -16,9 +357,10 @@ Add the following environment variables and the ones in `.env` in the Vercel das
|
||||
NEXT_PUBLIC_VERCEL=1
|
||||
```
|
||||
|
||||
##### `next` version
|
||||
## `next` version
|
||||
|
||||
The `next` version is 14.1.0, as we get the following error with 15.1.2 and above when accessing `/[username]` pages on Vercel:
|
||||
The `next` version is 14.1.0, as we get the following error with 15.1.2 and above when accessing `/[username]` pages on
|
||||
Vercel:
|
||||
|
||||
```
|
||||
Cannot find module 'next/dist/compiled/source-map'
|
||||
|
||||
Reference in New Issue
Block a user