simple website made
This commit is contained in:
parent
880e7850c1
commit
c089006386
8 changed files with 210 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.env
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
12
backend/Dockerfile
Normal file
12
backend/Dockerfile
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
FROM python:3.11-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"]
|
||||||
79
backend/main.py
Normal file
79
backend/main.py
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
from fastapi import FastAPI, HTTPException
|
||||||
|
from pydantic import BaseModel
|
||||||
|
import psycopg2
|
||||||
|
import psycopg2.extras
|
||||||
|
import os
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
app = FastAPI(title="Notes API")
|
||||||
|
|
||||||
|
def get_db():
|
||||||
|
return psycopg2.connect(
|
||||||
|
host=os.environ["DB_HOST"],
|
||||||
|
port=int(os.environ.get("DB_PORT", "5432")),
|
||||||
|
database=os.environ["DB_NAME"],
|
||||||
|
user=os.environ["DB_USER"],
|
||||||
|
password=os.environ["DB_PASSWORD"],
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.on_event("startup")
|
||||||
|
def startup():
|
||||||
|
conn = get_db()
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS notes (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
content TEXT DEFAULT '',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
conn.commit()
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
class NoteCreate(BaseModel):
|
||||||
|
title: str
|
||||||
|
content: Optional[str] = ""
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
def health():
|
||||||
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
@app.get("/notes")
|
||||||
|
def get_notes():
|
||||||
|
conn = get_db()
|
||||||
|
cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
||||||
|
cur.execute("SELECT * FROM notes ORDER BY created_at DESC")
|
||||||
|
notes = list(cur.fetchall())
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
return notes
|
||||||
|
|
||||||
|
@app.post("/notes")
|
||||||
|
def create_note(note: NoteCreate):
|
||||||
|
conn = get_db()
|
||||||
|
cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
||||||
|
cur.execute(
|
||||||
|
"INSERT INTO notes (title, content) VALUES (%s, %s) RETURNING *",
|
||||||
|
(note.title, note.content),
|
||||||
|
)
|
||||||
|
new_note = dict(cur.fetchone())
|
||||||
|
conn.commit()
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
return new_note
|
||||||
|
|
||||||
|
@app.delete("/notes/{note_id}")
|
||||||
|
def delete_note(note_id: int):
|
||||||
|
conn = get_db()
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("DELETE FROM notes WHERE id = %s", (note_id,))
|
||||||
|
if cur.rowcount == 0:
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
raise HTTPException(status_code=404, detail="Note not found")
|
||||||
|
conn.commit()
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
return {"deleted": note_id}
|
||||||
4
backend/requirements.txt
Normal file
4
backend/requirements.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
fastapi==0.111.0
|
||||||
|
uvicorn==0.29.0
|
||||||
|
psycopg2-binary==2.9.9
|
||||||
|
pydantic==2.7.1
|
||||||
38
docker-compose.yml
Normal file
38
docker-compose.yml
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:15
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: notesdb
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
volumes:
|
||||||
|
- pgdata:/var/lib/postgresql/data
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
|
||||||
|
backend:
|
||||||
|
build: ./backend
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
environment:
|
||||||
|
DB_HOST: db
|
||||||
|
DB_PORT: 5432
|
||||||
|
DB_NAME: notesdb
|
||||||
|
DB_USER: postgres
|
||||||
|
DB_PASSWORD: postgres
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
build: ./frontend
|
||||||
|
ports:
|
||||||
|
- "8501:8501"
|
||||||
|
environment:
|
||||||
|
BACKEND_URL: http://backend:8000
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pgdata:
|
||||||
12
frontend/Dockerfile
Normal file
12
frontend/Dockerfile
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
EXPOSE 8501
|
||||||
|
|
||||||
|
CMD ["streamlit", "run", "app.py", "--server.address", "0.0.0.0", "--server.port", "8501", "--server.headless", "true"]
|
||||||
58
frontend/app.py
Normal file
58
frontend/app.py
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import streamlit as st
|
||||||
|
import requests
|
||||||
|
import os
|
||||||
|
|
||||||
|
BACKEND_URL = os.environ.get("BACKEND_URL", "http://localhost:8000")
|
||||||
|
|
||||||
|
st.set_page_config(page_title="Notes", page_icon="📝")
|
||||||
|
st.title("📝 Notes")
|
||||||
|
|
||||||
|
# Add note
|
||||||
|
with st.form("add_note", clear_on_submit=True):
|
||||||
|
st.subheader("New Note")
|
||||||
|
title = st.text_input("Title")
|
||||||
|
content = st.text_area("Content", height=100)
|
||||||
|
submitted = st.form_submit_button("Add Note", use_container_width=True)
|
||||||
|
|
||||||
|
if submitted:
|
||||||
|
if not title.strip():
|
||||||
|
st.error("Title is required.")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
r = requests.post(
|
||||||
|
f"{BACKEND_URL}/notes",
|
||||||
|
json={"title": title, "content": content},
|
||||||
|
timeout=5,
|
||||||
|
)
|
||||||
|
if r.status_code == 200:
|
||||||
|
st.success("Note added!")
|
||||||
|
st.rerun()
|
||||||
|
else:
|
||||||
|
st.error(f"Backend error: {r.text}")
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Could not reach backend: {e}")
|
||||||
|
|
||||||
|
st.divider()
|
||||||
|
st.subheader("All Notes")
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.get(f"{BACKEND_URL}/notes", timeout=5)
|
||||||
|
notes = r.json()
|
||||||
|
|
||||||
|
if not notes:
|
||||||
|
st.info("No notes yet. Add one above.")
|
||||||
|
else:
|
||||||
|
for note in notes:
|
||||||
|
with st.container(border=True):
|
||||||
|
col1, col2 = st.columns([5, 1])
|
||||||
|
with col1:
|
||||||
|
st.markdown(f"**{note['title']}**")
|
||||||
|
if note.get("content"):
|
||||||
|
st.write(note["content"])
|
||||||
|
st.caption(note["created_at"])
|
||||||
|
with col2:
|
||||||
|
if st.button("Delete", key=f"del_{note['id']}"):
|
||||||
|
requests.delete(f"{BACKEND_URL}/notes/{note['id']}", timeout=5)
|
||||||
|
st.rerun()
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Could not reach backend: {e}")
|
||||||
2
frontend/requirements.txt
Normal file
2
frontend/requirements.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
streamlit==1.35.0
|
||||||
|
requests==2.32.3
|
||||||
Loading…
Add table
Add a link
Reference in a new issue