feat: Migrate to ultrathin monolith architecture

Consolidate 3 separate applications (reports-app, data-entry-app, telegram-bot) into a unified
architecture with single backend and frontend:

Backend Changes:
- Unified FastAPI backend at backend/ with modular structure
- Modules: reports, data_entry, telegram in backend/modules/
- Centralized config.py and main.py with all routers registered
- Single worker mode (--workers 1) for Telegram bot compatibility
- Shared Oracle connection pool and JWT authentication
- Unified requirements.txt and environment configuration

Frontend Changes:
- Single Vue.js SPA with module-based routing
- Unified frontend at src/ with modules in src/modules/{reports,data-entry}/
- Shared components and stores in src/shared/
- Error boundaries for module isolation
- Dual API proxy in Vite for module communication

Infrastructure:
- New unified startup scripts: start-prod.sh, start-test.sh, start-backend.sh
- Environment templates: .env.dev.example, .env.test.example, .env.prod.example
- Updated deployment scripts for Windows IIS
- Simplified SSH tunnel management

Documentation:
- Comprehensive CLAUDE.md with architecture overview
- Module-specific docs in docs/{data-entry,telegram}/
- Architecture decision records in docs/ARCHITECTURE-DECISIONS.md
- Deployment guides consolidated in deployment/windows/docs/

This migration reduces complexity, improves maintainability, and enables easier
deployment while maintaining all existing functionality.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-29 23:48:14 +02:00
parent 2a101f1ef5
commit c5e051ad80
378 changed files with 7566 additions and 73730 deletions

View File

@@ -1,140 +0,0 @@
{
"patterns": [
{
"id": "pat_20251222_182000",
"timestamp": "2025-12-22T18:20:00Z",
"title": "Unified Vue SPA with Module Isolation via Error Boundaries",
"description": "Consolidate multiple Vue apps into a single SPA using lazy-loaded modules wrapped in error boundaries. Each module has its own layout component with ErrorBoundary wrapper to prevent crashes from propagating across modules.",
"context": "Implemented while unifying Reports App and Data Entry App into single deployment",
"example": {
"file": "src/modules/reports/ReportsLayout.vue",
"lines": "1-7",
"snippet": "<template>\n <ErrorBoundary module-name=\"Rapoarte\">\n <router-view />\n </ErrorBoundary>\n</template>\n\n<script setup>\nimport ErrorBoundary from '@shared/components/ErrorBoundary.vue'\n</script>"
},
"tags": ["vue", "spa", "error-boundary", "module-isolation", "architecture"],
"feature": "unified-app",
"usageCount": 0
},
{
"id": "pat_20251222_182001",
"timestamp": "2025-12-22T18:20:01Z",
"title": "Dual API Proxy Pattern in Vite for Microservices",
"description": "Configure Vite dev server to proxy multiple backend microservices under different paths. Allows unified frontend to communicate with separate backend services while maintaining CORS and authentication.",
"context": "Needed to route /api/reports to port 8001 and /api/data-entry to port 8003 from single frontend",
"example": {
"file": "vite.config.js",
"lines": "38-62",
"snippet": "proxy: {\n '/api/reports': {\n target: 'http://localhost:8001',\n changeOrigin: true,\n rewrite: (path) => path.replace(/^\\/api\\/reports/, '/api'),\n configure: (proxy) => {\n proxy.on('proxyReq', (proxyReq, req) => {\n if (req.headers.authorization) {\n proxyReq.setHeader('Authorization', req.headers.authorization);\n }\n });\n }\n },\n '/api/data-entry': {\n target: 'http://localhost:8003',\n changeOrigin: true,\n rewrite: (path) => path.replace(/^\\/api\\/data-entry/, '/api')\n }\n}"
},
"tags": ["vite", "proxy", "microservices", "api", "configuration"],
"feature": "unified-app",
"usageCount": 0
},
{
"id": "pat_20251222_182002",
"timestamp": "2025-12-22T18:20:02Z",
"title": "Pinia Store Factory Pattern for Shared Stores",
"description": "Create shared Pinia stores as factory functions that accept API service instances. Each module instantiates the shared stores with its own API service, ensuring proper module isolation while sharing store logic.",
"context": "Auth, companies, and accounting period stores needed to work with both Reports (port 8001) and Data Entry (port 8003) APIs",
"example": {
"file": "src/shared/stores/auth.js",
"lines": "21-32",
"snippet": "export function createAuthStore(apiService) {\n return defineStore('auth', () => {\n const accessToken = ref(localStorage.getItem('access_token'))\n // ... state\n\n const login = async (credentials) => {\n const response = await apiService.post('/auth/login', credentials)\n // ... handle response\n }\n\n return { login, logout, isAuthenticated, currentUser }\n })\n}"
},
"tags": ["pinia", "stores", "factory-pattern", "module-isolation", "vue"],
"feature": "unified-app",
"usageCount": 0
},
{
"id": "pat_20251222_182003",
"timestamp": "2025-12-22T18:20:03Z",
"title": "Module-Specific Shared Store Instances",
"description": "Instantiate shared store factories in each module's dedicated file to ensure proper API service binding. Prevents import confusion and ensures each module uses its own API base URL.",
"context": "Needed to prevent modules from accidentally using wrong API service when importing shared stores",
"example": {
"file": "src/modules/reports/stores/sharedStores.js",
"lines": "1-18",
"snippet": "import { createAuthStore } from '@shared/stores/auth'\nimport { createCompaniesStore } from '@shared/stores/companies'\nimport { createAccountingPeriodStore } from '@shared/stores/accountingPeriod'\nimport api from '@reports/services/api'\n\n// Create instances with Reports API service\nexport const useAuthStore = createAuthStore(api)\nexport const useCompanyStore = createCompaniesStore(api, useAuthStore)\nexport const useAccountingPeriodStore = createAccountingPeriodStore(api)\n\n// All reports components import from this file, not directly from @shared"
},
"tags": ["pinia", "stores", "module-isolation", "api", "architecture"],
"feature": "unified-app",
"usageCount": 0
},
{
"id": "pat_20251222_182004",
"timestamp": "2025-12-22T18:20:04Z",
"title": "Vite Alias Strategy for Module Organization",
"description": "Use Vite path aliases to create clear module boundaries: @shared for shared code, @reports and @data-entry for module-specific code. Makes imports explicit and prevents accidental cross-module dependencies.",
"context": "Needed clear import paths when consolidating two apps with different import patterns",
"example": {
"file": "vite.config.js",
"lines": "19-26",
"snippet": "resolve: {\n alias: {\n '@': fileURLToPath(new URL('./src', import.meta.url)),\n '@shared': fileURLToPath(new URL('./src/shared', import.meta.url)),\n '@reports': fileURLToPath(new URL('./src/modules/reports', import.meta.url)),\n '@data-entry': fileURLToPath(new URL('./src/modules/data-entry', import.meta.url))\n },\n dedupe: ['vue', 'vue-router', 'pinia', 'primevue']\n}"
},
"tags": ["vite", "aliases", "imports", "module-organization", "architecture"],
"feature": "unified-app",
"usageCount": 0
},
{
"id": "pat_20251222_182005",
"timestamp": "2025-12-22T18:20:05Z",
"title": "IIS URL Rewrite Rules for SPA with Multiple API Backends",
"description": "Configure IIS web.config to proxy different API paths to different backend ports while serving SPA for all other routes. Enables single IIS site to route to multiple microservices.",
"context": "Production deployment needs unified frontend to communicate with both backend services through IIS",
"example": {
"file": "public/web.config",
"lines": "5-28",
"snippet": "<rewrite>\n <rules>\n <rule name=\"Proxy Reports API\" stopProcessing=\"true\">\n <match url=\"^api/reports/(.*)\" />\n <action type=\"Rewrite\" url=\"http://localhost:8001/api/{R:1}\" />\n </rule>\n <rule name=\"Proxy Data Entry API\" stopProcessing=\"true\">\n <match url=\"^api/data-entry/(.*)\" />\n <action type=\"Rewrite\" url=\"http://localhost:8003/api/{R:1}\" />\n </rule>\n <rule name=\"SPA Fallback\" stopProcessing=\"true\">\n <match url=\".*\" />\n <conditions logicalGrouping=\"MatchAll\">\n <add input=\"{REQUEST_FILENAME}\" matchType=\"IsFile\" negate=\"true\" />\n </conditions>\n <action type=\"Rewrite\" url=\"/index.html\" />\n </rule>\n </rules>\n</rewrite>"
},
"tags": ["iis", "deployment", "spa", "microservices", "proxy"],
"feature": "unified-app",
"usageCount": 0
},
{
"id": "pat_20251224_001000",
"timestamp": "2025-12-24T00:10:00Z",
"title": "Vue Watcher for Auto-Loading Dependent Data",
"description": "Use Vue watch() to automatically trigger data loading when dependent selections change. Watch company selection changes to auto-load accounting periods, ensuring UI stays synchronized without manual intervention.",
"context": "Users manually selected company but period dropdown stayed on placeholder instead of auto-selecting current period",
"example": {
"file": "src/App.vue",
"lines": "88-100",
"snippet": "watch(\n () => companyStore.selectedCompany,\n async (newCompany, oldCompany) => {\n if (newCompany && newCompany.id_firma && newCompany !== oldCompany) {\n console.log('[App] Company changed via watch, loading periods for:', newCompany.id_firma)\n await periodStore.loadPeriods(newCompany.id_firma)\n console.log('[App] Periods auto-loaded successfully')\n }\n },\n { immediate: true }\n)"
},
"tags": ["vue", "watch", "reactive", "auto-load", "ux"],
"feature": "unified-app-ux",
"usageCount": 0
},
{
"id": "pat_20251224_001001",
"timestamp": "2025-12-24T00:10:01Z",
"title": "Axios Request Interceptor for JWT Token Injection",
"description": "Add axios request interceptor to automatically inject JWT Bearer token from localStorage into all API requests. Eliminates manual token handling in every API call and prevents 401/500 authentication errors.",
"context": "API requests were failing with 500 errors because JWT token wasn't being sent in Authorization header",
"example": {
"file": "src/App.vue",
"lines": "61-68",
"snippet": "authApi.interceptors.request.use(config => {\n const token = localStorage.getItem('access_token')\n if (token) {\n config.headers.Authorization = `Bearer ${token}`\n }\n return config\n})"
},
"tags": ["axios", "jwt", "authentication", "interceptor", "api"],
"feature": "unified-app-ux",
"usageCount": 0
},
{
"id": "pat_20251224_001002",
"timestamp": "2025-12-24T00:10:02Z",
"title": "Pinia Store Factory with Lazy Instantiation",
"description": "When store factories need to access other stores, use lazy instantiation with try-catch to avoid timing issues. Access stores inside functions (not at module level) and gracefully handle cases where stores aren't ready yet.",
"context": "Period store tried to access auth/company stores for localStorage key generation but got 'useAuthStore is not a function' error",
"example": {
"file": "src/shared/stores/accountingPeriod.js",
"lines": "52-64",
"snippet": "const getStorageKey = () => {\n try {\n const authStore = useAuthStore();\n const companyStore = useCompanyStore();\n const username = authStore.user?.username;\n const companyId = companyStore.selectedCompany?.id_firma;\n if (!username || !companyId) return null;\n return `selected_period_${username}_${companyId}`;\n } catch (e) {\n // Stores not yet initialized, skip localStorage\n return null;\n }\n};"
},
"tags": ["pinia", "stores", "lazy-initialization", "try-catch", "timing"],
"feature": "unified-app-ux",
"usageCount": 0
}
],
"updated": "2025-12-24T00:10:00Z"
}