From 83f46e6619666249ef32e4d0a41736ee1f67fa7e Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Thu, 19 Feb 2026 15:26:21 +0100 Subject: [PATCH 1/9] implemented improved account verification flow with recovery and resend --- backend/api/views.py | 35 +++++++++++++++++++++++++- frontend/src/i18n.ts | 28 +++++++++++++++++++++ frontend/src/pages/Login.tsx | 49 +++++++++++++++++++++++++++--------- 3 files changed, 99 insertions(+), 13 deletions(-) diff --git a/backend/api/views.py b/backend/api/views.py index 23fd3d8..990b095 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -323,7 +323,40 @@ class UserViewSet(viewsets.ModelViewSet): status=status.HTTP_500_INTERNAL_SERVER_ERROR ) - return Response({'status': 'User created, please verify your email'}, status=status.HTTP_201_CREATED) + return Response({'status': 'User created, please verify your email', 'username': username, 'email': email}, status=status.HTTP_201_CREATED) + + @action(detail=False, methods=['post'], permission_classes=[permissions.AllowAny]) + def resend_verification(self, request): + username = request.data.get('username') + if not username: + return Response({'error': 'Username required'}, status=status.HTTP_400_BAD_REQUEST) + + try: + user = User.objects.get(username=username, is_active=False) + except User.DoesNotExist: + return Response({'error': 'User not found or already active'}, status=status.HTTP_404_NOT_FOUND) + + import secrets + import string + code = ''.join(secrets.choice(string.digits) for _ in range(6)) + user.email_confirmation_code = code + user.save() + + try: + send_mail( + 'Verify your Holy Flavors account', + f'Hi {user.username},\n\nYour new verification code is: {code}', + settings.DEFAULT_FROM_EMAIL, + [user.email], + fail_silently=False, + ) + except Exception as e: + return Response( + {'error': f'Failed to send email: {e}'}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR + ) + + return Response({'status': 'Verification code resent!'}) @action(detail=False, methods=['post'], permission_classes=[permissions.AllowAny]) def verify_signup(self, request): diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index b71a800..e98987e 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -118,6 +118,20 @@ const resources = { "save": "Save", "cancel": "Cancel" }, + "auth": { + "loginTitle": "Login", + "signupTitle": "Signup", + "verifyTitle": "Verify Account", + "verifyDesc": "Enter the code sent to your email for {{username}}.", + "resendButton": "Resend Verification Code", + "resendSuccess": "A new verification code has been sent to your email.", + "verifySuccess": "Account verified! You can now login.", + "unverifiedError": "Account not verified. Please verify your account to login.", + "loginFailed": "Login failed. Please check your credentials.", + "signupSuccess": "Signup successful! Please enter the verification code sent to your email.", + "forgotPassword": "Forgot Password?", + "backToAuth": "Back to Signup/Login" + }, "community": { "title": "Community Activity", "subtitle": "Stay updated with your circle's latest ratings and discussions.", @@ -344,6 +358,20 @@ const resources = { "save": "Speichern", "cancel": "Abbrechen" }, + "auth": { + "loginTitle": "Anmelden", + "signupTitle": "Registrieren", + "verifyTitle": "Konto verifizieren", + "verifyDesc": "Gib den Code ein, der an deine E-Mail für {{username}} gesendet wurde.", + "resendButton": "Verifizierungscode erneut senden", + "resendSuccess": "Ein neuer Verifizierungscode wurde an deine E-Mail gesendet.", + "verifySuccess": "Konto verifiziert! Du kannst dich jetzt anmelden.", + "unverifiedError": "Konto nicht verifiziert. Bitte verifiziere dein Konto, um dich anzumelden.", + "loginFailed": "Anmeldung fehlgeschlagen. Bitte überprüfe deine Zugangsdaten.", + "signupSuccess": "Registrierung erfolgreich! Bitte gib den Verifizierungscode ein, der an deine E-Mail gesendet wurde.", + "forgotPassword": "Passwort vergessen?", + "backToAuth": "Zurück zur Anmeldung" + }, "community": { "title": "Community-Aktivität", "subtitle": "Bleib auf dem Laufenden mit den neuesten Bewertungen und Diskussionen deines Kreises.", diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx index 0ca40d5..c20a3e1 100644 --- a/frontend/src/pages/Login.tsx +++ b/frontend/src/pages/Login.tsx @@ -16,6 +16,7 @@ const Login: React.FC = () => { const [email, setEmail] = useState(''); const [verificationCode, setVerificationCode] = useState(''); const [isVerifying, setIsVerifying] = useState(false); + const [showResend, setShowResend] = useState(false); const [isResetting, setIsResetting] = useState(false); const [resetStep, setResetStep] = useState(1); // 1: email, 2: code/new pass const [message, setMessage] = useState<{type: 'success' | 'error', text: string} | null>(null); @@ -31,7 +32,13 @@ const Login: React.FC = () => { localStorage.setItem('token', res.data.token); window.location.href = '/'; } catch (err: any) { - setMessage({ type: 'error', text: 'Login failed. Please check your credentials or verify your account.' }); + const errorMsg = err.response?.data?.non_field_errors?.[0] || t('auth.loginFailed'); + if (errorMsg.toLowerCase().includes('inactive') || errorMsg.toLowerCase().includes('verify') || errorMsg.includes('disabled')) { + setMessage({ type: 'error', text: t('auth.unverifiedError') }); + setIsVerifying(true); + } else { + setMessage({ type: 'error', text: errorMsg }); + } } }; @@ -41,7 +48,7 @@ const Login: React.FC = () => { try { await api.post('users/signup/', { username, email, password }); setIsVerifying(true); - setMessage({ type: 'success', text: 'Signup successful! Please enter the verification code sent to your email.' }); + setMessage({ type: 'success', text: t('auth.signupSuccess') }); } catch (err: any) { setMessage({ type: 'error', text: err.response?.data?.error || 'Signup failed' }); } @@ -52,12 +59,24 @@ const Login: React.FC = () => { setMessage(null); try { await api.post('users/verify_signup/', { username, code: verificationCode }); - setMessage({ type: 'success', text: 'Account verified! You can now login.' }); + setMessage({ type: 'success', text: t('auth.verifySuccess') }); setIsVerifying(false); setTab(0); setPassword(''); } catch (err: any) { setMessage({ type: 'error', text: err.response?.data?.error || 'Verification failed' }); + setShowResend(true); + } + }; + + const handleResendCode = async () => { + setMessage(null); + try { + await api.post('users/resend_verification/', { username }); + setMessage({ type: 'success', text: t('auth.resendSuccess') }); + setShowResend(false); + } catch (err: any) { + setMessage({ type: 'error', text: err.response?.data?.error || 'Failed to resend code' }); } }; @@ -93,20 +112,26 @@ const Login: React.FC = () => { if (isVerifying) { return (
- Verify Account - Enter the code sent to {email} + {t('auth.verifyTitle')} + setVerificationCode(e.target.value)} + autoFocus /> - + + ); @@ -156,8 +181,8 @@ const Login: React.FC = () => { return ( <> { setTab(v); setMessage(null); }} sx={{ mb: 2 }}> - - + + {tab === 0 ? ( @@ -178,10 +203,10 @@ const Login: React.FC = () => { onChange={(e) => setPassword(e.target.value)} /> ) : ( @@ -210,7 +235,7 @@ const Login: React.FC = () => { onChange={(e) => setPassword(e.target.value)} /> )} From 2882d3956ee525ac08096c6d4c17f5d5908a46b6 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Thu, 19 Feb 2026 15:29:39 +0100 Subject: [PATCH 2/9] added gitea action for frontend build and backend check --- .gitea/workflows/ci.yml | 60 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 .gitea/workflows/ci.yml diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..f25b86f --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,60 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + frontend-build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + + - name: Install Dependencies + run: npm install + working-directory: frontend + + - name: Build Frontend + run: npm run build + working-directory: frontend + + backend-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + cache: 'pip' + cache-dependency-path: backend/requirements.txt + + - name: Install Dependencies + run: pip install -r requirements.txt + working-directory: backend + + - name: Django System Check + run: python manage.py check + working-directory: backend + env: + SECRET_KEY: "ci-test-key" + DEBUG: "false" + ALLOWED_HOSTS: "localhost" + + - name: Check Migrations + run: python manage.py makemigrations --check --dry-run + working-directory: backend + env: + SECRET_KEY: "ci-test-key" + DEBUG: "false" + ALLOWED_HOSTS: "localhost" From 2bf68674ff04f33e35ec1dcd977da80c9b5f7e6f Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Thu, 19 Feb 2026 15:38:08 +0100 Subject: [PATCH 3/9] fixed frontend build errors and improved action compatibility --- .gitea/workflows/ci.yml | 2 +- frontend/src/pages/Login.tsx | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index f25b86f..ab5f4f6 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.11' cache: 'pip' cache-dependency-path: backend/requirements.txt diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx index c20a3e1..8273dfb 100644 --- a/frontend/src/pages/Login.tsx +++ b/frontend/src/pages/Login.tsx @@ -4,11 +4,13 @@ import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import { Link } from 'react-router-dom'; import api from '../api'; import { useTitle } from '../hooks/useTitle'; +import { useTranslation } from 'react-i18next'; interface LoginProps { } const Login: React.FC = () => { + const { t } = useTranslation(); useTitle('Login'); const [tab, setTab] = useState(0); const [username, setUsername] = useState(''); @@ -126,7 +128,13 @@ const Login: React.FC = () => { Verify - From 1f8823fbda05545f61b92deef512480f126cbde6 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Thu, 19 Feb 2026 15:39:19 +0100 Subject: [PATCH 4/9] implemented one-click email verification flow --- backend/api/views.py | 8 ++- backend/holy_backend/settings.py | 2 + frontend/src/App.tsx | 2 + frontend/src/pages/VerifyEmail.tsx | 99 ++++++++++++++++++++++++++++++ 4 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 frontend/src/pages/VerifyEmail.tsx diff --git a/backend/api/views.py b/backend/api/views.py index 990b095..78e4af5 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -307,10 +307,12 @@ class UserViewSet(viewsets.ModelViewSet): email_confirmation_code=code ) + verification_link = f"{settings.FRONTEND_URL}/verify-email?username={username}&code={code}" + try: send_mail( 'Verify your Holy Flavors account', - f'Hi {username},\n\nYour verification code is: {code}', + f'Hi {username},\n\nYour verification code is: {code}\n\nAlternatively, you can complete your registration by clicking the link below:\n{verification_link}\n\nWelcome to the archive!', settings.DEFAULT_FROM_EMAIL, [email], fail_silently=False, @@ -342,10 +344,12 @@ class UserViewSet(viewsets.ModelViewSet): user.email_confirmation_code = code user.save() + verification_link = f"{settings.FRONTEND_URL}/verify-email?username={user.username}&code={code}" + try: send_mail( 'Verify your Holy Flavors account', - f'Hi {user.username},\n\nYour new verification code is: {code}', + f'Hi {user.username},\n\nYour new verification code is: {code}\n\nAlternatively, you can complete your registration by clicking the link below:\n{verification_link}', settings.DEFAULT_FROM_EMAIL, [user.email], fail_silently=False, diff --git a/backend/holy_backend/settings.py b/backend/holy_backend/settings.py index f5168d5..7ec54cf 100644 --- a/backend/holy_backend/settings.py +++ b/backend/holy_backend/settings.py @@ -191,6 +191,8 @@ REST_FRAMEWORK = { CORS_ALLOWED_ORIGINS_STRING = os.environ.get('CORS_ALLOWED_ORIGINS', 'https://holy.narl.io,http://localhost:5173,http://127.0.0.1:5173') CORS_ALLOWED_ORIGINS = [origin.strip() for origin in CORS_ALLOWED_ORIGINS_STRING.split(',')] +FRONTEND_URL = os.environ.get('FRONTEND_URL', 'https://holy.narl.io').rstrip('/') + import os # ... (near top of file) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 72842b4..1f511a9 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -55,6 +55,7 @@ const Privacy = lazy(() => import('./pages/Privacy')); const Login = lazy(() => import('./pages/Login')); const Settings = lazy(() => import('./pages/Settings')); const Support = lazy(() => import('./pages/Support')); +const VerifyEmail = lazy(() => import('./pages/VerifyEmail')); const AdminDashboard = lazy(() => import('./pages/AdminDashboard')); const AdminUserDetail = lazy(() => import('./pages/AdminUserDetail')); const AdminRatingDetail = lazy(() => import('./pages/AdminRatingDetail')); @@ -696,6 +697,7 @@ const App: React.FC = () => { } /> } /> } /> + } /> } /> } /> } /> diff --git a/frontend/src/pages/VerifyEmail.tsx b/frontend/src/pages/VerifyEmail.tsx new file mode 100644 index 0000000..d61b90d --- /dev/null +++ b/frontend/src/pages/VerifyEmail.tsx @@ -0,0 +1,99 @@ +import React, { useEffect, useState } from 'react'; +import { useLocation, useNavigate, Link } from 'react-router-dom'; +import { Container, Paper, Typography, Box, CircularProgress, Button, Alert } from '@mui/material'; +import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; +import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; +import { useTranslation } from 'react-i18next'; +import api from '../api'; +import { useTitle } from '../hooks/useTitle'; + +const VerifyEmail: React.FC = () => { + const { t } = useTranslation(); + const location = useLocation(); + const navigate = useNavigate(); + const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading'); + const [errorMsg, setErrorMsg] = useState(''); + + useTitle(t('auth.verifyTitle')); + + useEffect(() => { + const query = new URLSearchParams(location.search); + const username = query.get('username'); + const code = query.get('code'); + + if (!username || !code) { + setStatus('error'); + setErrorMsg('Invalid verification link.'); + return; + } + + const verify = async () => { + try { + await api.post('users/verify_signup/', { username, code }); + setStatus('success'); + // Optional: Auto redirect after 3 seconds + setTimeout(() => { + navigate('/login'); + }, 3000); + } catch (err: any) { + setStatus('error'); + setErrorMsg(err.response?.data?.error || 'Verification failed.'); + } + }; + + verify(); + }, [location, navigate]); + + return ( + + + {status === 'loading' && ( + + + + Verifying your account... + + + Please wait while we confirm your email. + + + )} + + {status === 'success' && ( + + + + {t('auth.verifySuccess')} + + + You will be redirected to the login page shortly. + + + + )} + + {status === 'error' && ( + + + + Verification Failed + + + {errorMsg} + + + The link might be expired or already used. + + + + )} + + + ); +}; + +export default VerifyEmail; From 46be2f3d1bf275a649a112ef4d3a468b9a06a39b Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Thu, 19 Feb 2026 15:44:28 +0100 Subject: [PATCH 5/9] updated gitea action --- .gitea/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index ab5f4f6..c00d61b 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -10,10 +10,10 @@ jobs: frontend-build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: '20' cache: 'npm' @@ -30,12 +30,12 @@ jobs: backend-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: - python-version: '3.11' + python-version: '3.14' cache: 'pip' cache-dependency-path: backend/requirements.txt From e86baaddb75071ad0f55bea0a2d105374a773d0d Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Thu, 19 Feb 2026 15:47:34 +0100 Subject: [PATCH 6/9] fixed ci --- .gitea/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index c00d61b..7811380 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v6 with: - python-version: '3.14' + python-version: '3.14.3' cache: 'pip' cache-dependency-path: backend/requirements.txt From 68a9e71d040089cb5cbd02b01e030e7e620eeffa Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Thu, 19 Feb 2026 15:47:54 +0100 Subject: [PATCH 7/9] implemented follow notifications with profile redirection --- backend/api/models.py | 1 + backend/api/views.py | 11 ++++++- frontend/src/App.tsx | 4 +++ frontend/src/i18n.ts | 2 ++ frontend/src/pages/CommunityFeed.tsx | 47 ++++++++++++++++++---------- 5 files changed, 48 insertions(+), 17 deletions(-) diff --git a/backend/api/models.py b/backend/api/models.py index a8b5559..9c8b5ac 100644 --- a/backend/api/models.py +++ b/backend/api/models.py @@ -128,6 +128,7 @@ class Notification(models.Model): TYPE_CHOICES = [ ('reply', 'Reply'), ('mention', 'Mention'), + ('follow', 'Follow'), ('ticket_new', 'New Ticket'), ('ticket_reply', 'Ticket Reply'), ('profile_comment', 'Profile Comment'), diff --git a/backend/api/views.py b/backend/api/views.py index 78e4af5..b3598e6 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -553,7 +553,16 @@ class UserViewSet(viewsets.ModelViewSet): user_to_follow = self.get_object() if user_to_follow == request.user: return Response({'error': 'You cannot follow yourself'}, status=status.HTTP_400_BAD_REQUEST) - request.user.following.add(user_to_follow) + + if not request.user.following.filter(pk=user_to_follow.pk).exists(): + request.user.following.add(user_to_follow) + # Notify the user being followed + Notification.objects.create( + recipient=user_to_follow, + actor=request.user, + notification_type='follow' + ) + return Response({'status': 'followed'}) @action(detail=True, methods=['post'], permission_classes=[permissions.IsAuthenticated]) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 1f511a9..511c015 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -512,6 +512,9 @@ const App: React.FC = () => { } else if (n.notification_type === 'profile_comment') { handleNotificationClick(n); navigate(`/profile/${user?.username}`); + } else if (n.notification_type === 'follow') { + handleNotificationClick(n); + navigate(`/profile/${n.actor_username}`); } else { handleNotificationClick(n); } @@ -541,6 +544,7 @@ const App: React.FC = () => { {n.actor_username} { n.notification_type === 'reply' ? `replied to your review on ${n.flavor_name}` : n.notification_type === 'mention' ? `mentioned you on ${n.flavor_name}` : + n.notification_type === 'follow' ? t('community.notifFollow') : n.notification_type === 'profile_comment' ? `left a message on your guestbook` : n.notification_type === 'ticket_new' ? t('community.notifTicketNew') : user?.is_superuser ? t('community.notifTicketReplyAdmin') : t('community.notifTicketReply') diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index e98987e..3e9f089 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -141,6 +141,7 @@ const resources = { "notifications": "Recent Notifications", "notifReply": "replied", "notifMention": "mentioned you", + "notifFollow": "started following you", "notifTicketNew": "opened a new support ticket", "notifTicketReply": "replied to your support ticket", "notifTicketReplyAdmin": "replied to a support ticket", @@ -381,6 +382,7 @@ const resources = { "notifications": "Neueste Benachrichtigungen", "notifReply": "hat geantwortet", "notifMention": "hat dich erwähnt", + "notifFollow": "folgt dir jetzt", "notifTicketNew": "hat ein neues Support-Ticket eröffnet", "notifTicketReply": "hat auf dein Support-Ticket geantwortet", "notifTicketReplyAdmin": "hat auf ein Support-Ticket geantwortet", diff --git a/frontend/src/pages/CommunityFeed.tsx b/frontend/src/pages/CommunityFeed.tsx index eeee4d1..a8baec1 100644 --- a/frontend/src/pages/CommunityFeed.tsx +++ b/frontend/src/pages/CommunityFeed.tsx @@ -385,22 +385,37 @@ const CommunityFeed: React.FC = ({ adminMode }) => { {notifications.length === 0 ? ( {t('community.noNotifications')} ) : ( - notifications.map(n => ( - n.flavor_id && navigate(`/flavor/${n.flavor_id}`)} sx={{ py: 1.5 }}> - - {n.actor_username.charAt(0)} - - - {n.actor_username} {n.notification_type === 'reply' ? t('community.notifReply') : t('community.notifMention')} -
- } - secondary={formatTimestamp(n.created_at)} - secondaryTypographyProps={{ sx: { fontSize: '0.7rem' } }} - /> - - )) + notifications.map(n => { + const handleClick = () => { + if (n.notification_type === 'follow') { + navigate(`/profile/${n.actor_username}`); + } else if (n.flavor_id) { + navigate(`/flavor/${n.flavor_id}`); + } + }; + + return ( + + + {n.actor_username.charAt(0)} + + + {n.actor_username} { + n.notification_type === 'reply' ? t('community.notifReply') : + n.notification_type === 'mention' ? t('community.notifMention') : + n.notification_type === 'follow' ? t('community.notifFollow') : + n.notification_type + } + + } + secondary={formatTimestamp(n.created_at)} + secondaryTypographyProps={{ sx: { fontSize: '0.7rem' } }} + /> + + ); + }) )} From cd5064902a7ee1898f58089f36b923b7d321e005 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Thu, 19 Feb 2026 15:53:40 +0100 Subject: [PATCH 8/9] fixed ts error + ci --- .gitea/workflows/ci.yml | 2 ++ frontend/src/App.tsx | 2 +- frontend/src/pages/CommunityFeed.tsx | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 7811380..27d6578 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -29,6 +29,8 @@ jobs: backend-check: runs-on: ubuntu-latest + container: + image: catthehacker/ubuntu:act-latest steps: - uses: actions/checkout@v6 diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 511c015..d8a3d81 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -74,7 +74,7 @@ interface Notification { id: number; actor_username: string; actor_avatar: string | null; - notification_type: 'reply' | 'mention' | 'ticket_new' | 'ticket_reply' | 'profile_comment'; + notification_type: 'reply' | 'mention' | 'follow' | 'ticket_new' | 'ticket_reply' | 'profile_comment'; rating: number | null; reply: number | null; is_read: boolean; diff --git a/frontend/src/pages/CommunityFeed.tsx b/frontend/src/pages/CommunityFeed.tsx index a8baec1..dc7a515 100644 --- a/frontend/src/pages/CommunityFeed.tsx +++ b/frontend/src/pages/CommunityFeed.tsx @@ -70,7 +70,7 @@ interface Notification { id: number; actor_username: string; actor_avatar: string | null; - notification_type: 'reply' | 'mention'; + notification_type: 'reply' | 'mention' | 'follow' | 'ticket_new' | 'ticket_reply' | 'profile_comment'; rating: number | null; reply: number | null; is_read: boolean; From 27745160512021d934e087cd6f0c92045369b68d Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Thu, 19 Feb 2026 15:56:49 +0100 Subject: [PATCH 9/9] makemigrations --- ...027_alter_notification_notification_type.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 backend/api/migrations/0027_alter_notification_notification_type.py diff --git a/backend/api/migrations/0027_alter_notification_notification_type.py b/backend/api/migrations/0027_alter_notification_notification_type.py new file mode 100644 index 0000000..7208745 --- /dev/null +++ b/backend/api/migrations/0027_alter_notification_notification_type.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0.2 on 2026-02-19 14:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0026_banner_is_enabled_user_selected_banner_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='notification', + name='notification_type', + field=models.CharField(choices=[('reply', 'Reply'), ('mention', 'Mention'), ('follow', 'Follow'), ('ticket_new', 'New Ticket'), ('ticket_reply', 'Ticket Reply'), ('profile_comment', 'Profile Comment')], max_length=20), + ), + ]