Coding
PromptBeginner5 minmarkdown
Markdown Converter
Agent skill for markdown-converter
7
Monity is a full-stack personal finance management application with AI-powered transaction categorization, collaborative expense splitting, and financial health insights. Built with React and Node.js following MVC architecture.
Sign in to like and favorite skills
Monity is a full-stack personal finance management application with AI-powered transaction categorization, collaborative expense splitting, and financial health insights. Built with React and Node.js following MVC architecture.
backend/ ├── server.js # Application entry point ├── config/ # Environment & database config │ ├── supabase.js # Supabase client setup │ └── stripe.js # Stripe configuration ├── models/ # Data models (User, Transaction, Category, etc.) ├── controllers/ # Request handlers │ ├── authController.js │ ├── transactionController.js │ ├── categoryController.js │ ├── groupController.js │ ├── savingsGoalController.js │ ├── aiController.js │ ├── adminController.js │ └── index.js # Controller initialization ├── services/ # Business logic layer │ ├── smartCategorizationService.js # AI categorization │ ├── aiSchedulerService.js # Scheduled transactions │ ├── expenseSplittingService.js # Group expense logic │ ├── financialHealthService.js # Financial scoring │ ├── scheduledTransactionService.js │ └── index.js ├── routes/ # API endpoint definitions │ ├── auth.js │ ├── transactions.js │ ├── categories.js │ ├── groups.js │ ├── savingsGoals.js │ └── index.js # Route aggregation ├── middleware/ # Cross-cutting concerns │ ├── auth.js # JWT authentication │ ├── validation.js # Input validation │ ├── encryption.js # Data encryption │ ├── errorHandler.js # Error handling │ ├── rateLimiter.js # Rate limiting │ └── index.js ├── utils/ # Helper functions │ ├── logger.js # Winston logger │ ├── helpers.js # Utility functions │ └── constants.js # App constants ├── migrations/ # Database migrations └── __tests__/ # Backend test suite
frontend/src/ ├── App.jsx # Main router & layout ├── components/ # React components │ ├── auth/ # Login, Signup, etc. │ ├── dashboard/ # Dashboard views │ ├── transactions/ # Transaction management │ ├── groups/ # Expense splitting │ ├── settings/ # Settings & preferences │ ├── admin/ # Admin dashboard │ ├── ai/ # AI features │ ├── cashFlow/ # Cash flow calendar │ ├── ui/ # Reusable UI components │ └── index.js # Component exports ├── context/ # Global state │ └── AuthContext.jsx # Auth state management ├── hooks/ # Custom React hooks │ ├── usePageTracking.js # Analytics │ └── useLazyComponentPreloader.js ├── utils/ # Utilities │ ├── api.js # API client │ ├── supabase.js # Supabase client │ └── i18n/ # Translations (en, pt) └── styles/ # Global styles
Follow strict separation of concerns:
asyncHandler wrapper for async methods{ success, data, message, pagination }?page=1&limit=10/api/v1/resource// Component structure import React, { useState, useEffect, useCallback } from 'react'; import { useAuth } from '../context/AuthContext'; import API from '../utils/api'; const ComponentName = ({ prop1, prop2 }) => { const { user } = useAuth(); const [state, setState] = useState(null); useEffect(() => { // Side effects }, [dependencies]); const handleAction = useCallback(async () => { try { const response = await API.post('/endpoint', data); setState(response.data); } catch (error) { console.error('Error:', error); } }, [dependencies]); return ( <div className="container"> {/* JSX */} </div> ); }; export default React.memo(ComponentName);
// Controller pattern class ResourceController { constructor(supabase) { this.supabase = supabase; this.model = new ResourceModel(supabase); this.service = new ResourceService(supabase); } getAll = asyncHandler(async (req, res) => { const userId = req.user.id; const { page = 1, limit = 10 } = req.query; const data = await this.model.findByUser(userId, { page, limit }); const total = await this.model.countByUser(userId); res.status(200).json({ success: true, data, pagination: { page: parseInt(page), limit: parseInt(limit), total, pages: Math.ceil(total / limit) } }); }); }
// Backend throw new Error('Descriptive error message'); // Frontend try { await operation(); toast.success('Operation successful'); } catch (error) { console.error('Operation failed:', error); toast.error(error.message || 'Operation failed'); }
authenticate middlewareuserId === req.user.idrequireRole('admin') middlewaresubscriptionTier === 'premium'// Apply rate limiting router.use('/auth', rateLimiter.authLimiter); router.use('/api', rateLimiter.apiLimiter); // Authentication middleware router.use(middleware.auth.authenticate); // Input validation router.post('/', validationMiddleware.validateBody(schema), controller.create);
React.lazy()React.memo() for expensive componentsreact-window for long lists// Unit test example describe('ResourceController', () => { let controller; beforeEach(() => { controller = new ResourceController(mockSupabase); }); it('should return all resources for user', async () => { const req = { user: { id: 'user-123' }, query: {} }; const res = mockResponse(); await controller.getAll(req, res); expect(res.status).toHaveBeenCalledWith(200); expect(res.json).toHaveBeenCalledWith({ success: true, data: expect.any(Array) }); }); });
// Component test example import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; test('renders component and handles interaction', async () => { render(<Component />); const button = screen.getByRole('button', { name: /submit/i }); await userEvent.click(button); await waitFor(() => { expect(screen.getByText(/success/i)).toBeInTheDocument(); }); });
# Supabase SUPABASE_URL=your_supabase_url SUPABASE_KEY=your_service_role_key SUPABASE_ANON_KEY=your_anon_key # Server PORT=3000 NODE_ENV=development # Security JWT_SECRET=your_jwt_secret ENCRYPTION_KEY=your_encryption_key # Stripe STRIPE_SECRET_KEY=sk_test_... STRIPE_WEBHOOK_SECRET=whsec_... # Frontend URL CLIENT_URL=http://localhost:5173
VITE_SUPABASE_URL=your_supabase_url VITE_SUPABASE_ANON_KEY=your_anon_key VITE_API_URL=http://localhost:3000 VITE_STRIPE_PUBLIC_KEY=pk_test_...
GET /api/v1/resource # List all GET /api/v1/resource/:id # Get one POST /api/v1/resource # Create PUT /api/v1/resource/:id # Update DELETE /api/v1/resource/:id # Delete # Nested resources GET /api/v1/groups/:id/members # List group members POST /api/v1/groups/:id/expenses # Create group expense
// Success { "success": true, "data": {...}, "message": "Optional success message", "pagination": { "page": 1, "limit": 10, "total": 100, "pages": 10 } } // Error { "success": false, "message": "Error description", "errors": ["validation error 1", "validation error 2"], "code": "ERROR_CODE" }
// Query with RLS (as user) const { data, error } = await supabase .from('transactions') .select('*') .eq('user_id', userId) .order('date', { ascending: false }) .range(offset, offset + limit - 1); // Insert const { data, error } = await supabase .from('transactions') .insert({ ...transactionData, user_id: userId }) .select() .single(); // Update const { data, error } = await supabase .from('transactions') .update(updates) .eq('id', id) .eq('user_id', userId) .select() .single(); // Delete const { error } = await supabase .from('transactions') .delete() .eq('id', id) .eq('user_id', userId);
transactions, categories, usersuser_id, category_idcreated_at, updated_at// Usage in React import { useTranslation } from 'react-i18next'; const Component = () => { const { t } = useTranslation(); return <h1>{t('dashboard.welcome')}</h1>; }; // Translation files structure // frontend/src/utils/i18n/locales/en.json // frontend/src/utils/i18n/locales/pt.json
smartCategorizationService for ML predictions/ai/feedbackexpenseSplittingService for calculationsfeat: add new feature fix: fix bug in component refactor: restructure code docs: update documentation test: add tests style: formatting changes perf: performance improvement chore: maintenance tasks
feature/feature-name - New featuresfix/bug-description - Bug fixesrefactor/component-name - Code refactoringdocs/update-readme - Documentation updatesconst [loading, setLoading] = useState(true); if (loading) return <Spinner />;
import ErrorBoundary from './components/ui/ErrorBoundary'; <ErrorBoundary> <Component /> </ErrorBoundary>
import { ProtectedRoute, AdminRoute, PremiumRoute } from './App'; <Route path="/transactions" element={ <ProtectedRoute> <TransactionList /> </ProtectedRoute> } />
import { toast } from 'react-toastify'; toast.success('Operation successful'); toast.error('Operation failed'); toast.info('Information message'); toast.warning('Warning message');
import API from '../utils/api'; // GET const response = await API.get('/transactions'); // POST const response = await API.post('/transactions', data); // PUT const response = await API.put(`/transactions/${id}`, updates); // DELETE const response = await API.delete(`/transactions/${id}`);
npm run build (outputs to dist/)CLIENT_URL in backend .envnpm start # Start server npm run dev # Start with nodemon npm test # Run tests
npm start # Start dev server (Vite) npm run build # Production build npm run preview # Preview production build npm test # Run tests npm run lint # Run ESLint
Remember: This is a production application with real users. Prioritize security, performance, and user experience in all changes.