Skip to content

Commit 6f5c5d8

Browse files
author
User
committed
intial db set up
1 parent 0fd784f commit 6f5c5d8

File tree

26 files changed

+3356
-65
lines changed

26 files changed

+3356
-65
lines changed

backend/DB_SETUP.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# Database Setup and Migrations
2+
3+
This document explains how to set up the database and manage migrations for the Copilot backend.
4+
5+
## Prerequisites
6+
7+
- Python 3.8+
8+
- PostgreSQL 13+
9+
- pip
10+
- virtualenv (recommended)
11+
12+
## Setup
13+
14+
1. **Create a virtual environment** (recommended):
15+
```bash
16+
python -m venv .venv
17+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
18+
```
19+
20+
2. **Install dependencies**:
21+
```bash
22+
pip install -e ".[dev]"
23+
```
24+
25+
3. **Set up environment variables**:
26+
Copy `.env.example` to `.env` and update the values:
27+
```bash
28+
cp .env.example .env
29+
```
30+
31+
## Database Configuration
32+
33+
Update the following environment variables in your `.env` file:
34+
35+
```
36+
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/copilot
37+
ASYNC_DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/copilot
38+
```
39+
40+
## Running Migrations
41+
42+
### Create a new migration
43+
44+
To create a new migration after making changes to your models:
45+
46+
```bash
47+
python -m scripts.migrate create --message "your migration message"
48+
```
49+
50+
### Apply migrations
51+
52+
To apply all pending migrations:
53+
54+
```bash
55+
python -m scripts.migrate upgrade head
56+
```
57+
58+
### Rollback a migration
59+
60+
To rollback to a previous migration:
61+
62+
```bash
63+
python -m scripts.migrate downgrade <revision>
64+
```
65+
66+
### Show current migration
67+
68+
To show the current migration:
69+
70+
```bash
71+
python -m scripts.migrate current
72+
```
73+
74+
### Show migration history
75+
76+
To show the migration history:
77+
78+
```bash
79+
python -m scripts.migrate history
80+
```
81+
82+
## Initial Setup
83+
84+
To set up the database and run all migrations:
85+
86+
```bash
87+
./scripts/setup_db.sh
88+
```
89+
90+
This will:
91+
1. Check if the database is accessible
92+
2. Run all pending migrations
93+
3. Create an initial admin user if it doesn't exist
94+
95+
## Database Models
96+
97+
The database models are defined in `app/models/`:
98+
99+
- `base.py`: Base model classes and mixins
100+
- `user.py`: User-related models and schemas
101+
102+
## Common Issues
103+
104+
### Database Connection Issues
105+
106+
If you encounter connection issues:
107+
108+
1. Ensure PostgreSQL is running
109+
2. Check that the database exists and the user has the correct permissions
110+
3. Verify the connection string in your `.env` file
111+
112+
### Migration Issues
113+
114+
If you encounter issues with migrations:
115+
116+
1. Make sure all models are properly imported in `app/models/__init__.py`
117+
2. Check for any syntax errors in your models
118+
3. If needed, you can delete the migration files in `app/alembic/versions/` and create a new initial migration

backend/alembic.ini

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,26 @@
55
script_location = app/alembic
66

77
# template used to generate migration files
8-
# file_template = %%(rev)s_%%(slug)s
8+
file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d_%%(slug)s
99

1010
# timezone to use when rendering the date
1111
# within the migration file as well as the filename.
1212
# string value is passed to dateutil.tz.gettz()
1313
# leave blank for localtime
14-
# timezone =
14+
timezone = UTC
1515

1616
# max length of characters to apply to the
1717
# "slug" field
18-
#truncate_slug_length = 40
18+
truncate_slug_length = 40
1919

2020
# set to 'true' to run the environment during
2121
# the 'revision' command, regardless of autogenerate
22-
# revision_environment = false
22+
revision_environment = false
2323

2424
# set to 'true' to allow .pyc and .pyo files without
2525
# a source .py file to be detected as revisions in the
2626
# versions/ directory
27-
# sourceless = false
27+
sourceless = false
2828

2929
# version location specification; this defaults
3030
# to alembic/versions. When using multiple version
@@ -33,7 +33,10 @@ script_location = app/alembic
3333

3434
# the output encoding used when revision files
3535
# are written from script.py.mako
36-
# output_encoding = utf-8
36+
output_encoding = utf-8
37+
38+
# Database connection string (overridden by env.py)
39+
sqlalchemy.url = postgresql://postgres:postgres@localhost:5432/copilot
3740

3841
# Logging configuration
3942
[loggers]
@@ -46,18 +49,18 @@ keys = console
4649
keys = generic
4750

4851
[logger_root]
49-
level = WARN
52+
level = INFO
5053
handlers = console
5154
qualname =
5255

5356
[logger_sqlalchemy]
54-
level = WARN
55-
handlers =
57+
level = WARNING
58+
handlers =
5659
qualname = sqlalchemy.engine
5760

5861
[logger_alembic]
5962
level = INFO
60-
handlers =
63+
handlers =
6164
qualname = alembic
6265

6366
[handler_console]

backend/app/api/main.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
from fastapi import APIRouter
22

33
from app.api.routes import items, login, private, users, utils
4+
from app.api.routes.auth.router import router as auth_router
45
from app.core.config import settings
56

67
api_router = APIRouter()
7-
api_router.include_router(login.router)
8-
api_router.include_router(users.router)
9-
api_router.include_router(utils.router)
10-
api_router.include_router(items.router)
118

9+
# Include auth routes
10+
api_router.include_router(auth_router, prefix=settings.API_V1_STR)
1211

12+
# Include other routes
13+
api_router.include_router(login.router, tags=["login"])
14+
api_router.include_router(users.router, prefix="/users", tags=["users"])
15+
api_router.include_router(utils.router, tags=["utils"])
16+
api_router.include_router(items.router, prefix="/items", tags=["items"])
17+
18+
# Include private routes in local environment
1319
if settings.ENVIRONMENT == "local":
14-
api_router.include_router(private.router)
20+
api_router.include_router(private.router, tags=["private"])
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# This file makes the auth directory a Python package
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
from datetime import timedelta
2+
from typing import Any
3+
4+
from fastapi import APIRouter, Depends, HTTPException, status
5+
from fastapi.security import OAuth2PasswordRequestForm
6+
from sqlmodel import Session
7+
8+
from app.core import security
9+
from app.core.config import settings
10+
from app.db import get_db
11+
from app.models import User
12+
from app.schemas.auth import Token, UserLogin, UserRegister, UserOut, PasswordResetRequest, PasswordResetConfirm
13+
from app.crud import create_user, get_user_by_email, update_user
14+
15+
router = APIRouter()
16+
17+
@router.post("/signup", response_model=UserOut, status_code=status.HTTP_201_CREATED)
18+
def signup(
19+
*,
20+
db: Session = Depends(get_db),
21+
user_in: UserRegister,
22+
) -> Any:
23+
"""
24+
Create new user with email and password.
25+
"""
26+
# Check if user with this email already exists
27+
db_user = get_user_by_email(db, email=user_in.email)
28+
if db_user:
29+
raise HTTPException(
30+
status_code=status.HTTP_400_BAD_REQUEST,
31+
detail="The user with this email already exists in the system.",
32+
)
33+
34+
# Create new user
35+
user = create_user(db, user_in)
36+
37+
# TODO: Send verification email
38+
39+
return user
40+
41+
@router.post("/login", response_model=Token)
42+
def login(
43+
db: Session = Depends(get_db),
44+
form_data: OAuth2PasswordRequestForm = Depends(),
45+
) -> Any:
46+
"""
47+
OAuth2 compatible token login, get an access token for future requests.
48+
"""
49+
# Authenticate user
50+
user = security.authenticate(
51+
db, email=form_data.username, password=form_data.password
52+
)
53+
if not user:
54+
raise HTTPException(
55+
status_code=status.HTTP_400_BAD_REQUEST,
56+
detail="Incorrect email or password",
57+
)
58+
59+
if not user.is_active:
60+
raise HTTPException(
61+
status_code=status.HTTP_400_BAD_REQUEST,
62+
detail="Inactive user",
63+
)
64+
65+
# Generate tokens
66+
tokens = security.generate_token_response(str(user.id))
67+
68+
# Store refresh token in database
69+
# TODO: Implement refresh token storage
70+
71+
return tokens
72+
73+
@router.post("/refresh", response_model=Token)
74+
def refresh_token(
75+
refresh_token: str,
76+
db: Session = Depends(get_db),
77+
) -> Any:
78+
"""
79+
Refresh access token using a valid refresh token.
80+
"""
81+
# Verify refresh token
82+
try:
83+
token_data = security.verify_refresh_token(refresh_token)
84+
except HTTPException as e:
85+
raise e
86+
87+
# Get user
88+
user = get_user(db, user_id=token_data["sub"])
89+
if not user:
90+
raise HTTPException(
91+
status_code=status.HTTP_404_NOT_FOUND,
92+
detail="User not found",
93+
)
94+
95+
# Generate new tokens
96+
tokens = security.generate_token_response(str(user.id))
97+
98+
# TODO: Update refresh token in database
99+
100+
return tokens
101+
102+
@router.post("/forgot-password", status_code=status.HTTP_202_ACCEPTED)
103+
def forgot_password(
104+
password_reset: PasswordResetRequest,
105+
db: Session = Depends(get_db),
106+
) -> Any:
107+
"""
108+
Request password reset.
109+
"""
110+
user = get_user_by_email(db, email=password_reset.email)
111+
if not user:
112+
# Don't reveal that the user doesn't exist
113+
return {"message": "If your email is registered, you will receive a password reset link."}
114+
115+
# TODO: Generate password reset token and send email
116+
117+
return {"message": "If your email is registered, you will receive a password reset link."}
118+
119+
@router.post("/reset-password", status_code=status.HTTP_200_OK)
120+
def reset_password(
121+
reset_data: PasswordResetConfirm,
122+
db: Session = Depends(get_db),
123+
) -> Any:
124+
"""
125+
Reset password using a valid token.
126+
"""
127+
# TODO: Verify reset token
128+
# TODO: Update user password
129+
130+
return {"message": "Password updated successfully"}
131+
132+
@router.get("/me", response_model=UserOut)
133+
def read_users_me(
134+
current_user: User = Depends(security.get_current_user),
135+
) -> Any:
136+
"""
137+
Get current user.
138+
"""
139+
return current_user

0 commit comments

Comments
 (0)