Init
This commit is contained in:
commit
c79ac06b58
16 changed files with 2180 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
node_modules
|
||||||
13
backend/Dockerfile
Normal file
13
backend/Dockerfile
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# ---- Build stage (optional) ----
|
||||||
|
FROM python:3.12-slim AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# ---- Runtime stage ----
|
||||||
|
FROM python:3.12-slim
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
|
||||||
|
COPY app ./app
|
||||||
|
EXPOSE 8000
|
||||||
|
CMD ["uvicorn", "app.main:app", "--port", "8000"]
|
||||||
0
backend/app/__init__.py
Normal file
0
backend/app/__init__.py
Normal file
BIN
backend/app/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
backend/app/__pycache__/__init__.cpython-314.pyc
Normal file
Binary file not shown.
BIN
backend/app/__pycache__/main.cpython-314.pyc
Normal file
BIN
backend/app/__pycache__/main.cpython-314.pyc
Normal file
Binary file not shown.
BIN
backend/app/__pycache__/models.cpython-314.pyc
Normal file
BIN
backend/app/__pycache__/models.cpython-314.pyc
Normal file
Binary file not shown.
34
backend/app/main.py
Normal file
34
backend/app/main.py
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
# backend/app/main.py
|
||||||
|
from fastapi import FastAPI, HTTPException
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from .models import add_note, get_all_notes
|
||||||
|
|
||||||
|
app = FastAPI(title="Simple Note API")
|
||||||
|
|
||||||
|
|
||||||
|
class NoteIn(BaseModel):
|
||||||
|
title: str
|
||||||
|
body: str
|
||||||
|
|
||||||
|
|
||||||
|
class NoteOut(NoteIn):
|
||||||
|
id: int
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
def health():
|
||||||
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/notes", response_model=list[NoteOut])
|
||||||
|
def list_notes():
|
||||||
|
return get_all_notes()
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/notes", response_model=NoteOut, status_code=201)
|
||||||
|
def create_note(note: NoteIn):
|
||||||
|
# Very tiny validation – you can expand later
|
||||||
|
if not note.title.strip():
|
||||||
|
raise HTTPException(status_code=400, detail="Title cannot be empty")
|
||||||
|
return add_note(note.title, note.body)
|
||||||
16
backend/app/models.py
Normal file
16
backend/app/models.py
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# backend/app/models.py
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
|
# In‑memory “database”
|
||||||
|
_notes: List[Dict] = [] # each note is a dict with id, title, body
|
||||||
|
|
||||||
|
|
||||||
|
def add_note(title: str, body: str) -> Dict:
|
||||||
|
note_id = len(_notes) + 1
|
||||||
|
note = {"id": note_id, "title": title, "body": body}
|
||||||
|
_notes.append(note)
|
||||||
|
return note
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_notes() -> List[Dict]:
|
||||||
|
return _notes
|
||||||
0
backend/app/schemas.py
Normal file
0
backend/app/schemas.py
Normal file
54
backend/requirements.txt
Normal file
54
backend/requirements.txt
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
annotated-doc==0.0.4
|
||||||
|
annotated-types==0.7.0
|
||||||
|
anyio==4.11.0
|
||||||
|
bcrypt==5.0.0
|
||||||
|
certifi==2025.11.12
|
||||||
|
cffi==2.0.0
|
||||||
|
click==8.3.1
|
||||||
|
cryptography==46.0.3
|
||||||
|
dnspython==2.8.0
|
||||||
|
ecdsa==0.19.1
|
||||||
|
email-validator==2.3.0
|
||||||
|
fastapi==0.121.3
|
||||||
|
fastapi-cli==0.0.16
|
||||||
|
fastapi-cloud-cli==0.4.0
|
||||||
|
fastar==0.6.0
|
||||||
|
greenlet==3.2.4
|
||||||
|
h11==0.16.0
|
||||||
|
httpcore==1.0.9
|
||||||
|
httptools==0.7.1
|
||||||
|
httpx==0.28.1
|
||||||
|
idna==3.11
|
||||||
|
Jinja2==3.1.6
|
||||||
|
markdown-it-py==4.0.0
|
||||||
|
MarkupSafe==3.0.3
|
||||||
|
mdurl==0.1.2
|
||||||
|
passlib==1.7.4
|
||||||
|
pyasn1==0.6.1
|
||||||
|
pycparser==2.23
|
||||||
|
pydantic==2.12.4
|
||||||
|
pydantic_core==2.41.5
|
||||||
|
Pygments==2.19.2
|
||||||
|
python-dotenv==1.2.1
|
||||||
|
python-jose==3.5.0
|
||||||
|
python-multipart==0.0.20
|
||||||
|
PyYAML==6.0.3
|
||||||
|
rich==14.2.0
|
||||||
|
rich-toolkit==0.16.0
|
||||||
|
rignore==0.7.6
|
||||||
|
rsa==4.9.1
|
||||||
|
sentry-sdk==2.45.0
|
||||||
|
shellingham==1.5.4
|
||||||
|
six==1.17.0
|
||||||
|
sniffio==1.3.1
|
||||||
|
SQLAlchemy==2.0.44
|
||||||
|
sqlmodel==0.0.27
|
||||||
|
starlette==0.50.0
|
||||||
|
typer==0.20.0
|
||||||
|
typing-inspection==0.4.2
|
||||||
|
typing_extensions==4.15.0
|
||||||
|
urllib3==2.5.0
|
||||||
|
uvicorn==0.38.0
|
||||||
|
uvloop==0.22.1
|
||||||
|
watchfiles==1.1.1
|
||||||
|
websockets==15.0.1
|
||||||
11
frontend/index.html
Normal file
11
frontend/index.html
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>FastAPI + Vite Note Demo</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1913
frontend/package-lock.json
generated
Normal file
1913
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
19
frontend/package.json
Normal file
19
frontend/package.json
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "note-frontend",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.13.2",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-react": "^4.7.0",
|
||||||
|
"vite": "^5.4.21"
|
||||||
|
}
|
||||||
|
}
|
||||||
98
frontend/src/App.tsx
Normal file
98
frontend/src/App.tsx
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
type Note = {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
const [title, setTitle] = useState("");
|
||||||
|
const [body, setBody] = useState("");
|
||||||
|
const [saved, setSaved] = useState<Note | null>(null);
|
||||||
|
const [allNotes, setAllNotes] = useState<Note[]>([]);
|
||||||
|
|
||||||
|
// Load existing notes on mount
|
||||||
|
useEffect(() => {
|
||||||
|
axios.get<Note[]>("/api/notes").then((res) => setAllNotes(res.data));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
const resp = await axios.post<Note>("/api/notes", { title, body });
|
||||||
|
setSaved(resp.data);
|
||||||
|
setAllNotes((prev) => [...prev, resp.data]);
|
||||||
|
setTitle("");
|
||||||
|
setBody("");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
alert("Failed to save note – check console.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
maxWidth: "600px",
|
||||||
|
margin: "2rem auto",
|
||||||
|
fontFamily: "sans-serif",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h1>📝 Simple Note</h1>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} style={{ marginBottom: "2rem" }}>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
placeholder="Title"
|
||||||
|
value={title}
|
||||||
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
|
required
|
||||||
|
style={{ width: "100%", padding: "0.5rem", fontSize: "1.1rem" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: "0.5rem" }}>
|
||||||
|
<textarea
|
||||||
|
placeholder="Body"
|
||||||
|
value={body}
|
||||||
|
onChange={(e) => setBody(e.target.value)}
|
||||||
|
rows={4}
|
||||||
|
style={{ width: "100%", padding: "0.5rem" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
style={{ marginTop: "0.5rem", padding: "0.5rem 1rem" }}
|
||||||
|
>
|
||||||
|
Save note
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{saved && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background: "#f0f8ff",
|
||||||
|
padding: "1rem",
|
||||||
|
marginBottom: "2rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<strong>Saved:</strong> #{saved.id} – {saved.title}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<h2>All notes</h2>
|
||||||
|
{allNotes.length === 0 ? (
|
||||||
|
<p>No notes yet.</p>
|
||||||
|
) : (
|
||||||
|
<ul>
|
||||||
|
{allNotes.map((n) => (
|
||||||
|
<li key={n.id}>
|
||||||
|
<strong>{n.title}</strong>: {n.body}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
5
frontend/src/main.tsx
Normal file
5
frontend/src/main.tsx
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import React from "react";
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
import App from "./App";
|
||||||
|
|
||||||
|
createRoot(document.getElementById("root")!).render(<App />);
|
||||||
16
frontend/vite.config.ts
Normal file
16
frontend/vite.config.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
server: {
|
||||||
|
// Proxy API calls to the FastAPI container (or local dev server)
|
||||||
|
proxy: {
|
||||||
|
"/api": {
|
||||||
|
target: "http://localhost:8000",
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api/, ""),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue