update error.service, added register send email for activation

This commit is contained in:
Chneemann 2024-08-05 00:05:26 +02:00
parent 1d1e6e58d9
commit 537519a3dd
9 changed files with 161 additions and 24 deletions

View file

@ -4,6 +4,11 @@ from rest_framework import status
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from .serializer import LoginSerializer from .serializer import LoginSerializer
from users.serializer import UserSerializer
from django.utils.html import strip_tags
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from users.models import CustomUser
class LoginView(APIView): class LoginView(APIView):
serializer_class = LoginSerializer serializer_class = LoginSerializer
@ -17,11 +22,46 @@ class LoginView(APIView):
user = authenticate(request, email=email, password=password) user = authenticate(request, email=email, password=password)
if user: if user:
if not user.is_active:
return Response({'password': 'Account is inactive, please check your mails'}, status=status.HTTP_403_FORBIDDEN)
return self._create_token_response(user) return self._create_token_response(user)
return Response({'detail': 'Unable to login with provided credentials.'}, status=status.HTTP_401_UNAUTHORIZED) return Response({'detail': 'Unable to login with provided credentials.'}, status=status.HTTP_401_UNAUTHORIZED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def _create_token_response(self, user): def _create_token_response(self, user):
token, created = Token.objects.get_or_create(user=user) token = Token.objects.get_or_create(user=user)
return Response({token.key}, status=status.HTTP_200_OK) return Response({'token': token.key}, status=status.HTTP_200_OK)
class RegisterView(APIView):
def post(self, request):
email = request.data.get('email')
password = request.data.get('password')
if CustomUser.objects.filter(email=email).exists():
return Response({'detail': 'A user with this email already exists.'}, status=status.HTTP_400_BAD_REQUEST)
serialized = UserSerializer(data=request.data)
if serialized.is_valid():
user = serialized.save()
user.is_active = False
user.set_password(password)
user.save()
self.send_email(user)
return Response(serialized.data, status=status.HTTP_201_CREATED)
else:
return Response(serialized.errors, status=status.HTTP_400_BAD_REQUEST)
def send_email(self, user):
merge_data = {
'name': user.username
}
html_body = render_to_string("mail.html", merge_data)
message = EmailMultiAlternatives(
subject='Confirm your email',
body=strip_tags(html_body), # Plain text fallback
from_email='noreply@videoflix.com',
to=['andre.kempf.dev@gmail.com']
)
message.attach_alternative(html_body, "text/html")
message.send(fail_silently=False)

View file

@ -0,0 +1,50 @@
<html>
<head>
<style>
body { font-family: Arial, sans-serif; }
.btn {
display: flex;
align-items: center;
padding: 9px 18px;
width: fit-content;
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.5);
border-radius: 24px;
color: white;
font-size: 16px;
font-weight: 500;
background-color: #2e3edf;
border: 1px solid #2e3edf;
transition: 300ms ease-in-out;
text-decoration: none;
cursor: pointer;
}
.btn:hover {
background-color: #fff;
color: #2e3edf;
border: 1px solid #2e3edf;
transition: 300ms ease-in-out;
}
.center {
display: flex;
justify-content: center;
}
img {
width: 249px;
height: 40px;
}
</style>
<body>
<div class="center"><img src="https://andre-kempf.com/videoflix-logo.png" alt=""></div>
<p>Dear {{ name }},</p>
<p>
Thank you for registering with Videoflix. To complete your registration
and verify your email address, please click the link below:
</p>
<a class="btn" href="">Activate account</a>
<p>
If you did not create an account with us, please disregard this email.
</p>
<p>Best regards,</p>
<p>Your Videoflix Team.</p>
</body>
</html>

View file

@ -10,6 +10,7 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.0/ref/settings/ https://docs.djangoproject.com/en/5.0/ref/settings/
""" """
import os
from pathlib import Path from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
@ -25,6 +26,14 @@ SECRET_KEY = 'django-insecure-*o%q)*3l+9^fs!m0z_h=z9bc)su=%)fv$@ktb#pza-pvi^z*en
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
# EMAIL
EMAIL_USE_TLS = True
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_HOST_USER = 'andre.kempf.dev@gmail.com'
EMAIL_HOST_PASSWORD = 'oyxwawshudwytgud'
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
ALLOWED_HOSTS = [] ALLOWED_HOSTS = []
CORS_ALLOWED_ORIGINS = [ CORS_ALLOWED_ORIGINS = [
@ -61,7 +70,7 @@ ROOT_URLCONF = 'videoflix.urls'
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [], 'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [

View file

@ -17,12 +17,12 @@ Including another URLconf
from django.contrib import admin from django.contrib import admin
from django.urls import path, include from django.urls import path, include
from users import views from users import views
from auth.views import LoginView from auth.views import LoginView, RegisterView
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('users/', views.user_list), path('users/', views.user_list),
path('users/<int:id>/', views.user_detail), path('users/<int:id>/', views.user_detail),
path('auth/login/', LoginView.as_view()), path('auth/login/', LoginView.as_view()),
path('auth/register/', RegisterView.as_view()),
] ]

View file

@ -5,6 +5,7 @@
#loginForm="ngForm" #loginForm="ngForm"
(ngSubmit)="onSubmit(loginForm)" (ngSubmit)="onSubmit(loginForm)"
onsubmit="return false" onsubmit="return false"
(input)="this.errorService.clearError()"
> >
<input <input
type="mail" type="mail"
@ -28,9 +29,13 @@
} @else { @if (mail.touched && authData.mail.length > 0 && } @else { @if (mail.touched && authData.mail.length > 0 &&
!isUserEmailValid(authData.mail.toLowerCase())) { !isUserEmailValid(authData.mail.toLowerCase())) {
<p>This is not a valid email format</p> <p>This is not a valid email format</p>
}} @if ((errorService.error$ | async)) { }}
<p>{{ errorService.error$ | async }}</p> <!-- Backend error handling -->
} <div *ngIf="errorService.error$ | async as errors">
<div *ngIf="errors?.['mail']">
<p>{{ errors["mail"] }}</p>
</div>
</div>
</div> </div>
<input <input
type="password" type="password"
@ -55,6 +60,12 @@
password.touched) { password.touched) {
<p>Password is too short, min 6 characters</p> <p>Password is too short, min 6 characters</p>
} } } }
<!-- Backend error handling -->
<div *ngIf="errorService.error$ | async as errors">
<div *ngIf="errors?.['password']">
<p>{{ errors["password"] }}</p>
</div>
</div>
</div> </div>
<div class="checkbox"> <div class="checkbox">
<label class="container" <label class="container"

View file

@ -1,4 +1,4 @@
import { Component } from '@angular/core'; import { Component, ViewChild } from '@angular/core';
import { BtnLargeComponent } from '../../../shared/components/btn-large/btn-large.component'; import { BtnLargeComponent } from '../../../shared/components/btn-large/btn-large.component';
import { FormsModule, NgForm } from '@angular/forms'; import { FormsModule, NgForm } from '@angular/forms';
import { Router, RouterLink } from '@angular/router'; import { Router, RouterLink } from '@angular/router';
@ -45,12 +45,21 @@ export class LoginComponent {
this.router.navigate(['/browse/']); this.router.navigate(['/browse/']);
this.errorService.clearError(); this.errorService.clearError();
} catch (error) { } catch (error) {
this.errorMsg(error);
}
}
}
errorMsg(error: any) {
if (error instanceof HttpErrorResponse) { if (error instanceof HttpErrorResponse) {
this.errorService.setError(error.error.detail); const errorTypes = ['mail', 'password'];
} else { for (const type of errorTypes) {
this.errorService.setError('An unknown error has occurred.'); if (error.error[type]) {
this.errorService.setError(type, error.error[type]);
return;
} }
} }
this.errorService.clearError();
} }
} }
} }

View file

@ -14,6 +14,7 @@
#registerForm="ngForm" #registerForm="ngForm"
(ngSubmit)="onSubmit(registerForm)" (ngSubmit)="onSubmit(registerForm)"
onsubmit="return false" onsubmit="return false"
(input)="this.errorService.clearError()"
> >
<input <input
type="mail" type="mail"

View file

@ -55,11 +55,11 @@ export class RegisterComponent {
this.registrationSuccess = true; this.registrationSuccess = true;
this.errorService.clearError(); this.errorService.clearError();
} catch (error) { } catch (error) {
if (error instanceof HttpErrorResponse) { // if (error instanceof HttpErrorResponse) {
this.errorService.setError(error.error.detail); // this.errorService.setError(error.error.detail);
} else { // } else {
this.errorService.setError('An unknown error has occurred.'); // this.errorService.setError('An unknown error has occurred.');
} // }
} }
} }
} }

View file

@ -1,15 +1,32 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
export interface ErrorDetail {
type: string;
message: string;
}
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class ErrorService { export class ErrorService {
private errorSubject = new BehaviorSubject<string | null>(null); private errorSubject = new BehaviorSubject<ErrorDetail | null>(null);
error$ = this.errorSubject.asObservable();
setError(message: string | null) { error$: Observable<{ [key: string]: string } | null> = this.errorSubject
this.errorSubject.next(message); .asObservable()
.pipe(
map((errorDetail) => {
if (errorDetail) {
return { [errorDetail.type]: errorDetail.message };
}
return null;
})
);
setError(type: string, message: string) {
const errorDetail: ErrorDetail = { type, message };
this.errorSubject.next(errorDetail);
} }
clearError() { clearError() {