Skip to content

Commit d5b3345

Browse files
committed
add backend folder
1 parent 2b4c57c commit d5b3345

19 files changed

+758
-0
lines changed
File renamed without changes.

backend/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# piccolo_project
2+
3+
## Setup
4+
5+
### Install requirements
6+
7+
```bash
8+
pip install -r requirements.txt
9+
```
10+
11+
### Getting started guide
12+
13+
```bash
14+
python main.py
15+
```
16+
17+
### Running tests
18+
19+
```bash
20+
piccolo tester run
21+
```

backend/accounts/__init__.py

Whitespace-only changes.

backend/accounts/endpoints.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
from blacksheep import FromJSON, json
2+
from blacksheep.cookies import Cookie
3+
from blacksheep.server.controllers import Controller, get, post
4+
from blacksheep.server.responses import redirect
5+
from piccolo.apps.user.tables import BaseUser
6+
from piccolo_api.session_auth.tables import SessionsBase
7+
8+
from accounts.schema import UserModelLogin, UserModelRegister
9+
10+
11+
class AuthController(Controller):
12+
@post("/register")
13+
async def register(request, data: FromJSON[UserModelRegister]):
14+
"""
15+
Register and authenticate user
16+
"""
17+
data = data.__dict__
18+
payload = data["_value"].dict()
19+
if (
20+
await BaseUser.exists().where(BaseUser.email == payload["email"]).run()
21+
or await BaseUser.exists()
22+
.where(BaseUser.username == payload["username"])
23+
.run()
24+
):
25+
user_error = "User with that email or username already exists."
26+
return json({"error": user_error})
27+
# save user
28+
query = BaseUser(**payload)
29+
await query.save().run()
30+
# login user in
31+
valid_user = await BaseUser.login(
32+
username=payload["username"], password=payload["password"]
33+
)
34+
if not valid_user:
35+
user_error = "Invalid username or password"
36+
return json({"error": user_error})
37+
# create session
38+
session = await SessionsBase.create_session(user_id=valid_user)
39+
response = redirect("/")
40+
response.set_cookie(
41+
Cookie(
42+
"id",
43+
f"{session['token']}",
44+
)
45+
)
46+
response.add_header(b"Access-Control-Allow-Credentials", b"true")
47+
return response
48+
49+
@post("/login")
50+
async def login(request, data: FromJSON[UserModelLogin]):
51+
"""
52+
Login and authenticate user
53+
"""
54+
data = data.__dict__
55+
payload = data["_value"].dict()
56+
# login user in
57+
valid_user = await BaseUser.login(
58+
username=payload["username"], password=payload["password"]
59+
)
60+
if not valid_user:
61+
user_error = "Invalid username or password"
62+
return json({"error": user_error})
63+
# create session
64+
session = await SessionsBase.create_session(user_id=valid_user)
65+
response = redirect("/profile")
66+
response.set_cookie(
67+
Cookie(
68+
"id",
69+
f"{session['token']}",
70+
http_only=True,
71+
)
72+
)
73+
response.add_header(b"Access-Control-Allow-Credentials", b"true")
74+
return response
75+
76+
@get("/profile")
77+
async def profile(request):
78+
"""
79+
User profile
80+
"""
81+
data = (
82+
await SessionsBase.select(SessionsBase.user_id)
83+
.where(SessionsBase.token == request.cookies.get("id"))
84+
.first()
85+
.run()
86+
)
87+
if data:
88+
session_user = (
89+
await BaseUser.select(
90+
BaseUser.id,
91+
BaseUser.username,
92+
BaseUser.email,
93+
BaseUser.last_login,
94+
)
95+
.where(BaseUser._meta.primary_key == data["user_id"])
96+
.first()
97+
.run()
98+
)
99+
user = session_user
100+
response = json(
101+
{
102+
"user": user,
103+
}
104+
)
105+
response.add_header(b"Access-Control-Allow-Credentials", b"true")
106+
return response
107+
108+
@post("/logout")
109+
async def logout(request):
110+
"""
111+
Logout user
112+
"""
113+
response = redirect("/")
114+
response.unset_cookie("id")
115+
response.add_header(b"Access-Control-Allow-Credentials", b"true")
116+
return response

backend/accounts/schema.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from piccolo.apps.user.tables import BaseUser
2+
from piccolo_api.crud.serializers import create_pydantic_model
3+
4+
# user models
5+
UserModelRegister = create_pydantic_model(
6+
table=BaseUser,
7+
exclude_columns=(
8+
BaseUser.active,
9+
BaseUser.admin,
10+
BaseUser.superuser,
11+
),
12+
model_name="UserModelRegister",
13+
)
14+
UserModelLogin = create_pydantic_model(
15+
table=BaseUser,
16+
include_columns=(
17+
BaseUser.username,
18+
BaseUser.password,
19+
),
20+
model_name="UserModelLogin",
21+
)

backend/app.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
from blacksheep import Application, json
2+
from blacksheep.server.openapi.v3 import OpenAPIHandler
3+
from openapidocs.v3 import Info
4+
from piccolo.apps.user.tables import BaseUser
5+
from piccolo.engine import engine_finder
6+
from piccolo_admin.endpoints import create_admin
7+
from piccolo_api.session_auth.tables import SessionsBase
8+
9+
from accounts.endpoints import AuthController
10+
from home.endpoints import Tasks
11+
from home.tables import Task
12+
13+
app = Application(show_error_details=True)
14+
15+
16+
app.use_cors(
17+
allow_origins="http://localhost:8080",
18+
allow_methods="*",
19+
allow_headers="*",
20+
allow_credentials=True,
21+
expose_headers="next_cursor,first_row,last_row",
22+
)
23+
24+
# mount Piccolo ASGI apps
25+
app.mount(
26+
"/admin/",
27+
create_admin(
28+
tables=[Task, BaseUser, SessionsBase],
29+
# Required when running under HTTPS:
30+
# allowed_hosts=['my_site.com']
31+
),
32+
)
33+
34+
# Openpi docs
35+
docs = OpenAPIHandler(info=Info(title="BlackSheep API", version="0.0.1"))
36+
docs.bind_app(app)
37+
38+
39+
@docs.ignore()
40+
async def home(request) -> json:
41+
try:
42+
data = (
43+
await SessionsBase.select(SessionsBase.user_id)
44+
.where(SessionsBase.token == request.cookies.get("id"))
45+
.first()
46+
.run()
47+
)
48+
if data:
49+
session_user = (
50+
await BaseUser.select(BaseUser.username)
51+
.where(BaseUser._meta.primary_key == data["user_id"])
52+
.first()
53+
.run()
54+
)
55+
auth_user = session_user["username"]
56+
except UnboundLocalError:
57+
auth_user = ""
58+
response = json(
59+
{
60+
"message": "BlackSheep Piccolo ORM api",
61+
"auth_user": auth_user,
62+
}
63+
)
64+
response.add_header(b"Access-Control-Allow-Credentials", b"true")
65+
return response
66+
67+
68+
app.router.add_get("/", home)
69+
70+
71+
async def open_database_connection_pool(application):
72+
try:
73+
engine = engine_finder()
74+
await engine.start_connection_pool()
75+
except Exception:
76+
print("Unable to connect to the database")
77+
78+
79+
async def close_database_connection_pool(application):
80+
try:
81+
engine = engine_finder()
82+
await engine.close_connection_pool()
83+
except Exception:
84+
print("Unable to connect to the database")
85+
86+
87+
app.on_start += open_database_connection_pool
88+
app.on_stop += close_database_connection_pool

backend/conftest.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import os
2+
import sys
3+
4+
from piccolo.utils.warnings import colored_warning
5+
6+
7+
def pytest_configure(*args):
8+
if os.environ.get("PICCOLO_TEST_RUNNER") != "True":
9+
colored_warning(
10+
"\n\n"
11+
"We recommend running Piccolo tests using the "
12+
"`piccolo tester run` command, which wraps Pytest, and makes "
13+
"sure the test database is being used. "
14+
"To stop this warning, modify conftest.py."
15+
"\n\n"
16+
)
17+
sys.exit(1)

backend/home/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)