= Финална имплементација на случаи на употреба == Трансфер на средства со email известување **User-to-User трансфер** Најавен корисник се навигира кон страницата за трансфер на средства и внесува email адреса на примачот. [[Image(final1.png)]] Корисникот ги пополнува потребните информации и го кликнува копчето за испраќање. [[Image(final2.png)]] По успешен трансфер, и испраќачот и примачот добиваат email известување со детали за трансакцијата. [[Image(final3.png)]] [[Image(final4.png)]] **Backend имплементација - views.py** При испраќање на трансфер преку POST барање, најпрво се валидира изворната сметка и балансот. Потоа се проверува дали примачот постои во системот. {{{ @api_view(['POST']) @permission_classes([IsAuthenticated]) def transfer_funds(request): data = request.data source_account_id = data.get('source_account_id') recipient_email = data.get('recipient_email') amount = Decimal(str(data.get('amount', 0))) note = data.get('note', '') # Validate source account source_account = Account.objects.get(id=source_account_id, user=request.user) # Check balance if source_account.balance < amount: return Response({'error': 'Insufficient balance'}) }}} По успешна валидација, се креираат две трансакции - една за испраќачот (debit) и една за примачот (credit): {{{ with transaction.atomic(): Transaction.objects.create( account=source_account, transaction_type='debit', amount=amount, description=f'Sent to {recipient_user.first_name}' ) Transaction.objects.create( account=recipient_account, transaction_type='credit', amount=amount, description=f'Received from {request.user.first_name}' ) }}} **Email известување ** По завршување на трансферот, се праќаат два email-и преку функцијата send_transfer_email: {{{ def send_transfer_email(user, transfer_type, amount, recipient_name, reference_number, new_balance, is_sender=True, note=None): if is_sender: subject = f'Banker - Transfer Sent: ${amount}' else: subject = f'Banker - Payment Received: ${amount}' send_mail( subject=subject, message=message, from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=[user.email], html_message=html_message ) }}} Email-от содржи: сума, име на примач/испраќач, референтен број, датум и време, нов баланс, и опционална порака (note) доколку е внесена при трансферот. **Internal трансфер (меѓу свои сметки)** Корисникот може да пренесе средства меѓу своите сметки. [[Image(final5.png)]] При интерен трансфер, корисникот добива email со детали за двете сметки: [[Image(final6.png)]] {{{ def send_internal_transfer_email(user, amount, from_account, to_account, reference_number, from_balance, to_balance): subject = f'Banker - Internal Transfer Completed: ${amount}' # HTML email содржи табела со: # - Amount # - From Account (име на изворна сметка) # - To Account (име на дестинациска сметка) # - Updated Balances за двете сметки }}} == Најава преку Google OAuth **Ненајавен корисник** Ненајавениот корисник се навигира кон страницата за најава. [[Image(final7.png)]] Корисникот го кликнува копчето за најава преку Google профил и го селектира посакуваниот профил. [[Image(final8.png)]] По успешна најава, корисникот е пренасочен кон главната страница. **Frontend имплементација - OAuthButtons.tsx** Во frontend делот се иницијализира Google Sign-In SDK и се рендерира официјалното Google копче: {{{ useEffect(() => { if (googleLoaded && window.google) { window.google.accounts.id.initialize({ client_id: GOOGLE_CLIENT_ID, callback: handleGoogleCallback, ux_mode: 'popup', }); window.google.accounts.id.renderButton( googleButtonRef.current, { theme: 'outline', size: 'large', text: 'signin_with' } ); } }, [googleLoaded]); }}} По успешна автентикација, credential-от се праќа до backend-от: {{{ const handleGoogleCallback = async (response) => { const backendResponse = await fetch( 'http://localhost:8000/api/auth/google/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ credential: response.credential }) } ); const data = await backendResponse.json(); localStorage.setItem('access_token', data.access); localStorage.setItem('user', JSON.stringify(data.user)); }; }}} **Backend имплементација - oauth_views.py** Во backend-от се верифицира Google credential преку Google tokeninfo endpoint: {{{ @api_view(['POST']) @permission_classes([AllowAny]) def google_login(request): credential = request.data.get('credential') # Verify token with Google google_url = f'https://oauth2.googleapis.com/tokeninfo?id_token={credential}' response = requests.get(google_url) google_data = response.json() # Verify audience matches our app if google_data.get('aud') != settings.GOOGLE_CLIENT_ID: return Response({'error': 'Token not for this app'}) email = google_data.get('email') first_name = google_data.get('given_name', '') last_name = google_data.get('family_name', '') }}} Доколку корисникот не постои, се креира нов: {{{ user, created = User.objects.get_or_create( email=email, defaults={ 'username': email, 'first_name': first_name, 'last_name': last_name, } ) # Generate JWT tokens refresh = RefreshToken.for_user(user) # Send login notification email send_login_notification(user, 'Google Sign-in') }}} **Email известување при најава** При секоја успешна најава преку Google, корисникот добива security email: [[Image(final9.png)]] {{{ def send_login_notification(user, login_method): subject = 'Banker - New Sign-in to Your Account' # Email содржи: # - Account email # - Sign-in Method (Google Sign-in) # - Date & Time # - Security warning доколку не е корисникот }}} == Support страница со email известување **Корисник испраќа support барање** [[Image(final10.png)]] Најавениот корисник се навигира кон Support страницата. [[Image(final11.png)]] Корисникот пополнува форма со категорија, приоритет, предмет и порака. По испраќање, корисникот добива потврда со ticket ID. [[Image(final12.png)]] **Frontend имплементација - support/page.tsx** Support формата содржи селекција за категорија и приоритет, како и полиња за предмет и порака: {{{ const categories = [ { value: 'general', label: 'General Inquiry', icon: HelpCircle }, { value: 'bug', label: 'Bug Report', icon: Bug }, { value: 'feature', label: 'Feature Request', icon: Lightbulb }, { value: 'billing', label: 'Billing Issue', icon: CreditCard }, ]; const priorities = [ { value: 'low', label: 'Low', color: 'bg-gray-100' }, { value: 'normal', label: 'Normal', color: 'bg-blue-100' }, { value: 'high', label: 'High', color: 'bg-orange-100' }, { value: 'urgent', label: 'Urgent', color: 'bg-red-100' }, ]; }}} При submit, се праќа POST барање до backend-от: {{{ const handleSubmit = async (e) => { const response = await fetch('http://localhost:8000/api/banking/support/', { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ subject, message, category, priority }), }); }; }}} **Backend имплементација - contact_views.py** Backend-от го обработува support барањето и праќа два email-и: {{{ @api_view(['POST']) @permission_classes([IsAuthenticated]) def send_support_message(request): data = request.data user = request.user subject = data.get('subject', 'Support Request') message = data.get('message') category = data.get('category', 'general') priority = data.get('priority', 'normal') # Generate unique ticket ID ticket_id = f'TKT-{user.id}-{int(time.time())}' }}} {{{ Се праќаат два email-и: [[Image(final13.png)]] [[Image(final14.png)]] # Email 1: To support team send_mail( subject=f'[Banker Support - {priority.upper()}] {subject}', message=support_email_body, from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=[settings.SUPPORT_EMAIL], ) # Email 2: Confirmation to user send_mail( subject=f'[Banker] Support Request Received - {ticket_id}', message=user_confirmation, from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=[user.email], ) }}} == Бришење на банкарска сметка **Корисник брише сметка** Најавениот корисник се навигира кон My Banks страницата. [[Image(final15.png)]] Корисникот кликнува на копчето за бришење на сметка. [[Image(final16.png)]] Системот прво проверува дали сметката има баланс - доколку има, не дозволува бришење. **Backend имплементација - views.py** {{{ @api_view(['DELETE']) @permission_classes([IsAuthenticated]) def delete_account(request, account_id): account = Account.objects.get(id=account_id, user=request.user) # Check if account has balance if account.balance > 0: return Response({ 'error': f'Cannot delete account with balance. Current: ${account.balance}' }, status=status.HTTP_400_BAD_REQUEST) # Delete all transactions for this account Transaction.objects.filter(account=account).delete() # Delete the account account_name = f'{account.bank.name} {account.account_type.title()}' account.delete() return Response({ 'message': f'Account "{account_name}" deleted successfully' }) }}} **URL конфигурација - urls.py** {{{ path('accounts//', views.delete_account, name='delete_account'), }}} == Филтрирање на аналитика по категорија **Корисник филтрира аналитика** Најавениот корисник се навигира кон Analytics страницата. [[Image(final17.png)]] Корисникот може да филтрира по временски период и по категорија. [[Image(final18.png)]] [[Image(final19.png)]] **Backend имплементација - views.py** Analytics endpoint-от прима query параметри за филтрирање: {{{ @api_view(['GET']) @permission_classes([IsAuthenticated]) def analytics_view(request): # Get query parameters date_range = request.GET.get('range', 'all') category_filter = request.GET.get('category', 'all') # Filter transactions by date transactions = Transaction.objects.filter(account__in=user_accounts) if start_date: transactions = transactions.filter(created_at__gte=start_date) # Filter by category if specified if category_filter and category_filter != 'all': transactions = transactions.filter(category__iexact=category_filter) }}} Филтрирањето се применува на сите калкулации - category breakdown, monthly trends и income vs expenses. Исто така се враќа листа на достапни категории за dropdown менито: {{{ # Get all available categories for filter dropdown all_categories = list(Transaction.objects.filter( account__in=user_accounts ).values_list('category', flat=True).distinct()) return Response({ 'categoryBreakdown': category_breakdown, 'monthlyTrends': monthly_trends, 'incomeVsExpenses': income_vs_expenses, 'availableCategories': all_categories }) }}} == Конфигурација на системот **Django Settings - settings.py** Email конфигурација за Gmail SMTP: {{{ EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST = 'smtp.gmail.com' EMAIL_PORT = 587 EMAIL_USE_TLS = True EMAIL_HOST_USER = 'tomislav.filevski@gmail.com' EMAIL_HOST_PASSWORD = 'не е внесено поради безбедносни причини' DEFAULT_FROM_EMAIL = 'Banker App ' SUPPORT_EMAIL = 'tomislav.filevski@gmail.com' }}} Google OAuth конфигурација: {{{ GOOGLE_CLIENT_ID = 'не е внесено поради безбедносни причини' GOOGLE_CLIENT_SECRET = 'не е внесено поради безбедносни причини' }}} **URL Routing - urls.py** Authentication endpoints: {{{ # authentication/urls.py urlpatterns = [ path('google/', google_login, name='google_login'), path('login/', login_view, name='login'), path('register/', register_view, name='register'), ] }}} Banking endpoints: {{{ # banking/urls.py urlpatterns = [ path('transfer/', transfer_funds, name='transfer_funds'), path('support/', send_support_message, name='support'), path('analytics/', analytics_view, name='analytics'), path('accounts//', delete_account, name='delete_account'), ] }}}