From e581827e422cebfdfd67ea2f6361d03c71403087 Mon Sep 17 00:00:00 2001 From: Sarukesh Boominathan Date: Tue, 12 May 2026 15:19:22 +0200 Subject: [PATCH] SK1 --- SK1/README.md | 269 ++++++++++++++++ SK1/backend/Dockerfile | 12 + SK1/backend/main.py | 169 ++++++++++ SK1/backend/requirements.txt | 5 + SK1/docker-compose.yml | 34 ++ SK1/frontend/Dockerfile | 20 ++ SK1/frontend/index.html | 13 + SK1/frontend/nginx.conf | 31 ++ SK1/frontend/package.json | 20 ++ SK1/frontend/src/App.jsx | 31 ++ SK1/frontend/src/CreatePaste.jsx | 127 ++++++++ SK1/frontend/src/ViewPaste.jsx | 167 ++++++++++ SK1/frontend/src/index.css | 302 ++++++++++++++++++ SK1/frontend/src/main.jsx | 11 + SK1/frontend/vite.config.js | 11 + SK1/scripts/backup.sh | 73 +++++ SK1/scripts/prepare-app.sh | 172 ++++++++++ SK1/scripts/remove-app.sh | 52 +++ {noc-docker => Z1}/Documentation.pdf | Bin {noc-docker => Z1}/README.md | 0 {noc-docker => Z1}/backend/Dockerfile | 0 {noc-docker => Z1}/backend/app.py | 0 {noc-docker => Z1}/backend/requirements.txt | 0 {noc-docker => Z1}/docker-compose.yml | 0 {noc-docker => Z1}/frontend/Dockerfile | 0 {noc-docker => Z1}/frontend/app.py | 0 {noc-docker => Z1}/frontend/requirements.txt | 0 .../frontend/templates/index.html | 0 {noc-docker => Z1}/prepare-app.sh | 0 {noc-docker => Z1}/probe/Dockerfile | 0 {noc-docker => Z1}/probe/probe.py | 0 {noc-docker => Z1}/remove-app.sh | 0 {noc-docker => Z1}/start-app.sh | 0 {noc-docker => Z1}/stop-app.sh | 0 Z2/z2/port-forward.log | 240 ++++++++++++++ Z2/z2/show-url.sh | 9 - 36 files changed, 1759 insertions(+), 9 deletions(-) create mode 100644 SK1/README.md create mode 100644 SK1/backend/Dockerfile create mode 100644 SK1/backend/main.py create mode 100644 SK1/backend/requirements.txt create mode 100644 SK1/docker-compose.yml create mode 100644 SK1/frontend/Dockerfile create mode 100644 SK1/frontend/index.html create mode 100644 SK1/frontend/nginx.conf create mode 100644 SK1/frontend/package.json create mode 100644 SK1/frontend/src/App.jsx create mode 100644 SK1/frontend/src/CreatePaste.jsx create mode 100644 SK1/frontend/src/ViewPaste.jsx create mode 100644 SK1/frontend/src/index.css create mode 100644 SK1/frontend/src/main.jsx create mode 100644 SK1/frontend/vite.config.js create mode 100644 SK1/scripts/backup.sh create mode 100644 SK1/scripts/prepare-app.sh create mode 100644 SK1/scripts/remove-app.sh rename {noc-docker => Z1}/Documentation.pdf (100%) rename {noc-docker => Z1}/README.md (100%) rename {noc-docker => Z1}/backend/Dockerfile (100%) rename {noc-docker => Z1}/backend/app.py (100%) rename {noc-docker => Z1}/backend/requirements.txt (100%) rename {noc-docker => Z1}/docker-compose.yml (100%) rename {noc-docker => Z1}/frontend/Dockerfile (100%) rename {noc-docker => Z1}/frontend/app.py (100%) rename {noc-docker => Z1}/frontend/requirements.txt (100%) rename {noc-docker => Z1}/frontend/templates/index.html (100%) rename {noc-docker => Z1}/prepare-app.sh (100%) rename {noc-docker => Z1}/probe/Dockerfile (100%) rename {noc-docker => Z1}/probe/probe.py (100%) rename {noc-docker => Z1}/remove-app.sh (100%) rename {noc-docker => Z1}/start-app.sh (100%) rename {noc-docker => Z1}/stop-app.sh (100%) delete mode 100644 Z2/z2/show-url.sh diff --git a/SK1/README.md b/SK1/README.md new file mode 100644 index 0000000..b12aa32 --- /dev/null +++ b/SK1/README.md @@ -0,0 +1,269 @@ +# πŸ” PasteVault + +A secure, self-hosted code and text sharing tool with syntax highlighting, expiring links, and optional password protection. Think Pastebin β€” but private, expiring, and deployed on Azure. + +--- + +## What the Application Does + +PasteVault allows users to: +- Paste code or text and receive a unique shareable URL +- Select a programming language for syntax highlighting (15 languages supported) +- Set an expiry time (1 hour, 1 day, 7 days, 30 days, or never) +- Optionally password-protect a paste +- Share the link β€” only people with the URL (and password if set) can view it +- Copy paste content or the share URL with one click +- View metadata: creation time, expiry countdown, view count + +Pastes are automatically deleted from the database when they expire. + +--- + +## Architecture Overview + +The application uses a **hybrid cloud architecture**: + +- **Azure VM** (`Standard_D2s_v3` β€” 2 vCPU, 8 GB RAM, West Europe) runs all application containers via Docker Compose +- **Azure PostgreSQL Flexible Server** (`Standard_B1MS`, North Europe) stores all paste data persistently +- **Azure Blob Storage** (North Europe) stores database backup dumps +- **Cloudflare** (external, pre-existing) provides DNS, HTTPS/TLS termination, and access logs + +### Containers (3 total) + +| Container | Image | Role | +|---|---|---| +| `pastevault-frontend` | Custom (Nginx + React) | Serves the UI and proxies `/api/` to backend | +| `pastevault-backend` | Custom (Python FastAPI) | REST API β€” paste CRUD, expiry logic | +| `pastevault-redis` | `redis:7-alpine` | Caching paste reads, reducing DB load | + +### Communication Flow + +``` +Browser β†’ Cloudflare (HTTPS) β†’ VM port 80 β†’ Nginx (frontend container) + ↓ /api/* proxy + FastAPI (backend container) + ↓ + Redis (cache, 30s TTL) + ↓ cache miss + Azure PostgreSQL (persistent store) +``` + +--- + +## Cloud Services Used + +| Service | Purpose | Tier | +|---|---|---| +| Azure VM `Standard_D2s_v3` | Runs Docker Compose with all 3 containers | Pay-as-you-go | +| Azure PostgreSQL Flexible Server | Managed relational database | Burstable B1MS (free tier) | +| Azure Blob Storage | Backup storage for `pg_dump` files | Standard LRS | +| Cloudflare (pre-existing) | DNS, HTTPS certificate, access logs | Free plan | + +### Persistent Storage +- **PostgreSQL** stores all paste records (id, title, content, language, expiry, views, password hash) +- **Redis Docker volume** (`redis_data`) persists cache data across restarts using `--appendonly yes` +- **Azure Blob Storage** holds scheduled database backups + +### Auto-restart +All three containers use `restart: always` in `docker-compose.yml`. If any container crashes, Docker restarts it automatically without any manual intervention. + +--- + +## File Structure + +``` +sk1/ +β”œβ”€β”€ backend/ +β”‚ β”œβ”€β”€ main.py # FastAPI app β€” all API endpoints, DB logic, Redis caching +β”‚ β”œβ”€β”€ requirements.txt # Python dependencies +β”‚ └── Dockerfile # Python 3.12 slim image +β”œβ”€β”€ frontend/ +β”‚ β”œβ”€β”€ src/ +β”‚ β”‚ β”œβ”€β”€ main.jsx # React entry point +β”‚ β”‚ β”œβ”€β”€ App.jsx # Router β€” two routes: / and /paste/:id +β”‚ β”‚ β”œβ”€β”€ CreatePaste.jsx # Paste creation form +β”‚ β”‚ β”œβ”€β”€ ViewPaste.jsx # Paste viewer with syntax highlighting +β”‚ β”‚ └── index.css # Dark theme stylesheet +β”‚ β”œβ”€β”€ index.html # HTML shell +β”‚ β”œβ”€β”€ package.json # Node dependencies (React, Vite, highlight.js) +β”‚ β”œβ”€β”€ vite.config.js # Vite build config with API proxy for local dev +β”‚ β”œβ”€β”€ nginx.conf # Nginx config β€” serves React, proxies /api/ to backend +β”‚ └── Dockerfile # Multi-stage: Node build β†’ Nginx serve +β”œβ”€β”€ scripts/ +β”‚ β”œβ”€β”€ prepare-app.sh # Full Azure provisioning + deployment script +β”‚ β”œβ”€β”€ remove-app.sh # Teardown script β€” stops containers + deletes all Azure resources +β”‚ └── backup.sh # pg_dump β†’ Azure Blob Storage +β”œβ”€β”€ docker-compose.yml # Defines frontend, backend, redis services +β”œβ”€β”€ .env.example # Template for environment variables (safe to commit) +β”œβ”€β”€ .gitignore # Excludes .env, node_modules, __pycache__, dist +└── README.md # This file +``` + +--- + +## Configuration + +All configuration is done via environment variables in the `.env` file. This file is **never committed to Git**. + +| Variable | Description | +|---|---| +| `DATABASE_URL` | PostgreSQL connection string (without `?ssl=`) | +| `REDIS_URL` | Redis connection string (default: `redis://redis:6379`) | +| `SECRET_KEY` | Used to salt password hashes | +| `DB_PASS` | PostgreSQL admin password (used by backup script) | + +Copy `.env.example` to `.env` and fill in your values before running any scripts. + +--- + +## How to Run the Application + +### Prerequisites +- Azure CLI installed and logged in (`az login`) +- SSH key at `~/.ssh/id_rsa` +- `.env` file filled in + +### Deploy +```bash +cd sk1/ +bash scripts/prepare-app.sh +``` + +This script automatically: +1. Creates the Azure resource group +2. Provisions the VM (Standard_D2s_v3, West Europe) +3. Creates PostgreSQL Flexible Server (B1MS, North Europe) +4. Creates Azure Blob Storage for backups +5. Installs Docker on the VM +6. Copies app files to the VM +7. Builds and starts all containers + +### Teardown +```bash +bash scripts/remove-app.sh +``` + +Stops all containers and deletes the entire Azure resource group (VM, database, storage, IP). + +### Conditions to run scripts +- `prepare-app.sh`: Azure CLI logged in, `.env` present, SSH key at `~/.ssh/id_rsa`, no existing `pastevault-rg` resource group +- `remove-app.sh`: Azure CLI logged in, SSH access to VM still working + +--- + +## Using the Application + +1. Open the app URL in a browser (`https://yourdomain.com`) +2. Enter a title, paste your code or text into the editor +3. Select the language for syntax highlighting +4. Choose an expiry time (or never) +5. Optionally set a password +6. Click **"πŸ”’ Create Paste"** +7. You are redirected to the paste URL β€” copy it with the **"πŸ”— Share"** button +8. Anyone with the link can view the paste (and password if set) + +--- + +## Backup + +Run from the project root on your local machine (requires `postgresql-client`): + +```bash +bash scripts/backup.sh +``` + +This dumps the entire `pastevault` database with `pg_dump` and uploads it to Azure Blob Storage. + +**List all backups:** +```bash +az storage blob list \ + --account-name pastevaultstorage \ + --container-name backups \ + --output table +``` + +**Download a specific backup:** +```bash +az storage blob download \ + --account-name pastevaultstorage \ + --container-name backups \ + --name pastevault_backup_YYYYMMDD_HHMMSS.sql \ + --file ./restore.sql +``` + +**Restore from backup:** +```bash +PGPASSWORD=yourpassword psql \ + --host=pastevault-db.postgres.database.azure.com \ + --username=pvadmin \ + --dbname=pastevault \ + --file=./restore.sql \ + --sslmode=require +``` + +--- + +## Viewing Access Logs + +### From Cloudflare (internet traffic) +Cloudflare dashboard β†’ your domain β†’ **Analytics** tab shows: +- Total requests, unique visitors +- Geographic breakdown +- Traffic over time +- Blocked threats + +### From Nginx (per-request logs) +```bash +ssh azureuser@YOUR_VM_IP +docker logs pastevault-frontend +``` + +Each line shows: IP address, timestamp, HTTP method, path, status code, response size. + +### From the backend +```bash +docker logs pastevault-backend +``` + +### Live log stream +```bash +docker logs -f pastevault-frontend +``` + +--- + +## Cost Analysis β€” 1000 Users/Day, 50 GB Data + +| Resource | Spec | Billing | Monthly | Annual | +|---|---|---|---|---| +| Azure VM `Standard_D2s_v3` | 2 vCPU, 8 GB, West Europe | Per hour | ~$7.30 | ~$87.60 | +| Azure PostgreSQL B1MS | 1 vCPU, 2 GB, 50 GB storage | Per hour + GB | ~$13.80 | ~$165.60 | +| Azure Blob Storage | 50 GB backups, LRS | Per GB/month | ~$1.00 | ~$12.00 | +| Azure Public IP | Standard static IP | Per hour | ~$3.00 | ~$36.00 | +| Cloudflare | DNS + HTTPS + CDN | β€” | $0 | $0 | +| **Total** | | | **~$25.10** | **~$301.20** | + +At 1000 users/day the PostgreSQL B1MS handles load comfortably. For 10,000+ users/day, scaling to `Standard_D2s_v3` for the DB (~$75/month) would be recommended. + +The biggest cost saving in this architecture is using Cloudflare as the HTTPS/CDN layer for free instead of an Azure Application Gateway (~$120/month). + +--- + +## Known Issues Encountered During Deployment + +- **Azure B-series VMs unavailable in West Europe and North Europe** β€” at time of deployment, `Standard_B1s`, `Standard_B2s`, and `Standard_B2ats_v2` had capacity restrictions across multiple European regions. Resolved by using `Standard_D2s_v3` in West Europe. +- **PostgreSQL SSL configuration conflict** β€” `asyncpg` does not accept `?ssl=require` in the connection URL when also passed as a keyword argument. Resolved by keeping `ssl=True` in code only and removing it from the `DATABASE_URL`. +- **Docker Compose `version` field warning** β€” Docker Compose v2 treats the `version` key as obsolete. Harmless warning, does not affect functionality. + +--- + +## External Resources + +| Resource | Type | Usage | +|---|---|---| +| FastAPI documentation | Official docs | API structure, startup events, dependency injection | +| asyncpg documentation | Official docs | PostgreSQL async connection pool configuration | +| highlight.js | Open source library | Client-side syntax highlighting for 15 languages | +| Azure CLI documentation | Official docs | VM, PostgreSQL, and storage provisioning commands | +| Docker documentation | Official docs | Multi-stage Dockerfile, Compose configuration | +| **Claude (Anthropic)** | Generative AI | Used to generate application code, Dockerfiles, deployment scripts, and this documentation. All output reviewed and tested manually. | diff --git a/SK1/backend/Dockerfile b/SK1/backend/Dockerfile new file mode 100644 index 0000000..b3a66cf --- /dev/null +++ b/SK1/backend/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.12-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +EXPOSE 8000 + +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/SK1/backend/main.py b/SK1/backend/main.py new file mode 100644 index 0000000..5d490ef --- /dev/null +++ b/SK1/backend/main.py @@ -0,0 +1,169 @@ +from fastapi import FastAPI, HTTPException, Query +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel +from typing import Optional +import asyncpg +import redis.asyncio as aioredis +import uuid +import hashlib +import os +import json +from datetime import datetime, timedelta + +app = FastAPI(title="PasteVault API") + +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_methods=["*"], + allow_headers=["*"], +) + +DATABASE_URL = os.getenv("DATABASE_URL") +REDIS_URL = os.getenv("REDIS_URL", "redis://redis:6379") +SECRET_KEY = os.getenv("SECRET_KEY", "changeme") + +db_pool = None +redis_client = None + + +@app.on_event("startup") +async def startup(): + global db_pool, redis_client + db_pool = await asyncpg.create_pool(DATABASE_URL, ssl="require") + redis_client = await aioredis.from_url(REDIS_URL, decode_responses=True) + async with db_pool.acquire() as conn: + await conn.execute(""" + CREATE TABLE IF NOT EXISTS pastes ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + title VARCHAR(255) NOT NULL DEFAULT 'Untitled', + content TEXT NOT NULL, + language VARCHAR(50) DEFAULT 'plaintext', + password_hash VARCHAR(255), + expires_at TIMESTAMP, + created_at TIMESTAMP DEFAULT NOW(), + views INTEGER DEFAULT 0 + ) + """) + + +@app.on_event("shutdown") +async def shutdown(): + await db_pool.close() + await redis_client.aclose() + + +class PasteCreate(BaseModel): + title: str = "Untitled" + content: str + language: str = "plaintext" + password: Optional[str] = None + expiry: Optional[str] = None + + +def hash_password(password: str) -> str: + return hashlib.sha256((password + SECRET_KEY).encode()).hexdigest() + + +def get_expiry(expiry_str: Optional[str]) -> Optional[datetime]: + mapping = {"1h": timedelta(hours=1), "1d": timedelta(days=1), + "7d": timedelta(days=7), "30d": timedelta(days=30)} + if not expiry_str or expiry_str not in mapping: + return None + return datetime.utcnow() + mapping[expiry_str] + + +@app.get("/api/health") +async def health(): + return {"status": "ok", "service": "PasteVault"} + + +@app.post("/api/pastes") +async def create_paste(paste: PasteCreate): + if not paste.content.strip(): + raise HTTPException(status_code=400, detail="Content cannot be empty") + expires_at = get_expiry(paste.expiry) + password_hash = hash_password(paste.password) if paste.password else None + async with db_pool.acquire() as conn: + row = await conn.fetchrow(""" + INSERT INTO pastes (title, content, language, password_hash, expires_at) + VALUES ($1, $2, $3, $4, $5) + RETURNING id, title, language, expires_at, created_at + """, paste.title, paste.content, paste.language, password_hash, expires_at) + return { + "id": str(row["id"]), + "title": row["title"], + "language": row["language"], + "expires_at": row["expires_at"].isoformat() if row["expires_at"] else None, + "created_at": row["created_at"].isoformat(), + "has_password": password_hash is not None, + } + + +@app.get("/api/pastes/{paste_id}") +async def get_paste(paste_id: str, password: Optional[str] = Query(default=None)): + try: + pid = uuid.UUID(paste_id) + except ValueError: + raise HTTPException(status_code=400, detail="Invalid paste ID") + + cached = await redis_client.get(f"paste:{paste_id}") + if cached: + paste = json.loads(cached) + else: + async with db_pool.acquire() as conn: + row = await conn.fetchrow("SELECT * FROM pastes WHERE id = $1", pid) + if not row: + raise HTTPException(status_code=404, detail="Paste not found") + paste = dict(row) + paste["id"] = str(paste["id"]) + paste["expires_at"] = paste["expires_at"].isoformat() if paste["expires_at"] else None + paste["created_at"] = paste["created_at"].isoformat() + await redis_client.setex(f"paste:{paste_id}", 30, json.dumps(paste, default=str)) + + if paste["expires_at"]: + if datetime.fromisoformat(paste["expires_at"]) < datetime.utcnow(): + async with db_pool.acquire() as conn: + await conn.execute("DELETE FROM pastes WHERE id = $1", pid) + await redis_client.delete(f"paste:{paste_id}") + raise HTTPException(status_code=404, detail="Paste has expired") + + if paste["password_hash"]: + if not password: + return {"id": paste["id"], "title": paste["title"], "requires_password": True} + if hash_password(password) != paste["password_hash"]: + raise HTTPException(status_code=403, detail="Wrong password") + + async with db_pool.acquire() as conn: + await conn.execute("UPDATE pastes SET views = views + 1 WHERE id = $1", pid) + await redis_client.delete(f"paste:{paste_id}") + + return { + "id": paste["id"], + "title": paste["title"], + "content": paste["content"], + "language": paste["language"], + "expires_at": paste["expires_at"], + "created_at": paste["created_at"], + "views": paste["views"] + 1, + "has_password": paste["password_hash"] is not None, + "requires_password": False, + } + + +@app.delete("/api/pastes/{paste_id}") +async def delete_paste(paste_id: str, password: Optional[str] = Query(default=None)): + try: + pid = uuid.UUID(paste_id) + except ValueError: + raise HTTPException(status_code=400, detail="Invalid paste ID") + async with db_pool.acquire() as conn: + row = await conn.fetchrow("SELECT * FROM pastes WHERE id = $1", pid) + if not row: + raise HTTPException(status_code=404, detail="Paste not found") + if row["password_hash"] and (not password or hash_password(password) != row["password_hash"]): + raise HTTPException(status_code=403, detail="Wrong password") + async with db_pool.acquire() as conn: + await conn.execute("DELETE FROM pastes WHERE id = $1", pid) + await redis_client.delete(f"paste:{paste_id}") + return {"message": "Paste deleted successfully"} diff --git a/SK1/backend/requirements.txt b/SK1/backend/requirements.txt new file mode 100644 index 0000000..36caa61 --- /dev/null +++ b/SK1/backend/requirements.txt @@ -0,0 +1,5 @@ +fastapi==0.111.0 +uvicorn[standard]==0.30.0 +asyncpg==0.29.0 +redis==5.0.4 +pydantic==2.7.1 diff --git a/SK1/docker-compose.yml b/SK1/docker-compose.yml new file mode 100644 index 0000000..d4576d7 --- /dev/null +++ b/SK1/docker-compose.yml @@ -0,0 +1,34 @@ +version: '3.8' + +services: + + frontend: + build: ./frontend + container_name: pastevault-frontend + restart: always + ports: + - "80:80" + depends_on: + - backend + + backend: + build: ./backend + container_name: pastevault-backend + restart: always + expose: + - "8000" + env_file: + - .env + depends_on: + - redis + + redis: + image: redis:7-alpine + container_name: pastevault-redis + restart: always + volumes: + - redis_data:/data + command: redis-server --appendonly yes + +volumes: + redis_data: diff --git a/SK1/frontend/Dockerfile b/SK1/frontend/Dockerfile new file mode 100644 index 0000000..e4dc3f2 --- /dev/null +++ b/SK1/frontend/Dockerfile @@ -0,0 +1,20 @@ +# Stage 1 β€” Build React app +FROM node:20-alpine AS builder + +WORKDIR /app + +COPY package.json ./ +RUN npm install + +COPY . . +RUN npm run build + +# Stage 2 β€” Serve with Nginx +FROM nginx:alpine + +COPY --from=builder /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/SK1/frontend/index.html b/SK1/frontend/index.html new file mode 100644 index 0000000..9a2b5c4 --- /dev/null +++ b/SK1/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + PasteVault β€” Secure Code Sharing + + + +
+ + + diff --git a/SK1/frontend/nginx.conf b/SK1/frontend/nginx.conf new file mode 100644 index 0000000..b40a5d7 --- /dev/null +++ b/SK1/frontend/nginx.conf @@ -0,0 +1,31 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + # Serve React app β€” handle client-side routing + location / { + try_files $uri $uri/ /index.html; + } + + # Proxy all /api/* requests to FastAPI backend + location /api/ { + proxy_pass http://backend:8000; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 60s; + } + + # Access logs β€” viewable with: docker logs pastevault-frontend + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-Content-Type-Options "nosniff"; +} diff --git a/SK1/frontend/package.json b/SK1/frontend/package.json new file mode 100644 index 0000000..dc8b1cb --- /dev/null +++ b/SK1/frontend/package.json @@ -0,0 +1,20 @@ +{ + "name": "pastevault", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.23.1", + "highlight.js": "^11.9.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.0", + "vite": "^5.2.12" + } +} diff --git a/SK1/frontend/src/App.jsx b/SK1/frontend/src/App.jsx new file mode 100644 index 0000000..5c1a58c --- /dev/null +++ b/SK1/frontend/src/App.jsx @@ -0,0 +1,31 @@ +import React from 'react' +import { Routes, Route, Link } from 'react-router-dom' +import CreatePaste from './CreatePaste' +import ViewPaste from './ViewPaste' + +export default function App() { + return ( +
+
+ + πŸ” + PasteVault + + +
+ +
+ + } /> + } /> + +
+ +
+

PasteVault — Secure, expiring code sharing — Powered by Azure + Docker

+
+
+ ) +} diff --git a/SK1/frontend/src/CreatePaste.jsx b/SK1/frontend/src/CreatePaste.jsx new file mode 100644 index 0000000..d426a0f --- /dev/null +++ b/SK1/frontend/src/CreatePaste.jsx @@ -0,0 +1,127 @@ +import React, { useState } from 'react' +import { useNavigate } from 'react-router-dom' + +const LANGUAGES = [ + 'plaintext', 'bash', 'cpp', 'css', 'dockerfile', + 'go', 'html', 'java', 'javascript', 'json', + 'python', 'rust', 'sql', 'typescript', 'yaml', +] + +const EXPIRY_OPTIONS = [ + { value: '', label: 'β™Ύ Never' }, + { value: '1h', label: '⏱ 1 Hour' }, + { value: '1d', label: 'πŸ“… 1 Day' }, + { value: '7d', label: 'πŸ“… 7 Days' }, + { value: '30d', label: 'πŸ“… 30 Days' }, +] + +export default function CreatePaste() { + const navigate = useNavigate() + const [form, setForm] = useState({ + title: '', content: '', language: 'plaintext', password: '', expiry: '', + }) + const [loading, setLoading] = useState(false) + const [error, setError] = useState('') + + const set = (key, val) => setForm(f => ({ ...f, [key]: val })) + + const handleSubmit = async (e) => { + e.preventDefault() + if (!form.content.trim()) { setError('Content cannot be empty.'); return } + setLoading(true) + setError('') + try { + const res = await fetch('/api/pastes', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + title: form.title || 'Untitled', + content: form.content, + language: form.language, + password: form.password || null, + expiry: form.expiry || null, + }), + }) + if (!res.ok) { + const err = await res.json() + throw new Error(err.detail || 'Server error') + } + const data = await res.json() + navigate(`/paste/${data.id}`) + } catch (err) { + setError(err.message || 'Failed to create paste.') + } finally { + setLoading(false) + } + } + + return ( +
+
+

Share code securely.

+

Create an expiring, password-protected paste in seconds.

+
+ +
+ {error &&
{error}
} + +
+
+
+ + set('title', e.target.value)} + /> +
+
+ + +
+
+ + +
+
+ +
+ +