wiki:UseCaseImplementationsFinal

Version 2 (modified by 231104, 5 days ago) ( diff )

--

Финална имплементација на случаи на употреба

Трансфер на средства со email известување

User-to-User трансфер

Најавен корисник се навигира кон страницата за трансфер на средства и внесува email адреса на примачот.

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 трансфер (меѓу свои сметки)

Корисникот може да пренесе средства меѓу своите сметки.

При интерен трансфер, корисникот добива email со детали за двете сметки:

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

Ненајавен корисник

Ненајавениот корисник се навигира кон страницата за најава.

Корисникот го кликнува копчето за најава преку Google профил и го селектира посакуваниот профил.

По успешна најава, корисникот е пренасочен кон главната страница.

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:

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 барање

Најавениот корисник се навигира кон Support страницата.

Корисникот пополнува форма со категорија, приоритет, предмет и порака.

По испраќање, корисникот добива потврда со ticket ID.

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-и:

    # 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 страницата.

Корисникот кликнува на копчето за бришење на сметка.

Системот прво проверува дали сметката има баланс - доколку има, не дозволува бришење.

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/<uuid:account_id>/', views.delete_account, name='delete_account'),

Филтрирање на аналитика по категорија

Корисник филтрира аналитика

Најавениот корисник се навигира кон Analytics страницата.

Корисникот може да филтрира по временски период и по категорија.

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 <tomislav.filevski@gmail.com>'
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/<uuid:account_id>/', delete_account, name='delete_account'),
]

Attachments (19)

Download all attachments as: .zip

Note: See TracWiki for help on using the wiki.