Thank you for your interest in contributing to this photo sharing application! This guide will help you get started.
- Code of Conduct
- Getting Started
- Development Setup
- Project Structure
- Development Workflow
- Coding Standards
- Testing
- Submitting Changes
- Feature Requests
- Bug Reports
- Documentation
- Community
We are committed to providing a welcoming and inclusive environment for all contributors, regardless of background or experience level.
- Be respectful and considerate
- Welcome newcomers and help them get started
- Provide constructive feedback
- Focus on what is best for the project
- Show empathy towards other contributors
- Harassment or discrimination of any kind
- Trolling or insulting comments
- Personal or political attacks
- Publishing others' private information
- Other conduct which could reasonably be considered inappropriate
If you experience or witness unacceptable behavior, please report it to the project maintainers.
Before contributing, ensure you have:
- Node.js 18+ installed
- npm or yarn package manager
- Git for version control
- Cloudflare account (free tier is fine)
- Wrangler CLI installed globally
- Basic knowledge of TypeScript and React
- Familiarity with git workflows
If you're new to open source, start with:
- Issues labeled
good first issue - Documentation improvements
- Test coverage improvements
- Minor bug fixes
Don't be afraid to ask questions in discussions or issue comments!
# Fork the repository on GitHub, then clone your fork
git clone https://github.com/YOUR_USERNAME/photo-sharing-app.git
cd photo-sharing-app# Worker dependencies
cd apps/worker
npm install
# Web dependencies
cd ../web
npm installCreate .dev.vars in apps/worker/:
# Required for local development
ADMIN_EMAILS=your-email@example.com
JWT_SECRET=local-dev-secret-change-in-production
EVENT_COOKIE_SECRET=another-local-secret
# Optional for testing collaboration features
MAILGUN_API_KEY=your-mailgun-key
MAILGUN_DOMAIN=your-mailgun-domain
# Branding (optional)
APP_NAME=Photos Local Dev
BRAND_NAME=Your Name
COPYRIGHT_HOLDER=Your Name
APP_DOMAIN=localhost:5173
CONTACT_EMAIL=your-email@example.comCreate .env.local in apps/web/:
VITE_API_URL=http://localhost:8787
VITE_APP_NAME=Photos Local Dev
VITE_BRAND_NAME=Your Name
VITE_COPYRIGHT_HOLDER=Your Namecd apps/worker
# Create local D1 database
wrangler d1 create photos-db-local
# Update wrangler.toml with local database_id
# Run migrations (plain numbered SQL files, not Wrangler's migrations-tracking system)
for file in ../../migrations/*.sql; do wrangler d1 execute photos-db-local --local --file="$file"; done# Create development R2 bucket (or use local simulator)
wrangler r2 bucket create photos-dev
# Update wrangler.toml with dev bucket name# Terminal 1: Worker (backend)
cd apps/worker
npm run dev
# Runs on http://localhost:8787
# Terminal 2: Web (frontend)
cd apps/web
npm run dev
# Runs on http://localhost:5173- Visit http://localhost:5173
- Check browser console for errors
- Try viewing the gallery (should be empty)
- Test admin access with your ADMIN_EMAILS
.
├── apps/
│ ├── worker/ # Backend (Cloudflare Worker)
│ │ ├── src/
│ │ │ ├── routes/ # API endpoints
│ │ │ │ └── admin/ # Admin-only routes (modular)
│ │ │ ├── config.ts # Configuration system
│ │ │ ├── features.ts # Feature flag system
│ │ │ ├── auth.ts # Authentication middleware
│ │ │ └── index.ts # Worker entry point
│ │ ├── wrangler.toml # Worker configuration
│ │ └── package.json
│ │
│ └── web/ # Frontend (React SPA)
│ ├── src/
│ │ ├── components/ # Reusable UI components
│ │ ├── contexts/ # React contexts
│ │ ├── hooks/ # Custom hooks
│ │ ├── pages/ # Route components
│ │ ├── services/ # Business logic
│ │ ├── utils/ # Utilities
│ │ └── config.ts # Runtime config
│ ├── android/ # Capacitor Android
│ └── package.json
│
├── migrations/ # D1 database migrations
├── docs/ # Documentation
└── scripts/ # Utility scripts
Use descriptive branch names:
# Features
git checkout -b feature/add-video-transcoding
git checkout -b feature/bulk-download
# Bug fixes
git checkout -b fix/safari-upload-issue
git checkout -b fix/geocoding-timeout
# Documentation
git checkout -b docs/update-api-docs
git checkout -b docs/add-deployment-guide
# Refactoring
git checkout -b refactor/split-admin-routes
git checkout -b refactor/extract-upload-logicFollow Conventional Commits:
<type>(<scope>): <description>
[optional body]
[optional footer]
Types:
feat: New featurefix: Bug fixdocs: Documentation changesstyle: Code style changes (formatting)refactor: Code refactoringtest: Adding or updating testschore: Maintenance tasks
Examples:
feat(worker): add video transcoding support
Implements video transcoding using Cloudflare Stream API.
Includes thumbnail generation and adaptive bitrate streaming.
Closes #123
---
fix(web): resolve Safari upload issue
Safari was not properly handling multipart uploads due to
missing Content-Type header. Added explicit header.
Fixes #456
---
docs(readme): update installation instructions
Added troubleshooting section for common D1 migration errors.
---
refactor(admin): extract event routes to separate module
Split admin.ts from 1058 lines to modular structure.
Created admin/events.ts, admin/photos.ts, etc.
Part of #789-
Create Branch
git checkout -b feature/your-feature
-
Make Changes
- Write code following style guide
- Add tests for new functionality
- Update documentation as needed
-
Test Locally
# Run tests cd apps/web && npm test cd apps/worker && npm test # Check types (there's no dedicated type-check script; build runs tsc) cd apps/web && npx tsc --noEmit cd apps/worker && npx tsc --noEmit # Lint code (apps/web only - there's no root or worker lint script) cd apps/web && npm run lint
-
Commit Changes
git add . git commit -m "feat: add new feature"
-
Keep Branch Updated
git fetch origin git rebase origin/master
-
Push to Fork
git push origin feature/your-feature
-
Open Pull Request
- Go to GitHub and create PR
- Fill out PR template
- Link related issues
- Request review
-
Address Feedback
- Make requested changes
- Push updates to same branch
- Respond to comments
-
Merge
- Maintainer will merge when approved
- Delete branch after merge
Use Explicit Types:
// Good
function getEvent(slug: string): Event | null {
// ...
}
// Avoid
function getEvent(slug) {
// ...
}Prefer Interfaces for Objects:
// Good
interface Event {
id: number;
slug: string;
title: string;
}
// Avoid any
const event: any = { ... };Use Enums Sparingly:
// Prefer literal types
type MediaType = 'photo' | 'video';
// Over enums
enum MediaType {
Photo = 'photo',
Video = 'video'
}Use Functional Components:
// Good
const PhotoCard: React.FC<PhotoCardProps> = ({ photo }) => {
return <div>...</div>;
};
// Avoid class components
class PhotoCard extends React.Component {
// ...
}Custom Hooks for Logic:
// Extract reusable logic into hooks
function usePhotoSelection(photos: Photo[]) {
const [selected, setSelected] = useState<Set<number>>(new Set());
// ... logic
return { selected, toggleSelection, clearSelection };
}Keep Components Small:
// Split large components into smaller ones
// AdminEventUpload.tsx (600 lines)
// ↓
// AdminEventUpload.tsx (400 lines)
// + EventLocationPicker.tsx (120 lines)
// + UploadQueueList.tsx (115 lines)Modular Route Organization:
// Good: Separate concerns
// routes/admin/events.ts
const app = new Hono<{ Bindings: Env }>();
app.get('/', listEvents);
app.post('/', createEvent);
export default app;
// Avoid: Everything in one file
// routes/admin.ts (1058 lines)Use Middleware:
// Good: Reusable middleware
app.use('/*', requireAuth);
app.use('/admin/*', requireAdmin);
// Avoid: Checking auth in every route
app.get('/admin/events', async (c) => {
if (!isAdmin(c)) return c.json({ error: 'Forbidden' }, 403);
// ...
});Files:
- Components:
PascalCase.tsx(PhotoCard.tsx) - Hooks:
camelCase.ts(usePhotoSelection.ts) - Utils:
camelCase.ts(imageUtils.ts) - Routes:
kebab-case.ts(admin-routes.ts) orcamelCase.ts
Variables:
- Constants:
UPPER_SNAKE_CASE(MAX_FILE_SIZE) - Variables:
camelCase(photoList) - Types:
PascalCase(PhotoCardProps)
Functions:
- Functions:
camelCase(getEvent, createPhoto) - React Components:
PascalCase(PhotoCard) - Hooks:
camelCasestarting withuse(useAuth)
Import Order:
// 1. React/external libraries
import React, { useState } from 'react';
import { Hono } from 'hono';
// 2. Internal modules (absolute imports)
import { requireAuth } from '@/auth';
import { getConfig } from '@/config';
// 3. Relative imports
import { PhotoCard } from './PhotoCard';
import type { Photo } from '../types';
// 4. Assets/styles
import './styles.css';Avoid Barrel Exports:
// Avoid: index.ts that re-exports everything
export * from './PhotoCard';
export * from './EventCard';
// Prefer: Direct imports
import { PhotoCard } from './components/PhotoCard';Doc Comments for Public APIs:
/**
* Uploads a photo to an event with multipart upload
* @param eventSlug - The event slug
* @param file - The file to upload
* @returns Upload ID and signed URLs for parts
*/
async function startUpload(eventSlug: string, file: File) {
// ...
}Inline Comments for Complex Logic:
// Calculate optimal part size based on file size
// R2 requires 5MB minimum parts, except for last part
const partSize = Math.max(5 * 1024 * 1024, Math.ceil(fileSize / 100));Avoid Obvious Comments:
// Bad
const photos = []; // initialize photos array
// Good (no comment needed)
const photos: Photo[] = [];// components/PhotoCard.test.tsx
import { render, screen } from '@testing-library/react';
import { PhotoCard } from './PhotoCard';
describe('PhotoCard', () => {
it('should render photo with correct src', () => {
const photo = { id: 1, preview_url: 'https://...', ... };
render(<PhotoCard photo={photo} />);
const img = screen.getByRole('img');
expect(img).toHaveAttribute('src', photo.preview_url);
});
it('should call onSelect when clicked', () => {
const onSelect = vi.fn();
const photo = { id: 1, ... };
render(<PhotoCard photo={photo} onSelect={onSelect} />);
screen.getByRole('img').click();
expect(onSelect).toHaveBeenCalledWith(1);
});
});// e2e/gallery.spec.ts
import { test, expect } from '@playwright/test';
test('should display event gallery', async ({ page }) => {
await page.goto('/events/summer-festival-2024');
// Check event title
await expect(page.locator('h1')).toContainText('Summer Festival 2024');
// Check photos are loaded
const photos = page.locator('[data-testid="photo-card"]');
await expect(photos).toHaveCount(await photos.count());
});
test('should open lightbox on photo click', async ({ page }) => {
await page.goto('/events/summer-festival-2024');
// Click first photo
await page.locator('[data-testid="photo-card"]').first().click();
// Check lightbox opens
await expect(page.locator('[data-testid="lightbox"]')).toBeVisible();
});# Unit tests
npm test
# Unit tests (watch mode)
npm test -- --watch
# E2E tests
npm run test:e2e
# E2E tests (headed)
npm run test:e2e -- --headed
# Type checking
npm run type-check
# Linting
npm run lintAim for:
- Critical paths: 100% coverage (auth, uploads)
- Components: 80%+ coverage
- Utilities: 90%+ coverage
- Routes: 70%+ coverage
# Generate coverage report
npm test -- --coverageWhen opening a PR, include:
## Description
Brief description of what this PR does.
## Type of Change
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Documentation update
## Related Issues
Closes #123
Related to #456
## Testing
- [ ] Unit tests added/updated
- [ ] E2E tests added/updated
- [ ] Manual testing performed
## Screenshots (if applicable)
[Add screenshots for UI changes]
## Checklist
- [ ] Code follows style guidelines
- [ ] Self-review completed
- [ ] Comments added for complex code
- [ ] Documentation updated
- [ ] No new warnings generated
- [ ] Tests pass locally
- [ ] Dependent changes merged-
Automated Checks
- Tests must pass
- Linting must pass
- Type checking must pass
- No merge conflicts
-
Code Review
- At least one approval required
- Address all comments
- Keep discussions constructive
-
Merge Requirements
- All checks passing
- Approved by maintainer
- Up to date with main branch
- Squash commits if requested
-
Check Existing Issues
- Search for similar requests
- Comment on existing issues
-
Open Discussion
- Create GitHub Discussion
- Explain use case
- Describe desired behavior
- Suggest implementation approach
-
Get Feedback
- Wait for maintainer response
- Discuss alternatives
- Refine proposal
-
Create Issue
- If approved, create detailed issue
- Use feature request template
- Link to discussion
## Feature Description
Clear description of the feature.
## Use Case
Explain why this feature is needed.
## Proposed Solution
How you think it should work.
## Alternatives Considered
Other approaches you've considered.
## Additional Context
Screenshots, mockups, links, etc.
## Implementation Notes
Technical considerations (optional).-
Search Existing Issues
- Check if already reported
- Add info to existing issue
-
Create Detailed Report
- Use bug report template
- Include reproduction steps
- Add error messages/logs
- Specify environment
## Bug Description
Clear description of the bug.
## Steps to Reproduce
1. Go to '...'
2. Click on '...'
3. Scroll down to '...'
4. See error
## Expected Behavior
What should happen.
## Actual Behavior
What actually happens.
## Screenshots
If applicable.
## Environment
- OS: [e.g. macOS 14.1]
- Browser: [e.g. Chrome 120]
- Version: [e.g. v1.0.0]
- Node: [e.g. 18.19.0]
## Error Messages
~~~text
Paste error logs here
~~~
## Additional Context
Any other relevant information.- New features or changes to existing features
- Configuration options
- API endpoints
- Complex algorithms or business logic
- Setup instructions
- Troubleshooting guides
- Write clear, concise sentences
- Use active voice
- Include code examples
- Add screenshots for UI features
- Keep README.md updated
- Update CHANGELOG.md
- README.md - Project overview
- configuration.md - Setup guide
- api-reference.md - API reference
- ARCHITECTURE.md - Technical overview
- FEATURES.md - Feature descriptions
- CONTRIBUTING.md - This file
- CHANGELOG.md - Version history
- GitHub Issues - Bug reports, feature requests
- GitHub Discussions - Questions, ideas, general discussion
- Pull Requests - Code review, implementation discussion
- Check documentation first
- Search existing issues
- Ask in GitHub Discussions
- Be patient and respectful
- Answer questions in discussions
- Review pull requests
- Improve documentation
- Share your use cases
Contributors are recognized in:
- README.md contributors section
- CHANGELOG.md for significant contributions
- GitHub contributors page
Thank you for contributing! 🎉
# Setup
git clone <fork>
cd apps/worker && npm install
cd ../web && npm install
# Development
npm run dev # From repo root: starts both worker and web concurrently
npm test # Run tests (in apps/web or apps/worker)
npm run lint # Check code style (apps/web only)
npx tsc --noEmit # Check types (in apps/web or apps/worker)
# Before PR
npm test # Tests pass (in apps/web and apps/worker)
npm run lint # No lint errors (apps/web)
npx tsc --noEmit # No type errors (apps/web and apps/worker)
git rebase origin/master # Up to date
# Commit
git add .
git commit -m "feat: add feature"
git push origin feature-branch
# Create PR on GitHubBy contributing, you agree that your contributions will be licensed under the same license as the project (see LICENSE file).
Don't hesitate to ask! Open a discussion or comment on an issue.
Happy contributing! 🚀