simple website made

This commit is contained in:
Hugo Alberto Justisoesetya 2026-06-10 16:55:37 +08:00
parent 880e7850c1
commit c089006386
8 changed files with 210 additions and 0 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
__pycache__/
*.pyc
.env
.venv/
venv/

12
backend/Dockerfile Normal file
View 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
View 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
View 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
View 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
View 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
View 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}")

View file

@ -0,0 +1,2 @@
streamlit==1.35.0
requests==2.32.3