update error.service, added register send email for activation
This commit is contained in:
parent
1d1e6e58d9
commit
537519a3dd
9 changed files with 161 additions and 24 deletions
|
|
@ -4,6 +4,11 @@ from rest_framework import status
|
|||
from rest_framework.authtoken.models import Token
|
||||
from django.contrib.auth import authenticate
|
||||
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):
|
||||
serializer_class = LoginSerializer
|
||||
|
|
@ -17,11 +22,46 @@ class LoginView(APIView):
|
|||
user = authenticate(request, email=email, password=password)
|
||||
|
||||
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 Response({'detail': 'Unable to login with provided credentials.'}, status=status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def _create_token_response(self, user):
|
||||
token, created = Token.objects.get_or_create(user=user)
|
||||
return Response({token.key}, status=status.HTTP_200_OK)
|
||||
token = Token.objects.get_or_create(user=user)
|
||||
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)
|
||||
50
backend/videoflix/templates/mail.html
Normal file
50
backend/videoflix/templates/mail.html
Normal 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>
|
||||
|
|
@ -10,6 +10,7 @@ For the full list of settings and their values, see
|
|||
https://docs.djangoproject.com/en/5.0/ref/settings/
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# 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!
|
||||
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 = []
|
||||
|
||||
CORS_ALLOWED_ORIGINS = [
|
||||
|
|
@ -61,7 +70,7 @@ ROOT_URLCONF = 'videoflix.urls'
|
|||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'DIRS': [os.path.join(BASE_DIR, 'templates')],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
|
|
|
|||
|
|
@ -17,12 +17,12 @@ Including another URLconf
|
|||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
from users import views
|
||||
from auth.views import LoginView
|
||||
from auth.views import LoginView, RegisterView
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('users/', views.user_list),
|
||||
path('users/<int:id>/', views.user_detail),
|
||||
path('auth/login/', LoginView.as_view()),
|
||||
|
||||
path('auth/register/', RegisterView.as_view()),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#loginForm="ngForm"
|
||||
(ngSubmit)="onSubmit(loginForm)"
|
||||
onsubmit="return false"
|
||||
(input)="this.errorService.clearError()"
|
||||
>
|
||||
<input
|
||||
type="mail"
|
||||
|
|
@ -28,9 +29,13 @@
|
|||
} @else { @if (mail.touched && authData.mail.length > 0 &&
|
||||
!isUserEmailValid(authData.mail.toLowerCase())) {
|
||||
<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>
|
||||
<input
|
||||
type="password"
|
||||
|
|
@ -55,6 +60,12 @@
|
|||
password.touched) {
|
||||
<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 class="checkbox">
|
||||
<label class="container"
|
||||
|
|
|
|||
|
|
@ -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 { FormsModule, NgForm } from '@angular/forms';
|
||||
import { Router, RouterLink } from '@angular/router';
|
||||
|
|
@ -45,12 +45,21 @@ export class LoginComponent {
|
|||
this.router.navigate(['/browse/']);
|
||||
this.errorService.clearError();
|
||||
} catch (error) {
|
||||
this.errorMsg(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errorMsg(error: any) {
|
||||
if (error instanceof HttpErrorResponse) {
|
||||
this.errorService.setError(error.error.detail);
|
||||
} else {
|
||||
this.errorService.setError('An unknown error has occurred.');
|
||||
const errorTypes = ['mail', 'password'];
|
||||
for (const type of errorTypes) {
|
||||
if (error.error[type]) {
|
||||
this.errorService.setError(type, error.error[type]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.errorService.clearError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#registerForm="ngForm"
|
||||
(ngSubmit)="onSubmit(registerForm)"
|
||||
onsubmit="return false"
|
||||
(input)="this.errorService.clearError()"
|
||||
>
|
||||
<input
|
||||
type="mail"
|
||||
|
|
|
|||
|
|
@ -55,11 +55,11 @@ export class RegisterComponent {
|
|||
this.registrationSuccess = true;
|
||||
this.errorService.clearError();
|
||||
} catch (error) {
|
||||
if (error instanceof HttpErrorResponse) {
|
||||
this.errorService.setError(error.error.detail);
|
||||
} else {
|
||||
this.errorService.setError('An unknown error has occurred.');
|
||||
}
|
||||
// if (error instanceof HttpErrorResponse) {
|
||||
// this.errorService.setError(error.error.detail);
|
||||
// } else {
|
||||
// this.errorService.setError('An unknown error has occurred.');
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,32 @@
|
|||
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({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ErrorService {
|
||||
private errorSubject = new BehaviorSubject<string | null>(null);
|
||||
error$ = this.errorSubject.asObservable();
|
||||
private errorSubject = new BehaviorSubject<ErrorDetail | null>(null);
|
||||
|
||||
setError(message: string | null) {
|
||||
this.errorSubject.next(message);
|
||||
error$: Observable<{ [key: string]: string } | null> = this.errorSubject
|
||||
.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() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue