A web-based meme picture sharing application for discovering and sharing humorous Chinese-to-English translations.
- Image Upload: Upload meme pictures with metadata (description, category, tags)
- AI Analysis: Google Gemini integration to analyze improper Chinese-to-English translations
- Auto-generated Descriptions: AI-powered description generation based on translation analysis
- Location Tagging: Extract geographical information from images
- Public Browsing: Browse and filter memes by category and tags
- Tag System: Organize memes with tags, view tag cloud
- User Authentication: GitHub OAuth sign-in
- Bot Protection: Cloudflare Turnstile integration
- Meme Management: View, update, and delete own memes
- Frontend: React 19.2.3, Tailwind CSS v4, Next.js 16.1.6
- Backend: Next.js API Routes
- Database: Google Firestore (via Firebase Admin)
- File Storage: Cloudflare R2 (via AWS S3 SDK)
- AI Services: Google Gemini
- Authentication: NextAuth.js 4.24.13 with GitHub OAuth
- Bot Protection: Cloudflare Turnstile
- Image Processing: Sharp, piexifjs
- Testing: Jest 30, Testing Library
- Deployment: Google Cloud Run, Docker
- Node.js 20+
- npm or yarn
- Google Cloud account with Firestore and Gemini API enabled
- GitHub OAuth app
- Cloudflare R2 bucket with API tokens
- Cloudflare Turnstile site
git clone https://github.com/your-org/memepix.git
cd memepixnpm installCopy the example environment file:
cp .env.local.example .env.localEdit .env.local with your credentials:
| Variable | Description |
|---|---|
NEXTAUTH_URL |
Your app URL (default: http://localhost:3000) |
NEXTAUTH_SECRET |
NextAuth.js secret |
GITHUB_CLIENT_ID |
GitHub OAuth client ID |
GITHUB_CLIENT_SECRET |
GitHub OAuth client secret |
GOOGLE_CLOUD_PROJECT |
Google Cloud project ID |
GOOGLE_AI_API_KEY |
Google Gemini API key |
R2_ENDPOINT |
R2 endpoint URL |
R2_ACCESS_KEY_ID |
R2 access key ID |
R2_SECRET_ACCESS_KEY |
R2 secret access key |
R2_BUCKET_NAME |
R2 bucket name |
R2_PUBLIC_URL |
Public URL for R2 bucket |
TURNSTILE_SITE_KEY |
Cloudflare Turnstile site key |
TURNSTILE_SECRET_KEY |
Cloudflare Turnstile secret key |
- Create a Google Cloud project
- Enable Firestore API
- Enable Gemini API
- For local development, run:
gcloud auth application-default login
The app uses these collections:
memes- Meme documents with image URLs and metadatauploads- Upload tracking documentstags- Tag definitions with counts
- Go to GitHub Settings > Developer settings > OAuth Apps
- Create a new OAuth App
- Set Homepage URL to your app URL
- Set Authorization callback URL to
{NEXTAUTH_URL}/api/auth/callback/github - Copy the Client ID and generate a Client Secret
- Create an R2 bucket
- Generate API tokens (access key ID and secret access key)
- Configure CORS to allow uploads from your domain:
[
{
"AllowedOrigins": ["http://localhost:3000", "https://your-domain.com"],
"AllowedMethods": ["PUT", "GET"],
"AllowedHeaders": ["*"],
"MaxAgeSeconds": 3600
}
]- Go to Cloudflare Turnstile dashboard
- Create a new site
- Copy the Site Key and Secret Key
Run the development server:
npm run devOpen http://localhost:3000 in your browser.
Run tests:
npm testRun tests in watch mode:
npm run test:watchRun linting:
npm run lintThe project includes a cloudbuild.yaml for automated deployment to Cloud Run.
-
Create secrets in Google Secret Manager:
nextauth-secret: NextAuth.js secretgithub-client-id: GitHub OAuth client IDgithub-client-secret: GitHub OAuth client secretgoogle-ai-api-key: Gemini API keyr2-access-key-id: R2 access key IDr2-secret-access-key: R2 secret access keyr2-public-url: R2 public URLturnstile-site-key: Cloudflare Turnstile site keyturnstile-secret-key: Cloudflare Turnstile secret key
-
Update
cloudbuild.yamlwith your R2 endpoint URL. -
Set up a Cloud Build trigger:
- Connect your GitHub repository
- Trigger on push to main branch
- Use the
cloudbuild.yamlconfiguration
-
Grant the Cloud Run service account access to the secrets.
Build and deploy manually:
# Build the Docker image
docker build -t gcr.io/PROJECT_ID/memepix .
# Push to Google Container Registry
docker push gcr.io/PROJECT_ID/memepix
# Deploy to Cloud Run
gcloud run deploy memepix \
--image gcr.io/PROJECT_ID/memepix \
--region us-west1 \
--platform managed \
--allow-unauthenticatedTo rollback to a previous revision:
# List revisions
gcloud run revisions list --service memepix --region us-west1
# Rollback to a specific revision
gcloud run services update-traffic memepix \
--to-revisions REVISION_NAME=100 \
--region us-west1src/
├── __tests__/ # Test files
│ ├── components/ # Component tests
│ ├── image-utils.test.ts
│ ├── hash-utils.test.ts
│ ├── gemini.test.ts
│ ├── meme-service.test.ts
│ └── upload.test.ts
├── app/ # Next.js App Router pages and API routes
│ ├── api/ # API endpoints
│ │ ├── analyze/ # Gemini image analysis
│ │ ├── auth/ # NextAuth.js handler
│ │ ├── categories/ # Category CRUD
│ │ ├── config/ # Public configuration
│ │ ├── jobs/ # Background jobs
│ │ │ └── cleanup-tags/ # Tag cleanup job
│ │ ├── memes/ # Meme CRUD and listing
│ │ ├── tags/ # Tags listing
│ │ ├── upload/ # R2 presigned URL generation
│ │ └── upload-proxy/ # Upload proxy
│ ├── meme/[id]/ # Individual meme pages
│ │ └── client-page.tsx
│ ├── tags/ # Tags list page
│ │ └── [tag]/ # Individual tag pages
│ │ └── client-page.tsx
│ ├── upload/ # Upload page
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Home page
│ └── globals.css # Global styles
├── components/ # React components
│ ├── AuthButton.tsx
│ ├── CategoryFilter.tsx
│ ├── CategorySelect.tsx
│ ├── DescriptionInput.tsx
│ ├── Footer.tsx
│ ├── Header.tsx
│ ├── ImagePreview.tsx
│ ├── LocationTagSuggestion.tsx
│ ├── MemeDetail.tsx
│ ├── MemeGallery.tsx
│ ├── SessionProvider.tsx
│ ├── TagFilter.tsx
│ ├── TagInput.tsx
│ ├── Turnstile.tsx
│ └── UploadForm.tsx
├── jobs/ # Background job utilities
│ └── cleanup-tags.ts
├── lib/ # Utility functions and clients
│ ├── auth.ts # Authentication utilities
│ ├── env.ts # Environment variable handling
│ ├── firebase.ts # Firebase Admin SDK
│ ├── firestore-utils.ts # Firestore utilities
│ ├── gemini.ts # Gemini client
│ ├── hash-utils.ts # Hash utilities
│ ├── image-utils.ts # Image processing utilities
│ ├── meme-service.ts # Meme CRUD service
│ └── session.ts # Session utilities
└── types/ # TypeScript type definitions
└── index.ts
Dockerfile # Docker image for Cloud Run deployment
openspec/ # OpenSpec change artifacts
├── archive/ # Archived completed changes
├── changes/ # Active changes
└── config.yaml # OpenSpec configuration
| Endpoint | Method | Description |
|---|---|---|
/api/upload |
POST | Generate R2 presigned upload URL |
/api/upload-proxy |
POST | Proxy upload to R2 |
/api/analyze |
POST | Analyze image with Gemini |
/api/memes |
GET | List memes with optional filters |
/api/memes |
POST | Create a new meme |
/api/memes/[id] |
GET | Get a single meme |
/api/memes/[id] |
PATCH | Update a meme |
/api/memes/[id] |
DELETE | Delete a meme |
/api/categories |
GET | List all categories |
/api/categories |
POST | Create a new category |
/api/tags |
GET | List all tags with counts |
/api/config |
GET | Get public configuration |
/api/auth/[...nextauth] |
GET/POST | NextAuth.js handler |
/api/jobs/cleanup-tags |
POST | Run tag cleanup job (admin) |
MIT