├── .github └── workflows │ ├── deploy_backend.yml │ └── deploy_frontend.yml ├── .gitignore ├── .npmrc ├── README.md ├── apps ├── backend │ ├── .env.example │ ├── .gitignore │ ├── README.md │ ├── index.ts │ ├── middleware.ts │ ├── models │ │ ├── BaseModel.ts │ │ └── FalAIModel.ts │ ├── package.json │ ├── routes │ │ ├── payment.routes.ts │ │ └── webhook.routes.ts │ ├── services │ │ └── payment.ts │ ├── tsconfig.json │ └── types.d.ts └── web │ ├── .gitignore │ ├── README.md │ ├── app │ ├── config.ts │ ├── dashboard │ │ └── page.tsx │ ├── favicon.ico │ ├── fonts │ │ ├── GeistMonoVF.woff │ │ └── GeistVF.woff │ ├── globals.css │ ├── layout.tsx │ ├── page.module.css │ ├── page.tsx │ ├── payment │ │ ├── cancel │ │ │ └── page.tsx │ │ ├── success │ │ │ └── page.tsx │ │ └── verify │ │ │ └── page.tsx │ ├── pricing │ │ └── page.tsx │ ├── purchases │ │ └── page.tsx │ └── train │ │ └── page.tsx │ ├── components.json │ ├── components │ ├── Appbar.tsx │ ├── Camera.tsx │ ├── Footer.tsx │ ├── GenerateImage.tsx │ ├── GlowEffect.tsx │ ├── ImageCard.tsx │ ├── Models.tsx │ ├── PackCard.tsx │ ├── Packs.tsx │ ├── PacksClient.tsx │ ├── ThemeToggle.tsx │ ├── Train.tsx │ ├── home │ │ ├── BackgroundEffects.tsx │ │ ├── Features.tsx │ │ ├── Hero.tsx │ │ ├── HeroHeader.tsx │ │ ├── HowItWorks.tsx │ │ ├── ImageCarousel.tsx │ │ ├── PricingSection.tsx │ │ ├── ScrollIndicator.tsx │ │ ├── StatsSection.tsx │ │ ├── Testimonials.tsx │ │ ├── TrustedBy.tsx │ │ └── data.ts │ ├── navbar │ │ └── Credits.tsx │ ├── payment │ │ ├── PaymentCancelContent.tsx │ │ ├── PaymentSuccessContent.tsx │ │ ├── PurchasesPage.tsx │ │ └── VerifyContent.tsx │ ├── providers │ │ └── Providers.tsx │ ├── subscription │ │ └── PlanCard.tsx │ ├── theme-provider.tsx │ └── ui │ │ ├── badge.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── carousel.tsx │ │ ├── customLabel.tsx │ │ ├── dialog.tsx │ │ ├── dropdown-menu.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── progress.tsx │ │ ├── scroll-area.tsx │ │ ├── select.tsx │ │ ├── sheet.tsx │ │ ├── skeleton.tsx │ │ ├── switch.tsx │ │ ├── tabs.tsx │ │ ├── textarea.tsx │ │ ├── toast.tsx │ │ ├── toaster.tsx │ │ ├── tooltip.tsx │ │ └── upload.tsx │ ├── eslint.config.js │ ├── hooks │ ├── use-credits.ts │ ├── use-toast.ts │ ├── useAuth.ts │ ├── usePayment.ts │ └── useTransactions.ts │ ├── lib │ └── utils.ts │ ├── middleware.ts │ ├── next.config.js │ ├── package.json │ ├── postcss.config.mjs │ ├── public │ ├── file-text.svg │ ├── globe.svg │ ├── next.svg │ ├── turborepo-dark.svg │ ├── turborepo-light.svg │ ├── vercel.svg │ └── window.svg │ ├── tsconfig.json │ └── types │ └── index.ts ├── bun.lockb ├── docker-compose.yml ├── docker ├── Dockerfile.backend └── Dockerfile.frontend ├── package-lock.json ├── package.json ├── packages ├── common │ ├── .gitignore │ ├── README.md │ ├── index.ts │ ├── inferred-types.ts │ ├── package.json │ ├── tsconfig.json │ └── types.ts ├── db │ ├── .gitignore │ ├── README.md │ ├── index.ts │ ├── package.json │ ├── prisma │ │ ├── migrations │ │ │ ├── 20250211165544_init │ │ │ │ └── migration.sql │ │ │ ├── 20250211172909_ │ │ │ │ └── migration.sql │ │ │ ├── 20250211173124_change_color_enunm │ │ │ │ └── migration.sql │ │ │ ├── 20250211173427_added_user_id_field │ │ │ │ └── migration.sql │ │ │ ├── 20250211173843_added_status │ │ │ │ └── migration.sql │ │ │ ├── 20250211205852_added_fal_ai │ │ │ │ └── migration.sql │ │ │ ├── 20250211210635_added_index │ │ │ │ └── migration.sql │ │ │ ├── 20250211214338_init_db │ │ │ │ └── migration.sql │ │ │ ├── 20250212020444_ │ │ │ │ └── migration.sql │ │ │ ├── 20250212020828_added_image │ │ │ │ └── migration.sql │ │ │ ├── 20250212025528_thumbnail │ │ │ │ └── migration.sql │ │ │ ├── 20250212031625_added_open_models │ │ │ │ └── migration.sql │ │ │ ├── 20250213231325_subscription │ │ │ │ └── migration.sql │ │ │ ├── 20250213231834_usercredit │ │ │ │ └── migration.sql │ │ │ ├── 20250216194913_user_table_updated │ │ │ │ └── migration.sql │ │ │ ├── 20250216195632_user_table_updated │ │ │ │ └── migration.sql │ │ │ ├── 20250224171635_transaction │ │ │ │ └── migration.sql │ │ │ ├── 20250226214307_removed_annual │ │ │ │ └── migration.sql │ │ │ └── migration_lock.toml │ │ └── schema.prisma │ └── tsconfig.json ├── eslint-config │ ├── README.md │ ├── base.js │ ├── next.js │ ├── package.json │ └── react-internal.js ├── typescript-config │ ├── base.json │ ├── nextjs.json │ ├── package.json │ └── react-library.json └── ui │ ├── eslint.config.mjs │ ├── package.json │ ├── src │ ├── button.tsx │ ├── card.tsx │ └── code.tsx │ ├── tsconfig.json │ └── turbo │ └── generators │ ├── config.ts │ └── templates │ └── component.hbs └── turbo.json /.github/workflows/deploy_backend.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Deployment (Prod) (Backend) 2 | on: 3 | push: 4 | branches: [ main ] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | with: 11 | fetch-depth: 0 12 | 13 | - name: Docker login 14 | uses: docker/login-action@v2 15 | with: 16 | username: ${{ secrets.DOCKERHUB_USERNAME }} 17 | password: ${{ secrets.DOCKERHUB_TOKEN }} 18 | 19 | - name: Set up Docker Buildx 20 | uses: docker/setup-buildx-action@v2 21 | 22 | - name: Build and push 23 | uses: docker/build-push-action@v4 24 | with: 25 | context: . 26 | file: ./docker/Dockerfile.backend 27 | push: true 28 | tags: 100xdevs/photo-ai-backend:${{ github.sha }} 29 | 30 | - name: Clone staging-ops repo, update, and push 31 | env: 32 | PAT: ${{ secrets.PAT }} 33 | run: | 34 | git clone https://github.com/code100x/staging-ops.git 35 | cd staging-ops 36 | sed -i 's|image: 100xdevs/photo-ai-backend:.*|image: 100xdevs/photo-ai-backend:${{ github.sha }}|' prod/photo-ai/deployment.yml 37 | git config user.name "GitHub Actions Bot" 38 | git config user.email "actions@github.com" 39 | git add prod/photo-ai/deployment.yml 40 | git commit -m "Update dailycode image to ${{ github.sha }}" 41 | git push https://${PAT}@github.com/code100x/staging-ops.git main -------------------------------------------------------------------------------- /.github/workflows/deploy_frontend.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Deployment (Prod) (Frontend) 2 | on: 3 | push: 4 | branches: [ main ] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | with: 11 | fetch-depth: 0 12 | 13 | - name: Docker login 14 | uses: docker/login-action@v2 15 | with: 16 | username: ${{ secrets.DOCKERHUB_USERNAME }} 17 | password: ${{ secrets.DOCKERHUB_TOKEN }} 18 | 19 | - name: Set up Docker Buildx 20 | uses: docker/setup-buildx-action@v2 21 | 22 | - name: Build and push 23 | uses: docker/build-push-action@v4 24 | with: 25 | context: . 26 | file: ./docker/Dockerfile.frontend 27 | push: true 28 | tags: 100xdevs/photo-ai-frontend:${{ github.sha }} 29 | build-args: | 30 | CLERK_PUBLISHABLE_KEY=${{ secrets.CLERK_PUBLISHABLE_KEY }} 31 | 32 | - name: Clone staging-ops repo, update, and push 33 | env: 34 | PAT: ${{ secrets.PAT }} 35 | run: | 36 | git clone https://github.com/code100x/staging-ops.git 37 | cd staging-ops 38 | sed -i 's|image: 100xdevs/photo-ai-frontend:.*|image: 100xdevs/photo-ai-frontend:${{ github.sha }}|' prod/photo-ai/deployment.yml 39 | git config user.name "GitHub Actions Bot" 40 | git config user.email "actions@github.com" 41 | git add prod/photo-ai/deployment.yml 42 | git commit -m "Update dailycode image to ${{ github.sha }}" 43 | git push https://${PAT}@github.com/code100x/staging-ops.git main -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # Dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # Local env files 9 | .env 10 | .env.local 11 | .env.development.local 12 | .env.test.local 13 | .env.production.local 14 | 15 | # Testing 16 | coverage 17 | 18 | # Turbo 19 | .turbo 20 | 21 | # Vercel 22 | .vercel 23 | 24 | # Build Outputs 25 | .next/ 26 | out/ 27 | build 28 | dist 29 | 30 | 31 | # Debug 32 | npm-debug.log* 33 | yarn-debug.log* 34 | yarn-error.log* 35 | 36 | # Misc 37 | .DS_Store 38 | *.pem 39 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code100x/photo-ai/752707e61d8ad2e1abd0189ebc30668232c82a1d/.npmrc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 100xPhoto - AI Image Generation Platform 2 | 3 | 100xPhoto is a powerful AI image platform that lets you generate stunning images and train custom AI models. Built with cutting-edge technology, it enables users to create unique AI-generated artwork and train personalized models on their own image datasets. Whether you're an artist looking to expand your creative possibilities or a developer building AI-powered image applications, 100xPhoto provides an intuitive interface and robust capabilities for AI image generation and model training. 4 | 5 | ## Tech Stack 6 | 7 | - **Frontend**: Next.js 14 (App Router), TypeScript, Tailwind CSS, Shadcn/UI 8 | - **Backend**: Node.js with TypeScript 9 | - **Authentication**: Clerk 10 | - **Containerization**: Docker 11 | - **Package Management**: Bun 12 | - **Monorepo Management**: Turborepo 13 | 14 | ## Project Structure 15 | 16 | ### Apps and Packages 17 | 18 | - `web`: Next.js frontend application 19 | - `backend`: Node.js backend service 20 | - `@repo/ui`: Shared React component library 21 | - `@repo/typescript-config`: Shared TypeScript configurations 22 | - `@repo/eslint-config`: Shared ESLint configurations 23 | 24 | ## Getting Started 25 | 26 | ### Prerequisites 27 | 28 | - Docker 29 | - Bun (for local development) 30 | - Clerk Account (for authentication) 31 | 32 | ### Environment Setup 33 | 34 | 1. Create `.env` files: 35 | 36 | ```bash 37 | # apps/web/.env.local 38 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=your_publishable_key 39 | CLERK_SECRET_KEY=your_secret_key 40 | NEXT_PUBLIC_BACKEND_URL=http://localhost:8080 41 | ``` 42 | 43 | 44 | ### Local Development 45 | 46 | ```bash 47 | # Install dependencies 48 | bun install 49 | 50 | # Run development servers 51 | bun run dev 52 | 53 | # Build all packages 54 | bun run build 55 | ``` 56 | 57 | ## Features 58 | 59 | - AI-powered image generation 60 | - User authentication and authorization 61 | - Image gallery with preview 62 | - Download generated images 63 | - Responsive design 64 | 65 | ## Development Commands 66 | 67 | ```bash 68 | # Run frontend only 69 | bun run start:web 70 | 71 | # Run backend only 72 | bun run start:backend 73 | 74 | # Run both frontend and backend 75 | bun run dev 76 | ``` 77 | 78 | ## Docker Setup 79 | 80 | ### Environment Variables Required 81 | 82 | ```bash 83 | # Frontend Environment Variables 84 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_Y2xlcmsuMTAweGRldnMuY29tJA 85 | NEXT_PUBLIC_BACKEND_URL=https://api.photoaiv2.100xdevs.com 86 | NEXT_PUBLIC_STRIPE_KEY=pk_test_51QsCmFEI53oUr5PHZw5ErO4Xy2lNh9LkH9vXDb8wc7BOvfSPc0i4xt6I5Qy3jaBLnvg9wPenPoeW0LvQ1x3GtfUm00eNFHdBDd 87 | CLERK_SECRET_KEY=your_clerk_secret_key 88 | 89 | # Backend Environment Variables 90 | DATABASE_URL=your_database_url 91 | ``` 92 | 93 | ### Docker Commands 94 | 95 | ```bash 96 | # Navigate to docker directory 97 | cd docker 98 | 99 | # Build images 100 | docker build -f Dockerfile.frontend -t photoai-frontend .. 101 | docker build -f Dockerfile.backend -t photoai-backend .. 102 | 103 | # Run frontend container 104 | docker run -p 3000:3000 \ 105 | -e NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_Y2xlcmsuMTAweGRldnMuY29tJA \ 106 | -e NEXT_PUBLIC_BACKEND_URL=https://api.photoaiv2.100xdevs.com \ 107 | -e NEXT_PUBLIC_STRIPE_KEY=pk_test_51QsCmFEI53oUr5PHZw5ErO4Xy2lNh9LkH9vXDb8wc7BOvfSPc0i4xt6I5Qy3jaBLnvg9wPenPoeW0LvQ1x3GtfUm00eNFHdBDd \ 108 | -e CLERK_SECRET_KEY=your_clerk_secret_key \ 109 | photoai-frontend 110 | 111 | # Run backend container 112 | docker run -p 8080:8080 \ 113 | -e DATABASE_URL=your_database_url \ 114 | photoai-backend 115 | 116 | ``` 117 | 118 | 119 | ## Project Structure 120 | 121 | ``` 122 | . 123 | ├── apps 124 | │ ├── web/ # Next.js frontend 125 | │ └── backend/ # Node.js backend 126 | ├── packages 127 | │ ├── ui/ # Shared UI components 128 | │ ├── typescript-config/ # Shared TS config 129 | │ └── eslint-config/ # Shared ESLint config 130 | ├── docker/ # Docker configuration 131 | │ ├── Dockerfile.frontend 132 | │ └── Dockerfile.backend 133 | └── package.json 134 | ``` 135 | 136 | ## Contributing 137 | 138 | 1. Fork the repository 139 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`) 140 | 3. Commit your changes (`git commit -m 'Add some amazing feature'`) 141 | 4. Push to the branch (`git push origin feature/amazing-feature`) 142 | 5. Open a Pull Request 143 | 144 | ## License 145 | 146 | This project is licensed under the MIT License - see the LICENSE file for details 147 | -------------------------------------------------------------------------------- /apps/backend/.env.example: -------------------------------------------------------------------------------- 1 | FAL_KEY="" 2 | S3_ACCESS_KEY="" 3 | S3_SECRET_KEY="" 4 | BUCKET_NAME="" 5 | ENDPOINT="" 6 | AUTH_JWT_KEY="" 7 | RAZORPAY_KEY_ID="" 8 | RAZORPAY_KEY_SECRET="" 9 | SIGNING_SECRET="" 10 | CLERK_JWT_PUBLIC_KEY="" 11 | SIGNING_SECRET="" 12 | WEBHOOK_BASE_URL="" 13 | FRONTEND_URL="" -------------------------------------------------------------------------------- /apps/backend/.gitignore: -------------------------------------------------------------------------------- 1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore 2 | 3 | # Logs 4 | 5 | logs 6 | _.log 7 | npm-debug.log_ 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | .pnpm-debug.log* 12 | 13 | # Caches 14 | 15 | .cache 16 | 17 | # Diagnostic reports (https://nodejs.org/api/report.html) 18 | 19 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 20 | 21 | # Runtime data 22 | 23 | pids 24 | _.pid 25 | _.seed 26 | *.pid.lock 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | 30 | lib-cov 31 | 32 | # Coverage directory used by tools like istanbul 33 | 34 | coverage 35 | *.lcov 36 | 37 | # nyc test coverage 38 | 39 | .nyc_output 40 | 41 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 42 | 43 | .grunt 44 | 45 | # Bower dependency directory (https://bower.io/) 46 | 47 | bower_components 48 | 49 | # node-waf configuration 50 | 51 | .lock-wscript 52 | 53 | # Compiled binary addons (https://nodejs.org/api/addons.html) 54 | 55 | build/Release 56 | 57 | # Dependency directories 58 | 59 | node_modules/ 60 | jspm_packages/ 61 | 62 | # Snowpack dependency directory (https://snowpack.dev/) 63 | 64 | web_modules/ 65 | 66 | # TypeScript cache 67 | 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | 72 | .npm 73 | 74 | # Optional eslint cache 75 | 76 | .eslintcache 77 | 78 | # Optional stylelint cache 79 | 80 | .stylelintcache 81 | 82 | # Microbundle cache 83 | 84 | .rpt2_cache/ 85 | .rts2_cache_cjs/ 86 | .rts2_cache_es/ 87 | .rts2_cache_umd/ 88 | 89 | # Optional REPL history 90 | 91 | .node_repl_history 92 | 93 | # Output of 'npm pack' 94 | 95 | *.tgz 96 | 97 | # Yarn Integrity file 98 | 99 | .yarn-integrity 100 | 101 | # dotenv environment variable files 102 | 103 | .env 104 | .env.development.local 105 | .env.test.local 106 | .env.production.local 107 | .env.local 108 | 109 | # parcel-bundler cache (https://parceljs.org/) 110 | 111 | .parcel-cache 112 | 113 | # Next.js build output 114 | 115 | .next 116 | out 117 | 118 | # Nuxt.js build / generate output 119 | 120 | .nuxt 121 | dist 122 | 123 | # Gatsby files 124 | 125 | # Comment in the public line in if your project uses Gatsby and not Next.js 126 | 127 | # https://nextjs.org/blog/next-9-1#public-directory-support 128 | 129 | # public 130 | 131 | # vuepress build output 132 | 133 | .vuepress/dist 134 | 135 | # vuepress v2.x temp and cache directory 136 | 137 | .temp 138 | 139 | # Docusaurus cache and generated files 140 | 141 | .docusaurus 142 | 143 | # Serverless directories 144 | 145 | .serverless/ 146 | 147 | # FuseBox cache 148 | 149 | .fusebox/ 150 | 151 | # DynamoDB Local files 152 | 153 | .dynamodb/ 154 | 155 | # TernJS port file 156 | 157 | .tern-port 158 | 159 | # Stores VSCode versions used for testing VSCode extensions 160 | 161 | .vscode-test 162 | 163 | # yarn v2 164 | 165 | .yarn/cache 166 | .yarn/unplugged 167 | .yarn/build-state.yml 168 | .yarn/install-state.gz 169 | .pnp.* 170 | 171 | # IntelliJ based IDEs 172 | .idea 173 | 174 | # Finder (MacOS) folder config 175 | .DS_Store 176 | -------------------------------------------------------------------------------- /apps/backend/README.md: -------------------------------------------------------------------------------- 1 | # PhotoAI Backend Service 2 | 3 | The Node.js backend service for PhotoAI - an AI-powered image generation platform. 4 | 5 | ## Features 6 | 7 | - AI Image Generation with FalAI 8 | - Model Training Integration 9 | - S3 Image Storage 10 | - Payment Processing (Stripe & Razorpay) 11 | - User Credit Management 12 | - Clerk Authentication 13 | - Webhook Handlers 14 | 15 | ## Tech Stack 16 | 17 | - Node.js with TypeScript 18 | - Express.js 19 | - Prisma ORM 20 | - FalAI Client 21 | - S3 Storage 22 | - Stripe/Razorpay Integration 23 | - Clerk Authentication 24 | 25 | ## Environment Variables 26 | 27 | Create a `.env` file: 28 | 29 | ```bash 30 | # AI Service 31 | FAL_KEY=your_fal_ai_key 32 | 33 | # Storage 34 | S3_ACCESS_KEY=your_s3_access_key 35 | S3_SECRET_KEY=your_s3_secret_key 36 | BUCKET_NAME=your_bucket_name 37 | ENDPOINT=your_s3_endpoint 38 | 39 | # Authentication 40 | AUTH_JWT_KEY=your_jwt_key 41 | CLERK_JWT_PUBLIC_KEY=your_clerk_public_key 42 | SIGNING_SECRET=your_clerk_webhook_signing_secret 43 | 44 | # Payments 45 | STRIPE_SECRET_KEY=your_stripe_secret_key 46 | STRIPE_WEBHOOK_SECRET=your_stripe_webhook_secret 47 | RAZORPAY_KEY_ID=your_razorpay_key_id 48 | RAZORPAY_KEY_SECRET=your_razorpay_secret_key 49 | 50 | # URLs 51 | WEBHOOK_BASE_URL=your_webhook_base_url 52 | FRONTEND_URL=your_frontend_url 53 | ``` 54 | 55 | ## Development 56 | 57 | ```bash 58 | # Install dependencies 59 | bun install 60 | 61 | # Run development server 62 | bun dev 63 | 64 | # Start production server 65 | bun start 66 | ``` 67 | 68 | The server will be available at `http://localhost:8080`. 69 | 70 | ## API Endpoints 71 | 72 | ### Authentication 73 | 74 | - `POST /api/webhook/clerk` - Clerk webhook handler 75 | 76 | ### Image Generation 77 | 78 | - `POST /ai/training` - Train new AI model 79 | - `POST /ai/generate` - Generate images 80 | - `POST /pack/generate` - Generate images from pack 81 | - `GET /image/bulk` - Get generated images 82 | 83 | ### Models 84 | 85 | - `GET /models` - Get available models 86 | - `GET /pre-signed-url` - Get S3 upload URL 87 | 88 | ### Payments 89 | 90 | - `POST /payment/create` - Create payment session 91 | - `POST /payment/razorpay/verify` - Verify Razorpay payment 92 | - `GET /payment/subscription/:userId` - Get user subscription 93 | - `GET /payment/credits/:userId` - Get user credits 94 | - `POST /payment/webhook` - Payment webhook handler 95 | 96 | ## Project Structure 97 | 98 | ``` 99 | apps/backend/ 100 | ├── routes/ # API route handlers 101 | ├── services/ # Business logic 102 | ├── models/ # AI model integrations 103 | ├── middleware/ # Express middleware 104 | └── types/ # TypeScript types 105 | ``` 106 | 107 | ## Key Components 108 | 109 | - `index.ts` - Main application entry 110 | - `middleware.ts` - Authentication middleware 111 | - `models/FalAIModel.ts` - FalAI integration 112 | - `services/payment.ts` - Payment processing 113 | - `routes/payment.routes.ts` - Payment endpoints 114 | - `routes/webhook.routes.ts` - Webhook handlers 115 | 116 | ## Docker Support 117 | 118 | Build the backend container: 119 | 120 | ```bash 121 | docker build -f docker/Dockerfile.backend -t photoai-backend .. 122 | ``` 123 | 124 | Run the container: 125 | 126 | ```bash 127 | docker run -p 8080:8080 \ 128 | --env-file .env \ 129 | photoai-backend 130 | ``` 131 | 132 | ## Contributing 133 | 134 | 1. Fork the repository 135 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`) 136 | 3. Commit your changes (`git commit -m 'Add some amazing feature'`) 137 | 4. Push to the branch (`git push origin feature/amazing-feature`) 138 | 5. Open a Pull Request 139 | 140 | ## License 141 | 142 | This project is licensed under the MIT License - see the LICENSE file for details. 143 | -------------------------------------------------------------------------------- /apps/backend/middleware.ts: -------------------------------------------------------------------------------- 1 | import type { NextFunction, Request, Response } from "express"; 2 | import jwt from "jsonwebtoken"; 3 | import { clerkClient } from "@clerk/clerk-sdk-node"; 4 | 5 | declare global { 6 | namespace Express { 7 | interface Request { 8 | userId?: string; 9 | user?: { 10 | email: string; 11 | }; 12 | } 13 | } 14 | } 15 | 16 | export async function authMiddleware( 17 | req: Request, 18 | res: Response, 19 | next: NextFunction 20 | ) { 21 | try { 22 | const authHeader = req.headers["authorization"]; 23 | const token = authHeader?.split(" ")[1]; 24 | 25 | if (!token) { 26 | res.status(401).json({ message: "No token provided" }); 27 | return; 28 | } 29 | 30 | // Debug logs 31 | console.log("Received token:", token); 32 | 33 | // Get the JWT verification key from environment variable 34 | const publicKey = process.env.CLERK_JWT_PUBLIC_KEY!; 35 | 36 | if (!publicKey) { 37 | console.error("Missing CLERK_JWT_PUBLIC_KEY in environment variables"); 38 | res.status(500).json({ message: "Server configuration error" }); 39 | return; 40 | } 41 | 42 | // Format the public key properly 43 | const formattedKey = publicKey.replace(/\\n/g, "\n"); 44 | 45 | const decoded = jwt.verify(token, formattedKey, { 46 | algorithms: ["RS256"], 47 | issuer: 48 | process.env.CLERK_ISSUER || "https://clerk.100xdevs.com", 49 | complete: true, 50 | }); 51 | 52 | console.log("Decoded token:", decoded); 53 | 54 | // Extract user ID from the decoded token 55 | const userId = (decoded as any).payload.sub; 56 | 57 | if (!userId) { 58 | console.error("No user ID in token payload"); 59 | res.status(403).json({ message: "Invalid token payload" }); 60 | return; 61 | } 62 | 63 | // Fetch user details from Clerk 64 | const user = await clerkClient.users.getUser(userId); 65 | const primaryEmail = user.emailAddresses.find( 66 | (email) => email.id === user.primaryEmailAddressId 67 | ); 68 | 69 | if (!primaryEmail) { 70 | console.error("No email found for user"); 71 | res.status(400).json({ message: "User email not found" }); 72 | return; 73 | } 74 | 75 | // Attach the user ID and email to the request 76 | req.userId = userId; 77 | req.user = { 78 | email: primaryEmail.emailAddress, 79 | }; 80 | 81 | next(); 82 | } catch (error) { 83 | console.error("Auth error:", error); 84 | if (error instanceof jwt.JsonWebTokenError) { 85 | res.status(403).json({ 86 | message: "Invalid token", 87 | details: 88 | process.env.NODE_ENV === "development" ? error.message : undefined, 89 | }); 90 | return; 91 | } 92 | res.status(500).json({ 93 | message: "Error processing authentication", 94 | details: 95 | process.env.NODE_ENV === "development" 96 | ? (error as Error).message 97 | : undefined, 98 | }); 99 | return; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /apps/backend/models/BaseModel.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export class BaseModel { 4 | constructor() {} 5 | 6 | private async generateImage(prompt: string, tensorPath: string) { 7 | } 8 | 9 | private async trainModel(inputImages: string[], triggerWord: string) { 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /apps/backend/models/FalAIModel.ts: -------------------------------------------------------------------------------- 1 | import { fal } from "@fal-ai/client"; 2 | import { BaseModel } from "./BaseModel"; 3 | 4 | export class FalAIModel { 5 | constructor() {} 6 | 7 | public async generateImage(prompt: string, tensorPath: string) { 8 | const { request_id, response_url } = await fal.queue.submit( 9 | "fal-ai/flux-lora", 10 | { 11 | input: { 12 | prompt: prompt, 13 | loras: [{ path: tensorPath, scale: 1 }], 14 | }, 15 | webhookUrl: `${process.env.WEBHOOK_BASE_URL}/fal-ai/webhook/image`, 16 | } 17 | ); 18 | 19 | return { request_id, response_url }; 20 | } 21 | 22 | public async trainModel(zipUrl: string, triggerWord: string) { 23 | console.log("Training model with URL:", zipUrl); 24 | 25 | try { 26 | const response = await fetch(zipUrl, { method: "HEAD" }); 27 | if (!response.ok) { 28 | console.error( 29 | `ZIP URL not accessible: ${zipUrl}, status: ${response.status}` 30 | ); 31 | throw new Error(`ZIP URL not accessible: ${response.status}`); 32 | } 33 | } catch (error) { 34 | console.error("Error checking ZIP URL:", error); 35 | throw new Error(`ZIP URL validation failed: ${error as any}.message}`); 36 | } 37 | 38 | const { request_id, response_url } = await fal.queue.submit( 39 | "fal-ai/flux-lora-fast-training", 40 | { 41 | input: { 42 | images_data_url: zipUrl, 43 | trigger_word: triggerWord, 44 | }, 45 | webhookUrl: `${process.env.WEBHOOK_BASE_URL}/fal-ai/webhook/train`, 46 | } 47 | ); 48 | 49 | console.log("Model training submitted:", request_id); 50 | return { request_id, response_url }; 51 | } 52 | 53 | public async generateImageSync(tensorPath: string) { 54 | const response = await fal.subscribe("fal-ai/flux-lora", { 55 | input: { 56 | prompt: 57 | "Generate a head shot for this user in front of a white background", 58 | loras: [{ path: tensorPath, scale: 1 }], 59 | }, 60 | }); 61 | return { 62 | imageUrl: response.data.images[0].url, 63 | }; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /apps/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "module": "index.ts", 4 | "type": "module", 5 | "scripts": { 6 | "start": "bun run index.ts", 7 | "dev": "bun run index.ts" 8 | }, 9 | "devDependencies": { 10 | "@types/bun": "latest" 11 | }, 12 | "peerDependencies": { 13 | "typescript": "^5.0.0" 14 | }, 15 | "dependencies": { 16 | "@clerk/clerk-sdk-node": "^5.1.6", 17 | "@clerk/nextjs": "^6.11.2", 18 | "@fal-ai/client": "^1.2.3", 19 | "@neondatabase/serverless": "^0.10.4", 20 | "@types/cors": "^2.8.17", 21 | "@types/express": "^5.0.0", 22 | "common": "*", 23 | "cors": "^2.8.5", 24 | "db": "*", 25 | "express": "^4.21.2", 26 | "jsonwebtoken": "^9.0.2", 27 | "razorpay": "^2.9.5", 28 | "stripe": "^17.6.0", 29 | "svix": "^1.57.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /apps/backend/routes/webhook.routes.ts: -------------------------------------------------------------------------------- 1 | import { prismaClient } from "db"; 2 | import { Router } from "express"; 3 | import { Webhook } from "svix"; 4 | 5 | export const router = Router(); 6 | 7 | /** 8 | * POST api/webhook/clerk 9 | * Clerk webhook endpoint 10 | */ 11 | router.post("/clerk", async (req, res) => { 12 | const SIGNING_SECRET = process.env.SIGNING_SECRET; 13 | 14 | if (!SIGNING_SECRET) { 15 | throw new Error( 16 | "Error: Please add SIGNING_SECRET from Clerk Dashboard to .env" 17 | ); 18 | } 19 | 20 | const wh = new Webhook(SIGNING_SECRET); 21 | const headers = req.headers; 22 | const payload = req.body; 23 | 24 | const svix_id = headers["svix-id"]; 25 | const svix_timestamp = headers["svix-timestamp"]; 26 | const svix_signature = headers["svix-signature"]; 27 | 28 | if (!svix_id || !svix_timestamp || !svix_signature) { 29 | res.status(400).json({ 30 | success: false, 31 | message: "Error: Missing svix headers", 32 | }); 33 | return; 34 | } 35 | 36 | let evt: any; 37 | 38 | try { 39 | evt = wh.verify(JSON.stringify(payload), { 40 | "svix-id": svix_id as string, 41 | "svix-timestamp": svix_timestamp as string, 42 | "svix-signature": svix_signature as string, 43 | }); 44 | } catch (err) { 45 | console.log("Error: Could not verify webhook:", (err as Error).message); 46 | res.status(400).json({ 47 | success: false, 48 | message: (err as Error).message, 49 | }); 50 | return; 51 | } 52 | 53 | const { id } = evt.data; 54 | const eventType = evt.type; 55 | 56 | try { 57 | switch (eventType) { 58 | case "user.created": 59 | case "user.updated": { 60 | await prismaClient.user.upsert({ 61 | where: { clerkId: id }, 62 | update: { 63 | name: `${evt.data.first_name ?? ""} ${evt.data.last_name ?? ""}`.trim(), 64 | email: evt.data.email_addresses[0].email_address, 65 | profilePicture: evt.data.profile_image_url, 66 | }, 67 | create: { 68 | clerkId: id, 69 | name: `${evt.data.first_name ?? ""} ${evt.data.last_name ?? ""}`.trim(), 70 | email: evt.data.email_addresses[0].email_address, 71 | profilePicture: evt.data.profile_image_url, 72 | }, 73 | }); 74 | break; 75 | } 76 | 77 | case "user.deleted": { 78 | await prismaClient.user.delete({ 79 | where: { clerkId: id }, 80 | }); 81 | break; 82 | } 83 | 84 | default: 85 | console.log(`Unhandled event type: ${eventType}`); 86 | break; 87 | } 88 | } catch (error) { 89 | console.error("Error handling webhook:", error); 90 | res.status(500).json({ success: false, message: "Internal Server Error" }); 91 | return; 92 | } 93 | 94 | res.status(200).json({ success: true, message: "Webhook received" }); 95 | return; 96 | }); 97 | -------------------------------------------------------------------------------- /apps/backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Enable latest features 4 | "lib": ["ESNext", "DOM"], 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "moduleDetection": "force", 8 | "jsx": "react-jsx", 9 | "allowJs": true, 10 | 11 | // Bundler mode 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "verbatimModuleSyntax": true, 15 | "noEmit": true, 16 | 17 | // Best practices 18 | "strict": true, 19 | "skipLibCheck": true, 20 | "noFallthroughCasesInSwitch": true, 21 | 22 | // Some stricter flags (disabled by default) 23 | "noUnusedLocals": false, 24 | "noUnusedParameters": false, 25 | "noPropertyAccessFromIndexSignature": false 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/backend/types.d.ts: -------------------------------------------------------------------------------- 1 | 2 | declare namespace Express { 3 | interface Request { 4 | userId?: string; 5 | } 6 | } -------------------------------------------------------------------------------- /apps/web/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # env files (can opt-in for commiting if needed) 29 | .env* 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | # clerk configuration (can include secrets) 39 | /.clerk/ 40 | -------------------------------------------------------------------------------- /apps/web/README.md: -------------------------------------------------------------------------------- 1 | # PhotoAI Web Frontend 2 | 3 | The Next.js frontend application for PhotoAI - an AI-powered image generation platform. 4 | 5 | ## Features 6 | 7 | - AI Image Generation 8 | - Real-time Image Preview 9 | - Beautiful Image Gallery 10 | - Responsive Design 11 | - Authentication with Clerk 12 | - Secure Payment Integration with Stripe 13 | 14 | ## Tech Stack 15 | 16 | - Next.js 14 (App Router) 17 | - TypeScript 18 | - Tailwind CSS 19 | - Shadcn/UI 20 | - Clerk Authentication 21 | - Stripe/Razorpay Payments 22 | 23 | ## Getting Started 24 | 25 | ### Prerequisites 26 | 27 | - Node.js 18+ or Bun 28 | - Clerk Account 29 | - Stripe Account (for payments) 30 | 31 | ### Environment Variables 32 | 33 | Create a `.env.local` file: 34 | 35 | ```bash 36 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=your_publishable_key 37 | CLERK_SECRET_KEY=your_secret_key 38 | NEXT_PUBLIC_BACKEND_URL=http://localhost:8080 39 | NEXT_PUBLIC_STRIPE_KEY=your_stripe_key 40 | ``` 41 | 42 | ### Development 43 | 44 | ```bash 45 | # Install dependencies 46 | bun install 47 | 48 | # Run development server 49 | bun dev 50 | 51 | # Build for production 52 | bun run build 53 | 54 | # Start production server 55 | bun start 56 | ``` 57 | 58 | The application will be available at [http://localhost:3000](http://localhost:3000). 59 | 60 | ## Project Structure 61 | 62 | ``` 63 | apps/web/ 64 | ├── app/ # App Router pages 65 | ├── components/ # React components 66 | ├── lib/ # Utility functions 67 | ├── styles/ # Global styles 68 | ├── types/ # TypeScript types 69 | └── public/ # Static assets 70 | ``` 71 | 72 | ## Key Components 73 | 74 | - `app/page.tsx` - Homepage 75 | - `app/dashboard/page.tsx` - User Dashboard 76 | - `components/Camera.tsx` - Image Generation UI 77 | - `components/Gallery.tsx` - Image Gallery 78 | - `components/ui/` - Shared UI Components 79 | 80 | ## Docker Support 81 | 82 | Build the frontend container: 83 | 84 | ```bash 85 | docker build -f docker/Dockerfile.frontend -t photoai-frontend .. 86 | ``` 87 | 88 | Run the container: 89 | 90 | ```bash 91 | docker run -p 3000:3000 \ 92 | -e NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=your_key \ 93 | -e CLERK_SECRET_KEY=your_secret_key \ 94 | -e NEXT_PUBLIC_BACKEND_URL=http://localhost:8080 \ 95 | -e NEXT_PUBLIC_STRIPE_KEY=your_stripe_key \ 96 | photoai-frontend 97 | ``` 98 | 99 | ## API Integration 100 | 101 | The frontend communicates with the backend API at `NEXT_PUBLIC_BACKEND_URL`. Key endpoints: 102 | 103 | - `/api/generate` - Generate new images 104 | - `/api/images` - Fetch user's images 105 | - `/api/pack` - Manage credit packs 106 | 107 | ## Contributing 108 | 109 | 1. Fork the repository 110 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`) 111 | 3. Commit your changes (`git commit -m 'Add some amazing feature'`) 112 | 4. Push to the branch (`git push origin feature/amazing-feature`) 113 | 5. Open a Pull Request 114 | 115 | ## License 116 | 117 | This project is licensed under the MIT License - see the LICENSE file for details. 118 | -------------------------------------------------------------------------------- /apps/web/app/config.ts: -------------------------------------------------------------------------------- 1 | export const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL || "http://localhost:8080"; 2 | export const CLOUDFLARE_URL = "https://pub-b2acac8ef6a84c39b35165219b664570.r2.dev"; 3 | -------------------------------------------------------------------------------- /apps/web/app/dashboard/page.tsx: -------------------------------------------------------------------------------- 1 | import { GenerateImage } from "@/components/GenerateImage"; 2 | import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; 3 | import { Train } from "@/components/Train"; 4 | import { Packs } from "@/components/Packs"; 5 | import { Camera } from "@/components/Camera"; 6 | import { redirect } from "next/navigation"; 7 | import { auth } from "@clerk/nextjs/server"; 8 | export const dynamic = "force-dynamic"; 9 | 10 | export default async function DashboardPage() { 11 | const { userId } = await auth(); 12 | 13 | if (!userId) { 14 | redirect("/"); 15 | } 16 | 17 | return ( 18 |
19 |
20 | 21 | 22 | 26 | Camera 27 | 28 | 32 | GenerateImages 33 | 34 | 38 | Packs 39 | 40 | 44 | TrainModel 45 | 46 | 47 | 48 |
49 | 53 | 54 | 55 | 59 | 60 | 61 | 65 | 66 | 67 | 71 | 72 | 73 |
74 |
75 |
76 |
77 | ); 78 | } 79 | -------------------------------------------------------------------------------- /apps/web/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code100x/photo-ai/752707e61d8ad2e1abd0189ebc30668232c82a1d/apps/web/app/favicon.ico -------------------------------------------------------------------------------- /apps/web/app/fonts/GeistMonoVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code100x/photo-ai/752707e61d8ad2e1abd0189ebc30668232c82a1d/apps/web/app/fonts/GeistMonoVF.woff -------------------------------------------------------------------------------- /apps/web/app/fonts/GeistVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code100x/photo-ai/752707e61d8ad2e1abd0189ebc30668232c82a1d/apps/web/app/fonts/GeistVF.woff -------------------------------------------------------------------------------- /apps/web/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import localFont from "next/font/local"; 3 | import "./globals.css"; 4 | import { Appbar } from "@/components/Appbar"; 5 | import { Providers } from "@/components/providers/Providers"; 6 | import { Footer } from "@/components/Footer"; 7 | import Script from "next/script"; 8 | 9 | const geistSans = localFont({ 10 | src: "./fonts/GeistVF.woff", 11 | variable: "--font-geist-sans", 12 | }); 13 | 14 | const geistMono = localFont({ 15 | src: "./fonts/GeistMonoVF.woff", 16 | variable: "--font-geist-mono", 17 | }); 18 | 19 | export const metadata: Metadata = { 20 | title: "100xPhoto - AI-Powered Photo Enhancement", 21 | description: 22 | "Transform your photos with AI-powered enhancement and editing tools.", 23 | }; 24 | 25 | export default function RootLayout({ 26 | children, 27 | }: { 28 | children: React.ReactNode; 29 | }) { 30 | return ( 31 | 32 | 33 |