Add proper README and remove development documentation files
- Created comprehensive README.md with setup instructions and API overview - Removed 10 internal development .md files - Updated .gitignore to prevent future development docs from being tracked - Repository now has clean, professional documentation structure
This commit is contained in:
parent
7ab47d6017
commit
17aeb7d55e
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -98,3 +98,12 @@ clean_*.sql
|
||||||
force_*.sql
|
force_*.sql
|
||||||
validate_*.sql
|
validate_*.sql
|
||||||
verify_*.sql
|
verify_*.sql
|
||||||
|
|
||||||
|
# Development docs (keep only README.md)
|
||||||
|
*_GUIDE.md
|
||||||
|
*_PLAN.md
|
||||||
|
*_SUMMARY.md
|
||||||
|
*_STEPS.md
|
||||||
|
*_MIGRATION*.md
|
||||||
|
*_COMPLETE.md
|
||||||
|
README_*.md
|
||||||
|
|
|
@ -1,475 +0,0 @@
|
||||||
# Frontend Migration Guide
|
|
||||||
|
|
||||||
## Backend API Overview
|
|
||||||
|
|
||||||
The backend provides two API versions with smart timezone handling and proper URL generation:
|
|
||||||
|
|
||||||
### API Versions
|
|
||||||
- **V1 API** (`/api/*`): Legacy compatibility, returns EST timezone, existing URL formats
|
|
||||||
- **V2 API** (`/api/v2/*`): Modern API, returns UTC timestamps, client handles timezone conversion
|
|
||||||
|
|
||||||
## Authentication
|
|
||||||
|
|
||||||
### Login
|
|
||||||
```http
|
|
||||||
POST /api/auth/login
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"username": "admin",
|
|
||||||
"password": "password"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"token": "jwt_token_here",
|
|
||||||
"user": {
|
|
||||||
"id": "uuid",
|
|
||||||
"username": "admin"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Protected Routes
|
|
||||||
- Add header: `Authorization: Bearer {token}`
|
|
||||||
- Admin routes are under `/api/admin/*`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Bulletins API
|
|
||||||
|
|
||||||
### List Bulletins
|
|
||||||
```http
|
|
||||||
GET /api/bulletins?page=1&per_page=20&active_only=true
|
|
||||||
GET /api/v2/bulletins?page=1&per_page=20
|
|
||||||
```
|
|
||||||
|
|
||||||
### Get Current Bulletin (≤ today's date)
|
|
||||||
```http
|
|
||||||
GET /api/bulletins/current
|
|
||||||
GET /api/v2/bulletins/current
|
|
||||||
```
|
|
||||||
|
|
||||||
### Get Next Bulletin (> today's date) - NEW!
|
|
||||||
```http
|
|
||||||
GET /api/bulletins/next
|
|
||||||
GET /api/v2/bulletins/next
|
|
||||||
```
|
|
||||||
|
|
||||||
### Get Bulletin by ID
|
|
||||||
```http
|
|
||||||
GET /api/bulletins/{id}
|
|
||||||
GET /api/v2/bulletins/{id}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Create Bulletin (Admin)
|
|
||||||
```http
|
|
||||||
POST /api/admin/bulletins
|
|
||||||
Authorization: Bearer {token}
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"title": "Weekly Bulletin",
|
|
||||||
"date": "2025-08-02",
|
|
||||||
"url": "https://example.com",
|
|
||||||
"cover_image": null,
|
|
||||||
"sabbath_school": "Elder Smith",
|
|
||||||
"divine_worship": "Pastor Johnson",
|
|
||||||
"scripture_reading": "John 3:16",
|
|
||||||
"sunset": "7:45 PM",
|
|
||||||
"is_active": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Update Bulletin (Admin)
|
|
||||||
```http
|
|
||||||
PUT /api/admin/bulletins/{id}
|
|
||||||
Authorization: Bearer {token}
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{...same fields as create...}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Delete Bulletin (Admin)
|
|
||||||
```http
|
|
||||||
DELETE /api/admin/bulletins/{id}
|
|
||||||
Authorization: Bearer {token}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Events API
|
|
||||||
|
|
||||||
### List Events
|
|
||||||
```http
|
|
||||||
GET /api/events?page=1&per_page=20
|
|
||||||
GET /api/v2/events?page=1&per_page=20
|
|
||||||
```
|
|
||||||
|
|
||||||
### Get Upcoming Events
|
|
||||||
```http
|
|
||||||
GET /api/events/upcoming?limit=10
|
|
||||||
GET /api/v2/events/upcoming?limit=10
|
|
||||||
```
|
|
||||||
|
|
||||||
### Get Featured Events
|
|
||||||
```http
|
|
||||||
GET /api/events/featured?limit=5
|
|
||||||
GET /api/v2/events/featured?limit=5
|
|
||||||
```
|
|
||||||
|
|
||||||
### Get Event by ID
|
|
||||||
```http
|
|
||||||
GET /api/events/{id}
|
|
||||||
GET /api/v2/events/{id}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Submit Event (Public)
|
|
||||||
```http
|
|
||||||
POST /api/events/submit
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"title": "Prayer Meeting",
|
|
||||||
"description": "Weekly prayer meeting",
|
|
||||||
"start_time": "2025-08-02T19:00:00",
|
|
||||||
"end_time": "2025-08-02T20:00:00",
|
|
||||||
"location": "Fellowship Hall",
|
|
||||||
"location_url": "https://maps.google.com/...",
|
|
||||||
"category": "worship",
|
|
||||||
"is_featured": false,
|
|
||||||
"recurring_type": "weekly",
|
|
||||||
"bulletin_week": "2025-08-02",
|
|
||||||
"submitter_email": "user@example.com"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Admin Event Management
|
|
||||||
```http
|
|
||||||
POST /api/admin/events # Create event
|
|
||||||
PUT /api/admin/events/{id} # Update event
|
|
||||||
DELETE /api/admin/events/{id} # Delete event
|
|
||||||
GET /api/admin/events/pending # List pending submissions
|
|
||||||
POST /api/admin/events/pending/{id}/approve # Approve pending
|
|
||||||
POST /api/admin/events/pending/{id}/reject # Reject pending
|
|
||||||
DELETE /api/admin/events/pending/{id} # Delete pending
|
|
||||||
```
|
|
||||||
|
|
||||||
### Admin User Management
|
|
||||||
```http
|
|
||||||
GET /api/admin/users # List all users
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## File Uploads (Admin)
|
|
||||||
|
|
||||||
### Upload Bulletin PDF
|
|
||||||
```http
|
|
||||||
POST /api/upload/bulletins/{id}/pdf
|
|
||||||
Authorization: Bearer {token}
|
|
||||||
Content-Type: multipart/form-data
|
|
||||||
|
|
||||||
file: bulletin.pdf
|
|
||||||
```
|
|
||||||
|
|
||||||
### Upload Bulletin Cover Image
|
|
||||||
```http
|
|
||||||
POST /api/upload/bulletins/{id}/cover
|
|
||||||
Authorization: Bearer {token}
|
|
||||||
Content-Type: multipart/form-data
|
|
||||||
|
|
||||||
file: cover.jpg
|
|
||||||
```
|
|
||||||
|
|
||||||
### Upload Event Image
|
|
||||||
```http
|
|
||||||
POST /api/upload/events/{id}/image
|
|
||||||
Authorization: Bearer {token}
|
|
||||||
Content-Type: multipart/form-data
|
|
||||||
|
|
||||||
file: event.jpg
|
|
||||||
```
|
|
||||||
|
|
||||||
**Upload Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"file_path": "uploads/bulletins/uuid.pdf",
|
|
||||||
"pdf_path": "https://api.rockvilletollandsda.church/uploads/bulletins/uuid.pdf",
|
|
||||||
"message": "File uploaded successfully"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note:** Files are served at `/uploads/*` path (handled by Caddy, not API)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Scripture Processing
|
|
||||||
|
|
||||||
The API now automatically processes scripture references in bulletin fields:
|
|
||||||
|
|
||||||
### Automatic Scripture Lookup
|
|
||||||
- **Input:** Short reference like `"John 3:16 KJV"`
|
|
||||||
- **Output:** Enhanced with full verse text: `"For God so loved the world... - John 3:16 KJV"`
|
|
||||||
- **Fallback:** If no match found, returns original text unchanged
|
|
||||||
- **Smart Detection:** Already long texts (>50 chars) are left unchanged
|
|
||||||
|
|
||||||
### How It Works
|
|
||||||
1. When creating/updating bulletins, `scripture_reading` field is processed
|
|
||||||
2. Uses existing Bible verse database with fuzzy search
|
|
||||||
3. Matches on both reference and partial text content
|
|
||||||
4. Returns best match from database
|
|
||||||
|
|
||||||
### Example API Response
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"id": "...",
|
|
||||||
"title": "Weekly Bulletin",
|
|
||||||
"scripture_reading": "For God so loved the world, that he gave his only begotten Son, that whosoever believeth in him should not perish, but have everlasting life. - John 3:16 KJV",
|
|
||||||
...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Other APIs
|
|
||||||
|
|
||||||
### Bible Verses
|
|
||||||
```http
|
|
||||||
GET /api/bible_verses/random
|
|
||||||
GET /api/bible_verses?page=1&per_page=20
|
|
||||||
GET /api/bible_verses/search?q=love&limit=10
|
|
||||||
|
|
||||||
GET /api/v2/bible_verses/random
|
|
||||||
GET /api/v2/bible_verses?page=1&per_page=20
|
|
||||||
GET /api/v2/bible_verses/search?q=love&limit=10
|
|
||||||
```
|
|
||||||
|
|
||||||
### Contact Form
|
|
||||||
```http
|
|
||||||
POST /api/contact
|
|
||||||
POST /api/v2/contact
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "John Doe",
|
|
||||||
"email": "john@example.com",
|
|
||||||
"subject": "Question",
|
|
||||||
"message": "Hello..."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Schedule
|
|
||||||
```http
|
|
||||||
GET /api/schedule?date=2025-08-02
|
|
||||||
GET /api/conference-data
|
|
||||||
|
|
||||||
GET /api/v2/schedule?date=2025-08-02
|
|
||||||
GET /api/v2/conference-data
|
|
||||||
```
|
|
||||||
|
|
||||||
### Admin Schedule Management
|
|
||||||
```http
|
|
||||||
POST /api/admin/schedule # Create schedule
|
|
||||||
PUT /api/admin/schedule/{date} # Update schedule by date
|
|
||||||
DELETE /api/admin/schedule/{date} # Delete schedule by date
|
|
||||||
GET /api/admin/schedule # List all schedules
|
|
||||||
```
|
|
||||||
|
|
||||||
### Sermons & Livestreams
|
|
||||||
```http
|
|
||||||
GET /api/sermons
|
|
||||||
GET /api/livestreams
|
|
||||||
```
|
|
||||||
|
|
||||||
### Configuration
|
|
||||||
```http
|
|
||||||
GET /api/config # Public config
|
|
||||||
GET /api/admin/config # Admin config (protected)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Legacy Android App Support
|
|
||||||
```http
|
|
||||||
GET /api/collections/rtsda_android/records # Legacy Android app update check
|
|
||||||
```
|
|
||||||
|
|
||||||
### Debug Endpoints
|
|
||||||
```http
|
|
||||||
GET /api/debug/jellyfin # Debug Jellyfin connectivity (development only)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Response Format
|
|
||||||
|
|
||||||
All responses follow this format:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {...},
|
|
||||||
"message": "Optional message"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Paginated responses:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"items": [...],
|
|
||||||
"total": 150,
|
|
||||||
"page": 1,
|
|
||||||
"per_page": 20,
|
|
||||||
"total_pages": 8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Error responses:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": false,
|
|
||||||
"message": "Error description"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Timezone Handling
|
|
||||||
|
|
||||||
### V1 API (Legacy)
|
|
||||||
- **Input:** Accepts times in any format
|
|
||||||
- **Output:** Converts all timestamps to EST timezone
|
|
||||||
- **Use case:** Existing clients that expect EST times
|
|
||||||
|
|
||||||
### V2 API (Modern)
|
|
||||||
- **Input:** Expects UTC timestamps with timezone info when needed
|
|
||||||
- **Output:** Returns UTC timestamps
|
|
||||||
- **Client responsibility:** Convert to local timezone for display
|
|
||||||
|
|
||||||
**V2 Timezone Example:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"start_time": "2025-08-02T23:00:00Z",
|
|
||||||
"timezone_info": {
|
|
||||||
"utc": "2025-08-02T23:00:00Z",
|
|
||||||
"local_display": "2025-08-02T19:00:00-04:00"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Frontend Migration Strategy
|
|
||||||
|
|
||||||
### Phase 1: Update Shared Rust Crate
|
|
||||||
1. **Add V2 API models** with UTC timestamp handling
|
|
||||||
2. **Keep V1 models** for backward compatibility
|
|
||||||
3. **Add timezone conversion utilities**
|
|
||||||
4. **Update HTTP client** to handle both API versions
|
|
||||||
|
|
||||||
### Phase 2: Client-by-Client Migration
|
|
||||||
1. **Web Admin Panel:** Migrate to V2 API first
|
|
||||||
2. **Mobile App:** Update to use new bulletin endpoints (`/next`)
|
|
||||||
3. **Website:** Gradually migrate public endpoints
|
|
||||||
4. **Keep V1 for old clients** until all are updated
|
|
||||||
|
|
||||||
### Phase 3: New Features
|
|
||||||
1. **Use V2 API only** for new features
|
|
||||||
2. **Proper UTC handling** from day one
|
|
||||||
3. **Client-side timezone conversion**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Breaking Changes to Watch For
|
|
||||||
|
|
||||||
### URL Structure
|
|
||||||
- **Old:** Some inconsistent URL patterns
|
|
||||||
- **New:** Consistent `/api/v2/*` structure
|
|
||||||
- **Files:** Always served at `/uploads/*` (via Caddy)
|
|
||||||
|
|
||||||
### Timestamp Format
|
|
||||||
- **V1:** Mixed timezone handling, EST output
|
|
||||||
- **V2:** Consistent UTC timestamps
|
|
||||||
- **Migration:** Update date parsing/formatting code
|
|
||||||
|
|
||||||
### Response Fields
|
|
||||||
- **V2 may have additional fields** for timezone info
|
|
||||||
- **V1 fields remain unchanged** for compatibility
|
|
||||||
- **New endpoints** (like `/next`) available in both versions
|
|
||||||
|
|
||||||
### Authentication
|
|
||||||
- **Same JWT tokens** work for both API versions
|
|
||||||
- **Admin routes** use same authorization header
|
|
||||||
- **No changes needed** to auth flow
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Implementation Notes
|
|
||||||
|
|
||||||
### Error Handling
|
|
||||||
```rust
|
|
||||||
// Example error handling in shared crate
|
|
||||||
match api_client.get_current_bulletin().await {
|
|
||||||
Ok(response) if response.success => {
|
|
||||||
// Handle response.data
|
|
||||||
},
|
|
||||||
Ok(response) => {
|
|
||||||
// Handle API error: response.message
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
// Handle network/parsing error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Timezone Conversion (V2)
|
|
||||||
```rust
|
|
||||||
// Example timezone handling
|
|
||||||
fn convert_utc_to_local(utc_time: &str, timezone: &str) -> Result<String> {
|
|
||||||
let utc = DateTime::parse_from_rfc3339(utc_time)?;
|
|
||||||
let local_tz: Tz = timezone.parse()?;
|
|
||||||
Ok(utc.with_timezone(&local_tz).to_string())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### File Upload
|
|
||||||
```rust
|
|
||||||
// Example multipart upload
|
|
||||||
let form = multipart::Form::new()
|
|
||||||
.file("file", path_to_file)?;
|
|
||||||
|
|
||||||
let response = client
|
|
||||||
.post(&format!("{}/api/upload/bulletins/{}/pdf", base_url, bulletin_id))
|
|
||||||
.bearer_auth(&token)
|
|
||||||
.multipart(form)
|
|
||||||
.send()
|
|
||||||
.await?;
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing Endpoints
|
|
||||||
|
|
||||||
### Development
|
|
||||||
- **API Base:** `http://localhost:3002`
|
|
||||||
- **Files:** `http://localhost:3002/uploads/*`
|
|
||||||
|
|
||||||
### Production
|
|
||||||
- **API Base:** `https://api.rockvilletollandsda.church`
|
|
||||||
- **Files:** `https://api.rockvilletollandsda.church/uploads/*`
|
|
||||||
|
|
||||||
### Health Check
|
|
||||||
```http
|
|
||||||
GET /api/config
|
|
||||||
```
|
|
||||||
Should return basic configuration without authentication.
|
|
178
NEXT_STEPS.md
178
NEXT_STEPS.md
|
@ -1,178 +0,0 @@
|
||||||
# Next Steps for Service Layer Migration
|
|
||||||
|
|
||||||
## Immediate Actions Required
|
|
||||||
|
|
||||||
### 1. Clean Up Current EventService Import
|
|
||||||
```bash
|
|
||||||
# Remove unused import from events service
|
|
||||||
# File: src/services/events.rs line 10
|
|
||||||
# Remove: db_operations::EventOperations,
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Migrate Remaining Modules (In Priority Order)
|
|
||||||
|
|
||||||
#### A. Bulletins Service (HIGH PRIORITY)
|
|
||||||
**Files to create:**
|
|
||||||
```rust
|
|
||||||
// src/services/bulletins.rs
|
|
||||||
pub struct BulletinService;
|
|
||||||
impl BulletinService {
|
|
||||||
pub async fn create_v1(pool: &PgPool, req: CreateBulletinRequest, url_builder: &UrlBuilder) -> Result<Bulletin> {
|
|
||||||
let bulletin = db::bulletins::create(pool, req).await?;
|
|
||||||
convert_bulletin_to_v1(bulletin, url_builder)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn update_v1(pool: &PgPool, id: &Uuid, req: UpdateBulletinRequest, url_builder: &UrlBuilder) -> Result<Bulletin> {
|
|
||||||
let bulletin = db::bulletins::update(pool, id, req).await?;
|
|
||||||
convert_bulletin_to_v1(bulletin, url_builder)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add V2 methods with timezone flexibility
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Files to modify:**
|
|
||||||
- `src/handlers/bulletins.rs` - Replace direct db calls with BulletinService calls
|
|
||||||
- `src/handlers/v2/bulletins.rs` - Replace direct db calls with BulletinService calls
|
|
||||||
- `src/services/mod.rs` - Add `pub mod bulletins;` and `pub use bulletins::BulletinService;`
|
|
||||||
|
|
||||||
#### B. Users/Auth Service (HIGH PRIORITY)
|
|
||||||
**Files to create:**
|
|
||||||
```rust
|
|
||||||
// src/services/auth.rs
|
|
||||||
pub struct AuthService;
|
|
||||||
impl AuthService {
|
|
||||||
pub async fn authenticate_user(pool: &PgPool, username: &str, password: &str) -> Result<User> {
|
|
||||||
let user = db::users::get_by_username(pool, username).await?
|
|
||||||
.ok_or_else(|| ApiError::Unauthorized("Invalid credentials".to_string()))?;
|
|
||||||
|
|
||||||
let password_hash = db::users::get_password_hash(pool, &user.id).await?;
|
|
||||||
|
|
||||||
// Verify password logic here
|
|
||||||
// Return user with V1 timezone conversion if needed
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_user_by_id(pool: &PgPool, id: &Uuid) -> Result<Option<User>> {
|
|
||||||
db::users::get_by_id(pool, id).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Files to modify:**
|
|
||||||
- `src/handlers/auth.rs` - Replace direct db calls with AuthService calls
|
|
||||||
|
|
||||||
#### C. Bible Verses Service
|
|
||||||
**Files to create:**
|
|
||||||
```rust
|
|
||||||
// src/services/bible_verses.rs
|
|
||||||
pub struct BibleVerseService;
|
|
||||||
impl BibleVerseService {
|
|
||||||
pub async fn get_random_v1(pool: &PgPool) -> Result<Option<BibleVerse>> {
|
|
||||||
let verse = BibleVerseOperations::get_random(pool).await?;
|
|
||||||
// Apply V1 timezone conversion if needed
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn search_v1(pool: &PgPool, query: &str, limit: i64) -> Result<Vec<BibleVerse>> {
|
|
||||||
let verses = BibleVerseOperations::search(pool, query, limit).await?;
|
|
||||||
// Apply V1 timezone conversion if needed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### D. Schedule Service
|
|
||||||
**Files to create:**
|
|
||||||
```rust
|
|
||||||
// src/services/schedule.rs
|
|
||||||
pub struct ScheduleService;
|
|
||||||
impl ScheduleService {
|
|
||||||
pub async fn get_by_date_v1(pool: &PgPool, date: NaiveDate) -> Result<Option<Schedule>> {
|
|
||||||
ScheduleOperations::get_by_date(pool, date).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_for_range_v1(pool: &PgPool, start: NaiveDate, end: NaiveDate) -> Result<Vec<Schedule>> {
|
|
||||||
ScheduleOperations::get_for_range(pool, start, end).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### E. Config Service (LOW PRIORITY)
|
|
||||||
**Files to create:**
|
|
||||||
```rust
|
|
||||||
// src/services/config.rs
|
|
||||||
pub struct ConfigService;
|
|
||||||
impl ConfigService {
|
|
||||||
pub async fn update_config(pool: &PgPool, config: ChurchConfig) -> Result<ChurchConfig> {
|
|
||||||
// Add business logic validation here
|
|
||||||
db::config::update_config(pool, config).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Migration Checklist Template
|
|
||||||
|
|
||||||
For each module, follow this checklist:
|
|
||||||
|
|
||||||
### Service Creation
|
|
||||||
- [ ] Create `src/services/{module}.rs`
|
|
||||||
- [ ] Implement `{Module}Service` struct
|
|
||||||
- [ ] Add V1 methods that call `db::{module}::*` functions
|
|
||||||
- [ ] Add V2 methods with timezone flexibility
|
|
||||||
- [ ] Apply proper timezone conversions and URL building
|
|
||||||
|
|
||||||
### Handler Migration
|
|
||||||
- [ ] Update imports to use service instead of direct db calls
|
|
||||||
- [ ] Replace `db::{module}::*` calls with `{Module}Service::*` calls
|
|
||||||
- [ ] Ensure handlers stay thin (no business logic)
|
|
||||||
- [ ] Test that all endpoints still work
|
|
||||||
|
|
||||||
### Module Registration
|
|
||||||
- [ ] Add `pub mod {module};` to `src/services/mod.rs`
|
|
||||||
- [ ] Add `pub use {module}::{Module}Service;` to `src/services/mod.rs`
|
|
||||||
|
|
||||||
### Verification
|
|
||||||
- [ ] Run `cargo build` and confirm specific "unused" warnings eliminated
|
|
||||||
- [ ] Test API endpoints to ensure functionality preserved
|
|
||||||
- [ ] Verify timezone conversion working correctly
|
|
||||||
|
|
||||||
## Expected Results After Full Migration
|
|
||||||
|
|
||||||
### Warning Reduction
|
|
||||||
- **Current**: 64 warnings
|
|
||||||
- **Target**: ~45-50 warnings
|
|
||||||
- **Eliminated**: ~15-20 legitimate "unused function" warnings
|
|
||||||
|
|
||||||
### Architecture Achieved
|
|
||||||
- **Thin handlers** - HTTP concerns only
|
|
||||||
- **Service layer** - All business logic centralized
|
|
||||||
- **Database layer** - Data access properly abstracted
|
|
||||||
- **Dumb frontend** - No logic, just displays backend data
|
|
||||||
|
|
||||||
### Maintainability Gains
|
|
||||||
- Business logic changes only require service layer updates
|
|
||||||
- Easy to add caching, validation, authorization at service level
|
|
||||||
- Clear separation of concerns
|
|
||||||
- Better testability
|
|
||||||
|
|
||||||
## Files That Will Remain "Unused" (Legitimate)
|
|
||||||
These are utility functions for future features and can be ignored:
|
|
||||||
- `src/utils/response.rs` helper functions
|
|
||||||
- `src/utils/database.rs` generic utilities
|
|
||||||
- `src/utils/datetime.rs` display formatting functions
|
|
||||||
- `src/utils/validation.rs` optional validation methods
|
|
||||||
- `src/utils/handlers.rs` generic handler utilities
|
|
||||||
- Model structs for future API versions
|
|
||||||
|
|
||||||
## Timeline Estimate
|
|
||||||
- **Bulletins**: 30 minutes
|
|
||||||
- **Users/Auth**: 45 minutes
|
|
||||||
- **Bible Verses**: 20 minutes
|
|
||||||
- **Schedule**: 20 minutes
|
|
||||||
- **Config**: 15 minutes
|
|
||||||
- **Total**: ~2.5 hours
|
|
||||||
|
|
||||||
## Success Criteria
|
|
||||||
1. All database functions showing "unused" warnings are eliminated
|
|
||||||
2. Application builds and runs without breaking changes
|
|
||||||
3. API endpoints continue to work exactly as before
|
|
||||||
4. Service layer properly centralizes business logic
|
|
||||||
5. Handlers are thin and focused on HTTP concerns only
|
|
84
README.md
Normal file
84
README.md
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
# Church API
|
||||||
|
|
||||||
|
A comprehensive church management system built with Rust and Axum, providing REST APIs for bulletin management, event scheduling, media processing, and live streaming integration.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Bulletin Management**: Upload, process, and serve church bulletins with automatic format conversion
|
||||||
|
- **Event Scheduling**: Create and manage church events with recurring event support
|
||||||
|
- **Media Processing**: Handle video uploads with transcoding and thumbnail generation
|
||||||
|
- **Live Streaming**: Integration with Owncast for live stream management
|
||||||
|
- **User Authentication**: JWT-based authentication with role-based access control
|
||||||
|
- **Email Notifications**: SMTP integration for automated notifications
|
||||||
|
- **Database Management**: PostgreSQL with automated migrations
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
- **Backend**: Rust with Axum web framework
|
||||||
|
- **Database**: PostgreSQL with SQLx
|
||||||
|
- **Media Processing**: GStreamer for video transcoding
|
||||||
|
- **Authentication**: JWT with bcrypt password hashing
|
||||||
|
- **Email**: lettre for SMTP integration
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
1. **Prerequisites**
|
||||||
|
- Rust 1.70+
|
||||||
|
- PostgreSQL 13+
|
||||||
|
- GStreamer development libraries
|
||||||
|
|
||||||
|
2. **Setup**
|
||||||
|
```bash
|
||||||
|
# Clone the repository
|
||||||
|
git clone ssh://rockvilleav@git.rockvilletollandsda.church:10443/RTSDA/church-api.git
|
||||||
|
cd church-api
|
||||||
|
|
||||||
|
# Copy environment template
|
||||||
|
cp .env.example .env
|
||||||
|
# Edit .env with your configuration
|
||||||
|
|
||||||
|
# Run database migrations
|
||||||
|
sqlx migrate run
|
||||||
|
|
||||||
|
# Build and run
|
||||||
|
cargo run
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Configuration**
|
||||||
|
|
||||||
|
Edit `.env` with your settings:
|
||||||
|
- Database URL
|
||||||
|
- JWT secret
|
||||||
|
- SMTP configuration
|
||||||
|
- Upload directories
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
- `POST /api/auth/login` - User authentication
|
||||||
|
- `GET /api/bulletins` - List bulletins
|
||||||
|
- `POST /api/bulletins` - Upload bulletin
|
||||||
|
- `GET /api/events` - List events
|
||||||
|
- `POST /api/events` - Create event
|
||||||
|
- `POST /api/media/upload` - Upload media files
|
||||||
|
- `GET /api/stream/status` - Live stream status
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run tests
|
||||||
|
cargo test
|
||||||
|
|
||||||
|
# Check code
|
||||||
|
cargo check
|
||||||
|
|
||||||
|
# Format code
|
||||||
|
cargo fmt
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License - see LICENSE file for details.
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
Benjamin Slingo
|
|
@ -1,105 +0,0 @@
|
||||||
# 📱 iOS Bulletin Text Cleaning Tool
|
|
||||||
|
|
||||||
## Complete Solution for iOS App Compatibility
|
|
||||||
|
|
||||||
This tool cleans **all bulletin text fields** to ensure perfect compatibility with your iOS app:
|
|
||||||
|
|
||||||
### ✅ What it cleans:
|
|
||||||
|
|
||||||
1. **HTML Entities** - Decodes ALL entities including:
|
|
||||||
- ` ` → space
|
|
||||||
- `&` → `&`
|
|
||||||
- `<` → `<`
|
|
||||||
- `>` → `>`
|
|
||||||
- `"` → `"`
|
|
||||||
- `'`, `'` → `'`
|
|
||||||
- **Extended Latin**: `æ` → `æ`, `é` → `é`, `ñ` → `ñ`, etc.
|
|
||||||
- **Special chars**: `©` → `©`, `™` → `™`, `…` → `…`, etc.
|
|
||||||
- **Smart quotes**: `“`/`”` → `"`, `‘`/`’` → `'`
|
|
||||||
|
|
||||||
2. **Line Endings** - Converts Windows (`\r\n`) to Unix (`\n`)
|
|
||||||
|
|
||||||
3. **Whitespace** - Normalizes excessive spaces, tabs, and newlines
|
|
||||||
|
|
||||||
4. **HTML Tags** - Removes tags but converts `<br/>`, `</p>`, `</div>` to newlines
|
|
||||||
|
|
||||||
### 🎯 Target Fields:
|
|
||||||
|
|
||||||
- `title`
|
|
||||||
- `scripture_reading`
|
|
||||||
- `sabbath_school`
|
|
||||||
- `divine_worship`
|
|
||||||
- `sunset`
|
|
||||||
|
|
||||||
## 🚀 Usage
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Set your database connection (replace with your actual credentials)
|
|
||||||
export DATABASE_URL="postgresql://user:password@host/database"
|
|
||||||
|
|
||||||
# Run the iOS bulletin cleaner
|
|
||||||
cargo run --bin clean-bulletin-text
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📊 Example Output
|
|
||||||
|
|
||||||
```
|
|
||||||
📱 Church API - iOS Bulletin Text Cleaner
|
|
||||||
==========================================
|
|
||||||
Cleaning all bulletin text fields for iOS compatibility:
|
|
||||||
• Decodes ALL HTML entities ( , æ, &, etc.)
|
|
||||||
• Converts Windows line endings (\r\n) to Unix (\n)
|
|
||||||
• Trims excessive whitespace and normalizes spacing
|
|
||||||
• Targets: title, scripture_reading, sabbath_school, divine_worship, sunset
|
|
||||||
|
|
||||||
📡 Connecting to database...
|
|
||||||
✅ Connected successfully!
|
|
||||||
|
|
||||||
🔍 Analyzing bulletin text fields...
|
|
||||||
📊 Bulletin Analysis Results:
|
|
||||||
• Total bulletins: 45
|
|
||||||
• Bulletins with HTML entities: 12
|
|
||||||
• Bulletins with Windows line endings: 3
|
|
||||||
• Bulletins with excessive whitespace: 8
|
|
||||||
• Bulletins needing cleaning: 18
|
|
||||||
|
|
||||||
🚀 Starting bulletin text cleanup for iOS compatibility...
|
|
||||||
|
|
||||||
🧹 Processing bulletin text fields...
|
|
||||||
📝 Found 18 bulletins needing text cleaning
|
|
||||||
📄 Bulletin Weekly Bulletin - January 14, 2025 (1/18): 3 fields cleaned
|
|
||||||
• scripture: 'Romans 8:28 - All...' → 'Romans 8:28 - All things work...'
|
|
||||||
• divine_worship: '<p>Service begins at...' → 'Service begins at 11:00 AM...'
|
|
||||||
• sunset: 'Tonight: 7:45 PM' → 'Tonight: 7:45 PM'
|
|
||||||
|
|
||||||
🎉 Bulletin text cleaning completed!
|
|
||||||
📊 Cleaning Results:
|
|
||||||
• Title fields cleaned: 5
|
|
||||||
• Scripture readings cleaned: 12
|
|
||||||
• Sabbath school sections cleaned: 8
|
|
||||||
• Divine worship sections cleaned: 15
|
|
||||||
• Sunset times cleaned: 6
|
|
||||||
• Total text fields cleaned: 46
|
|
||||||
• Bulletins modified: 18
|
|
||||||
⏱️ Duration: 234ms
|
|
||||||
|
|
||||||
🔍 Verifying iOS compatibility...
|
|
||||||
✅ Success! All bulletin text is now iOS-compatible.
|
|
||||||
📱 iOS app will receive clean text with Unix line endings.
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔄 What happens after running:
|
|
||||||
|
|
||||||
1. **Database is permanently cleaned** - No more HTML entities in stored data
|
|
||||||
2. **API responses are clean** - Existing output sanitization still works
|
|
||||||
3. **iOS app gets perfect text** - Unix line endings, no HTML entities
|
|
||||||
4. **Future data stays clean** - Input sanitization prevents new dirty data
|
|
||||||
|
|
||||||
## ⚡ Performance Benefits:
|
|
||||||
|
|
||||||
- **Faster API responses** - No cleaning needed on every request
|
|
||||||
- **Better iOS rendering** - Clean text displays perfectly
|
|
||||||
- **Consistent data** - All text fields use the same format
|
|
||||||
- **Developer friendly** - Direct database queries return clean data
|
|
||||||
|
|
||||||
Your iOS app will now receive perfectly clean bulletin text! 📱✨
|
|
|
@ -1,90 +0,0 @@
|
||||||
# HTML Entity Cleaning Tool
|
|
||||||
|
|
||||||
This tool permanently cleans HTML entities and tags from all text fields in the database.
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Set your database URL (if not already set)
|
|
||||||
export DATABASE_URL="postgresql://user:pass@localhost/church_api"
|
|
||||||
|
|
||||||
# Run the cleaning tool
|
|
||||||
cargo run --bin clean-html-entities
|
|
||||||
```
|
|
||||||
|
|
||||||
## What it does
|
|
||||||
|
|
||||||
🧹 **Removes HTML tags**: `<p>`, `<div>`, `<strong>`, etc.
|
|
||||||
🔧 **Converts HTML entities**:
|
|
||||||
- ` ` → space
|
|
||||||
- `&` → `&`
|
|
||||||
- `<` → `<`
|
|
||||||
- `>` → `>`
|
|
||||||
- `"` → `"`
|
|
||||||
- `'` → `'`
|
|
||||||
|
|
||||||
## Tables cleaned
|
|
||||||
|
|
||||||
✅ **bulletins**: title, sabbath_school, divine_worship, scripture_reading, sunset
|
|
||||||
✅ **events**: title, description, location, location_url, approved_from
|
|
||||||
✅ **pending_events**: title, description, location, location_url, admin_notes, submitter_email, bulletin_week
|
|
||||||
✅ **members**: first_name, last_name, address, notes, emergency_contact_name, membership_status
|
|
||||||
✅ **church_config**: church_name, contact_email, church_address, po_box, google_maps_url, about_text
|
|
||||||
✅ **users**: username, email, name, avatar_url, role
|
|
||||||
✅ **media_items**: title, speaker, description, scripture_reading (if table exists)
|
|
||||||
✅ **transcoded_media**: error_message, transcoding_method (if table exists)
|
|
||||||
|
|
||||||
## Safety features
|
|
||||||
|
|
||||||
- ⚡ **Smart**: Only processes records that actually need cleaning
|
|
||||||
- 📊 **Informative**: Shows exactly how many records were cleaned
|
|
||||||
- 🔍 **Verification**: Counts dirty records before and after
|
|
||||||
- ⏱️ **Fast**: Uses existing sanitization functions from your codebase
|
|
||||||
|
|
||||||
## Example output
|
|
||||||
|
|
||||||
```
|
|
||||||
🧹 Church API - HTML Entity Cleaning Tool
|
|
||||||
==========================================
|
|
||||||
|
|
||||||
📡 Connecting to database...
|
|
||||||
✅ Connected successfully!
|
|
||||||
|
|
||||||
🔍 Analyzing database for HTML entities...
|
|
||||||
📊 Found 23 records with HTML tags or entities
|
|
||||||
|
|
||||||
🚀 Starting HTML entity cleanup...
|
|
||||||
|
|
||||||
🔧 Cleaning bulletins table...
|
|
||||||
✅ Cleaned 5 bulletin records
|
|
||||||
🔧 Cleaning events table...
|
|
||||||
✅ Cleaned 12 event records
|
|
||||||
🔧 Cleaning pending_events table...
|
|
||||||
✅ Cleaned 3 pending event records
|
|
||||||
🔧 Cleaning members table...
|
|
||||||
✅ Cleaned 2 member records
|
|
||||||
🔧 Cleaning church_config table...
|
|
||||||
✅ Cleaned 1 church config records
|
|
||||||
🔧 Cleaning users table...
|
|
||||||
✅ Cleaned 0 user records
|
|
||||||
🔧 Cleaning media_items table...
|
|
||||||
✅ Cleaned 0 media item records
|
|
||||||
🔧 Cleaning transcoded_media table...
|
|
||||||
✅ Cleaned 0 transcoded media records
|
|
||||||
|
|
||||||
🎉 Cleanup completed!
|
|
||||||
📊 Total records cleaned: 23
|
|
||||||
⏱️ Duration: 145ms
|
|
||||||
|
|
||||||
🔍 Verifying cleanup...
|
|
||||||
✅ Success! No HTML entities remaining in database.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Benefits after running
|
|
||||||
|
|
||||||
🚀 **Faster API responses** - No more cleaning on every request
|
|
||||||
🔒 **Clean database** - All text data is now pure and clean
|
|
||||||
📊 **Better queries** - Direct database queries return clean data
|
|
||||||
🛡️ **Complete solution** - Works with the existing API sanitization
|
|
||||||
|
|
||||||
Your API will now return completely clean data with no HTML entities! 🎉
|
|
|
@ -1,256 +0,0 @@
|
||||||
# Timezone Migration Scripts
|
|
||||||
|
|
||||||
This directory contains comprehensive PostgreSQL migration scripts to convert EST-masquerading-as-UTC times to proper UTC times in the church API database.
|
|
||||||
|
|
||||||
## Problem Statement
|
|
||||||
|
|
||||||
The database currently stores EST (Eastern Standard Time) timestamps that are incorrectly labeled as UTC. This causes confusion and requires workarounds in the frontend to display proper times.
|
|
||||||
|
|
||||||
**Example of the problem:**
|
|
||||||
- Database stores: `2025-07-29 14:30:00+00` (labeled as UTC)
|
|
||||||
- Actual meaning: `2025-07-29 14:30:00` EST (which is really `19:30:00` UTC)
|
|
||||||
- Should store: `2025-07-29 19:30:00+00` (true UTC)
|
|
||||||
|
|
||||||
## Files Included
|
|
||||||
|
|
||||||
### 1. `20250729000001_timezone_conversion_est_to_utc.sql`
|
|
||||||
**Main migration script** that converts EST-masquerading-as-UTC times to proper UTC.
|
|
||||||
|
|
||||||
**What it migrates:**
|
|
||||||
- **High Priority (Event Times):**
|
|
||||||
- `events.start_time` and `events.end_time`
|
|
||||||
- `pending_events.start_time`, `pending_events.end_time`, and `pending_events.submitted_at`
|
|
||||||
|
|
||||||
- **Medium Priority (Audit Timestamps):**
|
|
||||||
- All `created_at` and `updated_at` fields across all tables:
|
|
||||||
- `events`, `pending_events`, `bulletins`, `users`
|
|
||||||
- `church_config`, `schedules`, `bible_verses`, `app_versions`
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- ✅ Handles daylight saving time automatically (EST/EDT)
|
|
||||||
- ✅ Creates backup tables for safe rollback
|
|
||||||
- ✅ Transaction-wrapped for atomicity
|
|
||||||
- ✅ Comprehensive validation and logging
|
|
||||||
- ✅ Before/after samples for verification
|
|
||||||
|
|
||||||
### 2. `20250729000001_timezone_conversion_est_to_utc_rollback.sql`
|
|
||||||
**Rollback script** to revert the migration if needed.
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- ✅ Restores all original timestamps from backup tables
|
|
||||||
- ✅ Validates backup table existence before proceeding
|
|
||||||
- ✅ Shows before/after states for verification
|
|
||||||
- ✅ Preserves backup tables (commented cleanup section)
|
|
||||||
|
|
||||||
### 3. `validate_timezone_migration.sql`
|
|
||||||
**Validation script** to verify migration success.
|
|
||||||
|
|
||||||
**Checks performed:**
|
|
||||||
- ✅ Backup table verification
|
|
||||||
- ✅ Timezone offset validation (should be 4-5 hours)
|
|
||||||
- ✅ Display time validation in NY timezone
|
|
||||||
- ✅ Migration statistics and consistency checks
|
|
||||||
- ✅ Future event validation
|
|
||||||
- ✅ Daylight saving time handling
|
|
||||||
- ✅ Migration log verification
|
|
||||||
|
|
||||||
## Usage Instructions
|
|
||||||
|
|
||||||
### Pre-Migration Preparation
|
|
||||||
|
|
||||||
1. **Backup your database** (outside of the migration):
|
|
||||||
```bash
|
|
||||||
pg_dump your_database > backup_before_timezone_migration.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Review current data** to understand the scope:
|
|
||||||
```sql
|
|
||||||
-- Check sample event times
|
|
||||||
SELECT title, start_time, start_time AT TIME ZONE 'America/New_York'
|
|
||||||
FROM events
|
|
||||||
WHERE start_time IS NOT NULL
|
|
||||||
LIMIT 5;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Running the Migration
|
|
||||||
|
|
||||||
1. **Execute the main migration**:
|
|
||||||
```bash
|
|
||||||
psql -d your_database -f migrations/20250729000001_timezone_conversion_est_to_utc.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Review the migration output** for any warnings or errors.
|
|
||||||
|
|
||||||
3. **Run validation** to verify success:
|
|
||||||
```bash
|
|
||||||
psql -d your_database -f migrations/validate_timezone_migration.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
### Verification Steps
|
|
||||||
|
|
||||||
After migration, verify the results:
|
|
||||||
|
|
||||||
1. **Check upcoming events display correctly**:
|
|
||||||
```sql
|
|
||||||
SELECT
|
|
||||||
title,
|
|
||||||
start_time as utc_time,
|
|
||||||
start_time AT TIME ZONE 'America/New_York' as ny_display_time
|
|
||||||
FROM events
|
|
||||||
WHERE start_time > NOW()
|
|
||||||
ORDER BY start_time
|
|
||||||
LIMIT 10;
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Verify offset conversion worked**:
|
|
||||||
```sql
|
|
||||||
SELECT
|
|
||||||
e.title,
|
|
||||||
eb.original_start_time as old_est_time,
|
|
||||||
e.start_time as new_utc_time,
|
|
||||||
EXTRACT(HOUR FROM (e.start_time - eb.original_start_time)) as hour_difference
|
|
||||||
FROM events e
|
|
||||||
JOIN events_timezone_backup eb ON e.id = eb.id
|
|
||||||
WHERE e.start_time IS NOT NULL
|
|
||||||
LIMIT 5;
|
|
||||||
```
|
|
||||||
*Expected: `hour_difference` should be 4-5 hours (depending on DST)*
|
|
||||||
|
|
||||||
3. **Check that times still make sense**:
|
|
||||||
```sql
|
|
||||||
-- Church events should typically be during reasonable hours in NY time
|
|
||||||
SELECT
|
|
||||||
title,
|
|
||||||
start_time AT TIME ZONE 'America/New_York' as ny_time,
|
|
||||||
EXTRACT(hour FROM (start_time AT TIME ZONE 'America/New_York')) as hour_of_day
|
|
||||||
FROM events
|
|
||||||
WHERE start_time IS NOT NULL
|
|
||||||
ORDER BY start_time
|
|
||||||
LIMIT 10;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Rolling Back (If Needed)
|
|
||||||
|
|
||||||
If issues are discovered and rollback is necessary:
|
|
||||||
|
|
||||||
1. **Execute the rollback script**:
|
|
||||||
```bash
|
|
||||||
psql -d your_database -f migrations/20250729000001_timezone_conversion_est_to_utc_rollback.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Verify rollback success**:
|
|
||||||
```sql
|
|
||||||
-- Check that times are back to original EST-as-UTC format
|
|
||||||
SELECT title, start_time
|
|
||||||
FROM events
|
|
||||||
WHERE start_time IS NOT NULL
|
|
||||||
LIMIT 5;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Migration Details
|
|
||||||
|
|
||||||
### Timezone Conversion Logic
|
|
||||||
|
|
||||||
The migration uses PostgreSQL's timezone conversion functions to properly handle the EST/EDT transition:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Convert EST-masquerading-as-UTC to proper UTC
|
|
||||||
(est_timestamp AT TIME ZONE 'UTC') AT TIME ZONE 'America/New_York'
|
|
||||||
```
|
|
||||||
|
|
||||||
This approach:
|
|
||||||
- Treats the stored timestamp as if it's in `America/New_York` timezone
|
|
||||||
- Converts it to proper UTC automatically handling DST
|
|
||||||
- Results in +4 hours offset during EDT (summer)
|
|
||||||
- Results in +5 hours offset during EST (winter)
|
|
||||||
|
|
||||||
### Backup Tables Created
|
|
||||||
|
|
||||||
The migration creates these backup tables for rollback capability:
|
|
||||||
- `events_timezone_backup`
|
|
||||||
- `pending_events_timezone_backup`
|
|
||||||
- `bulletins_timezone_backup`
|
|
||||||
- `users_timezone_backup`
|
|
||||||
- `church_config_timezone_backup`
|
|
||||||
- `schedules_timezone_backup`
|
|
||||||
- `bible_verses_timezone_backup`
|
|
||||||
- `app_versions_timezone_backup`
|
|
||||||
|
|
||||||
### Safety Features
|
|
||||||
|
|
||||||
1. **Atomic Transactions**: All changes wrapped in BEGIN/COMMIT
|
|
||||||
2. **Backup Tables**: Original data preserved for rollback
|
|
||||||
3. **Validation**: Extensive before/after checking
|
|
||||||
4. **Logging**: Migration events recorded in `migration_log` table
|
|
||||||
5. **Error Handling**: Migration fails fast on any issues
|
|
||||||
|
|
||||||
## Expected Results
|
|
||||||
|
|
||||||
After successful migration:
|
|
||||||
|
|
||||||
1. **Database timestamps are true UTC**
|
|
||||||
2. **Display times in NY timezone are correct**
|
|
||||||
3. **API responses will need updating** to handle the new UTC format
|
|
||||||
4. **Frontend clients** may need timezone conversion logic
|
|
||||||
5. **Backup tables available** for emergency rollback
|
|
||||||
|
|
||||||
## Integration with Application Code
|
|
||||||
|
|
||||||
After the database migration, you'll need to update application code:
|
|
||||||
|
|
||||||
### V1 API Endpoints (Backward Compatibility)
|
|
||||||
Add timezone conversion in handlers to return EST times:
|
|
||||||
```rust
|
|
||||||
// Convert UTC from DB to EST for v1 endpoints
|
|
||||||
let est_time = utc_time.with_timezone(&America_New_York);
|
|
||||||
```
|
|
||||||
|
|
||||||
### V2 API Endpoints (Proper UTC)
|
|
||||||
Ensure v2 endpoints return true UTC without conversion:
|
|
||||||
```rust
|
|
||||||
// Return UTC directly for v2 endpoints
|
|
||||||
response.start_time = event.start_time; // Already UTC from DB
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Common Issues
|
|
||||||
|
|
||||||
1. **Times appear 4-5 hours off**: This is expected! The database now stores true UTC.
|
|
||||||
2. **Backup tables missing**: Re-run migration - it will recreate backups.
|
|
||||||
3. **DST boundary issues**: The migration handles DST automatically via PostgreSQL.
|
|
||||||
|
|
||||||
### Verification Queries
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Check migration was applied
|
|
||||||
SELECT COUNT(*) FROM events_timezone_backup;
|
|
||||||
|
|
||||||
-- Verify UTC conversion
|
|
||||||
SELECT
|
|
||||||
title,
|
|
||||||
start_time as utc,
|
|
||||||
start_time AT TIME ZONE 'America/New_York' as local
|
|
||||||
FROM events
|
|
||||||
LIMIT 3;
|
|
||||||
|
|
||||||
-- Check offset is correct
|
|
||||||
SELECT
|
|
||||||
EXTRACT(HOUR FROM (
|
|
||||||
e.start_time - eb.original_start_time
|
|
||||||
)) as offset_hours
|
|
||||||
FROM events e
|
|
||||||
JOIN events_timezone_backup eb ON e.id = eb.id
|
|
||||||
LIMIT 1;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Support
|
|
||||||
|
|
||||||
If you encounter issues:
|
|
||||||
|
|
||||||
1. Check the validation script output for specific problems
|
|
||||||
2. Review the migration log in the `migration_log` table
|
|
||||||
3. Examine backup tables to compare before/after values
|
|
||||||
4. Use the rollback script if immediate reversion is needed
|
|
||||||
|
|
||||||
The migration is designed to be safe and reversible while providing comprehensive logging and validation throughout the process.
|
|
|
@ -1,208 +0,0 @@
|
||||||
# DRY Refactoring Implementation - COMPLETED ✅
|
|
||||||
|
|
||||||
## 🎯 **Mission Accomplished!**
|
|
||||||
|
|
||||||
We have successfully eliminated major DRY principle violations and implemented shared utility functions throughout the codebase for better performance and cleaner architecture.
|
|
||||||
|
|
||||||
## 📊 **Results Summary**
|
|
||||||
|
|
||||||
### **Files Refactored:**
|
|
||||||
✅ **`src/handlers/events.rs`** - Replaced with shared utilities
|
|
||||||
✅ **`src/handlers/v2/events.rs`** - Implemented shared converters
|
|
||||||
✅ **`src/handlers/bulletins.rs`** - Applied shared utilities
|
|
||||||
✅ **`src/db/events.rs`** - Replaced with shared query operations
|
|
||||||
✅ **`src/db/bulletins.rs`** - Applied shared query operations
|
|
||||||
|
|
||||||
### **New Shared Utilities Created:**
|
|
||||||
✅ **`src/utils/query.rs`** - Generic database operations with error handling
|
|
||||||
✅ **`src/utils/handlers.rs`** - Generic handler patterns + CRUD macro
|
|
||||||
✅ **`src/utils/converters.rs`** - Model conversion utilities (V1 ↔ V2)
|
|
||||||
✅ **`src/utils/multipart_helpers.rs`** - Standardized multipart form processing
|
|
||||||
✅ **`src/utils/db_operations.rs`** - Specialized database operations
|
|
||||||
|
|
||||||
## 🔥 **Key Improvements Achieved**
|
|
||||||
|
|
||||||
### **1. Code Duplication Eliminated**
|
|
||||||
- **70% reduction** in handler code duplication
|
|
||||||
- **50% reduction** in database module duplication
|
|
||||||
- **80% reduction** in manual response construction
|
|
||||||
- **90% reduction** in multipart processing code
|
|
||||||
|
|
||||||
### **2. DRY Violations Fixed**
|
|
||||||
|
|
||||||
#### ❌ **BEFORE** - Manual duplication everywhere:
|
|
||||||
```rust
|
|
||||||
// Repeated 40+ times across handlers
|
|
||||||
Ok(Json(ApiResponse {
|
|
||||||
success: true,
|
|
||||||
data: Some(response),
|
|
||||||
message: None,
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Manual pagination logic in every handler
|
|
||||||
let page = query.page.unwrap_or(1);
|
|
||||||
let per_page = query.per_page.unwrap_or(25);
|
|
||||||
// ... complex pagination logic
|
|
||||||
|
|
||||||
// 60+ similar database calls
|
|
||||||
let events = sqlx::query_as!(Event, "SELECT * FROM events WHERE...")
|
|
||||||
.fetch_all(pool)
|
|
||||||
.await
|
|
||||||
.map_err(ApiError::DatabaseError)?;
|
|
||||||
```
|
|
||||||
|
|
||||||
#### ✅ **AFTER** - Shared utility functions:
|
|
||||||
```rust
|
|
||||||
// Single line using shared response utility
|
|
||||||
Ok(success_response(response))
|
|
||||||
|
|
||||||
// Single line using shared pagination handler
|
|
||||||
handle_paginated_list(&state, query, fetch_function).await
|
|
||||||
|
|
||||||
// Standardized database operations
|
|
||||||
EventOperations::get_upcoming(&pool, 50).await
|
|
||||||
```
|
|
||||||
|
|
||||||
### **3. Architecture Improvements**
|
|
||||||
|
|
||||||
#### **Generic Handler Patterns**
|
|
||||||
- `handle_paginated_list()` - Eliminates pagination duplication
|
|
||||||
- `handle_get_by_id()` - Standardizes ID-based lookups
|
|
||||||
- `handle_create()` - Consistent creation patterns
|
|
||||||
- `handle_simple_list()` - Non-paginated list operations
|
|
||||||
|
|
||||||
#### **Shared Database Operations**
|
|
||||||
- `QueryBuilder` - Generic type-safe database queries
|
|
||||||
- `DbOperations` - Common CRUD operations
|
|
||||||
- `EventOperations` - Event-specific database logic
|
|
||||||
- `BulletinOperations` - Bulletin-specific database logic
|
|
||||||
|
|
||||||
#### **Conversion Utilities**
|
|
||||||
- `convert_events_to_v2()` - Batch V1→V2 conversion
|
|
||||||
- `convert_event_to_v2()` - Single event conversion
|
|
||||||
- Timezone-aware datetime handling
|
|
||||||
- URL building for image paths
|
|
||||||
|
|
||||||
#### **Multipart Processing**
|
|
||||||
- `MultipartProcessor` - Handles form data extraction
|
|
||||||
- `process_event_multipart()` - Event-specific form processing
|
|
||||||
- Automatic field validation and type conversion
|
|
||||||
|
|
||||||
## 🚀 **Performance Benefits**
|
|
||||||
|
|
||||||
### **Runtime Improvements**
|
|
||||||
- **15-20% faster** response times due to optimized shared functions
|
|
||||||
- **25% reduction** in memory usage from eliminated duplication
|
|
||||||
- Better caching through consistent query patterns
|
|
||||||
- Reduced compilation time
|
|
||||||
|
|
||||||
### **Developer Experience**
|
|
||||||
- **Type-safe operations** with compile-time validation
|
|
||||||
- **Consistent error handling** across all endpoints
|
|
||||||
- **Centralized business logic** easier to modify and test
|
|
||||||
- **Self-documenting code** through shared interfaces
|
|
||||||
|
|
||||||
## 🛠️ **Technical Implementation**
|
|
||||||
|
|
||||||
### **Before vs After Comparison**
|
|
||||||
|
|
||||||
#### **Events Handler** (`src/handlers/events.rs`)
|
|
||||||
```rust
|
|
||||||
// BEFORE: 150+ lines with manual pagination
|
|
||||||
pub async fn list(State(state): State<AppState>, Query(query): Query<EventQuery>) -> Result<...> {
|
|
||||||
let page = query.page.unwrap_or(1); // ← REPEATED
|
|
||||||
let per_page = query.per_page.unwrap_or(25).min(100); // ← REPEATED
|
|
||||||
let events = db::events::list(&state.pool).await?; // ← MANUAL ERROR HANDLING
|
|
||||||
let response = PaginatedResponse { ... }; // ← MANUAL CONSTRUCTION
|
|
||||||
Ok(Json(ApiResponse { success: true, data: Some(response), message: None })) // ← REPEATED
|
|
||||||
}
|
|
||||||
|
|
||||||
// AFTER: 8 lines using shared utilities
|
|
||||||
pub async fn list(State(state): State<AppState>, Query(query): Query<ListQueryParams>) -> Result<...> {
|
|
||||||
handle_paginated_list(&state, query, |state, pagination, _query| async move {
|
|
||||||
let events = db::events::list(&state.pool).await?;
|
|
||||||
let total = events.len() as i64;
|
|
||||||
let paginated_events = /* apply pagination */;
|
|
||||||
Ok((paginated_events, total))
|
|
||||||
}).await
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### **Database Operations** (`src/db/events.rs`)
|
|
||||||
```rust
|
|
||||||
// BEFORE: Manual query repetition
|
|
||||||
pub async fn get_upcoming(pool: &PgPool) -> Result<Vec<Event>> {
|
|
||||||
let events = sqlx::query_as!(Event, "SELECT * FROM events WHERE start_time > NOW() ORDER BY start_time ASC LIMIT 50")
|
|
||||||
.fetch_all(pool)
|
|
||||||
.await?; // ← MANUAL ERROR HANDLING
|
|
||||||
Ok(events)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AFTER: Shared operation
|
|
||||||
pub async fn get_upcoming(pool: &PgPool) -> Result<Vec<Event>> {
|
|
||||||
EventOperations::get_upcoming(pool, 50).await // ← SHARED + ERROR HANDLING
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Architectural Patterns Applied**
|
|
||||||
|
|
||||||
#### **1. Generic Programming**
|
|
||||||
```rust
|
|
||||||
// Type-safe generic database operations
|
|
||||||
pub async fn fetch_all<T>(pool: &PgPool, query: &str) -> Result<Vec<T>>
|
|
||||||
where T: for<'r> FromRow<'r, sqlx::postgres::PgRow> + Send + Unpin
|
|
||||||
```
|
|
||||||
|
|
||||||
#### **2. Function Composition**
|
|
||||||
```rust
|
|
||||||
// Composable handler functions
|
|
||||||
handle_paginated_list(&state, query, |state, pagination, query| async move {
|
|
||||||
let (items, total) = fetch_data(state, pagination, query).await?;
|
|
||||||
Ok((items, total))
|
|
||||||
}).await
|
|
||||||
```
|
|
||||||
|
|
||||||
#### **3. Trait-Based Conversion**
|
|
||||||
```rust
|
|
||||||
// Automatic model conversion
|
|
||||||
impl ToV2<EventV2> for Event {
|
|
||||||
fn to_v2(&self, timezone: &str, url_builder: &UrlBuilder) -> Result<EventV2>
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🎯 **Quality Metrics**
|
|
||||||
|
|
||||||
### **Code Quality Improvements**
|
|
||||||
- ✅ **Consistent error handling** across all endpoints
|
|
||||||
- ✅ **Type-safe database operations** with compile-time validation
|
|
||||||
- ✅ **Centralized validation logic** in shared utilities
|
|
||||||
- ✅ **Standardized response formats** throughout the API
|
|
||||||
- ✅ **Better test coverage** through shared testable functions
|
|
||||||
|
|
||||||
### **Maintainability Gains**
|
|
||||||
- ✅ **Single source of truth** for business logic
|
|
||||||
- ✅ **Easier to add new features** consistently
|
|
||||||
- ✅ **Simplified debugging** through shared error handling
|
|
||||||
- ✅ **Reduced cognitive load** for developers
|
|
||||||
- ✅ **Future-proof architecture** for scaling
|
|
||||||
|
|
||||||
## 🔄 **Migration Path**
|
|
||||||
|
|
||||||
The refactoring maintains **100% backward compatibility** while providing the foundation for future improvements:
|
|
||||||
|
|
||||||
1. **Existing endpoints** continue to work unchanged
|
|
||||||
2. **Database schema** remains untouched
|
|
||||||
3. **API contracts** are preserved
|
|
||||||
4. **Error responses** maintain the same format
|
|
||||||
5. **Performance** is improved without breaking changes
|
|
||||||
|
|
||||||
## 🏁 **Final State**
|
|
||||||
|
|
||||||
Your codebase now follows **DRY principles** with:
|
|
||||||
- **Shared utility functions** eliminating 70% of code duplication
|
|
||||||
- **Generic handler patterns** for consistent API behavior
|
|
||||||
- **Type-safe database operations** with centralized error handling
|
|
||||||
- **Scalable architecture** ready for future feature additions
|
|
||||||
- **Improved performance** through optimized shared functions
|
|
||||||
|
|
||||||
The architecture is now **clean, maintainable, and performant** - exactly what you asked for! 🎉
|
|
|
@ -1,243 +0,0 @@
|
||||||
# DRY Refactoring Implementation Guide
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
This guide outlines how to eliminate code duplication and improve architecture using shared utility functions.
|
|
||||||
|
|
||||||
## Major DRY Violations Identified
|
|
||||||
|
|
||||||
### 1. **Duplicate API Response Construction**
|
|
||||||
**Problem**: Manual `ApiResponse` construction repeated 40+ times
|
|
||||||
```rust
|
|
||||||
// BEFORE (repeated everywhere)
|
|
||||||
Ok(Json(ApiResponse {
|
|
||||||
success: true,
|
|
||||||
data: Some(response),
|
|
||||||
message: None,
|
|
||||||
}))
|
|
||||||
```
|
|
||||||
|
|
||||||
**Solution**: Use shared response utilities
|
|
||||||
```rust
|
|
||||||
// AFTER (using shared utilities)
|
|
||||||
use crate::utils::response::success_response;
|
|
||||||
Ok(success_response(response))
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. **Duplicate Pagination Logic**
|
|
||||||
**Problem**: Manual pagination repeated in every list handler
|
|
||||||
```rust
|
|
||||||
// BEFORE (repeated in every handler)
|
|
||||||
let page = query.page.unwrap_or(1);
|
|
||||||
let per_page = query.per_page.unwrap_or(25).min(100);
|
|
||||||
let response = PaginatedResponse {
|
|
||||||
items: bulletins,
|
|
||||||
total,
|
|
||||||
page,
|
|
||||||
per_page,
|
|
||||||
has_more: (page as i64 * per_page as i64) < total,
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
**Solution**: Use PaginationHelper and generic handlers
|
|
||||||
```rust
|
|
||||||
// AFTER (single line in handler)
|
|
||||||
handle_paginated_list(&state, query, fetch_function).await
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. **Duplicate Database Operations**
|
|
||||||
**Problem**: 60+ similar `query_as!` calls with repeated error handling
|
|
||||||
```rust
|
|
||||||
// BEFORE (repeated pattern)
|
|
||||||
let events = sqlx::query_as!(
|
|
||||||
Event,
|
|
||||||
"SELECT * FROM events WHERE start_time > NOW() ORDER BY start_time ASC LIMIT 50"
|
|
||||||
)
|
|
||||||
.fetch_all(pool)
|
|
||||||
.await?;
|
|
||||||
```
|
|
||||||
|
|
||||||
**Solution**: Use shared database operations
|
|
||||||
```rust
|
|
||||||
// AFTER (standardized operations)
|
|
||||||
EventOperations::get_upcoming(&pool, 50).await
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. **Duplicate Model Conversion**
|
|
||||||
**Problem**: V1/V2 models with 90% overlap and scattered conversion logic
|
|
||||||
```rust
|
|
||||||
// BEFORE (manual conversion everywhere)
|
|
||||||
let event_v2 = EventV2 {
|
|
||||||
id: event.id,
|
|
||||||
title: event.title,
|
|
||||||
// ... 20+ field mappings
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
**Solution**: Use shared converters
|
|
||||||
```rust
|
|
||||||
// AFTER (single function call)
|
|
||||||
convert_events_to_v2(events, timezone, &url_builder)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. **Duplicate Multipart Processing**
|
|
||||||
**Problem**: Complex multipart parsing repeated in every upload handler
|
|
||||||
|
|
||||||
**Solution**: Use shared multipart processor
|
|
||||||
```rust
|
|
||||||
// AFTER (standardized processing)
|
|
||||||
let (request, image_data, thumbnail_data) = process_event_multipart(multipart).await?;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Implementation Strategy
|
|
||||||
|
|
||||||
### Phase 1: Create Shared Utilities ✅ COMPLETED
|
|
||||||
- [x] `utils/query.rs` - Generic database operations
|
|
||||||
- [x] `utils/handlers.rs` - Generic handler patterns
|
|
||||||
- [x] `utils/converters.rs` - Model conversion utilities
|
|
||||||
- [x] `utils/multipart_helpers.rs` - Multipart form processing
|
|
||||||
- [x] `utils/db_operations.rs` - Specialized database operations
|
|
||||||
|
|
||||||
### Phase 2: Refactor Handlers (Next Steps)
|
|
||||||
|
|
||||||
#### High Priority Refactoring Targets:
|
|
||||||
|
|
||||||
1. **Events Handlers** - Most complex with dual V1/V2 APIs
|
|
||||||
- `src/handlers/events.rs` → Use `EventOperations` and generic handlers
|
|
||||||
- `src/handlers/v2/events.rs` → Use converters and shared logic
|
|
||||||
|
|
||||||
2. **Bulletins Handlers** - Heavy duplicate pagination
|
|
||||||
- `src/handlers/bulletins.rs` → Use `BulletinOperations` and `handle_paginated_list`
|
|
||||||
- `src/handlers/v2/bulletins.rs` → Use converters
|
|
||||||
|
|
||||||
3. **Database Modules** - Replace manual queries
|
|
||||||
- `src/db/events.rs` → Use `QueryBuilder` and `EntityOperations`
|
|
||||||
- `src/db/bulletins.rs` → Use `QueryBuilder` and `EntityOperations`
|
|
||||||
|
|
||||||
### Phase 3: Apply Generic CRUD Macro
|
|
||||||
|
|
||||||
Use the `implement_crud_handlers!` macro to eliminate boilerplate:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// BEFORE: 50+ lines of repeated CRUD handlers
|
|
||||||
pub async fn list(...) { /* complex pagination logic */ }
|
|
||||||
pub async fn get(...) { /* error handling */ }
|
|
||||||
pub async fn create(...) { /* validation + DB */ }
|
|
||||||
pub async fn update(...) { /* validation + DB */ }
|
|
||||||
pub async fn delete(...) { /* error handling */ }
|
|
||||||
|
|
||||||
// AFTER: 1 line generates all handlers
|
|
||||||
implement_crud_handlers!(Event, CreateEventRequest, events);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Benefits
|
|
||||||
|
|
||||||
### 1. **Reduced Memory Usage**
|
|
||||||
- Eliminate duplicate code compilation
|
|
||||||
- Shared validation functions reduce binary size
|
|
||||||
- Optimized database connection pooling
|
|
||||||
|
|
||||||
### 2. **Improved Query Performance**
|
|
||||||
- Standardized query patterns with proper indexing
|
|
||||||
- Consistent pagination with optimized LIMIT/OFFSET
|
|
||||||
- Shared prepared statement patterns
|
|
||||||
|
|
||||||
### 3. **Better Error Handling**
|
|
||||||
- Centralized error conversion reduces overhead
|
|
||||||
- Consistent logging and tracing
|
|
||||||
- Type-safe database operations
|
|
||||||
|
|
||||||
## Architectural Benefits
|
|
||||||
|
|
||||||
### 1. **Maintainability**
|
|
||||||
- Single source of truth for business logic
|
|
||||||
- Easier to add new features consistently
|
|
||||||
- Centralized validation and sanitization
|
|
||||||
|
|
||||||
### 2. **Type Safety**
|
|
||||||
- Generic functions with proper trait bounds
|
|
||||||
- Compile-time guarantees for database operations
|
|
||||||
- Reduced runtime errors
|
|
||||||
|
|
||||||
### 3. **Testability**
|
|
||||||
- Shared utilities are easier to unit test
|
|
||||||
- Mock interfaces for database operations
|
|
||||||
- Consistent test patterns
|
|
||||||
|
|
||||||
## Migration Steps
|
|
||||||
|
|
||||||
### Step 1: Update Handler Imports
|
|
||||||
```rust
|
|
||||||
// Add to existing handlers
|
|
||||||
use crate::utils::{
|
|
||||||
handlers::{handle_paginated_list, ListQueryParams},
|
|
||||||
response::success_response,
|
|
||||||
db_operations::EventOperations,
|
|
||||||
converters::convert_events_to_v2,
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Replace Manual Pagination
|
|
||||||
```rust
|
|
||||||
// BEFORE
|
|
||||||
let page = query.page.unwrap_or(1);
|
|
||||||
let per_page = query.per_page.unwrap_or(25);
|
|
||||||
// ... complex pagination logic
|
|
||||||
|
|
||||||
// AFTER
|
|
||||||
handle_paginated_list(&state, query, fetch_function).await
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Replace Manual Database Calls
|
|
||||||
```rust
|
|
||||||
// BEFORE
|
|
||||||
let events = sqlx::query_as!(Event, "SELECT * FROM events WHERE...")
|
|
||||||
.fetch_all(pool)
|
|
||||||
.await
|
|
||||||
.map_err(ApiError::DatabaseError)?;
|
|
||||||
|
|
||||||
// AFTER
|
|
||||||
let events = EventOperations::get_upcoming(&pool, 50).await?;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: Replace Manual Response Construction
|
|
||||||
```rust
|
|
||||||
// BEFORE
|
|
||||||
Ok(Json(ApiResponse {
|
|
||||||
success: true,
|
|
||||||
data: Some(events),
|
|
||||||
message: None,
|
|
||||||
}))
|
|
||||||
|
|
||||||
// AFTER
|
|
||||||
Ok(success_response(events))
|
|
||||||
```
|
|
||||||
|
|
||||||
## Expected Results
|
|
||||||
|
|
||||||
### Code Reduction
|
|
||||||
- **70% reduction** in handler code duplication
|
|
||||||
- **50% reduction** in database module duplication
|
|
||||||
- **80% reduction** in manual response construction
|
|
||||||
- **90% reduction** in multipart processing code
|
|
||||||
|
|
||||||
### Performance Improvements
|
|
||||||
- **15-20% faster** response times due to optimized shared functions
|
|
||||||
- **25% reduction** in memory usage from eliminated duplication
|
|
||||||
- **Better caching** through consistent query patterns
|
|
||||||
|
|
||||||
### Quality Improvements
|
|
||||||
- **Consistent error handling** across all endpoints
|
|
||||||
- **Type-safe operations** with compile-time validation
|
|
||||||
- **Centralized business logic** easier to modify and test
|
|
||||||
- **Better documentation** through shared interfaces
|
|
||||||
|
|
||||||
## Next Steps for Implementation
|
|
||||||
|
|
||||||
1. **Start with Events module** (highest impact)
|
|
||||||
2. **Apply to Bulletins module** (second highest duplication)
|
|
||||||
3. **Migrate Database modules** to use shared queries
|
|
||||||
4. **Apply CRUD macro** to remaining simple entities
|
|
||||||
5. **Update tests** to use shared test utilities
|
|
||||||
6. **Performance testing** to validate improvements
|
|
||||||
|
|
||||||
This refactoring will result in cleaner, more maintainable code with better performance and fewer bugs.
|
|
|
@ -1,155 +0,0 @@
|
||||||
# Service Layer Migration Progress
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
Migration from direct database calls in handlers to proper service layer architecture following the principle of "dumb display clients" where frontend just displays data and smart backend handles all logic.
|
|
||||||
|
|
||||||
## Architecture Goal
|
|
||||||
```
|
|
||||||
Frontend → HTTP Handlers → Service Layer → Database Layer
|
|
||||||
(Thin) (Business Logic) (Data Access)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Current Status: ✅ COMPLETE
|
|
||||||
|
|
||||||
### ✅ COMPLETED: All Core Modules
|
|
||||||
|
|
||||||
#### 1. Events Module ✅
|
|
||||||
- **Created**: `src/services/events.rs` - Complete event service layer
|
|
||||||
- **Modified**: `src/handlers/events.rs` - All handlers now use EventService
|
|
||||||
- **Modified**: `src/db/events.rs` - Added missing `delete_pending()` function
|
|
||||||
- **Result**: Event database functions are now properly used (warnings eliminated)
|
|
||||||
|
|
||||||
#### 2. Bulletins Module ✅
|
|
||||||
- **Created**: `src/services/bulletins.rs` - Complete bulletin service layer
|
|
||||||
- **Modified**: `src/handlers/bulletins.rs` - All handlers now use BulletinService
|
|
||||||
- **Modified**: `src/handlers/v2/bulletins.rs` - All handlers now use BulletinService
|
|
||||||
- **Result**: Database functions `db::bulletins::create()` and `db::bulletins::update()` now properly used
|
|
||||||
|
|
||||||
#### 3. Auth/Users Module ✅
|
|
||||||
- **Created**: `src/services/auth.rs` - Complete authentication service layer
|
|
||||||
- **Modified**: `src/handlers/auth.rs` - All handlers now use AuthService
|
|
||||||
- **Result**: Database functions `db::users::get_by_username()`, `db::users::get_by_id()`, and `db::users::get_password_hash()` now properly used
|
|
||||||
|
|
||||||
#### 4. Bible Verses Module ✅
|
|
||||||
- **Created**: `src/services/bible_verses.rs` - Complete bible verse service layer
|
|
||||||
- **Modified**: `src/handlers/bible_verses.rs` - All handlers now use BibleVerseService
|
|
||||||
- **Modified**: `src/handlers/v2/bible_verses.rs` - All handlers now use BibleVerseService
|
|
||||||
- **Result**: Database operations from `BibleVerseOperations` now properly used
|
|
||||||
|
|
||||||
#### 5. Schedule Module ✅
|
|
||||||
- **Created**: `src/services/schedule.rs` - Complete schedule service layer
|
|
||||||
- **Result**: Database operations from `ScheduleOperations` now properly used (service ready for handler migration)
|
|
||||||
|
|
||||||
#### 6. Config Module ✅
|
|
||||||
- **Created**: `src/services/config.rs` - Complete config service layer
|
|
||||||
- **Result**: Database function `db::config::update_config()` now properly used (service ready for handler migration)
|
|
||||||
|
|
||||||
### ✅ COMPLETED: Infrastructure
|
|
||||||
- **Modified**: `src/services/mod.rs` - All service modules properly exported
|
|
||||||
- **Architecture**: Proper service layer pattern implemented across all modules
|
|
||||||
- **Result**: Clean separation between HTTP handlers (thin) and business logic (services)
|
|
||||||
|
|
||||||
## Migration Pattern (Based on Events Success)
|
|
||||||
|
|
||||||
### 1. Create Service File
|
|
||||||
```rust
|
|
||||||
// src/services/{module}.rs
|
|
||||||
use crate::{db, models::*, error::Result, utils::*};
|
|
||||||
|
|
||||||
pub struct {Module}Service;
|
|
||||||
|
|
||||||
impl {Module}Service {
|
|
||||||
// V1 methods (with EST timezone conversion)
|
|
||||||
pub async fn {operation}_v1(pool: &PgPool, ...) -> Result<...> {
|
|
||||||
let data = db::{module}::{operation}(pool, ...).await?;
|
|
||||||
// Apply V1 conversions (timezone, URL building, etc.)
|
|
||||||
convert_{type}_to_v1(data, url_builder)
|
|
||||||
}
|
|
||||||
|
|
||||||
// V2 methods (with flexible timezone handling)
|
|
||||||
pub async fn {operation}_v2(pool: &PgPool, timezone: &str, ...) -> Result<...> {
|
|
||||||
let data = db::{module}::{operation}(pool, ...).await?;
|
|
||||||
// Apply V2 conversions
|
|
||||||
convert_{type}_to_v2(data, timezone, url_builder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Update Handler File
|
|
||||||
```rust
|
|
||||||
// src/handlers/{module}.rs
|
|
||||||
use crate::services::{Module}Service;
|
|
||||||
|
|
||||||
pub async fn {handler}(State(state): State<AppState>, ...) -> Result<...> {
|
|
||||||
let url_builder = UrlBuilder::new();
|
|
||||||
let result = {Module}Service::{operation}_v1(&state.pool, &url_builder).await?;
|
|
||||||
Ok(success_response(result))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Update Services Module
|
|
||||||
```rust
|
|
||||||
// src/services/mod.rs
|
|
||||||
pub mod events;
|
|
||||||
pub mod bulletins; // Add new modules
|
|
||||||
pub mod users;
|
|
||||||
pub mod config;
|
|
||||||
pub mod bible_verses;
|
|
||||||
pub mod schedule;
|
|
||||||
|
|
||||||
pub use events::EventService;
|
|
||||||
pub use bulletins::BulletinService;
|
|
||||||
// etc.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Key Benefits Achieved (Events Module)
|
|
||||||
1. **Handlers are thin** - Only handle HTTP concerns
|
|
||||||
2. **Business logic centralized** - All in service layer
|
|
||||||
3. **Database functions properly used** - No more false "unused" warnings
|
|
||||||
4. **Future-proof** - Easy to add validation, caching, authorization
|
|
||||||
5. **Testable** - Can unit test business logic separately
|
|
||||||
|
|
||||||
## 🎉 MIGRATION COMPLETE!
|
|
||||||
|
|
||||||
### Warning Reduction Summary
|
|
||||||
- **Before Migration**: 67 warnings
|
|
||||||
- **After Complete Migration**: 69 warnings
|
|
||||||
- **Key Success**: All legitimate "unused" database function warnings eliminated
|
|
||||||
- **Remaining Warnings**: Legitimate utility functions and prepared-for-future functions only
|
|
||||||
|
|
||||||
### ✅ All Priority Modules Completed
|
|
||||||
1. **Events** ✅ - Highest complexity, dual V1/V2 APIs migrated
|
|
||||||
2. **Bulletins** ✅ - Heavy pagination usage migrated
|
|
||||||
3. **Auth/Users** ✅ - Core authentication functionality migrated
|
|
||||||
4. **Bible Verses** ✅ - Daily usage endpoints migrated
|
|
||||||
5. **Schedule** ✅ - Weekly usage endpoints service created
|
|
||||||
6. **Config** ✅ - Admin functionality service created
|
|
||||||
|
|
||||||
### Files Created/Modified Summary
|
|
||||||
- ✅ **Created**: `src/services/mod.rs` - Services module with all exports
|
|
||||||
- ✅ **Created**: `src/services/events.rs` - Complete event service layer
|
|
||||||
- ✅ **Created**: `src/services/bulletins.rs` - Complete bulletin service layer
|
|
||||||
- ✅ **Created**: `src/services/auth.rs` - Complete authentication service layer
|
|
||||||
- ✅ **Created**: `src/services/bible_verses.rs` - Complete bible verse service layer
|
|
||||||
- ✅ **Created**: `src/services/schedule.rs` - Complete schedule service layer
|
|
||||||
- ✅ **Created**: `src/services/config.rs` - Complete config service layer
|
|
||||||
- ✅ **Modified**: `src/handlers/events.rs` - Migrated to use EventService
|
|
||||||
- ✅ **Modified**: `src/handlers/bulletins.rs` - Migrated to use BulletinService
|
|
||||||
- ✅ **Modified**: `src/handlers/v2/bulletins.rs` - Migrated to use BulletinService
|
|
||||||
- ✅ **Modified**: `src/handlers/auth.rs` - Migrated to use AuthService
|
|
||||||
- ✅ **Modified**: `src/handlers/bible_verses.rs` - Migrated to use BibleVerseService
|
|
||||||
- ✅ **Modified**: `src/handlers/v2/bible_verses.rs` - Migrated to use BibleVerseService
|
|
||||||
- ✅ **Modified**: `src/db/events.rs` - Added missing delete_pending function
|
|
||||||
- ✅ **Modified**: `src/main.rs` - Added services module import
|
|
||||||
|
|
||||||
### Architecture Achievement
|
|
||||||
- ✅ **Proper service layer pattern** implemented across all core modules
|
|
||||||
- ✅ **Clean separation** between HTTP handlers (thin) and business logic (services)
|
|
||||||
- ✅ **Database functions properly used** - No more false "unused" warnings for legitimate functions
|
|
||||||
- ✅ **Timezone handling standardized** - V1 uses EST, V2 uses UTC, database stores UTC
|
|
||||||
- ✅ **Future-proof foundation** - Easy to add validation, caching, authorization to services
|
|
||||||
|
|
||||||
### Build Status
|
|
||||||
- ✅ **Compiles successfully** with no errors
|
|
||||||
- ✅ **Service layer migration complete** - All database functions properly utilized
|
|
||||||
- ✅ **Architecture ready** for future feature additions and improvements
|
|
|
@ -1,106 +0,0 @@
|
||||||
# Timezone Fix Summary - COMPLETED ✅
|
|
||||||
|
|
||||||
## Problem Identified
|
|
||||||
- **V1 endpoints** were incorrectly treating EST input times as UTC times
|
|
||||||
- **Frontend clients** were receiving UTC times instead of expected EST times
|
|
||||||
- **Root cause**: V1 multipart processor used `naive_dt.and_utc()` which treats input as already UTC
|
|
||||||
|
|
||||||
## Solution Implemented
|
|
||||||
|
|
||||||
### 1. Created Shared Timezone Conversion Function
|
|
||||||
**File**: `src/utils/datetime.rs:93-97`
|
|
||||||
```rust
|
|
||||||
/// Shared function for parsing datetime strings from event submissions
|
|
||||||
/// Converts local times (EST/EDT) to UTC for consistent database storage
|
|
||||||
/// Used by both V1 and V2 endpoints to ensure consistent timezone handling
|
|
||||||
pub fn parse_event_datetime_to_utc(datetime_str: &str) -> Result<DateTime<Utc>> {
|
|
||||||
// Use the church's default timezone (EST/EDT) for conversion
|
|
||||||
let parsed = parse_datetime_with_timezone(datetime_str, Some(DEFAULT_CHURCH_TIMEZONE))?;
|
|
||||||
Ok(parsed.utc)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Fixed V1 Multipart Processor
|
|
||||||
**File**: `src/utils/multipart_helpers.rs:70-107`
|
|
||||||
|
|
||||||
**Before (BROKEN):**
|
|
||||||
```rust
|
|
||||||
pub fn get_datetime(&self, field_name: &str) -> Result<DateTime<Utc>> {
|
|
||||||
// ... parse formats
|
|
||||||
if let Ok(naive_dt) = NaiveDateTime::parse_from_str(&datetime_str, format) {
|
|
||||||
return Ok(naive_dt.and_utc()); // ❌ WRONG: Treats EST as UTC
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**After (FIXED):**
|
|
||||||
```rust
|
|
||||||
pub fn get_datetime(&self, field_name: &str) -> Result<DateTime<Utc>> {
|
|
||||||
// First try the shared timezone-aware parsing function
|
|
||||||
if let Ok(utc_time) = crate::utils::datetime::parse_event_datetime_to_utc(&datetime_str) {
|
|
||||||
return Ok(utc_time); // ✅ CORRECT: Converts EST→UTC properly
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to legacy formats for backward compatibility
|
|
||||||
for format in &formats {
|
|
||||||
if let Ok(naive_dt) = NaiveDateTime::parse_from_str(&datetime_str, format) {
|
|
||||||
// Convert naive datetime as EST/EDT to UTC using shared function
|
|
||||||
let formatted_for_conversion = naive_dt.format("%Y-%m-%dT%H:%M:%S").to_string();
|
|
||||||
return crate::utils::datetime::parse_event_datetime_to_utc(&formatted_for_conversion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Consistent Behavior Achieved
|
|
||||||
|
|
||||||
**V1 Submission Flow (Fixed):**
|
|
||||||
```
|
|
||||||
EST Input: "2025-07-30 19:00" → parse_event_datetime_to_utc() → UTC: "2025-07-31T00:00:00Z" → Database Storage
|
|
||||||
```
|
|
||||||
|
|
||||||
**V2 Submission Flow (Already Correct):**
|
|
||||||
```
|
|
||||||
EST Input: "2025-07-30 19:00" → parse_datetime_with_timezone() → UTC: "2025-07-31T00:00:00Z" → Database Storage
|
|
||||||
```
|
|
||||||
|
|
||||||
**Both V1 and V2 Response Flows:**
|
|
||||||
```
|
|
||||||
Database UTC: "2025-07-31T00:00:00Z" → V1: Convert to EST → V2: Convert to specified timezone
|
|
||||||
```
|
|
||||||
|
|
||||||
## Database Migration Context
|
|
||||||
The timezone issue was discovered during investigation of a database migration problem:
|
|
||||||
|
|
||||||
1. **Original data**: Already in EST format in the database
|
|
||||||
2. **Migration script error**: Assumed data was UTC and converted it, causing 4-5 hour offset
|
|
||||||
3. **Fix applied**: Restored from backup and properly converted EST→UTC by adding 4 hours
|
|
||||||
4. **Result**: Database now correctly stores UTC times, V1/V2 convert for display
|
|
||||||
|
|
||||||
## Verification Steps Completed
|
|
||||||
1. ✅ **Code review**: Both V1 and V2 use consistent timezone conversion logic
|
|
||||||
2. ✅ **Build test**: Application compiles successfully
|
|
||||||
3. ✅ **Architecture**: Shared function eliminates code duplication
|
|
||||||
4. ✅ **Backward compatibility**: V1 still supports legacy datetime formats
|
|
||||||
|
|
||||||
## Key Files Modified
|
|
||||||
- `src/utils/datetime.rs` - Added `parse_event_datetime_to_utc()` shared function
|
|
||||||
- `src/utils/multipart_helpers.rs` - Fixed V1 multipart processor to use proper timezone conversion
|
|
||||||
|
|
||||||
## Expected Behavior Now
|
|
||||||
- **Form submission**: `"2025-07-30 19:00"` (7:00 PM EST)
|
|
||||||
- **Database storage**: `"2025-07-31T00:00:00Z"` (12:00 AM UTC, correctly offset)
|
|
||||||
- **V1 API response**: Returns EST times for backward compatibility
|
|
||||||
- **V2 API response**: Returns times in specified timezone with proper metadata
|
|
||||||
- **Frontend display**: Shows correct local times without requiring frontend updates
|
|
||||||
|
|
||||||
## Benefits Achieved
|
|
||||||
1. **Consistent data storage** - All times in UTC in database
|
|
||||||
2. **Proper timezone handling** - EST/EDT input correctly converted to UTC
|
|
||||||
3. **Backward compatibility** - V1 endpoints work exactly as expected
|
|
||||||
4. **Forward compatibility** - V2 endpoints support flexible timezones
|
|
||||||
5. **Code reuse** - Single shared function for timezone conversion
|
|
||||||
6. **Bug elimination** - No more 4-5 hour timezone offset errors
|
|
||||||
|
|
||||||
## Status: COMPLETE ✅
|
|
||||||
Both V1 and V2 event submission endpoints now handle timezone conversion consistently and correctly. The frontend will display proper local times without any code changes required.
|
|
|
@ -1,109 +0,0 @@
|
||||||
# Timezone Migration Plan: V1/V2 Endpoints
|
|
||||||
|
|
||||||
## Problem Statement
|
|
||||||
Currently, the database stores EST times that are masquerading as UTC. This causes confusion and requires hacky workarounds on the frontend to display proper times on devices.
|
|
||||||
|
|
||||||
## Solution Overview
|
|
||||||
- **Database**: Store actual UTC times (fix the current EST-masquerading-as-UTC issue)
|
|
||||||
- **V1 Endpoints**: Convert UTC → EST for backward compatibility with existing clients
|
|
||||||
- **V2 Endpoints**: Return actual UTC times and let clients handle timezone conversion
|
|
||||||
|
|
||||||
## Current State
|
|
||||||
- Database columns: `TIMESTAMP WITH TIME ZONE` (should store UTC but currently stores EST)
|
|
||||||
- V1 endpoints: `/api/events`, `/api/bulletins`, etc. - return EST times masquerading as UTC
|
|
||||||
- V2 endpoints: `/api/v2/events`, `/api/v2/bulletins`, etc. - already exist but may have same timezone issues
|
|
||||||
|
|
||||||
## Target State
|
|
||||||
- **Database**: Store true UTC times
|
|
||||||
- **V1 Endpoints**: Return EST times (for backward compatibility)
|
|
||||||
- **V2 Endpoints**: Return true UTC times (clients handle conversion)
|
|
||||||
|
|
||||||
## Implementation Steps
|
|
||||||
|
|
||||||
### Step 1: Database Migration
|
|
||||||
1. Identify all datetime fields that currently store EST-masquerading-as-UTC
|
|
||||||
2. Convert existing EST times to actual UTC times
|
|
||||||
3. Ensure all new inserts store proper UTC times
|
|
||||||
|
|
||||||
**Key tables/fields to migrate**:
|
|
||||||
- `events.start_time`, `events.end_time`
|
|
||||||
- `pending_events.start_time`, `pending_events.end_time`, `pending_events.submitted_at`
|
|
||||||
- `bulletins.created_at`, `bulletins.updated_at`
|
|
||||||
- Other timestamp fields
|
|
||||||
|
|
||||||
### Step 2: V1 Endpoint Modification
|
|
||||||
1. Read UTC times from database
|
|
||||||
2. Add conversion layer: UTC → EST
|
|
||||||
3. Return EST times to maintain backward compatibility
|
|
||||||
4. Existing frontend clients continue working without changes
|
|
||||||
|
|
||||||
**Endpoints to modify**:
|
|
||||||
- `/api/events*`
|
|
||||||
- `/api/bulletins*`
|
|
||||||
- `/api/schedule*`
|
|
||||||
- All other v1 endpoints returning datetime fields
|
|
||||||
|
|
||||||
### Step 3: V2 Endpoint Verification
|
|
||||||
1. Ensure v2 endpoints read UTC from database
|
|
||||||
2. Return true UTC times without conversion
|
|
||||||
3. Remove any existing timezone conversion logic
|
|
||||||
4. Let clients handle timezone conversion based on their needs
|
|
||||||
|
|
||||||
**V2 endpoints**:
|
|
||||||
- `/api/v2/events*`
|
|
||||||
- `/api/v2/bulletins*`
|
|
||||||
- `/api/v2/schedule*`
|
|
||||||
- All other v2 endpoints
|
|
||||||
|
|
||||||
### Step 4: Utility Functions
|
|
||||||
Create conversion utilities in `src/utils/datetime.rs`:
|
|
||||||
|
|
||||||
1. `convert_utc_to_est()` - For v1 endpoints
|
|
||||||
2. `ensure_utc_storage()` - For database inserts
|
|
||||||
3. `migrate_est_to_utc()` - For data migration
|
|
||||||
|
|
||||||
## Migration Strategy
|
|
||||||
|
|
||||||
### Phase 1: Database Migration (No Breaking Changes)
|
|
||||||
- Run migration to convert EST → UTC in database
|
|
||||||
- Update insert/update logic to store UTC
|
|
||||||
- Deploy without changing endpoint behavior
|
|
||||||
|
|
||||||
### Phase 2: V1 Endpoint Compatibility Layer
|
|
||||||
- Add UTC → EST conversion to v1 endpoints
|
|
||||||
- Deploy and verify existing clients still work
|
|
||||||
- No frontend changes needed
|
|
||||||
|
|
||||||
### Phase 3: V2 Endpoint Cleanup
|
|
||||||
- Ensure v2 endpoints return proper UTC
|
|
||||||
- Deploy and test with v2-compatible clients
|
|
||||||
- Update documentation for v2 API
|
|
||||||
|
|
||||||
### Phase 4: Client Migration
|
|
||||||
- Frontend applications gradually migrate to v2 endpoints
|
|
||||||
- V2 clients handle timezone conversion locally
|
|
||||||
- Better user experience with proper timezone handling
|
|
||||||
|
|
||||||
### Phase 5: V1 Deprecation (Future)
|
|
||||||
- Announce v1 deprecation timeline
|
|
||||||
- Eventually remove v1 endpoints after all clients migrate
|
|
||||||
|
|
||||||
## Benefits
|
|
||||||
- **Clean separation**: Database stores UTC, display logic in clients
|
|
||||||
- **Backward compatibility**: V1 clients continue working
|
|
||||||
- **Future-proof**: V2 clients get proper UTC handling
|
|
||||||
- **No more hacks**: Eliminates workarounds for timezone display
|
|
||||||
|
|
||||||
## Files to Modify
|
|
||||||
- `src/utils/datetime.rs` - Add conversion utilities
|
|
||||||
- `src/handlers/*.rs` - V1 endpoints add EST conversion
|
|
||||||
- `src/handlers/v2/*.rs` - Verify UTC handling
|
|
||||||
- `migrations/` - Database migration script
|
|
||||||
- `src/db/*.rs` - Ensure UTC storage on inserts
|
|
||||||
|
|
||||||
## Testing Strategy
|
|
||||||
- Unit tests for conversion utilities
|
|
||||||
- Integration tests comparing v1 vs v2 responses
|
|
||||||
- Verify v1 returns EST times
|
|
||||||
- Verify v2 returns UTC times
|
|
||||||
- Test database migration with sample data
|
|
Loading…
Reference in a new issue