Skip to content

Commit 923e191

Browse files
authored
Merge pull request #23 from Sheldenburg/chore/add-linting-and-test-automation
Chore/add linting and test automation
2 parents c4cef16 + 862dae3 commit 923e191

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+938
-180
lines changed

.github/workflows/db-migration.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,4 @@ jobs:
3232
POSTGRES_SERVER: ${{ secrets.POSTGRES_SERVER }}
3333
POSTGRES_PORT: ${{ secrets.POSTGRES_PORT }}
3434
run: |
35-
alembic upgrade head
35+
alembic upgrade head

.github/workflows/fastapi-to-gcr.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,4 @@ jobs:
4545
--platform managed \
4646
--set-env-vars POSTGRES_SERVER=${{ secrets.POSTGRES_SERVER }},POSTGRES_PORT=${{ secrets.POSTGRES_PORT }},POSTGRES_DB=${{ secrets.POSTGRES_DB }},POSTGRES_USER=${{ secrets.POSTGRES_USER }},POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD }},ENCRYPTION_KEY=${{ secrets.ENCRYPTION_KEY }},PROJECT_NAME=${{ env.PROJECT_ID }},FIRST_SUPERUSER=${{ secrets.FIRST_SUPERUSER }},FIRST_SUPERUSER_PASSWORD=${{ secrets.FIRST_SUPERUSER_PASSWORD }} \
4747
--region australia-southeast1 \
48-
--allow-unauthenticated
48+
--allow-unauthenticated

.github/workflows/nextjs-preview.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ jobs:
2121
working-directory: frontend
2222
- name: Deploy Project Artifacts to Vercel
2323
run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }}
24-
working-directory: frontend
24+
working-directory: frontend

.github/workflows/nextjs-production.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ jobs:
2121
working-directory: frontend
2222
- name: Deploy Project Artifacts to Vercel
2323
run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
24-
working-directory: frontend
24+
working-directory: frontend

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
.vscode
2-
.env
2+
.env

README.md

+17-17
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
### Demo video
1010
https://drive.google.com/file/d/1QfZvzOnOeBvtoDMlBb4IxDx3DoC-GC4Q/view?usp=sharing
1111

12-
### Tech stack
12+
### Tech stack
1313
The backend code in this repository originated from the [FastAPI full stack template](https://github.com/fastapi/full-stack-fastapi-template) and includes AI components that make this AI engineer template unique.
1414

1515
To fast-track the MVP building process, which is crucial for most AI products, we have chosen a simple tech stack. This allows people to focus more on iterating through product features rather than on DevOps from the outset.
@@ -21,7 +21,7 @@ To fast-track the MVP building process, which is crucial for most AI products, w
2121
```bash
2222
git clone https://github.com/Sheldenburg/nextjs-fastapi-template.git
2323
```
24-
2. Follow the original [README]('README-original.md') to docker compose and set up Postgres, reverse proxy and Fastapi server. You can leave the original React fronend running if you'd like. Or, you can just comment out the frontend bit in the docker compose file.
24+
2. Follow the original [README]('README-original.md') to docker compose and set up Postgres, reverse proxy and Fastapi server. You can leave the original React fronend running if you'd like. Or, you can just comment out the frontend bit in the docker compose file.
2525
3. Run nextjs app in local
2626
```bash
2727
cd frontend
@@ -33,25 +33,25 @@ npm install
3333
npm run dev
3434
```
3535

36-
Note: This is a fork from the original [Full Stack FastAPI Template](https://github.com/tiangolo/full-stack-fastapi-template "Full Stack FastAPI Template"). Instead of the original Reactjs frontend, we used Nextjs14 in this repository.
36+
Note: This is a fork from the original [Full Stack FastAPI Template](https://github.com/tiangolo/full-stack-fastapi-template "Full Stack FastAPI Template"). Instead of the original Reactjs frontend, we used Nextjs14 in this repository.
3737

38-
Here are the reasons why we wanted to build with Nextjs (a full stack framework built on top of React) over React.
38+
Here are the reasons why we wanted to build with Nextjs (a full stack framework built on top of React) over React.
3939
- Dependencies reduction
40-
A barebone Reactjs application would require installation of dependency packages to achieve multi-page routing, managing API requests, caching etc. There are a number of solutions out there, e.g. in the original repo, @tanstack/react-router is used for routing, @tanstack/react-query + axios are used for managing API requests and caching.
41-
In a Nextjs application, all these functions are built in without the need for third-party libraries. Also, Nextjs14 (app router) uses file-based routing which means the routing is automatically done via the folder structure.
40+
A barebone Reactjs application would require installation of dependency packages to achieve multi-page routing, managing API requests, caching etc. There are a number of solutions out there, e.g. in the original repo, @tanstack/react-router is used for routing, @tanstack/react-query + axios are used for managing API requests and caching.
41+
In a Nextjs application, all these functions are built in without the need for third-party libraries. Also, Nextjs14 (app router) uses file-based routing which means the routing is automatically done via the folder structure.
4242

43-
- Server side rendering
43+
- Server side rendering
4444
Data fetching and mutation in a Nextjs application is mostly dealt in server side while client side data fetching is also allowed. This offers advantages to get away with 'useEffect' and other cumbersome boiler plate codes in order to do data fetching at client side. Server side rendering also offers performance benefit.
4545

46-
- Popularity
47-
Nextjs is getting more and more popular. There are good amount of frontend projects and Youtube tutorials based on Nextjs, which are beginner friendly.
46+
- Popularity
47+
Nextjs is getting more and more popular. There are good amount of frontend projects and Youtube tutorials based on Nextjs, which are beginner friendly.
4848

4949
[![Openapi-fetch](https://openapi-ts.pages.dev/assets/openapi-fetch.svg "Openapi-fetch")](https://openapi-ts.pages.dev/openapi-fetch/ "Openapi-fetch")
5050

51-
Since Nextjs offers caching out-of-box (more caching details refer to [Nextjs caching](http://https://nextjs.org/docs/app/building-your-application/caching "Nextjs caching")), we don't have to use React Query, which is an awesome library to manage client side API requests but it has a little bit learning curve for beginners. Rather, the API requests can just be made via fetch (Nextjs added some improvement on the original javascript fetch function). We chose a very light-weight OpenAPI client library just to read the openapi specification file (saved as 'openapi.json') and make sure we have consistent and clearn code.
51+
Since Nextjs offers caching out-of-box (more caching details refer to [Nextjs caching](http://https://nextjs.org/docs/app/building-your-application/caching "Nextjs caching")), we don't have to use React Query, which is an awesome library to manage client side API requests but it has a little bit learning curve for beginners. Rather, the API requests can just be made via fetch (Nextjs added some improvement on the original javascript fetch function). We chose a very light-weight OpenAPI client library just to read the openapi specification file (saved as 'openapi.json') and make sure we have consistent and clearn code.
5252

53-
Here is an example.
54-
First, in '/frontend/lib/api/index.ts' we initiate the API client.
53+
Here is an example.
54+
First, in '/frontend/lib/api/index.ts' we initiate the API client.
5555

5656
```javascript
5757
import createClient from "openapi-fetch";
@@ -75,25 +75,25 @@ async function getItems() {
7575
return data;
7676
}
7777
```
78-
This is as cleanest as it can possibly get, in my personal opinion, for handling frontend API requests.
78+
This is as cleanest as it can possibly get, in my personal opinion, for handling frontend API requests.
7979
80-
### UI library
81-
We use [Shadcn](https://ui.shadcn.com/ "Shadcn"). It's light-weight, all the UI components are imported as plain javascript code for transparency. So, you can modify to suit your need.
80+
### UI library
81+
We use [Shadcn](https://ui.shadcn.com/ "Shadcn"). It's light-weight, all the UI components are imported as plain javascript code for transparency. So, you can modify to suit your need.
8282
8383
### Deployment
8484
Docker compose is up to date now with nextjs frontend. You can just deploy through docker compose on a remote server.
8585
A friendly warning is this code was recently written (in 3-4 days), so there are still bugs. All pull requests are welcome!
8686
8787
### Roadmap
88-
- Tidy up the error handling bit
88+
- Tidy up the error handling bit
8989
- Add task queue for long last jobs (Celery + Redis)
9090
- AI chat interface
9191
9292
### Backend
9393
We did not change the backend code, all the other details remain valid from the original [README](README-original.md)
9494
9595
### EuclideanAI
96-
Who the hell is EuclideanAI? we are a boutique AI & Data consultancy who provide purpose-built AI, data, machine learning solutions for our clients. [Feel free to reach out!](https://euclideanai.com/contactus/)
96+
Who the hell is EuclideanAI? we are a boutique AI & Data consultancy who provide purpose-built AI, data, machine learning solutions for our clients. [Feel free to reach out!](https://euclideanai.com/contactus/)
9797
9898
### License
9999

backend/docker-compose.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ services:
44
ports:
55
- "8080:8000"
66
volumes:
7-
- ./src:/src
7+
- ./src:/src

backend/src/app/alembic/README

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Generic single-database configuration.
1+
Generic single-database configuration.

backend/src/app/alembic/versions/ae5a34f2895e_add_chat_table.py

+25-13
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,41 @@
1111

1212

1313
# revision identifiers, used by Alembic.
14-
revision = 'ae5a34f2895e'
15-
down_revision = 'da90f41ddaa1'
14+
revision = "ae5a34f2895e"
15+
down_revision = "da90f41ddaa1"
1616
branch_labels = None
1717
depends_on = None
1818

1919

2020
def upgrade():
2121
# ### commands auto generated by Alembic - please adjust! ###
22-
op.create_table('chat',
23-
sa.Column('id', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
24-
sa.Column('chat_config_id', sa.Integer(), nullable=False),
25-
sa.Column('messages', sa.JSON(), nullable=True),
26-
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
27-
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
28-
sa.Column('owner_id', sa.Integer(), nullable=False),
29-
sa.ForeignKeyConstraint(['chat_config_id'], ['chatconfig.id'], ),
30-
sa.ForeignKeyConstraint(['owner_id'], ['user.id'], ),
31-
sa.PrimaryKeyConstraint('id')
22+
op.create_table(
23+
"chat",
24+
sa.Column("id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
25+
sa.Column("chat_config_id", sa.Integer(), nullable=False),
26+
sa.Column("messages", sa.JSON(), nullable=True),
27+
sa.Column(
28+
"created_at",
29+
sa.DateTime(timezone=True),
30+
server_default=sa.text("now()"),
31+
nullable=True,
32+
),
33+
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=True),
34+
sa.Column("owner_id", sa.Integer(), nullable=False),
35+
sa.ForeignKeyConstraint(
36+
["chat_config_id"],
37+
["chatconfig.id"],
38+
),
39+
sa.ForeignKeyConstraint(
40+
["owner_id"],
41+
["user.id"],
42+
),
43+
sa.PrimaryKeyConstraint("id"),
3244
)
3345
# ### end Alembic commands ###
3446

3547

3648
def downgrade():
3749
# ### commands auto generated by Alembic - please adjust! ###
38-
op.drop_table('chat')
50+
op.drop_table("chat")
3951
# ### end Alembic commands ###

backend/src/app/alembic/versions/da90f41ddaa1_add_chatconfig_table.py

+20-14
Original file line numberDiff line numberDiff line change
@@ -11,30 +11,36 @@
1111

1212

1313
# revision identifiers, used by Alembic.
14-
revision = 'da90f41ddaa1'
15-
down_revision = 'e2412789c190'
14+
revision = "da90f41ddaa1"
15+
down_revision = "e2412789c190"
1616
branch_labels = None
1717
depends_on = None
1818

1919

2020
def upgrade():
2121
# ### commands auto generated by Alembic - please adjust! ###
22-
op.create_table('chatconfig',
23-
sa.Column('model', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
24-
sa.Column('temperature', sa.Float(), nullable=False),
25-
sa.Column('top_p', sa.Float(), nullable=True),
26-
sa.Column('top_k', sa.Integer(), nullable=True),
27-
sa.Column('system_message', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
28-
sa.Column('id', sa.Integer(), nullable=False),
29-
sa.Column('owner_id', sa.Integer(), nullable=False),
30-
sa.Column('api_key_encrypted', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
31-
sa.ForeignKeyConstraint(['owner_id'], ['user.id'], ),
32-
sa.PrimaryKeyConstraint('id')
22+
op.create_table(
23+
"chatconfig",
24+
sa.Column("model", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
25+
sa.Column("temperature", sa.Float(), nullable=False),
26+
sa.Column("top_p", sa.Float(), nullable=True),
27+
sa.Column("top_k", sa.Integer(), nullable=True),
28+
sa.Column("system_message", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
29+
sa.Column("id", sa.Integer(), nullable=False),
30+
sa.Column("owner_id", sa.Integer(), nullable=False),
31+
sa.Column(
32+
"api_key_encrypted", sqlmodel.sql.sqltypes.AutoString(), nullable=True
33+
),
34+
sa.ForeignKeyConstraint(
35+
["owner_id"],
36+
["user.id"],
37+
),
38+
sa.PrimaryKeyConstraint("id"),
3339
)
3440
# ### end Alembic commands ###
3541

3642

3743
def downgrade():
3844
# ### commands auto generated by Alembic - please adjust! ###
39-
op.drop_table('chatconfig')
45+
op.drop_table("chatconfig")
4046
# ### end Alembic commands ###

backend/src/app/api/main_route.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from fastapi import APIRouter
22

3-
from app.api.routes import items, login, users, utils, chat
3+
from app.api.routes import chat, items, login, users, utils
44

55
api_router = APIRouter()
66
api_router.include_router(login.router, tags=["login"])

backend/src/app/api/routes/chat.py

+11-28
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,25 @@
11
from typing import Any
22

3-
from fastapi import APIRouter, HTTPException, Response, status
4-
from sqlmodel import func, select
5-
6-
from app.api.deps import CurrentUser, SessionDep
7-
from app.models import (
8-
Item,
9-
ItemCreate,
10-
ItemPublic,
11-
ItemsPublic,
12-
ItemUpdate,
13-
Message,
14-
MessageBase,
15-
)
16-
17-
from pydantic import BaseModel
18-
3+
from dotenv import load_dotenv
4+
from fastapi import APIRouter, HTTPException, status
195
from fastapi.responses import StreamingResponse
20-
216
from openai import OpenAI
7+
from pydantic import BaseModel
8+
from sqlmodel import func, select
229

23-
import os
24-
25-
from dotenv import load_dotenv
26-
27-
from app.api.utils import get_streamed_response
10+
from app.api.deps import CurrentUser, SessionDep
11+
from app.core.security import decrypt_api_key, encrypt_api_key
2812
from app.models import (
13+
Chat,
2914
ChatConfig,
3015
ChatConfigBase,
3116
ChatConfigCreate,
3217
ChatConfigPublic,
33-
Chat,
3418
ChatPublic,
19+
Message,
20+
MessageBase,
3521
)
3622

37-
from app.core.security import encrypt_api_key, decrypt_api_key
38-
3923
router = APIRouter()
4024

4125
load_dotenv()
@@ -196,11 +180,11 @@ def read_chat_config(
196180

197181

198182
@router.put("/config", response_model=ChatConfigPublic)
199-
def create_chat_config(
183+
def update_chat_config(
200184
*, session: SessionDep, current_user: CurrentUser, config_in: ChatConfigCreate
201185
) -> Any:
202186
"""
203-
Create chat config.
187+
Update chat config.
204188
"""
205189
# check if the user already has a chat config
206190
statement = select(ChatConfig).where(ChatConfig.owner_id == current_user.id)
@@ -240,7 +224,6 @@ async def chat_handler(
240224
async def chat_stream_handler(
241225
chat_request: ChatRequest, session: SessionDep, current_user: CurrentUser
242226
) -> StreamingResponse:
243-
content = ""
244227
# get the chat config from the database if it exists
245228
chatConfig = get_chat_config(session, current_user)
246229
try:

backend/src/app/api/routes/shop.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
# shop.py
2-
from typing import Any, Dict, List
2+
from typing import Any
33

4-
from fastapi import APIRouter, HTTPException
54
import requests
65
from bs4 import BeautifulSoup
7-
6+
from fastapi import APIRouter, HTTPException
87
from pydantic import BaseModel
98

109
router = APIRouter()
@@ -20,11 +19,11 @@ class GoogleShoppingRequest(BaseModel):
2019
class ScrapedData(BaseModel):
2120
title: str
2221
description: str
23-
links: List[str]
22+
links: list[str]
2423

2524

2625
@router.get("/google_shopping")
27-
def google_shopping_search(request: GoogleShoppingRequest) -> Dict[str, Any]:
26+
def google_shopping_search(request: GoogleShoppingRequest) -> dict[str, Any]:
2827
"""
2928
Fetches products from Google Shopping using SerpApi.
3029
"""

backend/src/app/api/routes/users.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from app.core.config import settings
1313
from app.core.security import get_password_hash, verify_password
1414
from app.models import (
15+
Chat,
1516
Item,
1617
Message,
1718
UpdatePassword,
@@ -22,7 +23,6 @@
2223
UsersPublic,
2324
UserUpdate,
2425
UserUpdateMe,
25-
Chat,
2626
)
2727
from app.utils import generate_new_account_email, send_email
2828

backend/src/app/core/config.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ def server_host(self) -> str:
4343
return f"http://{self.DOMAIN}"
4444
return f"https://{self.DOMAIN}"
4545

46-
BACKEND_CORS_ORIGINS: Annotated[list[AnyUrl] | str, BeforeValidator(parse_cors)] = (
47-
[]
48-
)
46+
BACKEND_CORS_ORIGINS: Annotated[
47+
list[AnyUrl] | str, BeforeValidator(parse_cors)
48+
] = []
4949

5050
PROJECT_NAME: str
5151
SENTRY_DSN: HttpUrl | None = None

backend/src/app/core/security.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
from datetime import datetime, timedelta
22
from typing import Any
33

4+
from cryptography.fernet import Fernet
45
from jose import jwt
56
from passlib.context import CryptContext
67

78
from app.core.config import settings
89

9-
from cryptography.fernet import Fernet
10-
1110
# Initialize Fernet with the key
1211
cipher_suite = Fernet(settings.ENCRYPTION_KEY)
1312

0 commit comments

Comments
 (0)