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