From 69fd1207720caac9a9f0ee1ea5185caafca454e2 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 6 Aug 2025 13:53:38 +0200 Subject: [PATCH] first commit --- CLINE_WORKSPACE_RULES.md | 339 ++++ MONACOUSA_PORTAL_IMPLEMENTATION.md | 2352 ++++++++++++++++++++++++++++ README.md | 125 ++ 3 files changed, 2816 insertions(+) create mode 100644 CLINE_WORKSPACE_RULES.md create mode 100644 MONACOUSA_PORTAL_IMPLEMENTATION.md create mode 100644 README.md diff --git a/CLINE_WORKSPACE_RULES.md b/CLINE_WORKSPACE_RULES.md new file mode 100644 index 0000000..f0f8a23 --- /dev/null +++ b/CLINE_WORKSPACE_RULES.md @@ -0,0 +1,339 @@ +# MonacoUSA Portal - Cline Workspace Rules + +## Project Overview + +This is the **MonacoUSA Portal** - a modern, responsive web portal built with Nuxt 3, Vuetify 3, and Keycloak authentication. The portal provides a unified dashboard for tools and services with mobile-first design and PWA capabilities. + +## Tech Stack & Architecture + +### Core Technologies +- **Framework**: Nuxt 3 with Vue 3 (SPA mode) +- **UI Library**: Vuetify 3 with MonacoUSA theme (#a31515 primary color) +- **Authentication**: Keycloak (OAuth2/OIDC) +- **Database**: NocoDB (API-first database) +- **File Storage**: MinIO (S3-compatible object storage) +- **PWA**: Vite PWA plugin with offline support +- **TypeScript**: Full TypeScript support throughout + +### Project Structure +``` +monacousa-portal/ +├── components/ # Vue components +├── composables/ # Vue composables (useAuth, etc.) +├── layouts/ # Nuxt layouts (dashboard layout) +├── middleware/ # Route middleware (auth middleware) +├── pages/ # Application pages +│ ├── auth/ # Authentication pages +│ └── dashboard/ # Dashboard pages +├── plugins/ # Nuxt plugins +├── public/ # Static assets +├── server/ # Server-side code +│ ├── api/ # API routes +│ ├── utils/ # Server utilities +│ └── plugins/ # Server plugins +├── utils/ # Shared utilities +└── docs/ # Documentation +``` + +## Development Guidelines + +### 1. Authentication System +- Uses Keycloak OAuth2/OIDC flow +- Session management with encrypted cookies +- `useAuth()` composable for authentication state +- Middleware protection for authenticated routes +- Support for user groups/roles (admin, manager, etc.) + +### 2. API Structure +- RESTful APIs in `server/api/` +- Database operations via NocoDB client +- File operations via MinIO client +- Consistent error handling and responses +- Health check endpoint at `/api/health` + +### 3. UI/UX Standards +- Mobile-first responsive design +- Vuetify 3 components with MonacoUSA theme +- Dashboard layout with collapsible sidebar +- PWA support with install prompts +- Consistent color scheme: #a31515 (primary), #ffffff (secondary) + +### 4. File Organization +- Components in `components/` directory +- Composables for reusable logic +- Server utilities in `server/utils/` +- TypeScript types in `utils/types.ts` +- Environment configuration in `.env` files + +## Coding Standards + +### 1. Vue/Nuxt Patterns +```typescript +// Use composition API with +``` + +### 2. API Route Patterns +```typescript +// server/api/example.ts +export default defineEventHandler(async (event) => { + try { + // Implementation + return { success: true, data: result }; + } catch (error) { + throw createError({ + statusCode: 500, + statusMessage: 'Operation failed' + }); + } +}); +``` + +### 3. Database Operations +```typescript +// Use NocoDB client +const nocodb = createNocoDBClient(); +const records = await nocodb.findAll('tableName', { limit: 10 }); +``` + +### 4. File Storage +```typescript +// Use MinIO client +const minio = createMinIOClient(); +await minio.uploadFile(fileName, buffer, contentType); +``` + +## Environment Configuration + +### Required Environment Variables +```env +# Keycloak Configuration +NUXT_KEYCLOAK_ISSUER=https://auth.monacousa.org/realms/monacousa-portal +NUXT_KEYCLOAK_CLIENT_ID=monacousa-portal +NUXT_KEYCLOAK_CLIENT_SECRET=your-keycloak-client-secret +NUXT_KEYCLOAK_CALLBACK_URL=https://monacousa.org/auth/callback + +# NocoDB Configuration +NUXT_NOCODB_URL=https://db.monacousa.org +NUXT_NOCODB_TOKEN=your-nocodb-token +NUXT_NOCODB_BASE_ID=your-nocodb-base-id + +# MinIO Configuration +NUXT_MINIO_ENDPOINT=s3.monacousa.org +NUXT_MINIO_PORT=443 +NUXT_MINIO_USE_SSL=true +NUXT_MINIO_ACCESS_KEY=your-minio-access-key +NUXT_MINIO_SECRET_KEY=your-minio-secret-key +NUXT_MINIO_BUCKET_NAME=monacousa-portal + +# Security Configuration +NUXT_SESSION_SECRET=your-48-character-session-secret-key-here +NUXT_ENCRYPTION_KEY=your-32-character-encryption-key-here + +# Public Configuration +NUXT_PUBLIC_DOMAIN=monacousa.org +``` + +## Key Features & Capabilities + +### 1. Authentication Flow +- OAuth2/OIDC with Keycloak +- Secure session management +- Role-based access control +- Automatic token refresh +- Logout functionality + +### 2. Dashboard System +- Responsive sidebar navigation +- Role-based menu items +- PWA install prompts +- Mobile-optimized layout +- User profile display + +### 3. File Management +- Upload/download via MinIO +- File type validation +- Progress indicators +- Error handling +- Secure file access + +### 4. Database Integration +- CRUD operations via NocoDB +- Dynamic table access +- Query parameters support +- Error handling +- Type safety + +### 5. PWA Features +- Offline support +- Install prompts +- Service worker +- App manifest +- Mobile optimization + +## Development Commands + +```bash +# Development +npm run dev + +# Build for production +npm run build + +# Preview production build +npm run preview + +# Type checking +npm run typecheck +``` + +## Testing & Health Checks + +### Health Check Endpoint +- `GET /api/health` - System health status +- Checks database, storage, and auth connectivity +- Returns status: healthy/degraded/unhealthy + +### Manual Testing +1. Authentication flow (login/logout) +2. Dashboard navigation +3. File upload/download +4. Database operations +5. Mobile responsiveness +6. PWA installation + +## Security Considerations + +### 1. Session Security +- Encrypted session cookies +- Secure cookie settings +- HTTPS required in production +- Session timeout handling + +### 2. API Security +- Authentication middleware +- Input validation +- Error message sanitization +- CORS configuration + +### 3. File Security +- File type validation +- Size limits +- Secure storage +- Access control + +## Deployment Guidelines + +### 1. Production Requirements +- Node.js 18+ +- SSL certificate +- Reverse proxy (nginx) +- Environment variables configured + +### 2. Build Process +```bash +npm ci --only=production +npm run build +npm run preview +``` + +### 3. Health Monitoring +- Monitor `/api/health` endpoint +- Check service dependencies +- Monitor error logs +- Performance metrics + +## Extension Guidelines + +### Adding New Tools +1. Create page in `pages/dashboard/` +2. Add navigation item to dashboard layout +3. Implement API routes if needed +4. Add database tables in NocoDB +5. Update TypeScript types +6. Test authentication and permissions + +### Adding New APIs +1. Create route in `server/api/` +2. Implement proper error handling +3. Add authentication if required +4. Update TypeScript types +5. Test with health checks + +### Adding New Components +1. Create in `components/` directory +2. Follow Vuetify patterns +3. Ensure mobile responsiveness +4. Add proper TypeScript types +5. Test across devices + +## Troubleshooting + +### Common Issues +1. **Authentication failures**: Check Keycloak configuration +2. **Database errors**: Verify NocoDB connection and tokens +3. **File upload issues**: Check MinIO configuration and permissions +4. **Build errors**: Verify all environment variables are set +5. **Mobile issues**: Test responsive design and PWA features + +### Debug Tools +- Browser developer tools +- Network tab for API calls +- Console for JavaScript errors +- Health check endpoint +- Server logs + +## Best Practices + +### 1. Code Quality +- Use TypeScript throughout +- Follow Vue 3 composition API patterns +- Implement proper error handling +- Write descriptive commit messages +- Keep components focused and reusable + +### 2. Performance +- Lazy load components where appropriate +- Optimize images and assets +- Use proper caching strategies +- Monitor bundle size +- Implement proper loading states + +### 3. Security +- Validate all inputs +- Use HTTPS in production +- Keep dependencies updated +- Follow OWASP guidelines +- Regular security audits + +### 4. Maintainability +- Document complex logic +- Use consistent naming conventions +- Keep functions small and focused +- Separate concerns properly +- Regular code reviews + +## Support & Resources + +### Documentation +- Implementation guide: `MONACOUSA_PORTAL_IMPLEMENTATION.md` +- Nuxt 3 documentation +- Vuetify 3 documentation +- Keycloak documentation +- NocoDB API documentation + +### Key Files to Reference +- `nuxt.config.ts` - Main configuration +- `server/utils/keycloak.ts` - Authentication logic +- `composables/useAuth.ts` - Auth composable +- `layouts/dashboard.vue` - Main layout +- `utils/types.ts` - TypeScript definitions + +This workspace is designed to be a solid foundation for building custom tools and features while maintaining consistency, security, and performance standards. diff --git a/MONACOUSA_PORTAL_IMPLEMENTATION.md b/MONACOUSA_PORTAL_IMPLEMENTATION.md new file mode 100644 index 0000000..1b79810 --- /dev/null +++ b/MONACOUSA_PORTAL_IMPLEMENTATION.md @@ -0,0 +1,2352 @@ +# MonacoUSA Portal - Complete Implementation Guide + +## Overview + +This document provides step-by-step instructions to create a complete portal foundation for MonacoUSA using the same proven tech stack as the Port Nimara client portal. The portal will feature Keycloak authentication, responsive design, PWA capabilities, and a modular structure for adding custom tools. + +## Project Specifications + +- **Project Name**: monacousa-portal +- **Domain**: monacousa.org (configurable) +- **Primary Color**: #a31515 (MonacoUSA red) +- **Secondary Color**: #ffffff (white) +- **Framework**: Nuxt 3 with Vue 3 +- **UI Library**: Vuetify 3 +- **Authentication**: Keycloak (OAuth2/OIDC) +- **Database**: NocoDB +- **File Storage**: MinIO (S3-compatible) +- **Features**: PWA, Mobile-responsive, Dashboard layout + +## Prerequisites + +Before starting, ensure you have: +- Node.js 18+ installed +- Git installed +- Access to a Keycloak server +- NocoDB instance (or ability to set one up) +- MinIO instance (or ability to set one up) + +## Phase 1: Project Initialization + +### 1.1 Create New Repository + +```bash +mkdir monacousa-portal +cd monacousa-portal +git init +``` + +### 1.2 Initialize Nuxt 3 Project + +```bash +npx nuxi@latest init . +``` + +### 1.3 Install Core Dependencies + +```bash +npm install @nuxt/ui@^3.2.0 vuetify-nuxt-module@^0.18.3 @vite-pwa/nuxt@^0.10.6 motion-v@^1.6.1 +``` + +### 1.4 Install Additional Dependencies + +```bash +npm install @types/node formidable@^3.5.4 mime-types@^3.0.1 minio@^8.0.5 sharp@^0.34.2 +npm install -D @types/formidable@^3.4.5 @types/mime-types@^3.0.1 +``` + +## Phase 2: Project Structure Setup + +### 2.1 Create Directory Structure + +```bash +mkdir -p components composables layouts middleware pages/auth pages/dashboard server/api server/utils server/plugins utils docs public/icons +``` + +### 2.2 Create package.json + +```json +{ + "name": "monacousa-portal", + "type": "module", + "scripts": { + "build": "nuxt build", + "dev": "nuxt dev", + "generate": "nuxt generate", + "preview": "nuxt preview", + "postinstall": "nuxt prepare" + }, + "dependencies": { + "@nuxt/ui": "^3.2.0", + "@vite-pwa/nuxt": "^0.10.6", + "formidable": "^3.5.4", + "mime-types": "^3.0.1", + "minio": "^8.0.5", + "motion-v": "^1.6.1", + "nuxt": "^3.15.4", + "sharp": "^0.34.2", + "vue": "latest", + "vue-router": "latest", + "vuetify-nuxt-module": "^0.18.3" + }, + "devDependencies": { + "@types/formidable": "^3.4.5", + "@types/mime-types": "^3.0.1" + } +} +``` + +## Phase 3: Core Configuration + +### 3.1 Create nuxt.config.ts + +```typescript +export default defineNuxtConfig({ + ssr: false, + compatibilityDate: "2024-11-01", + devtools: { enabled: true }, + modules: ["vuetify-nuxt-module", "@vite-pwa/nuxt", "motion-v/nuxt"], + app: { + head: { + titleTemplate: "%s • MonacoUSA Portal", + title: "MonacoUSA Portal", + meta: [ + { property: "og:title", content: "MonacoUSA Portal" }, + { property: "og:image", content: "/og-image.png" }, + { name: "twitter:card", content: "summary_large_image" }, + { name: "viewport", content: "width=device-width, initial-scale=1" }, + { name: "apple-mobile-web-app-capable", content: "yes" }, + { name: "apple-mobile-web-app-status-bar-style", content: "default" }, + { name: "apple-mobile-web-app-title", content: "MonacoUSA Portal" }, + ], + htmlAttrs: { + lang: "en", + }, + }, + }, + pwa: { + registerType: 'autoUpdate', + manifest: { + name: 'MonacoUSA Portal', + short_name: 'MonacoUSA', + description: 'MonacoUSA Portal - Unified dashboard for tools and services', + theme_color: '#a31515', + background_color: '#ffffff', + display: 'standalone', + orientation: 'portrait', + start_url: '/', + scope: '/', + icons: [ + { + src: '/icons/icon-72x72.png', + sizes: '72x72', + type: 'image/png' + }, + { + src: '/icons/icon-96x96.png', + sizes: '96x96', + type: 'image/png' + }, + { + src: '/icons/icon-128x128.png', + sizes: '128x128', + type: 'image/png' + }, + { + src: '/icons/icon-144x144.png', + sizes: '144x144', + type: 'image/png' + }, + { + src: '/icons/icon-152x152.png', + sizes: '152x152', + type: 'image/png' + }, + { + src: '/icons/icon-192x192.png', + sizes: '192x192', + type: 'image/png' + }, + { + src: '/icons/icon-384x384.png', + sizes: '384x384', + type: 'image/png' + }, + { + src: '/icons/icon-512x512.png', + sizes: '512x512', + type: 'image/png' + } + ] + }, + workbox: { + navigateFallback: '/', + globPatterns: ['**/*.{js,css,html,png,jpg,jpeg,svg,ico}'], + navigateFallbackDenylist: [/^\/api\//], + runtimeCaching: [ + { + urlPattern: /^https:\/\/.*\.monacousa\.org\/.*/i, + handler: 'NetworkFirst', + options: { + cacheName: 'api-cache', + expiration: { + maxEntries: 10, + maxAgeSeconds: 60 * 60 * 24 // 24 hours + }, + cacheableResponse: { + statuses: [0, 200] + } + } + } + ], + skipWaiting: true, + clientsClaim: true + }, + client: { + installPrompt: true, + periodicSyncForUpdates: 20 + }, + devOptions: { + enabled: true, + type: 'module' + } + }, + nitro: { + experimental: { + wasm: true + } + }, + runtimeConfig: { + // Server-side configuration + keycloak: { + issuer: process.env.NUXT_KEYCLOAK_ISSUER || "", + clientId: process.env.NUXT_KEYCLOAK_CLIENT_ID || "monacousa-portal", + clientSecret: process.env.NUXT_KEYCLOAK_CLIENT_SECRET || "", + callbackUrl: process.env.NUXT_KEYCLOAK_CALLBACK_URL || "https://monacousa.org/auth/callback", + }, + nocodb: { + url: process.env.NUXT_NOCODB_URL || "", + token: process.env.NUXT_NOCODB_TOKEN || "", + baseId: process.env.NUXT_NOCODB_BASE_ID || "", + }, + minio: { + endPoint: process.env.NUXT_MINIO_ENDPOINT || "s3.monacousa.org", + port: parseInt(process.env.NUXT_MINIO_PORT || "443"), + useSSL: process.env.NUXT_MINIO_USE_SSL !== "false", + accessKey: process.env.NUXT_MINIO_ACCESS_KEY || "", + secretKey: process.env.NUXT_MINIO_SECRET_KEY || "", + bucketName: process.env.NUXT_MINIO_BUCKET_NAME || "monacousa-portal", + }, + sessionSecret: process.env.NUXT_SESSION_SECRET || "", + encryptionKey: process.env.NUXT_ENCRYPTION_KEY || "", + public: { + // Client-side configuration + appName: "MonacoUSA Portal", + domain: process.env.NUXT_PUBLIC_DOMAIN || "monacousa.org", + }, + }, + vuetify: { + vuetifyOptions: { + theme: { + defaultTheme: "monacousa", + themes: { + monacousa: { + colors: { + primary: "#a31515", + secondary: "#ffffff", + accent: "#f5f5f5", + error: "#ff5252", + warning: "#ff9800", + info: "#2196f3", + success: "#4caf50", + }, + }, + }, + }, + }, + }, +}); +``` + +### 3.2 Create Environment Configuration (.env.example) + +```env +# Keycloak Configuration +NUXT_KEYCLOAK_ISSUER=https://auth.monacousa.org/realms/monacousa-portal +NUXT_KEYCLOAK_CLIENT_ID=monacousa-portal +NUXT_KEYCLOAK_CLIENT_SECRET=your-keycloak-client-secret +NUXT_KEYCLOAK_CALLBACK_URL=https://monacousa.org/auth/callback + +# NocoDB Configuration +NUXT_NOCODB_URL=https://db.monacousa.org +NUXT_NOCODB_TOKEN=your-nocodb-token +NUXT_NOCODB_BASE_ID=your-nocodb-base-id + +# MinIO Configuration +NUXT_MINIO_ENDPOINT=s3.monacousa.org +NUXT_MINIO_PORT=443 +NUXT_MINIO_USE_SSL=true +NUXT_MINIO_ACCESS_KEY=your-minio-access-key +NUXT_MINIO_SECRET_KEY=your-minio-secret-key +NUXT_MINIO_BUCKET_NAME=monacousa-portal + +# Security Configuration +NUXT_SESSION_SECRET=your-48-character-session-secret-key-here +NUXT_ENCRYPTION_KEY=your-32-character-encryption-key-here + +# Public Configuration +NUXT_PUBLIC_DOMAIN=monacousa.org +``` + +## Phase 4: Authentication Implementation + +### 4.1 Create Keycloak Utility (server/utils/keycloak.ts) + +```typescript +interface KeycloakConfig { + issuer: string; + clientId: string; + clientSecret: string; + callbackUrl: string; +} + +interface TokenResponse { + access_token: string; + refresh_token: string; + id_token: string; + token_type: string; + expires_in: number; +} + +interface UserInfo { + sub: string; + email: string; + given_name?: string; + family_name?: string; + name?: string; + groups?: string[]; + tier?: string; +} + +export class KeycloakClient { + private config: KeycloakConfig; + + constructor(config: KeycloakConfig) { + this.config = config; + } + + getAuthUrl(state: string): string { + const params = new URLSearchParams({ + client_id: this.config.clientId, + redirect_uri: this.config.callbackUrl, + response_type: 'code', + scope: 'openid email profile', + state, + }); + + return `${this.config.issuer}/protocol/openid-connect/auth?${params}`; + } + + async exchangeCodeForTokens(code: string): Promise { + const response = await fetch(`${this.config.issuer}/protocol/openid-connect/token`, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams({ + grant_type: 'authorization_code', + client_id: this.config.clientId, + client_secret: this.config.clientSecret, + code, + redirect_uri: this.config.callbackUrl, + }), + }); + + if (!response.ok) { + throw new Error(`Token exchange failed: ${response.statusText}`); + } + + return response.json(); + } + + async getUserInfo(accessToken: string): Promise { + const response = await fetch(`${this.config.issuer}/protocol/openid-connect/userinfo`, { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }); + + if (!response.ok) { + throw new Error(`Failed to get user info: ${response.statusText}`); + } + + return response.json(); + } + + async refreshToken(refreshToken: string): Promise { + const response = await fetch(`${this.config.issuer}/protocol/openid-connect/token`, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams({ + grant_type: 'refresh_token', + client_id: this.config.clientId, + client_secret: this.config.clientSecret, + refresh_token: refreshToken, + }), + }); + + if (!response.ok) { + throw new Error(`Token refresh failed: ${response.statusText}`); + } + + return response.json(); + } +} + +export function createKeycloakClient(): KeycloakClient { + const config = useRuntimeConfig(); + return new KeycloakClient(config.keycloak); +} +``` + +### 4.2 Create Session Management (server/utils/session.ts) + +```typescript +import { serialize, parse } from 'cookie'; +import { createHash, createCipheriv, createDecipheriv, randomBytes } from 'crypto'; + +interface SessionData { + user: { + id: string; + email: string; + name: string; + groups?: string[]; + tier?: string; + }; + tokens: { + accessToken: string; + refreshToken: string; + expiresAt: number; + }; + createdAt: number; + lastActivity: number; +} + +export class SessionManager { + private encryptionKey: Buffer; + private cookieName = 'monacousa-session'; + + constructor(encryptionKey: string) { + this.encryptionKey = Buffer.from(encryptionKey, 'hex'); + } + + private encrypt(data: string): string { + const iv = randomBytes(16); + const cipher = createCipheriv('aes-256-cbc', this.encryptionKey, iv); + let encrypted = cipher.update(data, 'utf8', 'hex'); + encrypted += cipher.final('hex'); + return iv.toString('hex') + ':' + encrypted; + } + + private decrypt(encryptedData: string): string { + const [ivHex, encrypted] = encryptedData.split(':'); + const iv = Buffer.from(ivHex, 'hex'); + const decipher = createDecipheriv('aes-256-cbc', this.encryptionKey, iv); + let decrypted = decipher.update(encrypted, 'hex', 'utf8'); + decrypted += decipher.final('utf8'); + return decrypted; + } + + createSession(sessionData: SessionData): string { + const data = JSON.stringify(sessionData); + const encrypted = this.encrypt(data); + + return serialize(this.cookieName, encrypted, { + httpOnly: true, + secure: true, + sameSite: 'lax', + maxAge: 60 * 60 * 24 * 7, // 7 days + path: '/', + }); + } + + getSession(cookieHeader?: string): SessionData | null { + if (!cookieHeader) return null; + + const cookies = parse(cookieHeader); + const sessionCookie = cookies[this.cookieName]; + + if (!sessionCookie) return null; + + try { + const decrypted = this.decrypt(sessionCookie); + const sessionData = JSON.parse(decrypted) as SessionData; + + // Check if session is expired + if (Date.now() > sessionData.tokens.expiresAt) { + return null; + } + + return sessionData; + } catch (error) { + console.error('Failed to decrypt session:', error); + return null; + } + } + + destroySession(): string { + return serialize(this.cookieName, '', { + httpOnly: true, + secure: true, + sameSite: 'lax', + maxAge: 0, + path: '/', + }); + } +} + +export function createSessionManager(): SessionManager { + const config = useRuntimeConfig(); + return new SessionManager(config.encryptionKey); +} +``` + +### 4.3 Create Authentication Middleware (middleware/auth.ts) + +```typescript +export default defineNuxtRouteMiddleware((to) => { + // Skip auth for public pages + if (to.meta.auth === false) { + return; + } + + // Check if user is authenticated + const authState = useState('auth.state', () => ({ + authenticated: false, + user: null, + groups: [], + })); + + if (!authState.value.authenticated) { + return navigateTo('/login'); + } +}); +``` + +### 4.4 Create Auth API Routes + +#### server/api/auth/login.get.ts + +```typescript +export default defineEventHandler(async (event) => { + const keycloak = createKeycloakClient(); + const state = randomBytes(32).toString('hex'); + + // Store state in session for verification + setCookie(event, 'oauth-state', state, { + httpOnly: true, + secure: true, + maxAge: 600, // 10 minutes + }); + + const authUrl = keycloak.getAuthUrl(state); + + return sendRedirect(event, authUrl); +}); +``` + +#### server/api/auth/callback.get.ts + +```typescript +export default defineEventHandler(async (event) => { + const query = getQuery(event); + const { code, state } = query; + + if (!code || !state) { + throw createError({ + statusCode: 400, + statusMessage: 'Missing authorization code or state', + }); + } + + // Verify state + const storedState = getCookie(event, 'oauth-state'); + if (state !== storedState) { + throw createError({ + statusCode: 400, + statusMessage: 'Invalid state parameter', + }); + } + + try { + const keycloak = createKeycloakClient(); + const sessionManager = createSessionManager(); + + // Exchange code for tokens + const tokens = await keycloak.exchangeCodeForTokens(code as string); + + // Get user info + const userInfo = await keycloak.getUserInfo(tokens.access_token); + + // Create session + const sessionData = { + user: { + id: userInfo.sub, + email: userInfo.email, + name: userInfo.name || `${userInfo.given_name} ${userInfo.family_name}`.trim(), + groups: userInfo.groups || [], + tier: userInfo.tier, + }, + tokens: { + accessToken: tokens.access_token, + refreshToken: tokens.refresh_token, + expiresAt: Date.now() + (tokens.expires_in * 1000), + }, + createdAt: Date.now(), + lastActivity: Date.now(), + }; + + const sessionCookie = sessionManager.createSession(sessionData); + + // Set session cookie + setHeader(event, 'Set-Cookie', sessionCookie); + + // Clear state cookie + deleteCookie(event, 'oauth-state'); + + return sendRedirect(event, '/dashboard'); + } catch (error) { + console.error('Auth callback error:', error); + throw createError({ + statusCode: 500, + statusMessage: 'Authentication failed', + }); + } +}); +``` + +#### server/api/auth/session.get.ts + +```typescript +export default defineEventHandler(async (event) => { + const sessionManager = createSessionManager(); + const cookieHeader = getHeader(event, 'cookie'); + const session = sessionManager.getSession(cookieHeader); + + if (!session) { + return { + authenticated: false, + user: null, + groups: [], + }; + } + + return { + authenticated: true, + user: session.user, + groups: session.user.groups || [], + }; +}); +``` + +#### server/api/auth/logout.post.ts + +```typescript +export default defineEventHandler(async (event) => { + const sessionManager = createSessionManager(); + const destroyCookie = sessionManager.destroySession(); + + setHeader(event, 'Set-Cookie', destroyCookie); + + return { success: true }; +}); +``` + +## Phase 5: Database Integration (NocoDB) + +### 5.1 Create NocoDB Utility (server/utils/nocodb.ts) + +```typescript +interface NocoDBConfig { + url: string; + token: string; + baseId: string; +} + +export class NocoDBClient { + private config: NocoDBConfig; + private baseUrl: string; + + constructor(config: NocoDBConfig) { + this.config = config; + this.baseUrl = `${config.url}/api/v2/tables`; + } + + private async request(endpoint: string, options: RequestInit = {}) { + const url = `${this.baseUrl}${endpoint}`; + + const response = await fetch(url, { + ...options, + headers: { + 'xc-token': this.config.token, + 'Content-Type': 'application/json', + ...options.headers, + }, + }); + + if (!response.ok) { + throw new Error(`NocoDB request failed: ${response.statusText}`); + } + + return response.json(); + } + + async findAll(tableName: string, params: Record = {}) { + const queryString = new URLSearchParams(params).toString(); + const endpoint = `/${tableName}/records${queryString ? `?${queryString}` : ''}`; + return this.request(endpoint); + } + + async findOne(tableName: string, id: string) { + return this.request(`/${tableName}/records/${id}`); + } + + async create(tableName: string, data: Record) { + return this.request(`/${tableName}/records`, { + method: 'POST', + body: JSON.stringify(data), + }); + } + + async update(tableName: string, id: string, data: Record) { + return this.request(`/${tableName}/records/${id}`, { + method: 'PATCH', + body: JSON.stringify(data), + }); + } + + async delete(tableName: string, id: string) { + return this.request(`/${tableName}/records/${id}`, { + method: 'DELETE', + }); + } +} + +export function createNocoDBClient(): NocoDBClient { + const config = useRuntimeConfig(); + return new NocoDBClient(config.nocodb); +} +``` + +### 5.2 Create Database API Template (server/api/data/[table]/[...params].ts) + +```typescript +export default defineEventHandler(async (event) => { + const method = getMethod(event); + const params = getRouterParams(event); + const table = params.table; + const additionalParams = params.params?.split('/') || []; + + const nocodb = createNocoDBClient(); + + try { + switch (method) { + case 'GET': + if (additionalParams.length > 0) { + // Get single record + const id = additionalParams[0]; + return await nocodb.findOne(table, id); + } else { + // Get all records + const query = getQuery(event); + return await nocodb.findAll(table, query); + } + + case 'POST': + const createData = await readBody(event); + return await nocodb.create(table, createData); + + case 'PATCH': + if (additionalParams.length === 0) { + throw createError({ + statusCode: 400, + statusMessage: 'Record ID required for update', + }); + } + const updateId = additionalParams[0]; + const updateData = await readBody(event); + return await nocodb.update(table, updateId, updateData); + + case 'DELETE': + if (additionalParams.length === 0) { + throw createError({ + statusCode: 400, + statusMessage: 'Record ID required for delete', + }); + } + const deleteId = additionalParams[0]; + return await nocodb.delete(table, deleteId); + + default: + throw createError({ + statusCode: 405, + statusMessage: 'Method not allowed', + }); + } + } catch (error) { + console.error('Database operation error:', error); + throw createError({ + statusCode: 500, + statusMessage: 'Database operation failed', + }); + } +}); +``` + +## Phase 6: File Storage Integration (MinIO) + +### 6.1 Create MinIO Utility (server/utils/minio.ts) + +```typescript +import { Client } from 'minio'; + +interface MinIOConfig { + endPoint: string; + port: number; + useSSL: boolean; + accessKey: string; + secretKey: string; + bucketName: string; +} + +export class MinIOClient { + private client: Client; + private bucketName: string; + + constructor(config: MinIOConfig) { + this.client = new Client({ + endPoint: config.endPoint, + port: config.port, + useSSL: config.useSSL, + accessKey: config.accessKey, + secretKey: config.secretKey, + }); + this.bucketName = config.bucketName; + } + + async ensureBucket(): Promise { + const exists = await this.client.bucketExists(this.bucketName); + if (!exists) { + await this.client.makeBucket(this.bucketName); + } + } + + async uploadFile(fileName: string, buffer: Buffer, contentType?: string): Promise { + await this.ensureBucket(); + + const metadata = contentType ? { 'Content-Type': contentType } : {}; + + await this.client.putObject(this.bucketName, fileName, buffer, buffer.length, metadata); + + return fileName; + } + + async getFile(fileName: string): Promise { + const stream = await this.client.getObject(this.bucketName, fileName); + const chunks: Buffer[] = []; + + return new Promise((resolve, reject) => { + stream.on('data', (chunk) => chunks.push(chunk)); + stream.on('end', () => resolve(Buffer.concat(chunks))); + stream.on('error', reject); + }); + } + + async deleteFile(fileName: string): Promise { + await this.client.removeObject(this.bucketName, fileName); + } + + async listFiles(prefix?: string): Promise { + const objects: string[] = []; + const stream = this.client.listObjects(this.bucketName, prefix); + + return new Promise((resolve, reject) => { + stream.on('data', (obj) => objects.push(obj.name!)); + stream.on('end', () => resolve(objects)); + stream.on('error', reject); + }); + } + + getPresignedUrl(fileName: string, expiry: number = 3600): Promise { + return this.client.presignedGetObject(this.bucketName, fileName, expiry); + } +} + +export function createMinIOClient(): MinIOClient { + const config = useRuntimeConfig(); + return new MinIOClient(config.minio); +} +``` + +### 6.2 Create File Upload API (server/api/files/upload.post.ts) + +```typescript +import formidable from 'formidable'; +import { readFileSync } from 'fs'; +import { lookup } from 'mime-types'; + +export default defineEventHandler(async (event) => { + const form = formidable({ + maxFileSize: 10 * 1024 * 1024, // 10MB + allowEmptyFiles: false, + }); + + try { + const [fields, files] = await form.parse(event.node.req); + const minio = createMinIOClient(); + + const uploadedFiles = []; + + for (const [fieldName, fileArray] of Object.entries(files)) { + const file = Array.isArray(fileArray) ? fileArray[0] : fileArray; + + if (file && file.filepath) { + const buffer = readFileSync(file.filepath); + const contentType = lookup(file.originalFilename || '') || 'application/octet-stream'; + const fileName = `${Date.now()}-${file.originalFilename}`; + + await minio.uploadFile(fileName, buffer, contentType); + + uploadedFiles.push({ + fieldName, + fileName, + originalName: file.originalFilename, + size: file.size, + contentType, + }); + } + } + + return { success: true, files: uploadedFiles }; + } catch (error) { + console.error('File upload error:', error); + throw createError({ + statusCode: 500, + statusMessage: 'File upload failed', + }); + } +}); +``` + +### 6.3 Create File Download API (server/api/files/[filename].get.ts) + +```typescript +export default defineEventHandler(async (event) => { + const filename = getRouterParam(event, 'filename'); + + if (!filename) { + throw createError({ + statusCode: 400, + statusMessage: 'Filename required', + }); + } + + try { + const minio = createMinIOClient(); + const buffer = await minio.getFile(filename); + + // Set appropriate headers + setHeader(event, 'Content-Type', 'application/octet-stream'); + setHeader(event, 'Content-Disposition', `attachment; filename="${filename}"`); + + return buffer; + } catch (error) { + console.error('File download error:', error); + throw createError({ + statusCode: 404, + statusMessage: 'File not found', + }); + } +}); +``` + +## Phase 7: UI Components and Layout + +### 7.1 Create Authentication Composable (composables/useAuth.ts) + +```typescript +interface User { + id: string; + email: string; + name: string; + groups?: string[]; + tier?: string; +} + +interface AuthState { + authenticated: boolean; + user: User | null; + groups: string[]; +} + +export const useAuth = () => { + const authState = useState('auth.state', () => ({ + authenticated: false, + user: null, + groups: [], + })); + + const login = () => { + return navigateTo('/api/auth/login'); + }; + + const logout = async () => { + try { + await $fetch('/api/auth/logout', { method: 'POST' }); + authState.value = { + authenticated: false, + user: null, + groups: [], + }; + await navigateTo('/login'); + } catch (error) { + console.error('Logout error:', error); + await navigateTo('/login'); + } + }; + + const checkAuth = async () => { + try { + const response = await $fetch('/api/auth/session'); + authState.value = response; + return response.authenticated; + } catch (error) { + console.error('Auth check error:', error); + authState.value = { + authenticated: false, + user: null, + groups: [], + }; + return false; + } + }; + + const isAdmin = computed(() => { + return authState.value.groups?.includes('admin') || false; + }); + + const hasRole = (role: string) => { + return authState.value.groups?.includes(role) || false; + }; + + return { + authState: readonly(authState), + user: computed(() => authState.value.user), + authenticated: computed(() => authState.value.authenticated), + groups: computed(() => authState.value.groups), + isAdmin, + hasRole, + login, + logout, + checkAuth, + }; +}; +``` + +### 7.2 Create Login Page (pages/login.vue) + +```vue + + + +``` + +### 7.3 Create Auth Callback Page (pages/auth/callback.vue) + +```vue + + + +``` + +### 7.4 Create Dashboard Layout (layouts/dashboard.vue) + +```vue + + + + + +``` + +### 7.5 Create Dashboard Index Page (pages/dashboard/index.vue) + +```vue + + + +``` + +### 7.6 Create Tools Page Template (pages/dashboard/tools.vue) + +```vue + + + +``` + +## Phase 8: Health Check and Startup + +### 8.1 Create Health Check API (server/api/health.get.ts) + +```typescript +export default defineEventHandler(async (event) => { + const checks = { + server: 'ok', + database: 'unknown', + storage: 'unknown', + auth: 'unknown', + }; + + try { + // Test NocoDB connection + const nocodb = createNocoDBClient(); + await nocodb.findAll('test', { limit: 1 }); + checks.database = 'ok'; + } catch (error) { + checks.database = 'error'; + } + + try { + // Test MinIO connection + const minio = createMinIOClient(); + await minio.ensureBucket(); + checks.storage = 'ok'; + } catch (error) { + checks.storage = 'error'; + } + + try { + // Test Keycloak connection + const keycloak = createKeycloakClient(); + // Simple connectivity test - you might want to implement a proper health check + checks.auth = 'ok'; + } catch (error) { + checks.auth = 'error'; + } + + const allHealthy = Object.values(checks).every(status => status === 'ok'); + + return { + status: allHealthy ? 'healthy' : 'degraded', + timestamp: new Date().toISOString(), + checks, + }; +}); +``` + +### 8.2 Create Startup Plugin (plugins/01.auth-check.client.ts) + +```typescript +export default defineNuxtPlugin(async () => { + const { checkAuth } = useAuth(); + + // Check authentication status on app startup + await checkAuth(); +}); +``` + +## Phase 9: TypeScript Types + +### 9.1 Create Type Definitions (utils/types.ts) + +```typescript +export interface User { + id: string; + email: string; + name: string; + groups?: string[]; + tier?: string; +} + +export interface AuthState { + authenticated: boolean; + user: User | null; + groups: string[]; +} + +export interface ApiResponse { + success: boolean; + data?: T; + error?: string; + message?: string; +} + +export interface FileUpload { + fieldName: string; + fileName: string; + originalName: string; + size: number; + contentType: string; +} + +export interface DatabaseRecord { + id: string; + created_at: string; + updated_at: string; + [key: string]: any; +} + +export interface HealthCheck { + status: 'healthy' | 'degraded' | 'unhealthy'; + timestamp: string; + checks: { + server: string; + database: string; + storage: string; + auth: string; + }; +} +``` + +## Phase 10: Final Setup and Testing + +### 10.1 Create Main App File (app.vue) + +```vue + + + +``` + +### 10.2 Create Index Page (pages/index.vue) + +```vue + + + +``` + +### 10.3 Create README.md + +```markdown +# MonacoUSA Portal + +A modern, responsive portal built with Nuxt 3, Vuetify, and Keycloak authentication. + +## Features + +- 🔐 **Keycloak Authentication** - Secure OAuth2/OIDC authentication +- 📱 **Mobile Responsive** - Works perfectly on all devices +- 🚀 **PWA Support** - Installable progressive web app +- 🎨 **Modern UI** - Beautiful Vuetify 3 interface with MonacoUSA branding +- 📁 **File Storage** - MinIO S3-compatible file storage +- 🗄️ **Database** - NocoDB for flexible data management +- 🔧 **Modular** - Easy to extend with new tools and features + +## Tech Stack + +- **Framework**: Nuxt 3 with Vue 3 +- **UI Library**: Vuetify 3 +- **Authentication**: Keycloak (OAuth2/OIDC) +- **Database**: NocoDB +- **File Storage**: MinIO (S3-compatible) +- **PWA**: Vite PWA plugin +- **TypeScript**: Full TypeScript support + +## Quick Start + +1. **Clone and Install** + ```bash + git clone + cd monacousa-portal + npm install + ``` + +2. **Environment Setup** + ```bash + cp .env.example .env + # Edit .env with your configuration + ``` + +3. **Development** + ```bash + npm run dev + ``` + +4. **Production Build** + ```bash + npm run build + npm run preview + ``` + +## Configuration + +### Environment Variables + +See `.env.example` for all required environment variables: + +- **Keycloak**: Authentication server configuration +- **NocoDB**: Database connection settings +- **MinIO**: File storage configuration +- **Security**: Encryption keys and session secrets + +### Keycloak Setup + +1. Create a new client in your Keycloak realm +2. Set client type to "Confidential" +3. Configure redirect URIs: + - `https://monacousa.org/auth/callback` + - `http://localhost:3000/auth/callback` (development) +4. Enable "Standard Flow" authentication +5. Set up user attributes and groups as needed + +### NocoDB Setup + +1. Set up your NocoDB instance +2. Create a new base/project +3. Generate an API token +4. Configure tables as needed for your tools + +### MinIO Setup + +1. Set up MinIO server +2. Create access keys +3. Configure bucket policies +4. Set CORS policies for web access + +## Project Structure + +``` +monacousa-portal/ +├── components/ # Vue components +├── composables/ # Vue composables +├── layouts/ # Nuxt layouts +├── middleware/ # Route middleware +├── pages/ # Application pages +│ ├── auth/ # Authentication pages +│ └── dashboard/ # Dashboard pages +├── plugins/ # Nuxt plugins +├── public/ # Static assets +├── server/ # Server-side code +│ ├── api/ # API routes +│ ├── utils/ # Server utilities +│ └── plugins/ # Server plugins +├── utils/ # Shared utilities +└── docs/ # Documentation +``` + +## Development + +### Adding New Tools + +1. Create a new page in `pages/dashboard/` +2. Add navigation item to dashboard layout +3. Implement API routes in `server/api/` if needed +4. Add database tables in NocoDB if required + +### API Usage + +The portal provides RESTful APIs for data operations: + +```typescript +// Get all records from a table +const data = await $fetch('/api/data/users'); + +// Get single record +const user = await $fetch('/api/data/users/123'); + +// Create new record +const newUser = await $fetch('/api/data/users', { + method: 'POST', + body: { name: 'John Doe', email: 'john@example.com' } +}); + +// Update record +const updatedUser = await $fetch('/api/data/users/123', { + method: 'PATCH', + body: { name: 'Jane Doe' } +}); + +// Delete record +await $fetch('/api/data/users/123', { method: 'DELETE' }); +``` + +### File Upload + +```typescript +// Upload files +const formData = new FormData(); +formData.append('file', file); + +const result = await $fetch('/api/files/upload', { + method: 'POST', + body: formData +}); +``` + +## Deployment + +### Docker Deployment + +```dockerfile +FROM node:18-alpine + +WORKDIR /app +COPY package*.json ./ +RUN npm ci --only=production + +COPY . . +RUN npm run build + +EXPOSE 3000 +CMD ["npm", "run", "preview"] +``` + +### Environment Variables for Production + +Ensure all environment variables are properly set: +- Use strong encryption keys +- Configure proper domain names +- Set up SSL certificates +- Configure firewall rules + +## Health Checks + +The portal includes health check endpoints: + +- `GET /api/health` - Overall system health +- Check database connectivity +- Check file storage connectivity +- Check authentication service + +## Security + +- All sessions are encrypted +- HTTPS required in production +- CSRF protection enabled +- Secure cookie settings +- Input validation on all APIs + +## Support + +For issues and questions: +1. Check the documentation +2. Review environment configuration +3. Check health endpoints +4. Review server logs + +## License + +[Your License Here] +``` + +### 10.4 Create TypeScript Configuration (tsconfig.json) + +```json +{ + "extends": "./.nuxt/tsconfig.json" +} +``` + +### 10.5 Create Git Ignore (.gitignore) + +```gitignore +# Nuxt dev/build outputs +.output +.nuxt +.nitro +.cache +dist + +# Node dependencies +node_modules + +# Logs +*.log* + +# Misc +.DS_Store +.fleet +.idea + +# Local env files +.env +.env.* +!.env.example + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +``` + +## Phase 11: Testing and Verification + +### 11.1 Development Testing + +After setting up the project, test the following: + +1. **Authentication Flow** + ```bash + npm run dev + # Visit http://localhost:3000 + # Test login/logout flow + ``` + +2. **API Endpoints** + ```bash + # Test health check + curl http://localhost:3000/api/health + + # Test authentication session + curl http://localhost:3000/api/auth/session + ``` + +3. **File Upload** + - Test file upload functionality + - Verify MinIO storage + - Check file download + +4. **Database Operations** + - Test CRUD operations + - Verify NocoDB integration + - Check data persistence + +### 11.2 Production Checklist + +Before deploying to production: + +- [ ] All environment variables configured +- [ ] SSL certificates installed +- [ ] Keycloak client properly configured +- [ ] NocoDB accessible and secured +- [ ] MinIO bucket policies configured +- [ ] Health checks passing +- [ ] PWA manifest and icons in place +- [ ] Error handling tested +- [ ] Mobile responsiveness verified + +## Phase 12: Deployment Guide + +### 12.1 Server Requirements + +- Node.js 18+ +- SSL certificate +- Reverse proxy (nginx/Apache) +- Firewall configuration + +### 12.2 Environment Setup + +1. **Production Environment Variables** + ```bash + # Copy and configure + cp .env.example .env.production + ``` + +2. **Build Application** + ```bash + npm run build + ``` + +3. **Start Production Server** + ```bash + npm run preview + # or use PM2 for process management + pm2 start ecosystem.config.js + ``` + +### 12.3 Nginx Configuration Example + +```nginx +server { + listen 443 ssl http2; + server_name monacousa.org; + + ssl_certificate /path/to/certificate.crt; + ssl_certificate_key /path/to/private.key; + + location / { + proxy_pass http://localhost:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + } +} +``` + +## Conclusion + +This implementation guide provides a complete foundation for the MonacoUSA Portal. The portal includes: + +✅ **Complete Authentication System** with Keycloak integration +✅ **Responsive Dashboard** that works on all devices +✅ **File Storage System** with MinIO integration +✅ **Database Integration** with NocoDB +✅ **PWA Support** for mobile installation +✅ **Modern UI** with Vuetify and MonacoUSA branding +✅ **Modular Architecture** for easy extension +✅ **Production-Ready** configuration and deployment guide + +The foundation is ready for you to build your custom tools and features on top of this solid, proven architecture. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9774c77 --- /dev/null +++ b/README.md @@ -0,0 +1,125 @@ +# MonacoUSA Portal Foundation + +This folder contains the complete foundation and implementation guide for creating the **MonacoUSA Portal** - a modern, responsive web portal built with the same proven tech stack as the Port Nimara client portal. + +## 📁 Contents + +### 1. `MONACOUSA_PORTAL_IMPLEMENTATION.md` +**Complete step-by-step implementation guide** containing: +- ✅ Full project setup instructions +- ✅ All code templates and configurations +- ✅ Keycloak authentication implementation +- ✅ NocoDB database integration +- ✅ MinIO file storage setup +- ✅ Responsive dashboard with Vuetify 3 +- ✅ PWA configuration +- ✅ Production deployment guide +- ✅ Testing and verification steps + +### 2. `CLINE_WORKSPACE_RULES.md` +**Cline workspace configuration file** containing: +- 🔧 Project overview and tech stack details +- 🔧 Development guidelines and coding standards +- 🔧 Environment configuration requirements +- 🔧 Key features and capabilities +- 🔧 Extension guidelines for adding new tools +- 🔧 Troubleshooting and best practices +- 🔧 Support resources and documentation + +## 🚀 Quick Start + +1. **Give the implementation guide to another Claude instance**: + - Copy the contents of `MONACOUSA_PORTAL_IMPLEMENTATION.md` + - Provide it to Claude with instructions to follow the guide step-by-step + +2. **Set up Cline workspace rules**: + - Copy the contents of `CLINE_WORKSPACE_RULES.md` + - Add it to your Cline workspace rules for the new project + +## 🎯 Project Specifications + +- **Name**: monacousa-portal +- **Domain**: monacousa.org (configurable) +- **Colors**: #a31515 (MonacoUSA red) primary, #ffffff (white) secondary +- **Tech Stack**: Nuxt 3, Vue 3, Vuetify 3, Keycloak, NocoDB, MinIO +- **Features**: PWA, Mobile-responsive, Dashboard layout, File storage + +## 📋 What You'll Get + +Following this implementation guide will create a complete portal foundation with: + +### ✅ Authentication System +- Keycloak OAuth2/OIDC integration +- Secure session management +- Role-based access control +- Login/logout functionality + +### ✅ Responsive Dashboard +- Mobile-first design +- Collapsible sidebar navigation +- User profile display +- Role-based menu items + +### ✅ File Management +- MinIO S3-compatible storage +- Upload/download functionality +- File type validation +- Secure access control + +### ✅ Database Integration +- NocoDB API-first database +- CRUD operations +- Dynamic table access +- Type-safe operations + +### ✅ PWA Features +- Offline support +- Install prompts +- Service worker +- Mobile optimization + +### ✅ Production Ready +- Health check endpoints +- Error handling +- Security best practices +- Deployment configuration + +## 🛠️ Usage Instructions + +### For Implementation +1. Create a new repository for your MonacoUSA Portal +2. Follow the step-by-step guide in `MONACOUSA_PORTAL_IMPLEMENTATION.md` +3. Configure your environment variables +4. Set up Keycloak, NocoDB, and MinIO services +5. Test the implementation +6. Deploy to production + +### For Development +1. Use `CLINE_WORKSPACE_RULES.md` as your Cline workspace rules +2. Follow the coding standards and guidelines +3. Extend the portal with your custom tools +4. Maintain consistency with the established patterns + +## 🎨 Customization + +The foundation is designed to be easily customizable: +- **Branding**: Update colors, logos, and text +- **Tools**: Add new dashboard pages and functionality +- **APIs**: Extend with custom server endpoints +- **Database**: Add new tables and data structures +- **UI**: Customize components and layouts + +## 📞 Support + +This foundation is based on the proven Port Nimara client portal architecture and includes: +- Comprehensive documentation +- Complete code examples +- Best practices and patterns +- Troubleshooting guides +- Extension guidelines + +The implementation guide is self-contained and can be followed by any developer or AI assistant to create the exact foundation you need. + +--- + +**Ready to build your MonacoUSA Portal!** 🚀