Skip to content

Commit 1d053a7

Browse files
committed
utilise postgres db
1 parent 79b54ae commit 1d053a7

File tree

12 files changed

+276
-14
lines changed

12 files changed

+276
-14
lines changed

api/app/crud.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from sqlalchemy.orm import Session
2+
3+
from . import models, schemas
4+
5+
6+
def get_user(db: Session, user_id: int):
7+
return db.query(models.User).filter(models.User.id == user_id).first()
8+
9+
10+
def get_user_by_email(db: Session, email: str):
11+
return db.query(models.User).filter(models.User.email == email).first()
12+
13+
14+
def get_users(db: Session, skip: int = 0, limit: int = 100):
15+
return db.query(models.User).offset(skip).limit(limit).all()
16+
17+
18+
def create_user(db: Session, user: schemas.UserCreate):
19+
fake_hashed_password = user.password + "notreallyhashed"
20+
db_user = models.User(email=user.email, hashed_password=fake_hashed_password)
21+
db.add(db_user)
22+
db.commit()
23+
db.refresh(db_user)
24+
return db_user
25+
26+
27+
def get_items(db: Session, skip: int = 0, limit: int = 100):
28+
return db.query(models.Item).offset(skip).limit(limit).all()
29+
30+
31+
def create_user_item(db: Session, item: schemas.ItemCreate, user_id: int):
32+
db_item = models.Item(**item.dict(), owner_id=user_id)
33+
db.add(db_item)
34+
db.commit()
35+
db.refresh(db_item)
36+
return db_item

api/app/database.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import os
2+
3+
from sqlalchemy import create_engine
4+
from sqlalchemy.ext.declarative import declarative_base
5+
from sqlalchemy.orm import sessionmaker
6+
7+
POSTGRES_USER = os.getenv("POSTGRES_USER")
8+
POSTGRES_PASSWORD = os.getenv("POSTGRES_PASSWORD")
9+
POSTGRES_SERVER = os.getenv("POSTGRES_SERVER", "db")
10+
POSTGRES_DB = os.getenv("POSTGRES_DB", "example")
11+
12+
SQLALCHEMY_DATABASE_URL = f"postgresql://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_SERVER}/{POSTGRES_DB}"
13+
14+
engine = create_engine(
15+
SQLALCHEMY_DATABASE_URL, connect_args={}
16+
)
17+
18+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
19+
20+
Base = declarative_base()

api/app/main.py

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,53 @@
1-
from typing import Union
1+
from fastapi import Depends, FastAPI, HTTPException
2+
from sqlalchemy.orm import Session
23

3-
from fastapi import FastAPI
4+
from . import crud, models, schemas
5+
from .database import SessionLocal, engine
6+
7+
models.Base.metadata.create_all(bind=engine)
48

59
app = FastAPI()
610

711

8-
@app.get("/")
9-
def read_root():
10-
return {"Hello": "World"}
12+
# Dependency
13+
def get_db():
14+
db = SessionLocal()
15+
try:
16+
yield db
17+
finally:
18+
db.close()
19+
20+
21+
@app.post("/users/", response_model=schemas.User)
22+
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
23+
db_user = crud.get_user_by_email(db, email=user.email)
24+
if db_user:
25+
raise HTTPException(status_code=400, detail="Email already registered")
26+
return crud.create_user(db=db, user=user)
27+
28+
29+
@app.get("/users/", response_model=list[schemas.User])
30+
def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
31+
users = crud.get_users(db, skip=skip, limit=limit)
32+
return users
33+
34+
35+
@app.get("/users/{user_id}", response_model=schemas.User)
36+
def read_user(user_id: int, db: Session = Depends(get_db)):
37+
db_user = crud.get_user(db, user_id=user_id)
38+
if db_user is None:
39+
raise HTTPException(status_code=404, detail="User not found")
40+
return db_user
41+
42+
43+
@app.post("/users/{user_id}/items/", response_model=schemas.Item)
44+
def create_item_for_user(
45+
user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
46+
):
47+
return crud.create_user_item(db=db, item=item, user_id=user_id)
1148

1249

13-
@app.get("/items/{item_id}")
14-
def read_item(item_id: int, q: Union[str, None] = None):
15-
return {"item_id": item_id, "q": q}
50+
@app.get("/items/", response_model=list[schemas.Item])
51+
def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
52+
items = crud.get_items(db, skip=skip, limit=limit)
53+
return items

api/app/models.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
2+
from sqlalchemy.orm import relationship
3+
4+
from .database import Base
5+
6+
7+
class User(Base):
8+
__tablename__ = "users"
9+
10+
id = Column(Integer, primary_key=True, index=True)
11+
email = Column(String, unique=True, index=True)
12+
hashed_password = Column(String)
13+
is_active = Column(Boolean, default=True)
14+
15+
items = relationship("Item", back_populates="owner")
16+
17+
18+
class Item(Base):
19+
__tablename__ = "items"
20+
21+
id = Column(Integer, primary_key=True, index=True)
22+
title = Column(String, index=True)
23+
description = Column(String, index=True)
24+
owner_id = Column(Integer, ForeignKey("users.id"))
25+
26+
owner = relationship("User", back_populates="items")

api/app/schemas.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from pydantic import BaseModel
2+
3+
4+
class ItemBase(BaseModel):
5+
title: str
6+
description: str | None = None
7+
8+
9+
class ItemCreate(ItemBase):
10+
pass
11+
12+
13+
class Item(ItemBase):
14+
id: int
15+
owner_id: int
16+
17+
class Config:
18+
orm_mode = True
19+
20+
21+
class UserBase(BaseModel):
22+
email: str
23+
24+
25+
class UserCreate(UserBase):
26+
password: str
27+
28+
29+
class User(UserBase):
30+
id: int
31+
is_active: bool
32+
items: list[Item] = []
33+
34+
class Config:
35+
orm_mode = True

api/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
fastapi
2+
psycopg2
23
pydantic
4+
SQLAlchemy
35
uvicorn

docker-compose.yaml

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ services:
66
ports:
77
- "8000:80"
88
environment:
9+
API_URL: http://api:80
910
ENVIRONMENT: dev
10-
API_URL: localhost:5000
11+
FLASK_DEBUG: 1
12+
SECRET_KEY: "0cX78ltAhxMlwTDAQQYdlXvapcJMWA=="
1113
volumes:
1214
- ./web/app:/code/app
1315
links:
@@ -19,7 +21,21 @@ services:
1921
- "5000:80"
2022
environment:
2123
ENVIRONMENT: dev
22-
WEB_URL: localhost:8000
24+
POSTGRES_DB: example
25+
POSTGRES_PASSWORD: postgres
26+
POSTGRES_USER: postgres
27+
WEB_URL: web:8000
2328
volumes:
2429
- ./api/app:/code/app
25-
- ./db:/code/app/db
30+
links:
31+
- db
32+
db:
33+
image: postgres:15
34+
ports:
35+
- "5432:5432"
36+
environment:
37+
POSTGRES_DB: example
38+
POSTGRES_PASSWORD: postgres
39+
POSTGRES_USER: postgres
40+
volumes:
41+
- ./db:/var/lib/postgresql/data

web/app/main.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,34 @@
1-
from flask import Flask
1+
import os
2+
import requests
23

3-
app = Flask(__name__)
4+
from flask import Flask, render_template, request, flash, redirect, url_for
45

6+
app = Flask(__name__)
7+
app.config['SECRET_KEY'] = os.environ.get("SECRET_KEY")
58

69
@app.route("/")
710
def root():
8-
return "Hello, world."
11+
api_url = os.environ.get("API_URL")
12+
request_url = f'{api_url}/users/'
13+
response = requests.get(request_url)
14+
users = response.json()
15+
16+
return render_template("index.jinja2", users=users)
17+
18+
@app.route("/add", methods=['GET', 'POST'])
19+
def add_user():
20+
if request.method == 'POST':
21+
email = request.form.get('email', "")
22+
password = request.form.get('password', "")
23+
24+
if not email:
25+
flash('Email is required!')
26+
elif not password:
27+
flash('Password is required!')
28+
else:
29+
api_url = os.environ.get("API_URL")
30+
request_url = f'{api_url}/users/'
31+
response = requests.post(request_url, json=request.form)
32+
return redirect(url_for('root'))
33+
else:
34+
return render_template ("add.jinja2")

web/app/templates/add.jinja2

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{% extends 'base.jinja2' %}
2+
3+
{% block content %}
4+
<h1>{% block title %} Add User {% endblock %}</h1>
5+
6+
{% for message in get_flashed_messages() %}
7+
<div class="alert">{{ message }}</div>
8+
{% endfor %}
9+
10+
<form method="post">
11+
<div class="form">
12+
<label for="email">Enter email address: </label>
13+
<input type="text" name="email" id="email" required>
14+
</div>
15+
<div class="form">
16+
<label for="password">Enter your password: </label>
17+
<input type="password" name="password" id="password" required>
18+
</div>
19+
<div class="form">
20+
<input type="submit" value="Submit">
21+
</div>
22+
</form>
23+
{% endblock %}

web/app/templates/base.jinja2

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
6+
<title>{% block title %} {% endblock %}</title>
7+
</head>
8+
<body>
9+
<nav class="navbar navbar-expand-md navbar-light bg-light">
10+
</nav>
11+
<div class="container">
12+
{% block content %} {% endblock %}
13+
</div>
14+
</body>
15+
</html>

0 commit comments

Comments
 (0)