Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ Before you begin, ensure you have the following installed:

Or with uvicorn:
```bash
uvicorn app:app --reload --host 0.0.0.0 --port 8000
uvicorn backend.app:app --reload --host 0.0.0.0 --port 8000
```

5. **Access the API:**
Expand Down Expand Up @@ -478,4 +478,3 @@ Using new technology can be tricky, but don't worry. Help is always available.
**Made with ❤️ for The Villages Community**

*Stay connected, stay engaged, stay active!*

22 changes: 19 additions & 3 deletions backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pip install -r requirements.txt
python app.py

# Or using uvicorn
uvicorn app:app --reload --host 0.0.0.0 --port 8000
uvicorn backend.app:app --reload --host 0.0.0.0 --port 8000
```

The server will start on `http://localhost:8000`
Expand Down Expand Up @@ -70,14 +70,30 @@ backend/
### Health Check
```
GET /
GET /health
GET /api/v1/health
```

### Events (Placeholder)
### Events (Mock data)
```
GET /events
GET /api/v1/events
```

### Recreation Centers (Mock data)
```
GET /rec_centers
```

### Villages (Mock data)
```
GET /villages
```

### Example Fixtures

Sample JSON fixtures live in `docs/examples/`, with JSON schema definitions in `docs/schemas/`.

*More endpoints will be added as features are implemented.*

## Development
Expand Down Expand Up @@ -136,7 +152,7 @@ For production deployment:
5. Enable HTTPS/SSL

```bash
uvicorn app:app --host 0.0.0.0 --port 8000 --workers 4
uvicorn backend.app:app --host 0.0.0.0 --port 8000 --workers 4
```

## Contributing
Expand Down
36 changes: 36 additions & 0 deletions backend/alembic.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[alembic]
script_location = migrations
prepend_sys_path = .
sqlalchemy.url = sqlite:///./village_connect.db

[loggers]
keys = root,sqlalchemy,alembic

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = WARN
handlers = console

[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine

[logger_alembic]
level = INFO
handlers =
qualname = alembic

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
65 changes: 48 additions & 17 deletions backend/app.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
"""
Village Connect API Server
FastAPI application for serving community data
"""
"""Village Connect API Server."""

import json
from pathlib import Path

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

FIXTURES_DIR = Path(__file__).resolve().parent / "fixtures"
EVENTS_FIXTURE = FIXTURES_DIR / "events.json"
REC_CENTERS_FIXTURE = FIXTURES_DIR / "rec_centers.json"
VILLAGES_FIXTURE = FIXTURES_DIR / "villages.json"


app = FastAPI(
title="Village Connect API",
description="API for community events, residents, and services",
version="0.1.0"
version="0.1.0",
)

# CORS middleware for mobile app access
Expand All @@ -22,29 +28,54 @@
)


def load_fixture(path: Path) -> list[dict]:
if not path.exists():
return []
with path.open(encoding="utf-8") as file:
return json.load(file)


def health_payload() -> dict:
return {"status": "ok"}


@app.get("/")
async def root():
"""Health check endpoint"""
async def root() -> dict:
return {
"status": "ok",
"message": "Village Connect API is running",
"version": "0.1.0"
"version": "0.1.0",
}


@app.get("/health")
async def health() -> dict:
return health_payload()


@app.get("/events")
async def get_events() -> list[dict]:
return load_fixture(EVENTS_FIXTURE)


@app.get("/rec_centers")
async def get_rec_centers() -> list[dict]:
return load_fixture(REC_CENTERS_FIXTURE)


@app.get("/villages")
async def get_villages() -> list[dict]:
return load_fixture(VILLAGES_FIXTURE)


@app.get("/api/v1/events")
async def get_events():
"""Get community events - placeholder endpoint"""
return {
"events": [],
"message": "Events endpoint - to be implemented"
}
async def legacy_events() -> list[dict]:
return load_fixture(EVENTS_FIXTURE)


@app.get("/api/v1/health")
async def health_check():
"""API health check"""
return {"status": "healthy"}
async def legacy_health() -> dict:
return health_payload()


if __name__ == "__main__":
Expand Down
14 changes: 14 additions & 0 deletions backend/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Database configuration and session management."""

import os

from sqlalchemy import create_engine
from sqlalchemy.orm import declarative_base, sessionmaker

DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./village_connect.db")

connect_args = {"check_same_thread": False} if DATABASE_URL.startswith("sqlite") else {}
engine = create_engine(DATABASE_URL, connect_args=connect_args, future=True)

SessionLocal = sessionmaker(bind=engine, autocommit=False, autoflush=False, future=True)
Base = declarative_base()
20 changes: 20 additions & 0 deletions backend/fixtures/events.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[
{
"id": 1,
"title": "Sunrise Yoga",
"description": "Start your day with a gentle yoga session.",
"start_time": "2026-01-21T07:30:00",
"end_time": "2026-01-21T08:30:00",
"location": "Lakeview Lawn",
"rec_center_id": 101
},
{
"id": 2,
"title": "Community Potluck",
"description": "Bring a dish to share and meet neighbors.",
"start_time": "2026-01-22T18:00:00",
"end_time": "2026-01-22T20:00:00",
"location": "Harbor Hall",
"rec_center_id": 102
}
]
16 changes: 16 additions & 0 deletions backend/fixtures/rec_centers.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[
{
"id": 101,
"name": "Lakeview Recreation Center",
"address": "123 Lakeview Drive, The Villages, FL",
"phone": "352-555-0101",
"village_id": 201
},
{
"id": 102,
"name": "Harbor Recreation Center",
"address": "456 Harbor Way, The Villages, FL",
"phone": "352-555-0102",
"village_id": 202
}
]
16 changes: 16 additions & 0 deletions backend/fixtures/villages.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[
{
"id": 201,
"name": "Lakeview",
"county": "Sumter",
"state": "FL",
"description": "Lakeside neighborhood with walking paths."
},
{
"id": 202,
"name": "Harbor",
"county": "Marion",
"state": "FL",
"description": "Harbor-side village with scenic views."
}
]
1 change: 1 addition & 0 deletions backend/migrations/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Alembic migration scripts live in this directory.
52 changes: 52 additions & 0 deletions backend/migrations/env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Alembic environment configuration."""

from logging.config import fileConfig
import os
import sys

from alembic import context
from sqlalchemy import engine_from_config, pool

sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))

config = context.config

if config.config_file_name is not None:
fileConfig(config.config_file_name)

from backend.database import Base # noqa: E402

target_metadata = Base.metadata


def run_migrations_offline() -> None:
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)

with context.begin_transaction():
context.run_migrations()


def run_migrations_online() -> None:
connectable = engine_from_config(
config.get_section(config.config_ini_section, {}),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)

with connectable.connect() as connection:
context.configure(connection=connection, target_metadata=target_metadata)

with context.begin_transaction():
context.run_migrations()


if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
24 changes: 24 additions & 0 deletions backend/migrations/script.py.mako
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""${message}

Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}


def upgrade() -> None:
${upgrades if upgrades else "pass"}


def downgrade() -> None:
${downgrades if downgrades else "pass"}
Empty file.
6 changes: 3 additions & 3 deletions backend/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""
Data models and schemas
"""
"""Data models."""

from .base import Base # noqa: F401
5 changes: 5 additions & 0 deletions backend/models/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Base SQLAlchemy model."""

from backend.database import Base

__all__ = ["Base"]
6 changes: 6 additions & 0 deletions backend/pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[pytest]
testpaths = tests
addopts = --cov=backend --cov-report=term-missing
pythonpath =
.
..
2 changes: 1 addition & 1 deletion backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ python-multipart==0.0.6
# Testing
pytest==7.4.4
pytest-asyncio==0.23.3
httpx==0.26.0
pytest-cov==4.1.0

# Code Quality
black==23.12.1
Expand Down
5 changes: 5 additions & 0 deletions backend/schemas/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Pydantic schemas for API payloads."""

from .event import Event # noqa: F401
from .rec_center import RecCenter # noqa: F401
from .village import Village # noqa: F401
18 changes: 18 additions & 0 deletions backend/schemas/event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""Event schema definitions."""

from datetime import datetime
from typing import Optional

from pydantic import BaseModel, ConfigDict


class Event(BaseModel):
model_config = ConfigDict(from_attributes=True)

id: int
title: str
description: Optional[str] = None
start_time: datetime
end_time: Optional[datetime] = None
location: str
rec_center_id: int
Loading
Loading