diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..ed948b63 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,82 @@ +name: Deploy to Vercel + +on: + push: + branches: + - main + +jobs: + deploy-frontend: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: "21.2.0" + + - name: Install Vercel CLI + run: npm install -g vercel@latest + + - name: List files in client directory + run: ls -la ./client + + - name: Link and pull Vercel project for frontend + run: | + vercel link --yes --project ${{ secrets.FRONTEND_PROJECT_ID }} --scope ${{ secrets.VERCEL_TEAM }} --token=${{ secrets.VERCEL_TOKEN }} + vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} + working-directory: ./client + + - name: Install frontend dependencies + run: npm install + working-directory: ./client + + - name: Build frontend + run: npm run build + working-directory: ./client + + - name: Deploy frontend to Vercel + run: vercel --token ${{ secrets.VERCEL_TOKEN }} + working-directory: ./client + + deploy-backend: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: "21.2.0" + + - name: Install Vercel CLI + run: npm install -g vercel@latest + + - name: List files in server directory + run: ls -la ./server + + - name: Link and pull Vercel project for backend + run: | + vercel link --yes --project ${{ secrets.BACKEND_PROJECT_ID }} --scope ${{ secrets.VERCEL_TEAM }} --token=${{ secrets.VERCEL_TOKEN }} + vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} + working-directory: ./server + + - name: Install backend dependencies + run: npm install + working-directory: ./server + + - name: Build backend + run: npm run build + working-directory: ./server + + - name: List files in server directory after build + run: ls -la ./server/public + + - name: Deploy backend to Vercel + run: vercel --token ${{ secrets.VERCEL_TOKEN }} + working-directory: ./server diff --git a/.gitignore b/.gitignore index 907aa4c4..7d1bee05 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,6 @@ dev dist-ssr client/dist *.local -dist # Editor directories and files .vscode/* diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 00000000..e985853e --- /dev/null +++ b/client/.gitignore @@ -0,0 +1 @@ +.vercel diff --git a/client/vercel.json b/client/vercel.json deleted file mode 100644 index 3cc3e4ea..00000000 --- a/client/vercel.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "rewrites": [ - { "source": "/robots.txt", "destination": "/public/robots.txt" }, - { "source": "/sitemap.xml", "destination": "/public/sitemap.xml" }, - { "source": "/(.*)", "destination": "/" } - ] -} diff --git a/package-lock.json b/package-lock.json index fc10297f..beea970e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,9 @@ "": { "name": "thefluentspanishhouse", "version": "1.0.0", + "dependencies": { + "prettier": "^3.4.2" + }, "devDependencies": { "concurrently": "^9.1.2" } @@ -182,6 +185,21 @@ "dev": true, "license": "MIT" }, + "node_modules/prettier": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", diff --git a/package.json b/package.json index ef4e20bf..c31faec1 100644 --- a/package.json +++ b/package.json @@ -21,5 +21,8 @@ "type": "GNU", "url": "https://www.gnu.org/licenses/gpl-3.0.html" } - ] + ], + "dependencies": { + "prettier": "^3.4.2" + } } diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 00000000..e985853e --- /dev/null +++ b/server/.gitignore @@ -0,0 +1 @@ +.vercel diff --git a/server/lib/resend/resend.ts b/server/lib/resend/resend.ts index 4074eba3..f95f1537 100644 --- a/server/lib/resend/resend.ts +++ b/server/lib/resend/resend.ts @@ -1,7 +1,7 @@ import dotenv from "dotenv"; dotenv.config(); //////////////////////////////////////////////////////////////// -import { Message, NoteType, SubscriberType } from "types/types"; +import { NoteType, SubscriberType } from "types/types"; import { Resend } from "resend"; // Configurar el cliente de Mandrill diff --git a/server/package-lock.json b/server/package-lock.json index a9ece140..d7202adc 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -28,7 +28,6 @@ "@types/express": "^4.17.21", "@types/jsonwebtoken": "^9.0.6", "@types/mailchimp__mailchimp_marketing": "^3.0.20", - "@types/mailchimp__mailchimp_transactional": "^1.0.10", "dotenv": "^16.4.5", "ts-node": "^10.9.2", "typescript": "^5.5.4" @@ -1220,16 +1219,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/mailchimp__mailchimp_transactional": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@types/mailchimp__mailchimp_transactional/-/mailchimp__mailchimp_transactional-1.0.10.tgz", - "integrity": "sha512-W8EcDqZspM4Mb6shET603kce4C11U29C3s0o8ZchWWcMyaz4MPa2nTyAVUSwnY0atuPii6Jpg3Gs6MuwupT3xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "axios": "^1.6.7" - } - }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -2848,20 +2837,6 @@ "node": ">= 0.8" } }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", diff --git a/server/package.json b/server/package.json index 8df370d0..6c2f2e6b 100644 --- a/server/package.json +++ b/server/package.json @@ -4,14 +4,13 @@ "type": "module", "main": "index.ts", "scripts": { - "start": "npm run build && node dist/index.js", + "start": "node public/src/index.js", "dev": "node --loader ts-node/esm src/index.js", "test": "npm run build && playwright test", "build": "tsc" }, "dependencies": { "@mailchimp/mailchimp_marketing": "^3.0.80", - "@vercel/node": "^2.10.3", "axios": "^1.7.7", "body-parser": "^1.20.2", "cors": "^2.8.5", diff --git a/server/routes/resend.ts b/server/routes/resend.ts deleted file mode 100644 index feb64bd4..00000000 --- a/server/routes/resend.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Router } from "express"; -import { submitEmailStudent, submitNote } from "../lib/resend/resend.js"; -import { SubscriberType, type NoteType } from "types/types"; -import { log, verifyIdToken } from "../middelware/token-logs.js"; - -const router = Router(); - -// Enviamos un email con una nota de contact us -router.post("/note", log, verifyIdToken, async (req, res) => { - const newNote: NoteType = req.body; - - if (!newNote.email_user || !newNote.username || !newNote.subject || !newNote.note) - return res.status(400).send({ message: "Missing required fields" }); - - submitNote(newNote) - .then((response) => res.status(201).json({ message: "Email sent successfully", response })) - .catch((error) => res.status(500).send({ message: "Error sending email", error })); -}); - -// Avisamos de que se a suscrito un nuevo estudiante a una clase -router.post("/newstudent", log, verifyIdToken, async (req, res) => { - const newSubcriber: SubscriberType = req.body; - - console.log(newSubcriber); - if (!newSubcriber.email || !newSubcriber.name || !newSubcriber.lastname || !newSubcriber.class) - return res.status(400).send({ message: "Missing required fields" }); - - console.log("Enviando email"); - submitEmailStudent(newSubcriber) - .then((response) => res.status(201).json({ message: "Email sent successfully", response })) - .catch((error) => res.status(500).send({ message: "Error sending email", error })); -}); - -export { router }; diff --git a/server/routes/routes.ts b/server/routes/routes.ts index 428d2921..cdca8644 100644 --- a/server/routes/routes.ts +++ b/server/routes/routes.ts @@ -12,7 +12,12 @@ readdirSync(PATH_ROUTER).forEach((file) => { const fileClean = cleanExtension(file); const fileExtension = path.extname(file); - if (fileClean !== "routes" && (fileExtension === ".js" || fileExtension === ".ts") && !file.endsWith(".map")) { + if ( + fileClean !== "routes" && + (fileExtension === ".js" || fileExtension === ".ts") && + !file.endsWith(".js.map") && + !file.endsWith(".ts.map") + ) { const modulePath = path.join(PATH_ROUTER, `${fileClean}${fileExtension}`); const moduleURL = pathToFileURL(modulePath).href; diff --git a/server/index.ts b/server/src/index.ts similarity index 74% rename from server/index.ts rename to server/src/index.ts index 0bce527a..c8f6024c 100644 --- a/server/index.ts +++ b/server/src/index.ts @@ -1,12 +1,12 @@ import dotenv from "dotenv"; dotenv.config(); /////////////////////////////////////////// -import { connectDB } from "./src/mongodb/mongodb.js"; +import { connectDB } from "./mongodb/mongodb.js"; import express from "express"; import cors from "cors"; -import { router } from "./routes/routes.js"; +import { router } from "../routes/routes.js"; import net from "net"; -import admin from "./lib/firebase/firebase-config.js"; +import admin from "../lib/firebase/firebase-config.js"; // Extendemos el límite para que pueda almacenar imagenes en base64 async function inicializeApp() { @@ -60,24 +60,26 @@ async function inicializeApp() { } }); - // Si es development y preview asignamos el puerto disponible a partir de 8080 - if (process.env.NODE_ENV !== "production") { - const PORT_BACKEND = 8080; - // Creamos una funcion flecha que devuelbe una promesa + return app; +} + +async function startServer() { + try { + const app = await inicializeApp(); + const PORT_BACKEND = parseInt(process.env.PORT || "8080"); + + // Funcion para chekear si esta ocupado const checkPort = (port: number): Promise => { return new Promise((resolve, reject) => { // Creamos un servidor con net nativo de (NodeJS) + // He intentamos escuchar en el puerto const server = net.createServer(); - // Intentar escuchar en el puerto server.listen(port); // Verificamos si el puerto esta en uso si lo esta devuleve false, si ocurre un error lo rechaza server.once("error", (err: any) => { - if (err.code === "EADDRINUSE") { - resolve(false); - } else { - reject(err); - } + if (err.code === "EADDRINUSE") resolve(false); + else reject(err); }); // Si el puerto esta libre lo cerramos y resolvemos la promesa server.once("listening", () => { @@ -87,35 +89,37 @@ async function inicializeApp() { }); }; - const startServer = async (port: number) => { - // Encontramos un puerto libre de [8080, 8090] + // Función para cambiar el puerto si esta ocupado + const changePort = async (port: number) => { + // Puertos libres de [8080, 8090] if (port > 8090) { console.log("No ports available"); return; } + // Encontra un puerto libre y se lo asigna al servidor, si no encuentra uno libre lo asigna al siguiente const isPortFree = await checkPort(port); + + // Si el puerto esta libre lo asigna al servidor if (isPortFree) { app.listen(port, () => { console.log(`Server running: http://localhost:${port}`); }); } else { - console.log(`Port ${port} is in use, please choose another port.`); - startServer(port + 1); + console.log(`Port ${port} is in use, please choose another port`); + changePort(port + 1); } }; - startServer(PORT_BACKEND).catch((error) => { + changePort(PORT_BACKEND).catch((error) => { console.error("Error starting the server:", error); }); - } else { - const PORT_BACKEND = 8080; - app.listen(PORT_BACKEND, () => { - console.log(`Server runing: http://localhost:${PORT_BACKEND}`); - }); + } catch (error) { + console.error("Error starting the server:", error); + process.exit(1); } } -inicializeApp().catch((error) => { - console.error("Error starting the server:", error); -}); +if (process.env.NODE_ENV !== "production") startServer(); + +export default inicializeApp; diff --git a/server/tsconfig.json b/server/tsconfig.json index 61df3bf8..d76e1367 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -6,7 +6,7 @@ "rootDir": "./" /* Specify the root folder within your source files. */, "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, "baseUrl": "./" /* Specify the base directory to resolve non-relative module names. */, - "outDir": "dist" /* Specify an output folder for all emitted files. */, + "outDir": "public" /* Specify an output folder for all emitted files. */, "paths": { "types/*": ["types/*"] } /* Specify a set of entries that re-map imports to additional lookup locations. */, @@ -20,5 +20,5 @@ "esm": true } /* Enable all strict type-checking options. */, "include": ["./**/*.ts", "types"], - "exclude": ["node_modules", "dist"] + "exclude": ["node_modules", "public"] } diff --git a/server/vercel.json b/server/vercel.json index 957d5ead..37b3de23 100644 --- a/server/vercel.json +++ b/server/vercel.json @@ -1,16 +1,15 @@ { "version": 2, + "name": "thefluenspanishhouse-server", "builds": [ { - "src": "package.json", + "src": "public/src/index.js", "use": "@vercel/node", - "config": { "distDir": "dist" } + "config": { + "outputDirectory": "public" + } } ], - "routes": [ - { - "src": "/(.*)", - "dest": "/dist/index.js" - } - ] + + "routes": [{ "src": "/(.*)", "dest": "public/src/index.js" }] }