340 lines
No EOL
10 KiB
Markdown
340 lines
No EOL
10 KiB
Markdown
# Direct API Architecture Plan
|
|
|
|
## Executive Summary
|
|
|
|
This document outlines a comprehensive plan to migrate from proxy-endpoint-based API calls to direct API calls in the Hensei SvelteKit application. This change will resolve current authentication and routing errors while simplifying the codebase and improving performance.
|
|
|
|
### Key Problems Being Solved
|
|
1. **404 Errors**: Grid operations failing due to missing/incorrect proxy endpoints
|
|
2. **401 Authentication Errors**: Cookie-based auth not working properly through proxies
|
|
3. **Complexity**: Maintaining duplicate routing logic in both adapters and proxy endpoints
|
|
4. **Performance**: Extra network hop through proxy adds latency
|
|
5. **Inconsistency**: Some operations use SSR, some use proxies, some try direct calls
|
|
|
|
### Solution
|
|
Implement direct API calls from the browser to the Rails API using Bearer token authentication, which is the standard approach for modern SvelteKit applications.
|
|
|
|
## Current Architecture Analysis
|
|
|
|
### What We Have Now
|
|
|
|
```
|
|
Browser → SvelteKit Proxy (/api/*) → Rails API (localhost:3000/api/v1/*)
|
|
```
|
|
|
|
#### Current Authentication Flow
|
|
1. User logs in via `/auth/login/+server.ts`
|
|
2. OAuth token received and stored in httpOnly cookies
|
|
3. Proxy endpoints read cookies and forward to Rails
|
|
4. Rails uses Doorkeeper OAuth to authenticate via Bearer token
|
|
|
|
#### Current Problems
|
|
- **Proxy Endpoints**: 20+ proxy files in `/src/routes/api/`
|
|
- **Adapter Configuration**: Uses `/api` in browser, expecting proxies that don't exist
|
|
- **Grid Operations**: Broken due to missing/incorrect proxy endpoints
|
|
- **URL Mismatches**: Grid adapter uses wrong URL patterns
|
|
- **Parameter Wrapping**: Inconsistent parameter structures
|
|
|
|
## Proposed Architecture
|
|
|
|
### Direct API Calls
|
|
|
|
```
|
|
Browser → Rails API (localhost:3000/api/v1/*)
|
|
```
|
|
|
|
#### New Authentication Flow
|
|
1. User logs in and receives OAuth token
|
|
2. Store access token in:
|
|
- Server-side: httpOnly cookie (for SSR)
|
|
- Client-side: Memory/store (for browser requests)
|
|
3. Include Bearer token in Authorization header for all API calls
|
|
4. Rails authenticates directly via Doorkeeper
|
|
|
|
#### CORS Configuration
|
|
Rails already has proper CORS configuration:
|
|
```ruby
|
|
origins %w[localhost:5173 127.0.0.1:5173]
|
|
credentials: true
|
|
methods: %i[get post put patch delete options head]
|
|
```
|
|
|
|
## Implementation Plan
|
|
|
|
### Phase 1: Update Authentication System
|
|
|
|
#### 1.1 Create Token Store
|
|
Create `/src/lib/stores/auth.store.ts`:
|
|
```typescript
|
|
import { writable, get } from 'svelte/store'
|
|
|
|
interface AuthState {
|
|
accessToken: string | null
|
|
user: UserInfo | null
|
|
expiresAt: Date | null
|
|
}
|
|
|
|
function createAuthStore() {
|
|
const { subscribe, set, update } = writable<AuthState>({
|
|
accessToken: null,
|
|
user: null,
|
|
expiresAt: null
|
|
})
|
|
|
|
return {
|
|
subscribe,
|
|
setAuth: (token: string, user: UserInfo, expiresAt: Date) => {
|
|
set({ accessToken: token, user, expiresAt })
|
|
},
|
|
clearAuth: () => {
|
|
set({ accessToken: null, user: null, expiresAt: null })
|
|
},
|
|
getToken: () => get(authStore).accessToken
|
|
}
|
|
}
|
|
|
|
export const authStore = createAuthStore()
|
|
```
|
|
|
|
#### 1.2 Update Login Handler
|
|
Modify `/src/routes/auth/login/+server.ts`:
|
|
- Continue setting httpOnly cookies for SSR
|
|
- Also return access token in response for client storage
|
|
|
|
#### 1.3 Update Root Layout
|
|
Modify `/src/routes/+layout.svelte`:
|
|
- Initialize auth store from page data
|
|
- Handle token refresh
|
|
|
|
### Phase 2: Update Adapter System
|
|
|
|
#### 2.1 Fix Adapter Configuration
|
|
Update `/src/lib/api/adapters/config.ts`:
|
|
```typescript
|
|
export function getApiBaseUrl(): string {
|
|
// Always use direct API URL
|
|
const base = PUBLIC_SIERO_API_URL || 'http://localhost:3000'
|
|
return `${base}/api/v1`
|
|
}
|
|
```
|
|
|
|
#### 2.2 Update Base Adapter
|
|
Modify `/src/lib/api/adapters/base.adapter.ts`:
|
|
```typescript
|
|
protected async request<T>(
|
|
path: string,
|
|
options: RequestOptions = {}
|
|
): Promise<T> {
|
|
// Add Bearer token from auth store
|
|
const token = authStore.getToken()
|
|
|
|
const fetchOptions: RequestInit = {
|
|
...options,
|
|
credentials: 'include', // Still include for CORS
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...(token ? { 'Authorization': `Bearer ${token}` } : {}),
|
|
...(options.headers || {})
|
|
}
|
|
}
|
|
|
|
// ... rest of implementation
|
|
}
|
|
```
|
|
|
|
### Phase 3: Fix Grid Adapter Issues
|
|
|
|
#### 3.1 Fix DELETE Methods
|
|
Update `/src/lib/api/adapters/grid.adapter.ts`:
|
|
```typescript
|
|
async deleteWeapon(params: { id: string; partyId: string }): Promise<void> {
|
|
return this.request<void>(`/grid_weapons/${params.id}`, {
|
|
method: 'DELETE'
|
|
})
|
|
}
|
|
// Similar for deleteCharacter and deleteSummon
|
|
```
|
|
|
|
#### 3.2 Fix Position Update URLs
|
|
```typescript
|
|
async updateWeaponPosition(params: UpdatePositionParams): Promise<GridWeapon> {
|
|
const { id, position, container } = params
|
|
return this.request<GridWeapon>(`/grid_weapons/${id}/update_position`, {
|
|
method: 'POST',
|
|
body: { position, container }
|
|
})
|
|
}
|
|
```
|
|
|
|
#### 3.3 Fix Swap URLs
|
|
```typescript
|
|
async swapWeapons(params: SwapPositionsParams): Promise<{
|
|
source: GridWeapon
|
|
target: GridWeapon
|
|
}> {
|
|
return this.request('/grid_weapons/swap', {
|
|
method: 'POST',
|
|
body: params
|
|
})
|
|
}
|
|
```
|
|
|
|
### Phase 4: Remove Proxy Endpoints
|
|
|
|
Delete the entire `/src/routes/api/` directory:
|
|
```bash
|
|
rm -rf /src/routes/api/
|
|
```
|
|
|
|
### Phase 5: Update Services
|
|
|
|
#### 5.1 Party Service
|
|
Update to pass auth token if needed for SSR:
|
|
```typescript
|
|
class PartyService {
|
|
constructor(private fetch?: typeof window.fetch) {}
|
|
|
|
async getByShortcode(shortcode: string): Promise<Party> {
|
|
// On server, use fetch with cookies
|
|
// On client, adapter will use auth store
|
|
return partyAdapter.getByShortcode(shortcode)
|
|
}
|
|
}
|
|
```
|
|
|
|
### Phase 6: Update Components
|
|
|
|
#### 6.1 Party Component
|
|
No changes needed - already uses services correctly
|
|
|
|
#### 6.2 Search Components
|
|
Update to use adapters directly instead of proxy endpoints
|
|
|
|
## Files to Change
|
|
|
|
### Core Authentication Files
|
|
1. `/src/lib/stores/auth.store.ts` - **CREATE** - Token storage
|
|
2. `/src/routes/auth/login/+server.ts` - Return token in response
|
|
3. `/src/routes/+layout.svelte` - Initialize auth store
|
|
4. `/src/routes/+layout.server.ts` - Pass token to client
|
|
|
|
### Adapter System Files
|
|
5. `/src/lib/api/adapters/config.ts` - Remove proxy logic
|
|
6. `/src/lib/api/adapters/base.adapter.ts` - Add Bearer token support
|
|
7. `/src/lib/api/adapters/grid.adapter.ts` - Fix all URL patterns and methods
|
|
8. `/src/lib/api/adapters/party.adapter.ts` - Ensure proper auth
|
|
9. `/src/lib/api/adapters/search.adapter.ts` - Ensure proper auth
|
|
10. `/src/lib/api/adapters/entity.adapter.ts` - Ensure proper auth
|
|
11. `/src/lib/api/adapters/user.adapter.ts` - Ensure proper auth
|
|
|
|
### Service Files (Minor Updates)
|
|
12. `/src/lib/services/grid.service.ts` - Already correct
|
|
13. `/src/lib/services/party.service.ts` - Already correct
|
|
14. `/src/lib/services/conflict.service.ts` - Already correct
|
|
|
|
### Component Files (No Changes)
|
|
- `/src/lib/components/party/Party.svelte` - Already uses services
|
|
- All grid components - Already use context correctly
|
|
|
|
### Files to DELETE
|
|
15. `/src/routes/api/` - **DELETE ENTIRE DIRECTORY**
|
|
|
|
### SSR Routes (No Changes Needed)
|
|
- `/src/routes/teams/[id]/+page.server.ts` - Keep as-is
|
|
- `/src/routes/teams/explore/+page.server.ts` - Keep as-is
|
|
- All other `+page.server.ts` files - Keep as-is
|
|
|
|
## Benefits of This Approach
|
|
|
|
### Performance
|
|
- **Eliminates proxy latency**: Direct calls are faster
|
|
- **Reduces server load**: No proxy processing
|
|
- **Better caching**: Browser can cache API responses directly
|
|
|
|
### Simplicity
|
|
- **Less code**: Remove 20+ proxy endpoint files
|
|
- **Single source of truth**: Adapters handle all API logic
|
|
- **Standard pattern**: Follows SvelteKit best practices
|
|
|
|
### Reliability
|
|
- **Fixes authentication**: Bearer tokens work consistently
|
|
- **Fixes routing**: Direct URLs eliminate 404 errors
|
|
- **Better error handling**: Errors come directly from API
|
|
|
|
### Developer Experience
|
|
- **Easier debugging**: Network tab shows actual API calls
|
|
- **Less complexity**: No proxy layer to understand
|
|
- **Industry standard**: What most SvelteKit apps do
|
|
|
|
## Trade-offs and Considerations
|
|
|
|
### Security Considerations
|
|
1. **API URL exposed**: Browser can see Rails API URL (acceptable)
|
|
2. **Token in memory**: XSS vulnerability (mitigated by httpOnly refresh token)
|
|
3. **CORS required**: Must trust frontend origin (already configured)
|
|
|
|
### Migration Risks
|
|
1. **Breaking change**: All API calls will change
|
|
2. **Testing required**: Need to test all operations
|
|
3. **Token management**: Need to handle expiry/refresh
|
|
|
|
### Mitigation Strategies
|
|
1. **Incremental rollout**: Can update adapters one at a time
|
|
2. **Feature flags**: Can toggle between old/new approach
|
|
3. **Comprehensive testing**: Test each operation before removing proxies
|
|
|
|
## Implementation Timeline
|
|
|
|
### Day 1: Authentication System
|
|
- Create auth store
|
|
- Update login flow
|
|
- Test authentication
|
|
|
|
### Day 2: Adapter Updates
|
|
- Update adapter configuration
|
|
- Add Bearer token support
|
|
- Fix Grid adapter URLs
|
|
|
|
### Day 3: Testing & Cleanup
|
|
- Test all grid operations
|
|
- Test search, favorites, etc.
|
|
- Remove proxy endpoints
|
|
|
|
### Day 4: Final Testing
|
|
- End-to-end testing
|
|
- Performance testing
|
|
- Documentation updates
|
|
|
|
## Success Criteria
|
|
|
|
1. **All grid operations work**: Add, update, delete, move, swap
|
|
2. **Authentication works**: Login, logout, refresh
|
|
3. **No 404 errors**: All API calls succeed
|
|
4. **No 401 errors**: Authentication works consistently
|
|
5. **Performance improvement**: Measurable latency reduction
|
|
|
|
## Conclusion
|
|
|
|
This migration to direct API calls will:
|
|
1. **Solve immediate problems**: Fix broken grid operations
|
|
2. **Improve architecture**: Align with SvelteKit best practices
|
|
3. **Reduce complexity**: Remove unnecessary proxy layer
|
|
4. **Improve performance**: Eliminate proxy latency
|
|
5. **Enhance maintainability**: Single source of truth for API logic
|
|
|
|
The approach is standard for modern SvelteKit applications and is what "9 out of 10 Svelte developers" would implement. It leverages the existing CORS configuration in Rails and uses industry-standard Bearer token authentication.
|
|
|
|
## Next Steps
|
|
|
|
1. Review and approve this plan
|
|
2. Create auth store implementation
|
|
3. Update adapters incrementally
|
|
4. Test thoroughly
|
|
5. Remove proxy endpoints
|
|
6. Deploy with confidence
|
|
|
|
---
|
|
|
|
*Document created: November 2024*
|
|
*Author: Claude Assistant*
|
|
*Status: Ready for Implementation* |