From 98cf36606b66dd584d066353a45e0d84ba2ec6d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20P=C3=A9rez=20Arcas?= Date: Wed, 29 Apr 2026 06:50:53 +0000 Subject: [PATCH] Subir archivos a "finalexam" --- finalexam/Dockerfile | 13 ++ finalexam/README.md | 234 +++++++++++++++++++++++++++++++++++ finalexam/backup-local.sh | 6 + finalexam/backup.js | 32 +++++ finalexam/docker-compose.yml | 41 ++++++ finalexam/index.html | 1 + finalexam/logs-local.sh | 4 + finalexam/main.jsx | 176 ++++++++++++++++++++++++++ finalexam/nginx.conf | 10 ++ finalexam/package.json | 16 +++ finalexam/prepare-app.sh | 40 ++++++ finalexam/remove-app.sh | 11 ++ finalexam/render.yaml | 40 ++++++ finalexam/server.js | 214 ++++++++++++++++++++++++++++++++ finalexam/styles.css | 111 +++++++++++++++++ 15 files changed, 949 insertions(+) create mode 100644 finalexam/Dockerfile create mode 100644 finalexam/README.md create mode 100644 finalexam/backup-local.sh create mode 100644 finalexam/backup.js create mode 100644 finalexam/docker-compose.yml create mode 100644 finalexam/index.html create mode 100644 finalexam/logs-local.sh create mode 100644 finalexam/main.jsx create mode 100644 finalexam/nginx.conf create mode 100644 finalexam/package.json create mode 100644 finalexam/prepare-app.sh create mode 100644 finalexam/remove-app.sh create mode 100644 finalexam/render.yaml create mode 100644 finalexam/server.js create mode 100644 finalexam/styles.css diff --git a/finalexam/Dockerfile b/finalexam/Dockerfile new file mode 100644 index 0000000..bee0d44 --- /dev/null +++ b/finalexam/Dockerfile @@ -0,0 +1,13 @@ +FROM node:20-alpine + +WORKDIR /app + +COPY package*.json ./ +RUN npm install --omit=dev + +COPY src ./src + +ENV NODE_ENV=production +EXPOSE 10000 + +CMD ["npm", "start"] diff --git a/finalexam/README.md b/finalexam/README.md new file mode 100644 index 0000000..9b08e3c --- /dev/null +++ b/finalexam/README.md @@ -0,0 +1,234 @@ +# TaskNotes Cloud — Final Exam Project + +## 1. Description of the application + +TaskNotes Cloud is a small web application for saving tasks and study notes. +The user can create, list, mark as completed and delete notes. Each note has a title, description and priority. + +The application is useful for students who want to organize work for seminars, exams or small projects. + +## 2. Cloud and architecture used + +The proposed public cloud is **Render**. + +The application has three main components: + +1. **Frontend container** + - React + Vite application. + - Served by Nginx. + - Public HTTPS URL. + +2. **Backend container** + - Node.js + Express REST API. + - Provides endpoints under `/api/notes`. + - Has a `/health` endpoint for health checks. + - Logs HTTP access requests with Morgan. + +3. **PostgreSQL database** + - Managed Render Postgres database. + - Stores persistent notes data. + - Data remains available after backend restart or redeploy. + +Communication: + +- Browser → Frontend over HTTPS. +- Frontend → Backend API over HTTPS. +- Backend → PostgreSQL using the `DATABASE_URL` environment variable. + +## 3. Docker / cloud objects + +Local objects: + +- `frontend` service +- `backend` service +- `db` service +- `postgres_data` persistent volume + +Cloud objects in `render.yaml`: + +- `tasknotes-frontend`: Docker web service +- `tasknotes-backend`: Docker web service +- `tasknotes-db`: Render Postgres database + +## 4. Uploaded files and their content + +- `render.yaml` — Infrastructure as Code configuration for Render. +- `docker-compose.yml` — local three-container version for testing. +- `prepare-app.sh` — prepares and tests the application locally; also gives deployment instructions. +- `remove-app.sh` — removes local containers and local persistent volume. +- `backend/Dockerfile` — builds the backend container. +- `backend/src/server.js` — Express backend API and database initialization. +- `backend/src/backup.js` — exports notes to a JSON backup. +- `frontend/Dockerfile` — builds React frontend and serves it through Nginx. +- `frontend/src/main.jsx` — React user interface. +- `frontend/src/styles.css` — visual styling. +- `scripts/backup-local.sh` — local PostgreSQL backup script. +- `scripts/logs-local.sh` — local backend log viewer. + +## 5. Configuration + +The application is configurable using environment variables: + +Backend: + +- `DATABASE_URL` — PostgreSQL connection string. +- `CORS_ORIGIN` — allowed frontend origins. +- `PORT` — backend port. +- `NODE_ENV` — production/development mode. + +Frontend: + +- `VITE_API_URL` — public URL of the backend API. + +Secrets are not stored in Git. The database connection string is injected by Render from the managed database. + +## 6. How to run locally + +Requirements: + +- Docker +- Docker Compose +- Git +- Bash terminal + +Run: + +```bash +chmod +x prepare-app.sh remove-app.sh scripts/*.sh +./prepare-app.sh +``` + +Open: + +- Frontend: `http://localhost:8080` +- Backend health: `http://localhost:10000/health` + +## 7. How to deploy to the public cloud + +1. Upload the `sk1` directory to Git. +2. Make sure `render.yaml` is in the repository root or configure the correct Blueprint path. +3. Create a Render Blueprint from `render.yaml`. +4. Render will create: + - frontend service + - backend service + - PostgreSQL database +5. After the backend is deployed, copy the backend public URL. +6. Set the frontend environment variable: + +```text +VITE_API_URL=https://YOUR-BACKEND-URL.onrender.com +``` + +7. Redeploy the frontend. +8. Open the frontend HTTPS URL in the browser. + +## 8. How to use the application + +1. Open the public frontend URL in a browser. +2. Write a title and details. +3. Select priority. +4. Click **Add note**. +5. Mark notes as completed or delete them. + +## 9. Backup instructions + +Local backup: + +```bash +./scripts/backup-local.sh +``` + +This creates a SQL dump in the `backups/` directory. + +Backend JSON export: + +```bash +cd backend +DATABASE_URL="your_database_url" npm run backup +``` + +Cloud backup: + +- Use Render Postgres backup/export options, or connect with `pg_dump` using the external database URL. +- Example: + +```bash +pg_dump "$DATABASE_URL" > tasknotes-cloud-backup.sql +``` + +## 10. Access logs from the internet + +Local logs: + +```bash +./scripts/logs-local.sh +``` + +Cloud logs: + +- Open the backend service logs in Render, or use the Render CLI if configured. +- The backend uses Morgan with `combined` format, so it logs IP, method, URL, status code, response time and user agent. + +## 11. Conditions for scripts + +`prepare-app.sh` can be run when: + +- Docker is installed. +- Docker Compose is available. +- Ports `8080`, `10000` and `5432` are free. +- The user is in the project root directory. + +`remove-app.sh` can be run when: + +- Docker is installed. +- The local containers were created with Docker Compose. + +## 12. Cost analysis for one year + +Assumption: + +- 1000 users per day. +- Database/file size: 50 GB. +- Small educational application. +- Two small web services and one PostgreSQL database. + +Example Render estimate: + +- Frontend web service: Free or Starter. For production estimate, Starter: 7 USD/month. +- Backend web service: Starter: 7 USD/month. +- PostgreSQL Basic-256mb: 6 USD/month. +- PostgreSQL storage: 50 GB × 0.30 USD/GB/month = 15 USD/month. +- Total monthly estimate: 7 + 7 + 6 + 15 = 35 USD/month. +- Total yearly estimate: 35 × 12 = 420 USD/year. + +This is an approximate educational estimate. Real cost depends on traffic, bandwidth, region, scaling and selected plans. + +## 13. External resources and generative AI use + +External resources used: + +- Render documentation for Blueprints, Docker web services, environment variables, HTTPS and PostgreSQL. +- Docker documentation for Dockerfiles and Docker Compose. +- PostgreSQL documentation for database concepts and backup using `pg_dump`. + +Generative AI use: + +- ChatGPT was used to help design the structure of the project, generate example configuration files, improve documentation and prepare defense explanations. +- The final implementation was reviewed and adapted for this exam project. + +## 14. Defense summary + +Short explanation: + +> My project is TaskNotes Cloud, a web application for students to save tasks and study notes. It has a React frontend, a Node.js backend and a PostgreSQL database. The deployment is defined in configuration files, mainly `render.yaml` and Dockerfiles. The app is publicly accessible using HTTPS, stores data persistently in PostgreSQL, restarts automatically in the cloud, and can be backed up with `pg_dump`. + +Important points to mention: + +- Three components: frontend, backend, database. +- HTTPS certificate is provided by Render. +- Secrets are environment variables, not committed to Git. +- Database is persistent. +- Backend health check is `/health`. +- Logs can be viewed in Render. +- Backup can be done with `pg_dump`. +- Local testing is possible with Docker Compose. diff --git a/finalexam/backup-local.sh b/finalexam/backup-local.sh new file mode 100644 index 0000000..d44855f --- /dev/null +++ b/finalexam/backup-local.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -euo pipefail + +mkdir -p backups +docker exec tasknotes-db pg_dump -U tasknotes -d tasknotes > "backups/tasknotes-$(date +%Y%m%d-%H%M%S).sql" +echo "Backup created in backups/" diff --git a/finalexam/backup.js b/finalexam/backup.js new file mode 100644 index 0000000..8129ed7 --- /dev/null +++ b/finalexam/backup.js @@ -0,0 +1,32 @@ +import pg from "pg"; +import fs from "fs"; +import dotenv from "dotenv"; + +dotenv.config(); + +const { Pool } = pg; +const DATABASE_URL = process.env.DATABASE_URL; + +if (!DATABASE_URL) { + console.error("Missing DATABASE_URL environment variable"); + process.exit(1); +} + +const pool = new Pool({ + connectionString: DATABASE_URL, + ssl: process.env.NODE_ENV === "production" ? { rejectUnauthorized: false } : false +}); + +const result = await pool.query("SELECT * FROM notes ORDER BY id ASC"); +const backup = { + exportedAt: new Date().toISOString(), + table: "notes", + rows: result.rows +}; + +fs.mkdirSync("backups", { recursive: true }); +const filename = `backups/notes-backup-${new Date().toISOString().replace(/[:.]/g, "-")}.json`; +fs.writeFileSync(filename, JSON.stringify(backup, null, 2)); +console.log(`Backup written to ${filename}`); + +await pool.end(); diff --git a/finalexam/docker-compose.yml b/finalexam/docker-compose.yml new file mode 100644 index 0000000..2701a9d --- /dev/null +++ b/finalexam/docker-compose.yml @@ -0,0 +1,41 @@ +services: + db: + image: postgres:16-alpine + container_name: tasknotes-db + restart: unless-stopped + environment: + POSTGRES_DB: tasknotes + POSTGRES_USER: tasknotes + POSTGRES_PASSWORD: tasknotes_password + volumes: + - postgres_data:/var/lib/postgresql/data + ports: + - "5432:5432" + + backend: + build: ./backend + container_name: tasknotes-backend + restart: unless-stopped + environment: + DATABASE_URL: postgres://tasknotes:tasknotes_password@db:5432/tasknotes + CORS_ORIGIN: http://localhost:5173,http://localhost:8080 + NODE_ENV: development + PORT: 10000 + ports: + - "10000:10000" + depends_on: + - db + + frontend: + build: ./frontend + container_name: tasknotes-frontend + restart: unless-stopped + environment: + VITE_API_URL: http://localhost:10000 + ports: + - "8080:80" + depends_on: + - backend + +volumes: + postgres_data: \ No newline at end of file diff --git a/finalexam/index.html b/finalexam/index.html new file mode 100644 index 0000000..9b7fe07 --- /dev/null +++ b/finalexam/index.html @@ -0,0 +1 @@ +TaskNotes Cloud
diff --git a/finalexam/logs-local.sh b/finalexam/logs-local.sh new file mode 100644 index 0000000..716d897 --- /dev/null +++ b/finalexam/logs-local.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail + +docker compose logs -f backend diff --git a/finalexam/main.jsx b/finalexam/main.jsx new file mode 100644 index 0000000..8a44429 --- /dev/null +++ b/finalexam/main.jsx @@ -0,0 +1,176 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { createRoot } from 'react-dom/client'; +import './styles.css'; + +const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:10000'; + +function formatDate(date) { + if (!date) return 'No deadline'; + return new Date(date + 'T00:00:00').toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }); +} + +function isOverdue(task) { + if (!task.deadline || task.completed) return false; + const today = new Date(); + today.setHours(0, 0, 0, 0); + return new Date(task.deadline + 'T00:00:00') < today; +} + +function App() { + const [tasks, setTasks] = useState([]); + const [status, setStatus] = useState('Loading...'); + const [filter, setFilter] = useState('all'); + const [form, setForm] = useState({ + title: '', + description: '', + subject: '', + task_type: 'assignment', + priority: 'normal', + deadline: '', + }); + + async function loadTasks() { + try { + const res = await fetch(`${API_URL}/api/tasks`); + if (!res.ok) throw new Error('API error'); + const data = await res.json(); + setTasks(data); + setStatus('Connected to backend and PostgreSQL'); + } catch (error) { + setStatus('Backend is not reachable. Check VITE_API_URL and backend logs.'); + } + } + + useEffect(() => { loadTasks(); }, []); + + async function addTask(event) { + event.preventDefault(); + if (!form.title.trim()) return; + await fetch(`${API_URL}/api/tasks`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(form), + }); + setForm({ title: '', description: '', subject: '', task_type: 'assignment', priority: 'normal', deadline: '' }); + loadTasks(); + } + + async function toggleTask(id) { + await fetch(`${API_URL}/api/tasks/${id}/toggle`, { method: 'PATCH' }); + loadTasks(); + } + + async function deleteTask(id) { + await fetch(`${API_URL}/api/tasks/${id}`, { method: 'DELETE' }); + loadTasks(); + } + + const stats = useMemo(() => { + const total = tasks.length; + const completed = tasks.filter(t => t.completed).length; + const pending = total - completed; + const high = tasks.filter(t => t.priority === 'high' && !t.completed).length; + const overdue = tasks.filter(isOverdue).length; + return { total, pending, completed, high, overdue }; + }, [tasks]); + + const filteredTasks = tasks.filter((task) => { + if (filter === 'pending') return !task.completed; + if (filter === 'completed') return task.completed; + if (filter === 'high') return task.priority === 'high' && !task.completed; + if (filter === 'overdue') return isOverdue(task); + return true; + }); + + return ( +
+
+
+ Final cloud exam project +

Student Study Planner

+

+ Manage assignments, exams, projects and study deadlines. Data is stored in PostgreSQL, + so it survives restarts and redeploys. +

+
+
+ Cloud architecture + React frontend + Node.js backend API + PostgreSQL persistent database +
+
+ +
+
Total tasks{stats.total}
+
Pending{stats.pending}
+
Completed{stats.completed}
+
High priority{stats.high}
+
Overdue{stats.overdue}
+
+ +
+
+
+

Add a study task

+

Create a clear plan for homework, exams and projects.

+
+ {status} +
+ +
+ setForm({ ...form, title: e.target.value })} placeholder="Task title, e.g. Prepare Kubernetes defense" /> + setForm({ ...form, subject: e.target.value })} placeholder="Subject, e.g. Cloud Technologies" /> +