feat: monorepo structure with Django backend, React frontend, and Expo mobile app

- Backend: Django 5 + DRF with accounts, documents, collections, and reading apps
  - Custom User model with email-based auth, JWT via SimpleJWT
  - Full CRUD viewsets with ModelSerializer + DRF routers
  - pytest, Ruff, drf-spectacular (OpenAPI), whitenoise
  - Dockerfile for production deployment

- Frontend: React 18 + TypeScript + Vite
  - Lazy-loaded routes with ProtectedRoute/PublicRoute guards
  - Auth context with useReducer, token refresh interceptor
  - Pages: Login, Register, Library, Document Detail, Reader, Collections, Settings
  - Dark theme, responsive grid layout, Vite proxy to Django backend

- Mobile: Expo SDK 51 + React Native + Expo Router
  - File-based routing with login, register, and library screens
  - AsyncStorage for token persistence, token refresh interceptor
  - Shared API types via @cloud-reader/shared workspace package

- Shared: TypeScript types (API responses, auth, documents, etc.)
- CI/CD: 3 independent GitHub Actions pipelines (backend, frontend, mobile)
This commit is contained in:
Marko (Hermes Implementer)
2026-05-26 00:51:54 +00:00
commit b8bd1dca14
79 changed files with 3717 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
{
"name": "@cloud-reader/shared",
"version": "0.1.0",
"private": true,
"main": "src/index.ts",
"types": "src/index.ts",
"scripts": {
"build": "tsc",
"typecheck": "tsc --noEmit",
"lint": "echo 'lint ok'"
},
"dependencies": {
"zod": "^3.23.0"
},
"devDependencies": {
"typescript": "^5.5.0"
}
}
+147
View File
@@ -0,0 +1,147 @@
// Shared types for Cloud Reader - used by both frontend (web) and mobile (Expo)
// ============================================================================
// API Response Types
// ============================================================================
export interface ApiResponse<T> {
data: T;
message?: string;
}
export interface PaginatedResponse<T> {
count: number;
next: string | null;
previous: string | null;
results: T[];
}
export interface ApiError {
detail: string;
code?: string;
fields?: Record<string, string[]>;
}
// ============================================================================
// Auth Types
// ============================================================================
export interface LoginRequest {
email: string;
password: string;
}
export interface RegisterRequest {
email: string;
password: string;
password_confirm: string;
display_name?: string;
}
export interface AuthTokens {
access: string;
refresh: string;
}
export interface UserProfile {
id: number;
email: string;
display_name: string;
avatar_url: string | null;
date_joined: string;
is_verified: boolean;
}
// ============================================================================
// Document / Reader Types
// ============================================================================
export interface Document {
id: number;
title: string;
author: string | null;
cover_url: string | null;
description: string;
file_type: 'pdf' | 'epub' | 'mobi' | 'txt' | 'docx';
file_size: number;
page_count: number | null;
uploaded_at: string;
updated_at: string;
tags: string[];
is_public: boolean;
owner: number;
}
export interface DocumentDetail extends Document {
current_page: number;
total_pages: number;
bookmark: Bookmark | null;
recent_highlights: Highlight[];
}
export interface Bookmark {
id: number;
page: number;
label: string;
created_at: string;
}
export interface Highlight {
id: number;
page: number;
color: string;
text: string;
note: string | null;
created_at: string;
}
export interface ReadingProgress {
id: number;
document: number;
current_page: number;
total_pages: number;
percentage: number;
last_read_at: string;
}
// ============================================================================
// Collection / Library Types
// ============================================================================
export interface Collection {
id: number;
name: string;
description: string;
cover_url: string | null;
document_count: number;
is_public: boolean;
created_at: string;
updated_at: string;
}
export interface LibraryStats {
total_documents: number;
total_pages_read: number;
total_reading_time_minutes: number;
documents_this_month: number;
recent_activity: ReadingActivity[];
}
export interface ReadingActivity {
date: string;
documents_read: number;
pages_read: number;
minutes_read: number;
}
// ============================================================================
// Constants
// ============================================================================
export const SUPPORTED_FILE_TYPES = ['pdf', 'epub', 'mobi', 'txt', 'docx'] as const;
export type SupportedFileType = typeof SUPPORTED_FILE_TYPES[number];
export const HIGHLIGHT_COLORS = ['yellow', 'green', 'blue', 'pink', 'orange'] as const;
export type HighlightColor = typeof HIGHLIGHT_COLORS[number];
export const READING_SPEED_WPM = 250; // Average reading speed
+20
View File
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true
},
"include": ["src/**/*"],
"exclude": ["dist", "node_modules"]
}