Skip to content

Commit 4454e46

Browse files
authored
FastAPI backend (#109)
1 parent 22145e1 commit 4454e46

File tree

131 files changed

+4166
-2864
lines changed

Some content is hidden

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

131 files changed

+4166
-2864
lines changed

.github/workflows/e2e-docker.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: E2E Docker Image
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
pull_request:
7+
branches: [ "main" ]
8+
9+
jobs:
10+
build-and-test:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Check out repository
14+
uses: actions/checkout@v3
15+
16+
- name: Build Docker image
17+
run: |
18+
docker build -t frameos .
19+
20+
- name: Run Docker container
21+
run: |
22+
docker run -d \
23+
-p 8989:8989 \
24+
-v ./db:/app/db \
25+
--name frameos \
26+
--restart always \
27+
-e SECRET_KEY="bananana" \
28+
frameos
29+
30+
- name: Wait for service on port 8989
31+
run: |
32+
echo "Waiting up to 30s for the service to respond on port 8989..."
33+
timeout 30 bash -c "until curl -s http://localhost:8989; do sleep 1; done"
34+
35+
- name: Test /signup endpoint
36+
id: signup-test
37+
run: |
38+
# We'll capture the HTTP status code so we can fail the step if it's not 2xx
39+
echo "Testing /api/signup with a sample payload..."
40+
HTTP_CODE=$(curl -s -o /dev/stderr -w "%{http_code}" -X POST http://localhost:8989/api/signup \
41+
-H "Content-Type: application/json" \
42+
-d '{"email":"[email protected]","password":"asdfasdf","password2":"asdfasdf","newsletter":false}')
43+
44+
if [ "$HTTP_CODE" -ne 200 ]; then
45+
echo "Signup request failed with status code $HTTP_CODE"
46+
exit 1
47+
else
48+
echo "Signup request succeeded with status code $HTTP_CODE"
49+
fi

.github/workflows/pull-request-tests.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ jobs:
9595
run: |
9696
cd backend
9797
source .venv/bin/activate
98+
# fake these folders so static asset serving doesn't fail
99+
mkdir -p ../frontend/dist/assets
100+
mkdir -p ../frontend/dist/img
101+
mkdir -p ../frontend/dist/static
98102
TEST=1 pytest
99103
100104
typescript:

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ dist-ssr
2121
# Editor directories and files
2222
.vscode/*
2323
!.vscode/extensions.json
24+
!.vscode/launch.json
2425
.idea
2526
.DS_Store
2627
*.suo
@@ -32,4 +33,7 @@ dist-ssr
3233

3334
# Nim
3435
frameos/tmp/
35-
e2e/tmp/
36+
e2e/tmp/
37+
38+
gpt.txt
39+
.env

.vscode/launch.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
"name": "Python: FastAPI",
6+
"type": "debugpy",
7+
"request": "launch",
8+
"cwd": "${workspaceFolder}/backend",
9+
"module": "app.fastapi",
10+
"console": "integratedTerminal",
11+
"env": {
12+
"PYTHONPATH": "${workspaceFolder}",
13+
"GEVENT_SUPPORT": "True",
14+
"DEBUG": "True",
15+
},
16+
"justMyCode": false
17+
}
18+
]
19+
}

CONTRIBUTING.md

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,37 +12,66 @@ Python >= 3.11
1212

1313
## FrameOS Backend
1414

15+
Start a redis server if not running
16+
17+
```bash
18+
redis-server --daemonize yes
19+
```
20+
21+
Installing deps
1522

1623
```bash
1724
cd backend
1825
python3 -m venv env
1926
source env/bin/activate
20-
pip install -r requirements.txt
21-
flask db upgrade
27+
uv pip install -r requirements.txt
28+
DEBUG=1 alembic ugprade head
2229

2330
cd ../frontend
2431
npm install
2532

2633
cd ../frameos
2734
nimble install -d
2835
nimble setup
36+
cd ..
37+
```
38+
39+
To run all services at once:
40+
41+
```bash
42+
cd frontend
43+
npm run dev &
44+
cd ../backend
45+
bin/dev
46+
```
47+
48+
To run all of these separately:
2949

50+
```bash
51+
# start the frontend
52+
cd frontend
53+
npm run dev
3054
cd ..
3155

32-
# start a redis server
33-
redis-server --daemonize yes
56+
# apply any migrations
57+
cd backend
58+
DEBUG=1 python -m alembic upgrade head
59+
60+
# start the backend
61+
cd backend
62+
DEBUG=1 python -m app.fastapi
3463

35-
honcho start
64+
# start the job queue
65+
cd backend
66+
DEBUG=1 arq app.tasks.worker
3667
```
3768

3869
## Running migrations
3970

4071
```bash
4172
cd backend
4273
# create migration after changing a model
43-
flask db migrate -m "something changed"
44-
# apply the migrations
45-
flask db upgrade
74+
DEBUG=1 python -m alembic revision --autogenerate -m "name of migration"
4675
```
4776

4877
## Installing pre-commit
@@ -60,7 +89,7 @@ pre-commit uninstall
6089

6190
```bash
6291
cd backend
63-
bin/tests
92+
pytest
6493
```
6594

6695
## FrameOS on-frame software

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ RUN apt-get update && \
1818
apt-get install -y curl xz-utils gcc openssl ca-certificates git # &&
1919

2020
RUN mkdir -p /opt/nim && \
21-
curl -L https://nim-lang.org/download/nim-2.0.4.tar.xz | tar -xJf - -C /opt/nim --strip-components=1 && \
21+
curl -L https://nim-lang.org/download/nim-2.2.0.tar.xz | tar -xJf - -C /opt/nim --strip-components=1 && \
2222
cd /opt/nim && \
2323
sh build.sh && \
2424
bin/nim c koch && \

backend/migrations/alembic.ini renamed to backend/alembic.ini

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
[alembic]
44
# template used to generate migration files
55
# file_template = %%(rev)s_%%(slug)s
6+
script_location = migrations
67

78
# set to 'true' to run the environment during
89
# the 'revision' command, regardless of autogenerate
@@ -11,7 +12,7 @@
1112

1213
# Logging configuration
1314
[loggers]
14-
keys = root,sqlalchemy,alembic,flask_migrate
15+
keys = root,sqlalchemy,alembic
1516

1617
[handlers]
1718
keys = console
@@ -34,11 +35,6 @@ level = INFO
3435
handlers =
3536
qualname = alembic
3637

37-
[logger_flask_migrate]
38-
level = INFO
39-
handlers =
40-
qualname = flask_migrate
41-
4238
[handler_console]
4339
class = StreamHandler
4440
args = (sys.stderr,)

backend/app/__init__.py

Lines changed: 0 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +0,0 @@
1-
from typing import Optional
2-
3-
from gevent import monkey
4-
monkey.patch_all()
5-
6-
import sentry_sdk
7-
import os
8-
from sqlalchemy.exc import OperationalError
9-
10-
from flask import Flask
11-
from flask_login import LoginManager
12-
from flask_sqlalchemy import SQLAlchemy
13-
from flask_migrate import Migrate
14-
from flask_socketio import SocketIO
15-
from config import Config, get_config
16-
from urllib.parse import urlparse
17-
from redis import Redis
18-
19-
# Initialize extensions
20-
db = SQLAlchemy()
21-
login_manager = LoginManager()
22-
login_manager.login_view = 'api.login'
23-
migrate = Migrate()
24-
socketio = SocketIO(async_mode='gevent')
25-
26-
DEFAULT_REDIS_URL = 'redis://localhost:6379/0'
27-
28-
# Redis setup
29-
def create_redis_connection():
30-
redis_url = os.environ.get('REDIS_URL', DEFAULT_REDIS_URL)
31-
parsed_url = urlparse(redis_url)
32-
redis_host = parsed_url.hostname
33-
redis_port = parsed_url.port or 6379
34-
return Redis(host=redis_host, port=redis_port)
35-
36-
redis = create_redis_connection()
37-
38-
# Sentry setup
39-
def initialize_sentry(app):
40-
with app.app_context():
41-
from .models import get_settings_dict
42-
try:
43-
settings = get_settings_dict()
44-
dsn = settings.get('sentry', {}).get('controller_dsn', None)
45-
except OperationalError:
46-
print("Could not get settings dict, db not initialized.")
47-
return
48-
if dsn:
49-
sentry_sdk.init(dsn=dsn, traces_sample_rate=1.0, profiles_sample_rate=1.0)
50-
51-
def create_app(config: Optional[Config] = None):
52-
config = config or get_config()
53-
app = Flask(__name__, static_folder='../../frontend/dist', static_url_path='/', template_folder='../templates')
54-
app.config.from_object(config)
55-
56-
db.init_app(app)
57-
with app.app_context():
58-
os.makedirs('../db', exist_ok=True)
59-
60-
db.create_all()
61-
login_manager.init_app(app)
62-
migrate.init_app(app, db)
63-
socketio.init_app(app, cors_allowed_origins="*", message_queue=os.environ.get('REDIS_URL', DEFAULT_REDIS_URL))
64-
initialize_sentry(app)
65-
66-
from app.views.base import setup_base_routes
67-
setup_base_routes(app)
68-
69-
from app.api import api as api_blueprint
70-
app.register_blueprint(api_blueprint, url_prefix='/api')
71-
72-
return app

backend/app/api/__init__.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
from flask import Blueprint
1+
from fastapi import APIRouter
22

3-
api = Blueprint('api', __name__)
3+
public_api = APIRouter()
4+
private_api = APIRouter()
5+
6+
from .auth import * # noqa: E402, F403
7+
from .apps import * # noqa: E402, F403
8+
from .frames import * # noqa: E402, F403
9+
from .log import * # noqa: E402, F403
10+
from .repositories import * # noqa: E402, F403
11+
from .settings import * # noqa: E402, F403
12+
from .ssh import * # noqa: E402, F403
13+
from .templates import * # noqa: E402, F403
14+
from .users import * # noqa: E402, F403
415

5-
from .apps import *
6-
from .frames import *
7-
from .log import *
8-
from .login import *
9-
from .repositories import *
10-
from .signup import *
11-
from .settings import *
12-
from .templates import *
13-
from .misc import *

0 commit comments

Comments
 (0)