diff --git a/Dockerfile.backend b/Dockerfile.backend index b2d58b1..be660d8 100644 --- a/Dockerfile.backend +++ b/Dockerfile.backend @@ -10,11 +10,9 @@ RUN pnpm install --frozen-lockfile WORKDIR /app/apps/backend -COPY apps/backend/.env .env - RUN pnpm --filter backend exec prisma generate RUN pnpm --filter backend exec prisma migrate deploy RUN pnpm --filter backend build -CMD ["pnpm", "--filter", "backend", "start:prod"] +CMD ["pnpm", "--filter", "backend", "start:prod"] \ No newline at end of file diff --git a/Dockerfile.frontend b/Dockerfile.frontend index b219ac2..6faee6d 100644 --- a/Dockerfile.frontend +++ b/Dockerfile.frontend @@ -8,7 +8,6 @@ RUN corepack enable && corepack prepare pnpm@latest --activate RUN pnpm install --frozen-lockfile -WORKDIR /app/apps/frontend RUN pnpm build diff --git a/README.md b/README.md index 3cdc9d0..ac5f5a7 100644 --- a/README.md +++ b/README.md @@ -45,11 +45,68 @@ --- -## 🚀 Getting Started (Coming Soon) +# 🚀 Getting Started + + + +## 🐳 Docker & Production Deployment + +Dyan ships with a production-ready Docker Compose setup that runs the backend, frontend, and SQLite database with a single command. + +### 1. Prerequisites + +- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/) installed + +### 2. Environment Variables + +Create a `.env` file in the project root (same directory as `docker-compose.yml`) with the following content: + +```env +# .env +JWT_SECRET=your_super_secret_jwt_key +VITE_API_URL=http://backend:3000 +``` + +- `JWT_SECRET` is required for backend authentication. +- `VITE_API_URL` should point to the backend service as seen by the frontend container (use `http://backend:3000` for Docker Compose). + +You can also refer to `apps/backend/example.env` and `apps/frontend/example.env` for more details. + +### 3. Build & Run + +From the project root, run: + +```bash +docker compose up --build +``` + +- The backend will be available at [http://localhost:3000](http://localhost:3000) +- The frontend will be available at [http://localhost:5173](http://localhost:5173) + +### 4. Data Persistence + +- The SQLite database is persisted in a Docker volume (`sqlite-data`), so your data survives container restarts. + +### 5. Customization + +- To change ports or add environment variables, edit `docker-compose.yml`. +- For production, consider setting up HTTPS and using a production-grade database. + +### 6. Stopping & Cleaning Up + +To stop the services: + +```bash +docker compose down +``` + +To remove all data (including the SQLite database): + +```bash +docker compose down -v +``` -> Full setup instructions, Docker templates, and CLI will be available soon. ---- ## 💡 Example Use Cases diff --git a/apps/backend/example.env b/apps/backend/example.env new file mode 100644 index 0000000..2cf5224 --- /dev/null +++ b/apps/backend/example.env @@ -0,0 +1 @@ +JWT_SECRET="YOUR_SECRET" \ No newline at end of file diff --git a/apps/backend/src/auth/auth.controller.ts b/apps/backend/src/auth/auth.controller.ts index 15132e9..66dc997 100644 --- a/apps/backend/src/auth/auth.controller.ts +++ b/apps/backend/src/auth/auth.controller.ts @@ -73,7 +73,21 @@ async me(@Req() req: Request) { }); } - return this.authService.sendMagicLink(email); + // Generate the magic link (modify sendMagicLink to return the link) + // const magicLink = await this.authService.sendMagicLink(email); + const mockMode = process.env.MOCK_EMAIL_ENABLED === 'true'; + const magicLink = await this.authService.sendMagicLink(email, mockMode); + + // Check the environment variable + if (process.env.MOCK_EMAIL_ENABLED === 'true') { + console.log(`\n=== MOCK LOGIN LINK ===\n${magicLink}\n======================\n`); + return { message: 'Mock login link printed to console.' }; + } + + // Otherwise, proceed as normal (send email) + return { message: 'Login link sent to email.' }; + + // return this.authService.sendMagicLink(email); } @Get('verify') diff --git a/apps/frontend/.gitignore b/apps/frontend/.gitignore index a547bf3..438657a 100644 --- a/apps/frontend/.gitignore +++ b/apps/frontend/.gitignore @@ -11,6 +11,7 @@ node_modules dist dist-ssr *.local +.env # Editor directories and files .vscode/* diff --git a/apps/frontend/example.env b/apps/frontend/example.env new file mode 100644 index 0000000..e01aec6 --- /dev/null +++ b/apps/frontend/example.env @@ -0,0 +1 @@ +VITE_API_URL="http://localhost:3000" \ No newline at end of file diff --git a/apps/frontend/src/App.tsx b/apps/frontend/src/App.tsx index 35f629b..38d5e95 100644 --- a/apps/frontend/src/App.tsx +++ b/apps/frontend/src/App.tsx @@ -1,11 +1,11 @@ import { useEffect, useState } from 'react'; import './App.css' - function App() { const [message, setMessage] = useState(''); - + const apiUrl = import.meta.env.VITE_API_URL; + useEffect(() => { - fetch('http://localhost:3000/api/hello') + fetch(`${apiUrl}/api/hello`) .then(res => res.json()) .then(data => setMessage(data.message)) .catch(err => console.error(err)); diff --git a/apps/frontend/src/components/SavedEndpointsSidebar.tsx b/apps/frontend/src/components/SavedEndpointsSidebar.tsx index 3832b90..7e16046 100644 --- a/apps/frontend/src/components/SavedEndpointsSidebar.tsx +++ b/apps/frontend/src/components/SavedEndpointsSidebar.tsx @@ -1,13 +1,13 @@ import { Button } from "../components/ui/button"; import { Badge } from "../components/ui/badge"; import { ScrollArea } from "../components/ui/scroll-area"; -import { Separator } from "../components/ui/separator"; +// import { Separator } from "../components/ui/separator"; import { PlusCircle, Pencil, Trash2, Settings, - Code2, + // Code2, Layers, Globe, } from "lucide-react"; diff --git a/apps/frontend/src/pages/builder.tsx b/apps/frontend/src/pages/builder.tsx index e1afb7d..5bdb28a 100644 --- a/apps/frontend/src/pages/builder.tsx +++ b/apps/frontend/src/pages/builder.tsx @@ -60,10 +60,12 @@ export default function BuilderPage() { const [liveHeaders, setLiveHeaders] = useState("{}"); const [liveQuery, setLiveQuery] = useState("{}"); + const apiUrl = import.meta.env.VITE_API_URL; + useEffect(() => { const loadEndpoints = async () => { try { - const res = await fetch("http://localhost:3000/dyan/endpoints", { + const res = await fetch(`${apiUrl}/dyan/endpoints`, { credentials: "include", }); const data = await res.json(); @@ -96,7 +98,7 @@ export default function BuilderPage() { const methodType = existing ? "PUT" : "POST"; - const res = await fetch("http://localhost:3000/dyan/endpoint", { + const res = await fetch(`${apiUrl}/dyan/endpoint`, { method: methodType, credentials: "include", headers: { "Content-Type": "application/json" }, @@ -135,7 +137,7 @@ export default function BuilderPage() { try { const res = await fetch( - `http://localhost:3000/dyan/endpoint?path=${encodeURIComponent(ep.path)}&method=${ep.method}`, + `${apiUrl}/dyan/endpoint?path=${encodeURIComponent(ep.path)}&method=${ep.method}`, { credentials: "include", } @@ -152,7 +154,7 @@ export default function BuilderPage() { const ep = endpoints[index]; try { - await fetch("http://localhost:3000/dyan/endpoint", { + await fetch(`${apiUrl}/dyan/endpoint`, { method: "DELETE", credentials: "include", headers: { "Content-Type": "application/json" }, @@ -195,7 +197,7 @@ export default function BuilderPage() { try { const queryParams = new URLSearchParams(parsedQuery).toString(); - const targetUrl = `http://localhost:3000${path}${queryParams ? `?${queryParams}` : ""}`; + const targetUrl = `${apiUrl}${path}${queryParams ? `?${queryParams}` : ""}`; const res = await fetch(targetUrl, { method, @@ -222,7 +224,7 @@ export default function BuilderPage() { const handleLogout = async () => { try { - await fetch("http://localhost:3000/dyan/auth/logout", { + await fetch(`${apiUrl}/dyan/auth/logout`, { method: "POST", credentials: "include", }); diff --git a/apps/frontend/src/pages/login.tsx b/apps/frontend/src/pages/login.tsx index 23498c9..4f92a61 100644 --- a/apps/frontend/src/pages/login.tsx +++ b/apps/frontend/src/pages/login.tsx @@ -13,12 +13,14 @@ export default function LoginPage() { ); const [errorMessage, setErrorMessage] = useState(""); + const apiUrl = import.meta.env.VITE_API_URL; + const requestLogin = async () => { setStatus("loading"); setErrorMessage(""); try { - const res = await fetch("http://localhost:3000/dyan/auth/request", { + const res = await fetch(`${apiUrl}/dyan/auth/request`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email }), diff --git a/apps/frontend/src/pages/mainPage.tsx b/apps/frontend/src/pages/mainPage.tsx index ec61fa4..9a27136 100644 --- a/apps/frontend/src/pages/mainPage.tsx +++ b/apps/frontend/src/pages/mainPage.tsx @@ -5,9 +5,10 @@ import BuilderPage from "./builder"; export default function MainPage() { const [loading, setLoading] = useState(true); const navigate = useNavigate(); + const apiUrl = import.meta.env.VITE_API_URL; useEffect(() => { - fetch("http://localhost:3000/dyan/auth/me", { + fetch(`${apiUrl}/dyan/auth/me`, { credentials: "include", }) .then((res) => { diff --git a/apps/frontend/src/pages/verify.tsx b/apps/frontend/src/pages/verify.tsx index b475f06..d0e098b 100644 --- a/apps/frontend/src/pages/verify.tsx +++ b/apps/frontend/src/pages/verify.tsx @@ -9,6 +9,7 @@ export default function VerifyPage() { ); const [searchParams] = useSearchParams(); const navigate = useNavigate(); + const apiUrl = import.meta.env.VITE_API_URL; useEffect(() => { const verify = async () => { @@ -20,7 +21,7 @@ export default function VerifyPage() { try { const res = await fetch( - `http://localhost:3000/dyan/auth/verify?token=${token}`, + `${apiUrl}/dyan/auth/verify?token=${token}`, { credentials: "include", } diff --git a/docker-compose.yml b/docker-compose.yml index 724b64d..f077f85 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,11 @@ services: dockerfile: Dockerfile.backend ports: - "3000:3000" + environment: + - JWT_SECRET=${JWT_SECRET} + - BACKEND_PORT=3000 + volumes: + - sqlite-data:/app/apps/backend/prisma frontend: build: @@ -12,5 +17,10 @@ services: dockerfile: Dockerfile.frontend ports: - "5173:80" + environment: + - VITE_API_URL=${VITE_API_URL} depends_on: - backend + +volumes: + sqlite-data: \ No newline at end of file