Lect. univ. dr. MARIETA GÂTA [611705]
UNIVERSITATEA TEHNICĂ DIN CLUJ‐NAPOCA
CENTRUL UNIVERSITAR NORD DIN BAIA MARE
FACULTATEA DE ȘTIINȚE
SPECIALIZAREA INFORMATIC Ă
APLICA ȚIE WEB DE
PUBLICITATE
LUCRARE DE LICENȚĂ
Absolvent: [anonimizat]:
Lect. univ. dr. MARIETA GÂTA
Baia Mare
2018
APLICA ȚIE WEB DE PUBLICITATE
2
APLICA ȚIE WEB DE PUBLICITATE
3
Cuprins
Introducere …………………………………………………………………………………………………….. 5
Tehnologii si limbaje folosite ……………………………………………………………………………… 6
1.1. Angular ………………………………………………………………………………………………… 6
1.2. Ngrx/Store ……………………………………………………………………………………………. 9
1.3. TypeScript …………………………………………………………………………………………… 11
1.4. Pug …………………………………………………………………………………………………….. 12
1.5. SCSS …………………………………………………………………………………………………… 13
1.6. NodeJS ……………………………………………………………………………………………….. 14
1.7. MongoDB ……………………………………………………………………………………………. 15
Descrierea Aplicației ………………………………………………………………………………………. 16
2.1. Prezentare …………………………………………………………………………………………… 16
2.1.1. Client ……………………………………………………………………………………………. 16
2.1.2. Server …………………………………………………………………………………………… 17
2.1.3. Baza de date ………………………………………………………………………………….. 18
2.2. Arhitectura ………………………………………………………………………………………….. 18
2.2.1. Client ……………………………………………………………………………………………. 18
2.2.2. Server …………………………………………………………………………………………… 20
2.2.3. Baza de date ………………………………………………………………………………….. 27
2.2.3.1. User …………………………………………………………………………………………… 28
2.2.3.2. Anunt ………………………………………………………………………………………… 30
2.2.3.3. Atribut ……………………………………………………………………………………….. 31
2.2.3.4. Atribut_detaliu ……………………………………………………………………………. 33
2.2.3.5. Categorie ……………………………………………………………………………………. 34
2.2.3.6. Subcategorie ………………………………………………………………………………. 34
2.2.3.7. Convo ………………………………………………………………………………………… 35
2.2.3.8. Message …………………………………………………………………………………….. 36
2.2.3.9. Imagine ……………………………………………………………………………………… 37
2.2.3.10 Judet ………………………………………………………………………………………… 38
2.2.3.11 Localitate …………………………………………………………………………………… 38
APLICA ȚIE WEB DE PUBLICITATE
4
2.2.3.12 Role ………………………………………………………………………………………….. 39
2.2.3.13 Valuta ……………………………………………………………………………………….. 40
Aplicația ……………………………………………………………………………………………………….. 42
3.1. Autentificare, Autorizare și Înregistrare …………………………………………………… 42
3.2. Panoul de administrator ……………………………………………………………………….. 45
3.2.1 Gestionarea resurselor ………………………………………………………………………… 45
3.3. Clientul ……………………………………………………………………………………………….. 51
3.3.1. Acasă ……………………………………………………………………………………………….. 52
3.3.2. Anunț Nou ………………………………………………………………………………………… 54
3.3.3. Anunțuri …………………………………………………………………………………………… 58
3.3.4. Detalii anunț …………………………………………………………………………………….. 62
3.3.5. Mesageria Instantă ……………………………………………………………………………. 65
3.3.6. Anunțurile Tale ………………………………………………………………………………….. 67
3.3.7. Setări ……………………………………………………………………………………………….. 68
Concluzii ………………………………………………………………………………………………………. 69
4.1 Dezvoltări în viitor …………………………………………………………………………………. 69
Bibliografie ……………………………………………………………………………………………………. 70
APLICA ȚIE WEB DE PUBLICITATE
5
Introducere
În ultimul deceniu mica publicitate, ca majoritatea formelor de media
scrisa, s‐a extins pe internet. La început doar ziarele online au oferit acest
serviciu însă în curând au apărut noi grupuri pe piața, realizând potențialul
acestei nișe.
Aplicațiile online de mica publicitate nu pretind un „cost pe linie” pentru
anunțurile publicate, utilizatorii având posibilitatea de a își descrie produsele
date spre vânzare în detaliu. De asemenea platforma pune la dispoziția
utilizatorilor anunțuri de pe o arie geografica largă, fiind vorba adesea de o țara
întreaga. Un alt avantaj major este faptul ca utilizatorul poate filtra foarte ușor
anunțurile de pe platform ă după anumite caracteristici/atribute de care este
interesat. El are posibilitatea de a introduce termeni de căutare care se pot regăsi
în titlul sau descrierea unui anunț, acest lucru fiind imposibil când vine vorba
de mica publicitate regăsita în ziarele fizice.
Aplicația creată de mine are ca scop implementarea acestor avantaje într‐
un mod care duce la o experien ță cât mai placută în timpul utilizării și aduce un
plus de funcționalitate, care lipsește din platformele active în domeniu.
APLICA ȚIE WEB DE PUBLICITATE
6
Capitolul 1
Tehnologii si limbaje folosite
1.1. Angular
Angular este o platforma destinat dezvoltării aplicațiilor web de tip
,,single page”, pe partea de client. Este dezvoltat ă de echipa Angular de la
Google împreun ă cu o comunitate de dezvoltatori independen ți și este o
rescriere completă a primei versiuni, AngularJS .
Folosește ca limbaj de dezvoltare o versiune proprie a limbajul
TypeScript, numită “TypeScript with Decorators”. Angular implementeaz ă
funcționalități de bază și opționale sub forma unui set de librării TypeScript ce
se pot importa în aplicații.
La baza unei aplicații Angular stă modulul. Un modul declară un context,
folosit în faza de compilare, pentru un set de componente dedicate unui
domeniu al aplicației, unui „workflow” sau unui set de capabilit ăți strâns
legate. Un modul asociază componentele care fac parte din el cu alte elemente,
precum serviciile, pentru a forma unități funcționale. O aplicație Angular este
definită de un set de module. Fiecare aplicație Angular are cel puțin un modul
de baza, numit AppModule, pentru a oferi un punct de pornire, și de obicei
importă mai multe module pentru diferitele funcționalități pe care le oferă
aplicația. Similar cu modulele de JavaScript, modulele de Angular pot exporta
APLICA ȚIE WEB DE PUBLICITATE
7
funcționalitate iar aceasta poate fi importat ă în alte module, stând la dispoziția
componentelor care alcătuiesc modulul respectiv. Organizarea codului în
module distincte și independente, bazate pe funcționalitate, ajută la dezvoltarea
aplicațiilor complexe și mentenan ța acestora. Această separare a funcționalității
ne permite
să folosim “lazy‐loading” pentru a minimiza cantitatea de cod care
se încarcă în memorie în momentul pornirii aplicației. Elementele care nu țin de
secțiunea aplicației care se acceseaz ă fiind ignorate.
Componentele definesc view‐uri, reprezentând elementele pe care le
vede utilizatorul. Angular poate manipula aceste elementele în funcție de logica
din cod și datele furnizate. Acestea folosesc servicii, care pun la dispoziție
funcționalități bine definite, care nu țin însă de componente. Serviciile se pot
injecta în componente ca dependin țe, ducând la modularitatea și eficiența
codului. Fiecare aplicație are cel puțin o component ă, o component ă de bază,
care realizeaz ă conexiunea dintre ierarhia de componente Angular și DOM‐ul
paginii. Fiecare component ă definește o clasă de TypeScript, care conține date
și logică, și este asociată cu un template de HTML care reprezint ă elementele
propriu‐zise care vor fi afișate utilizatorului. Clasa este decorată cu decoratorul
@Component care îl identifica și furnizeaz ă metadatele necesare în faza de
compilare.
Template ‐urile combină sintaxa HTML cu sintaxa Angular, care poate
manipula elementele HTML înainte ca acestea să fie afișate. Directivele pun la
dispoziție logica programabil ă iar sintaxa de data binding conecteaz ă datele
aplicației și DOM‐ul. „Event binding” permite aplicației sa reacționeze la
inputul utilizatorului, actualizând datele aplicației. „Property binding” ne
permite să interpolăm valori din datele componentelor direct în HTML. Înainte
APLICA ȚIE WEB DE PUBLICITATE
8
ca un template să fie afișat utilizatorului, Angular evalueaz ă directivele și
proceseaz ă sintaxa de binding pentru a modifica elementele corespunz ătoare
din DOM, în concordan ță cu logica și datele aplicației. De asemenea, Angular
ne pune la dispoziție „two‐way data binding”, ceea ce înseamnă că modific
ările
făcute la nivelul DOM‐ului, precum decizii ale utilizatorului, sunt reflectate în
timp real în datele aplicației. Template ‐urile pot folosi „pipes” care transforma
valorile menite afișării. Un pipe se folosește, de exemplu, în cazul în care dorim
să afișăm o dată formatat ă în funcție de locația utilizatorului fără să modificam
data în mod direct, doar în afișaj. Angular vine cu un set de pipes predefinite
însă putem defini cu ușurința un pipe nou.
Pentru logica care nu este asociată cu un view anume, și pe care dorim să
o folosim în mai multe componente sau servicii se folosește o clasă de tip service.
O clasă de tip service este decorată cu decoratorul @Injectable dacă se dorește
injectarea dependin țelor în aceasta. În lipsa decoratorului injectarea
dependin țelor în serviciu nu este posibilă și va genera o eroare in momentul
rulării aplicației. „Dependency injection” duce la minimizarea codul din
componente și previne repetarea redundant ă a secvențelor de cod similare,
delegând serviciilor acțiuni folosite des precum cereri HTTP către server,
validarea inputului de la utilizator, tratarea erorilor etc.
Modulul Router ne pune la dispoziție un serviciu care ne permite să
definim rutele de navigare pentru diferitele stări ale aplicației. Acest Router
creează legătura dintre o cale URL și view‐urile din aplicație. Pentru a definii
reguli de navigare, asociem căi de navigare cu componentele din aplicație.
Odată definite, putem aplica logica pentru a afișa/ascunde aceste rute sau
pentru restricționarea accesul la ele. Când un utilizator interacționează cu
APLICA ȚIE WEB DE PUBLICITATE
9
pagina într‐un mod care ar duce la deschiderea unei pagini noi în browser,
precum click‐ul pe un link, router‐ul intercepteaz ă comportamentul standard al
browserului și, în schimb, afișează sau ascunde view‐uri. Dacă router‐ul
stabilește că starea curenta al aplicației necesită o
funcționalitate anume, iar
modulul care definește funcționalitatea respectiv ă nu a fost încărcat, router‐ul
poate încărca modulul respectiv. Acest proces se numește „lazy‐loading”.
Routerul introduce înregistrări în istoricul de navigare a browserului, astfel
încât butoanele de înapoi și înainte să funcționeze in mod obișnuit.
1.2. Ngrx/Store
O problemă majora cu care se confrunt ă aplicațiile „single page” este
faptul că atunci când acestea devin foarte complexe gestiunea datelor devine
foarte dificilă. Când vine vorba de ecosistemul Angular, această dificultate se
trage din faptul că Angular ne permite să facem „two‐way data binding”,
modelul din controller fiind strâns legat de modelul din view și vice‐versa
Această legătură înseamnă că orice modificare efectuata de către utilizator
asupra datelor din view va duce automat la modificarea datelor
corespunz ătoare din controller. Odată cu creșterea complexit ății datelor aceste
modificări pot avea efecte secundare nedorite, o modificare având potențialul
să provoace, pe cont propriu, o altă modificare.
Una dintre soluțiile pentru această problemă este Ngrx/Store. Store
funcționează pe baza unei „singure surse de adevăr”, un loc central și unic unde
sunt stocate datele aplicației. Orice modificare a datelor se face doar prin
APLICA ȚIE WEB DE PUBLICITATE
10
intermediul unor acțiuni bine definite iar datele stocate în Store respectă
conceptul de imutabilitate, modificarea lor în mod direct fiind imposibil ă. Ca
urmare, orice modificare se poate supraveghea cu ușurința în faza de
dezvoltare.
Figura 1.2.1 Diferența dintre „two‐way data binding” și Store
O altă problemă pe care o rezolvă Store este cea de comunicare între
componente. Dacă ar fi să ne folosim de funcționalitatea de bază pe care ne‐o
pune la dispoziție Angular, pentru a realiza comunicarea dintre doua
componente este necesară o legătură „event emitter ‐ event listener”. Această
legătură este ușor de realizat atunci când avem o pereche de componente cu
relația ,,părinte‐copil,, dar devine foarte dificilă atunci când componentele sunt
separate din punct de vedere ierarhic. Store rezolvă această problemă prin
faptul că ne pune la dispoziție un serviciu injectabil. Acest serviciu este injectabil
în orice component ă din cadrul aplicației iar prin intermediul său avem acces la
APLICA ȚIE WEB DE PUBLICITATE
11
un „Observable” care emite noua stare a aplicației, stare stocată în Store, de
fiecare dată când se fac modificări asupra acesteia prin intermediul acțiunilor.
Acțiunile care modifică starea din Store pot fi lansate din orice component ă iar
toate componentele care „ascultă” pentru modificări
asupra stării vor primii
automat noua stare, indiferent de relația dintre componenta care a efectuat
acțiunea si cea care ascultă.
1.3. TypeScript
TypeScript este un limbaj de programare a cărui scop este extinderea
capabilit ăților limbajului Java Scrip. TypeScript oferă typing static clase și
interfețe. Cel mai mare beneficiu, atunci când vine vorba de TypeScript, este
faptul că acesta verifica corectitudinea codului în timpul scrierii acestuia. Acest
lucru face ca
erorile cel mai des întâlnite, precum greșelile de sintaxa, utilizarea
unei variabile nedeclarate sau inaccesibile etc să prevină compilarea codului și
să genereze mesaje de eroare. În cazul limbajului JavaScript, aceste erori nu vor
fi depistate decât în faza de rulare al aplicației. Un alt avantaj major al limbajului
este faptul că este compatibil cu marea majoritate a IDE‐urilor, oferindu ‐ne
„intellisense”, ceva ce lipsește în totalitate când vine vorba de JavaScript.
O capabilitate majoră a limbajului este cea de typing static. Aceasta ne
permite să definim explicit tipul unei variabile și genereaz ă erori în cazul în care
încercam să sa stocam într‐o variabilă o valoare incompatibila sau în cazul în
care efectuăm un apel de funcție cu parametri de tip greșit. Astfel, se elimină
APLICA ȚIE WEB DE PUBLICITATE
12
situațiile în care se realizeaz ă un apel de funcție cu un parametru de tip
neașteptat.
TypeScript nu poate fi rulat direct de către browser, fiind necesară
compilarea acestuia în JavaScript.
TypeScript
class Greeter {
msg: string;
constructor (message: string) {
this.msg = message ;
}
greet() {
return `Hello ${ this.msg};
}
}
JavaScript
var Greeter = (function () {
function Greeter(message) {
this. msg = message ;
}
Greeter.prototype .greet = function () {
return `Hello ${ this.msg};
};
return Greeter ;
})();
1.4. Pug
Pug este un „templating engine” care ne pune la dispoziție o sintaxă
scurta, simplă și curată care face scrierea de cod HTML mai rapidă. Pug
folosește spațiul alb pentru a stabili ierarhia elementelor HTML.
APLICA ȚIE WEB DE PUBLICITATE
13
Pug:
doctype html
html(lang="en")
head
title Pug
body
h1 Pug
#container.col
p.large-text Paragraf
HTML
<!DOCTYPE html >
<html lang="en">
<head>
<title>Pug</title>
</head>
<body>
<h1>Pug</h1>
<div class="col" id="container ">
<p class="l-text”> Paragraf </p>
</div>
</body>
</html>
1.5. SCSS
SCSS este un limbaj de „style sheet” care extinde limbajul CSS și ne pune
la dispoziție o sintaxă îmbunătățită, posibilitatea de a declara variabile,
posibilitatea de a folosi logică „if‐else” și posibilitatea de a folosi funcții.
SCSS
$color: #ffffff;
$width: 800px;
body{
width: $width;
color: $color;
.content{
width: $width; CSS
body{
width: 800px;
color: #ffffff;
}
body .content{
width:750px; background :#ffffff;
}
APLICA ȚIE WEB DE PUBLICITATE
14
background :$color;
&:hover{
background : red;
}
}
}
body .content:hover{
background : red;
}
1.6. NodeJS
NodeJS este un „runtime” de JavaScript construit peste Chrome V8 Java
Script Engine. Acesta este combinat cu un „event loop” ne blocator care rulează
pe un singur fir de execuție și un API de Input/Output.
În cazul serverelor tradiționale, pentru fiecare cerere primita de la client
sau pentru fiecare conexiune noua se creează un fir de execuție nou care preia
cererea, o proceseaz ă, și trimite înapoi un răspuns. Însă, acest mod de tratare a
cererilor nu este cel mai eficient deoarece prezența unui număr foarte mare de
fire de execuție intr‐un sistem încărcat duce la o încetinire considerabil ă a
acestuia, el fiind nevoit să gestioneze programarea firelor de execuție și
schimbarea contextului.
În schimb, NodeJS rulează pe un singur fir de execuție iar fiecare cerere
primiă de la client va duce la execuția unei funcții „callback” de JavaScript.
Funcția callback poate trata cererea într‐un mod care nu blocheaz ă firul de
execuție iar, daca este necesar, Node poate să creeze fire de execuție noi pentru
executarea operațiilor intense din punct de vedere a puterii de procesare
necesare. Ca urmare, un server Node folosește mult mai puțină memorie pentru
APLICA ȚIE WEB DE PUBLICITATE
15
gestionarea cererilor în compara ție cu cele mai populare arhitecturi care se
bazează în totalitate pe crearea de fire de execuție noi, precum Apache HTTP
Server, IIS, ASP.NET si Ruby on Rails.
Figura următoare reprezint ă codul necesar pentru a rula un server HTTP,
folosind sintaxa ,,ES6
arrow function” (funcții anonime Lambda) pentru
funcțiile „callback”.
const http = require('http');
const port = 3000;
http.createServer ().listen(port, ()=>{
console.log(`Serverul ruleaza pe adresa: http://localhost: ${port}`);
});
1.7. MongoDB
MongoDB este o bază de date non‐relațională care stocheaz ă datele în
documente cu format BSON (Binary JSON). Aceste documente sunt stocate în
colecții, care le dau context. Interogarea datelor se face folosind MongoDB query
language. Câmpurile pot varia între documentele unei colecții iar declararea în
prealabil a structurii unei colecții nu este necesară. Adăugarea proprietăților noi
pentru o înregistrare din colecție se face fără modificarea documentelor
existente în colecția respectiv ă.
Datele din documente sunt reprezentate in format JSON ceea ce face ca
stocarea structurilor complexe, precum array‐uri, să fie ușoară.
APLICA ȚIE WEB DE PUBLICITATE
16
Capitolul 2
Descrierea Aplica ției
2.1. Prezentare
2.1.1. Client
Pe partea de client aplicația este scrisă folosind platforma Angular,
versiunea 5 și este un website de tip „single page”. Deoarece este o aplicație de
tip ,,single‐page,, aceasta se ocupa în întregime de partea de navigare. Clientul
nu trimite o cerere în mod tradițional pentru celelalte pagini care alcătuiesc
aplicația iar experien ța utilizatorului în timpul utilizării este una mult mai
plăcută, deoarece browser‐ul nu încarcă vizibil pagina de fiecare dată când se
navigheaz ă. De asemenea, modul în care Angular manipuleaz ă DOM‐ul
permite modificarea dinamică a datelor din afișaj, fără să fie nevoie reîncărcarea
paginii.
Pentru gestionarea datelor pe partea de client aplicația folosește
Ngrx/Store. Am ales să folosesc Ngrx/Store pentru a îmbunătăți performan ța
aplicației și pentru a ușura transmiterea datelor între componente. Datele
folosite în mai multe componente sunt aduse o singură data din server și stocate
APLICA ȚIE WEB DE PUBLICITATE
17
in Store. Eventualele modificări asupra acestora sunt persistate în baza de date
iar afișajul este actualizat pentru a reflecta noua stare.
Pentru partea de ,,templating,, folosesc Pug, un templating engine, pentru
că am componente cu template ‐uri lungi iar Pug reduce cantitatea de cod.
Deoarece configura ț
ia de Webpack cu care vine Angular nu conține regulile
necesare compilării template ‐ului Pug în HTML folosesc un pachet numit „pug‐
ng‐html‐loader”. Acest pachet este inserat în configura ția Webpack.
Pentru stilizare folosesc SCSS deoarece îmi permite să folosesc variabile
și mixins. O altă tehnologie folosită pentru partea de stilizare este Angular
Material, o adaptare specială pentru Angular 2+ a principiilor Material Design
de la Google. Acest pachet include diferite componente HTML, similar cu
Bootstrap.
2.1.2. Server
Pe partea de server folosesc NodeJS împreuna cu platforma Express.
Severul este scris folosind sintaxa de import ES6, iar acesta este compilat
folosind Webpack. Am ales să construiesc severul pe modelul REST API pentru
o integrare ușoară cu aplicații mobile, pe viitor. Serverul poate fi pornit în trei
moduri diferite: development, testing și producție. Fiecare mod tratează erorile
în mod diferit, are o configura ție diferită și are o baza de date proprie.
Autorizarea pe server se face cu ajutorul unor funcții intermediare pe rute.
Aceste funcții verifică tokenuri de tip JSON Web Token primite de la utilizatorii
autentifica ți pe client.
APLICA ȚIE WEB DE PUBLICITATE
18
2.1.3. Baza de date
Ca bază de date folosesc MongoDB iar pentru a interacționa cu acesta
folosesc un pachet de NodeJS numit Mongoose. Acesta extinde sintaxa
limbajului de interogare MongoDB si ne permite să definim structura colecțiilor
prin intermediul unei scheme. Această schemă este compilat ă la prima pornire
a serverului și garanteaz ă integritatea datelor inserate/modificate. În cadrul
unei scheme putem defini dacă o proprietate este obligatorie, lungimea minima,
maxima, o valoare implicită etc. Am ales să folosesc MongoDB deoarece este
ușor de integrat cu serverul de NodeJS și partea de client. Această ușurință
reiese din faptul că MongoDB ne permite sa stocam strcturi complexe iar
pachetul Mongoose creează un obiect Model pentru fiecare document din baza
de date în momentul interogării. Acest obiect are metode care ne permit să
efectuam operațiile cele mai des folosite, precum cele de CRUD, cu ușurință.
2.2. Arhitectura
2.2.1. Client
Pe partea de client aplicația este împărțită în două module principale:
Client și Admin. Aceste module importă modulul Shared.
APLICA ȚIE WEB DE PUBLICITATE
19
Modulul Shared exportă componentele și modulele folosite in cadrul
întregii aplicații. Acest modul importă modulele necesare din pachetul
„@angular/material” și declară componentele comune celor două module,
precum componentele: ConfirmDialog, IconPicker, CategoryCard și
UploadGrid.
Modulul Client este unul dintre modulele principale, conținutul lui fiind
partea cu care
interacționează marea majoritate a utilizatorilor. În cadrul acestui
modul se află componentele care alcătuiesc pagina de acasă, pagina de filtrare
anunțuri, pagina de afișare a detaliilor unui anunț anume, paginile de setări etc.
Acest modul, fiind un modul principal, are propriul modul de rutare, în care
sunt definite rutele pe partea de client. La baza modului stă componenta
ClientComponent iar componentele care sunt încărcate în interiorul acesteia
sunt stabilite de către routerul de Angular.
Modulul Admin este cel de al doilea modul principal. Acest modul
declară componentele care alcătuiesc panoul de administrare al aplicației. În
cadrul panului de administrare se pot adaugă categorii, subcategorii, atribute
pentru subcategorii, detalii pentru atribute etc.
Cele două module, sunt înregistrate în modulul principal al aplicației,
numit AppModule. Pentru rutare, ele sunt înregistrate în modulul principal de
rutare.
const appRoutes : Routes = [
{ path: '',loadChildren : 'app/client/client.module#ClientModule '},
{ path: 'admin', canActivate : [AdminGuard ], loadChildren :
'app/admin/admin.module#AdminModule '},
{ path: '404', component : NotFoundComponent }];
APLICA ȚIE WEB DE PUBLICITATE
20
Datorită faptului că aceste module sunt „lazy‐loaded”, performan ță
aplicației crește deoarece modulele nu sunt încărcate în memoria clientului
decât în momentul în care acesta acceseaz ă rutele specifice modului respectiv.
De exemplu, modulul Admin nu va fi încărcat dacă utilizatorul nu încearcă să
acceseze
ruta „/admin”.
Un element central al aplicației, atunci când vine vorba de partea de
client, este Store‐ul. Acesta reprezint ă locul central unde este stocată starea
aplicației. Store‐ul din cadrul aplicației conține patru reductori principali:
anunț, auth, category și valută. Aceste reductori sunt importate într‐un reductor
principal care alcătuiește Store‐ul. Toți acești reductori conțin acțiuni și efecte
pentru operațiile uzuale CRUD și o acțiune pentru tratarea erorilor. Reductorul
auth conține acțiuni pentru înregistrare, autentificare și ieșire din cont împreuna
cu starea legată de utilizatorul curent, precum numele de utilizator,
identificatorul unic din bază de date (_id) și emailul acestuia, dacă acesta este
autentificat etc. Acest Store este injectat în majoritatea componentelor din cadrul
aplicației, atât din modulul Admin cât și din modulul Client. Componentele
efectueaz ă modificări asupra stării din Store numai prin intermediul acțiunilor
predefinite.
2.2.2. Server
Serverul este construit dupa modelul REST API pentru a facilita
integrarea cu aplicațiile mobile, pe viitor.
APLICA ȚIE WEB DE PUBLICITATE
21
Pe partea de rutare, serverul are doar două rute pentru servirea fișierelor
statice. Ruta „/images” pentru servirea imaginilor și ruta „/*” care va trimite
fișierul index.html pentru fiecare rută nedefinit ă. Acest lucru se face în cazul în
care aplicația client este una „single page” deoarece
aceasta va gestiona rutarea
în totalitate. Restul rutelor sunt prefixate cu „api/[num ăr versiune]/”, în
concordan ță cu standardul REST API.
Fiecare element este separat în module, fiecare modul reprezentând o
colecție din baza de date. Un modul conține cel puțin patru fișiere cu roluri
diferite:
Model, care conține definiția schemei Mongoose pentru entitatea
respectiv ă
Controller, care conține funcții asincrone, cu rolul de a trata cererile
primite pe rutele definite ca rute API, și funcții sincrone, cu diferite
roluri.
Validation, care exportă un obiect validator folosit ca parametru
pentru o funcție intermediara de validare pe rute..
Routes, care conține definițiile de rute pentru modulul respectiv
Un model se definește prin apelarea funcției „model”, care primește ca
parametru numele modelului, un obiect de tip Schema, care definește structura
modelului și, opțional, numele colecției. O schemă se definește prin apelarea
constructorului Schema din cadrul pachetului Mongoose. Acest constructor
primește ca parametru un obiect JSON care conține definițiile proprietățile care
vor face parte din fiecare înregistrare din colecția respectiv ă. Pentru fiecare
proprietate putem definii tipul, dacă valoarea introdus ă trebuie să fie unică la
APLICA ȚIE WEB DE PUBLICITATE
22
nivelul colecției, lungime minimă, lungime maximă, dacă este obligatoriu, dacă
dorim să eliminăm spațiile albe din valoare introdus ă etc. Aceste proprietăți,
spre deosebire de SQL, pot conține structuri de date complexe precum array‐uri
sau obiecte JSON. Următoarea secvență de cod
reprezint ă definirea modelului
pentru Localitate:
import mongoose , {Schema} from 'mongoose ';
const LocalitateSchema = new Schema({
nume :{
type : String,
required : [true, 'Nume is required! '],
trim : true,
},
coordonate : {
type : [Number], //[longitudine, latitudine]
index : '2dsphere ',
required : true
},
judet :{
type : Schema.Types.ObjectId ,
ref : 'Judet'
}
}, {timestamps : true});
LocalitateSchema .index({coordonate : '2dsphere '});
export default mongoose .model('Localitate ', LocalitateSchema , 'localitate ');
APLICA ȚIE WEB DE PUBLICITATE
23
Fiecare model are asociat un controller, care exportă funcții asincrone în
cadrul cărora se gestioneaz ă cererile primite de la client. Routerul Express
injecteaz ă automat doi parametri în aceste funcții: primul parametru reprezint ă
obiectul cerere și al doilea un obiect răspuns. Obiectul care reprezinta cererea
venită de pe client va conține, printre altele, datele transmise de acesta. Aceste
date se află pe proprietăți diferite ale obiectului, în funcție de metoda HTTP
pentru care a fost declarată ruta. De exemplu, pentru o cerere de tip POST,
,,request.body” va conține datele primite de la client.
În următoarea secvență de cod avem funcția care gestioneaz ă un apel
către ruta „/api/v1/loca ții/:str”. Unde :str reprezint ă textul introdus de către
utilizator în câmpul de selecție a locației. Acel text se află pe proprietatea
„params” a obiectului req. Dacă există acea proprietate apelăm funcția „find”
pe modelul Localitate definit anterior. Această funcție primește ca parametru
un obiect JSON. În cadrul acestui obiect JSON cheia reprezint ă numele
câmpului din colecție și valoare asociată cheii valoarea de query. În acest caz
valoarea de query este un obiect JSON prin care specificăm că dorim ca
înregistrările pe care le căutăm să conțină în câmpul „nume” textul introdus de
utilizator, indiferent de poziția în cadrul câmpului.
Acestui apel de funcție îi adăugăm un apel către funcția „populate” cu
parametrul „judet”. Funcția, „populate” este similară cu „join” din SQL. În
cadrul colecției „localitate”, în câmpul județ, înregistrările conțin id‐ul unei
înregistrări din colecția „judet”, similar cu o cheie străină din SQL. Funcția
„populate” va înlocui valoarea de pe proprietatea „judet” a obiectelor
„Localitate” cu înregistrarea corespunz ătoare din colecția „judet”.
APLICA ȚIE WEB DE PUBLICITATE
24
Înainte apelurilor de funcție, însă, am pus cuvântul cheie „await”. Acest
cuvânt este un cuvânt rezervat în limbajul JavaScript și are rolul de a semnala
că ceea ce urmează după el este o operație asincron ă iar executarea funcției să
se reia numai după rezolvarea apelului
de funcție. La întâlnirea unei erori
serverul va trimite un răspuns cu statusul 400 înapoi la client, împreuna cu
eroarea respectiva.
export async function getLocatie (req, res) {
try {
if(!req.params.str){throw `Invalid search term! `};
const locatii = await Localitate .find({
nume : {'$regex':req.params.str, '$options ': 'i'}
}).populate ('judet');
return res.status(Status.OK).json(locatii);
} catch (err) {
console.log(err);
return res.status(Status.BAD_REQUEST ).send(err);
}};
Unele modele au asociate un fișier cu rolul de validare. Acest fișier
exportă un obiect de tip JSON cu reguli de validare folosind pachetul „joi”.
Acest obiect este folosit ulterior ca parametru la apelul funcției „validate” din
cadrul pachetului „express ‐validation”. Funcția se pune pe o ruta de Express
înainte de referința spre funcția din controller care va gestiona cererea. În cazul
în care datele din cerere nu corespund cu validatorul, serverul va returna un
răspuns cu status de eroare, împreun ă cu eroarea din validator.
APLICA ȚIE WEB DE PUBLICITATE
25
import validate from 'express-validation ';
import userValidation from './user.validation ';
const router = new Router();
router.post('/signup',validate (userValidation .signup),UserController .signUp);
Toate modelele au asociat un fișier care conține definițiile de rute API
pentru modelul respectiv. Fiecare rută este creată folosind un obiect creat prin
apelarea constructorului Router din pachetul Express Acest obiect are diferite
metode, reprezentând metodele prin care se poate face o cerere HTTP. Obiectul
router este apoi importat în fișierul principal de rutare unde sunt declarate
rutele pentru întreaga parte de API.
Fiecare metodă de acest gen primește cel puțin trei parametri:
Adresa URL care va fi concatenat ă adresei API și reprezint ă adresa
rutei
Una sau mai multe funcții intermediare, prin care va trece cererea
O referință către funcția care va trata cererea, dacă acesta nu a fost
respins de către una dintre funcțiile intermediare
Următoarea secvența de cod reprezint ă definiția rutelor pentru modulul
„subcategorii”.
import {Router} from 'express';
import validate from 'express-validation ';
import { authLocal , authJWT , isAdmin } from '../../services/auth.service ';
import * as UserController from './user.controller ';
import userValidation from './user.validation ';
import errorHandler from '../../config/error ';
APLICA ȚIE WEB DE PUBLICITATE
26
const router = new Router();
router.post('/confirm/email ', errorHandler (UserController .confirmEmail ));
router.post('/confirm/password ', errorHandler (UserController .confirmPassword ));
router.post('/signup', validate (userValidation .signup), errorHandler (UserControl-
ler.signUp));
router.post('/login', authLocal , errorHandler (UserController .login));
router.get('/settings ', authJWT , errorHandler (UserController .getSettings ));
router.get('/checkAdmin ', isAdmin , errorHandler (UserController .checkAuth ));
router.get('/checkAuth ', authJWT , errorHandler (UserController .checkAuth ));
router.patch('/update', authJWT , errorHandler (UserController .updateUser ));
router.patch('/updateEmail ', authJWT , errorHandler (UserController .updateEmail ));
router.patch('/updatePassword ', authJWT , errorHandler (UserController .updatePassword ));
export default router;
Pentru ruta de autentificare avem ca funcție intermediara o funcție
numita authLocal, care reprezint ă strategia locală de autentificare cu email și
parolă. Această strategie este scrisă folosind pachetul „passport” și „passport ‐
local”. În cazul autentific ării cu succes a utilizatorului, cererea ajunge la funcția
„login” din cadrul UserController. În cazul în care utilizatorul și‐a confirmat
adresa de e‐mail, această funcție returneaz ă un răspuns cu statusul 200 care
conține date despre utilizatorul autentificat, precum numele de utilizator,
identificatorul unic din baza de date, email și cel mai important, JSON Web
Token‐ul generat. Acest token va fi stocat pe client și va fi trimis ca valoarea
pentru câmpul ,,Authorization” în cadrul fiecărei cereri HTTP. Unele rute
folosesc o funcție intermediara care verifică validitatea tokenului iar dacă
APLICA ȚIE WEB DE PUBLICITATE
27
clientul trimite o cerere cu token invalid pe o rută care necesită ca utilizatorul să
fie autentificat serverul va returna un răspuns cu statusul de 401.
export async function login(req, res, next){
if(!req.user || !req.user.isConfirmed ){
return res.status(HTTPStatus .BAD_REQUEST ).json({message: `Adresa de email nu a fost
confirmata! `});
}
return res.status(HTTPStatus .OK).json(await req.user._toAuthJSON ());
}
Pentru configurarea serverului se folosește un fișier JSON care conține
diverse date, precum: numărul de port, datele de conexiune la baza de date,
cheie secretă pentru generarea token‐ului etc. Acest fișier este citit la pornirea
serverului și, în funcție de modul în care a fost pornit serverul, se va alege o
configura ție diferită din cadrul obiectul citit.
Întregul cod sursă al serverului este compilat într‐un singur fișier,
folosind Webpack. Iar sintaxa de import, specifică versiunii ES6 a limbajului
JavaScript, este procesată folosind Babel. Acest fișier compilat este optimizat din
mai multe puncte de vedere, cel mai important dintre ele fiind optimizarea
importurilor de pachete. Performan ța serverului este, în consecin ță,
îmbunătățită.
2.2.3. Baza de date
APLICA ȚIE WEB DE PUBLICITATE
28
Bază de date este alcătuită din 13 colecții de documente. Relațiile
descrise mai jos nu sunt o funcționalitate a MongoDB, ci sunt definite folosind
pachetul Mongoose.
2.2.3.1. User
În cadrul documentelor din această colecție se stocheaz ă datele
utilizatorilor înregistra ți. Proprietatea „email” este unică în cadrul
documentelor din colecție deoarece pentru autentificare pe client se folosește
combina ția adresa de email si parola.
Valoarea proprietăților „password” și „passwordChange” sunt criptate
ireversibil, folosind funcția „bcrypt”.
În momentul în care un utilizator creează un cont nou câmpul
„isConfirmed” primește valoarea implicită „fals” iar autentificarea acestuia nu
este permisă până când își confirmă adresa de e‐mail.
În cazul în care utilizatorul cere schimbarea adresei de e‐mail,
proprietatea „emailChange” primește ca valoare noua adresă de e‐mail.
Utilizatorul va fi notificat pe client să își confirme noua adresă de e‐mail atâta
timp cât proprietatea „emailChange” nu are valoarea null. În momentul în care
utilizatorul își confirmă noua adresă de e‐mail valoarea proprietății „email”
primește valoarea proprietății „emailChange” iar acesta primește valoarea null.
Procesul este similar în cazul în care utilizatorul își schimbă parolă, proprietatea
folosită în acest caz fiind „passwordChange”.
APLICA ȚIE WEB DE PUBLICITATE
29
Referințele către rolurile pe care le are utilizatorul sunt stocate în tabloul
„roles”. În momentul de față toți utilizatorii primesc implicit rolul „user” dar
un utilizator poate avea și rolul de „admin”. Aceste roluri sunt folosite în faza
de autorizare, atât pe client cât și
pe server.
Dacă utilizatorul își setează locația sau o poză de profil se salvează o
referință către aceasta în proprietatea „localitate” respectiv „avatar”.
Referințele către anunțurile pe care utilizatorul le‐a adăugat la favorite
sunt salvate în tabloul „favorites.anunturi”.
Structura Relații
Câmp Tip
_id String
username String
email String
password String
isConfirmed Boolean
roles Array
[0] ObjectId
favorites Document
anunturi Array
[0] ObjectId
avatar ObjectId
localitate ObjectId Colecție
Părinte Prop.
PărinteColecție
Copil Proprietate
Copil
anunt _id user favorites.anunturi[0]
imagine _id user avatar
localitate _id user localitate
role _id user roles[0]
user _id anunt user
user _id convo user1
user _id convo user2
user _id imagine user
user _id message from
user _id valuta user
APLICA ȚIE WEB DE PUBLICITATE
30
telefon String
emailChange String
passwordChange String
createdAt Date
updatedAt Date
2.2.3.2. Anunt
Documentele din această colecție reprezint ă atributele unei anumite
subcategorii. Un atribut este o caracteristic ă care definește un obiect ce aparține
de subcategoria respectiv ă. Un exemplu de atribut fiind atributul
„Combustibil” pentru subcategoria „Automobile”. Aceste atribute sunt folosite
în cadrul filtrării anunțurilor. Un atribut poate depinde de un alt atribut. În acest
caz se salvează o referință către atributul părinte în cadrul câmpului „parinte”.
În cazul în care un atribut are părinte detaliile afișate pentru acesta în client vor
depinde de detaliul selectat pentru atributul părinte. În consecin ță, detaliile
adăugate pentru acest atribut sunt salvate în cadrul array‐ului „detalii” a
detaliului atributului părinte. Un exemplu care descrie acest scenariu sunt
atributele „Model” și „Marca”. Detaliile pentru atributul „Marca” depind de
detaliul selectat pentru atributul „Model”.
Structura Relații
Câmp Tip
_id String Colecție
Părinte Prop.
PărinteColecție
Copil Proprietate
Copil
anunt _id convo anunt
APLICA ȚIE WEB DE PUBLICITATE
31
nume String
descriere String
user ObjectId
subcategorie ObjectId
valuta ObjectId
locatie ObjectId
atribute Array
[0] Document
_id String
atribut ObjectId
atributDetaliu ObjectId
valoare String
imagini Array
[0] Document
isNegociabil Boolean
isSchimb Boolean
isActive Boolean
telefon String
vizualizari Numeric
anunt _id user favorites.anunturi[0]
atribut _id anunt atribute[0].atribut
atribut_detaliu _id anunt atribute[0].atributdetaliu
imagine _id anunt imagini[0]
locatie _id anunt locatie
subcategorie _id anunt subcategorie
user _id anunt user
valuta _id anunt valuta
2.2.3.3. Atribut
APLICA ȚIE WEB DE PUBLICITATE
32
Documentele din această colecție reprezint ă atributele unei anumite
subcategorii. Un atribut este o caracteristic ă care definește un obiect ce aparține
de subcategoria respectiv ă. Un exemplu de atribut fiind atributul
„Combustibil” pentru subcategoria „Automobile”. Aceste atribute sunt folosite
în cadrul filtrării anunțurilor. Un
atribut poate depinde de un alt atribut. În acest
caz se salvează o referință către atributul părinte în cadrul câmpului „parinte”.
În cazul în care un atribut are părinte detaliile afișate pentru acesta în client vor
depinde de detaliul selectat pentru atributul părinte. În consecin ță, detaliile
adăugate pentru acest atribut sunt salvate în cadrul array‐ului „detalii” a
detaliului atributului părinte. Un exemplu care descrie acest scenariu sunt
atributele „Model” și „Marca”. Detaliile pentru atributul „Marca” depind de
detaliul selectat pentru atributul „Model”.
Structura Relații
Câmp Tip
_id String
nume String
parinte ObjectId
detalii Array
[0] ObjectId
sortedAtribute ObjectId
forSorting Boolean
forValue Boolean Colecție
Părinte Prop.
Părinte Colecție
Copil Proprietate
Copil
atribut _id anunt atribute[0].atribut
atribut _id atribut atributparinte
atribut _id atribut sortedAtribute
atribut _id atribut_detaliu atribut
atribut _id subcategorie atribute[0]
APLICA ȚIE WEB DE PUBLICITATE
33
isRequired Boolean
isMin Boolean
isMax Boolean
createdAt Date
updatedAt Date
2.2.3.4. Atribut_detaliu
Această colecție conține documente ce reprezint ă detaliile unui anumit
atribut. Un exemplu de detaliu este „Diesel” pentru atributul „Combustibil”. În
cazul în care un atribut depinde de un alt atribut, se vor adaugă referințe către
detaliile atributului copil în array‐ului „detalii”.
Structura Relații
Câmp Tip
_id String
nume String
atribut ObjectId
detalii Array
[0] ObjectId
tags Array
[0] String
createdAt Date Colecție
Părinte Prop.
Părin
te Colecție
Copil Proprietate
Copil
atribut_detaliu _id Anunt Atribute[0].atributDetaliu
atribut_detaliu _id Atribut Detalii[0]
atribut_detaliu _id atribut_d
etaliu detalii
APLICA ȚIE WEB DE PUBLICITATE
34
updatedAt Date
2.2.3.5. Categorie
Structura Relații
Câmp Tip
_id String
nume String
subcategorii Array
[0] ObjectId
orderNr Numeric
iconClass String
color String
slug String
createdAt Date
updatedAt Date
Colecție
Părinte Prop.
PărinteColecție
Copil Proprietate
Copil
categorie _id subcategorie parentCategory
subcategorie _id categorie subcategorii[0]
2.2.3.6. Subcategorie
APLICA ȚIE WEB DE PUBLICITATE
35
Structura Relații
Câmp Tip
_id String
nume String
ParentCategory ObjectId
atribute Array
[0] ObjectId
maxImageCount Numeric
daysValid Numeric
orderNr Numeric
iconClass String
color String
slug String
createdAt Date
updatedAt Date
Colecție
Părinte Prop.
PărinteColecție
Copil Proprietate
Copil
atribut _id subcategorie atribute[0]
categorie _id subcategorie parentcategory
subcategorie _id anunt subcategorie
subcategorie _id categorie subcategorii[0]
2.2.3.7. Convo
Această colecție are rolul de a stoca câte un document pentru fiecare
conversa ție dintre doi utilizatori. În documentul respectiv se face referință către
mesajele, din colecția „messages”, care au fost trimise în cadrul conversa ției. De
asemenea, se face referință către anunțul prin intermediul căruia s‐a inițiat
conversa ția. Acest lucru este folositor în momentul afișării conversa țiilor în
APLICA ȚIE WEB DE PUBLICITATE
36
secțiunea „Conversa ții Recente”, deoarece utilizatorul poate recunoaște o
conversa ție mult mai ușor pe baza anunțului, decât pe baza numelui de
utilizator.
Structura Relații
Câmp Tip
_id String
user1 ObjectId
user2 ObjectId
anunt ObjectId
messages Array
[0] String
createdAt Date
updatedAt Date
Colecție
Părinte Prop.
PărinteColecție
Copil Proprietate
Copil
message _id convo messages[0]
anunt _id convo anunt
user _id convo user1
user _id convo user2
2.2.3.8. Message
Documentele din această colecție reprezint ă mesajele din cadrul unei
conversa ții dintre doi utilizatori. Se stocheaz ă o referință către imaginile care au
fost trimise precum și o referință către utilizatorul care a trimis mesajul. Câmpul
„seen” se completeaz ă automat cu valoarea „true” atunci când destinatarul
mesajului l‐a văzut.
APLICA ȚIE WEB DE PUBLICITATE
37
Structura Relații
Câmp Tip
_id String
nume String
atribut ObjectId
detalii Array
[0] ObjectId
tags Array
[0] String
createdAt Date
updatedAt Date
Colecție
Părinte Prop.
PărinteColecție
Copil Proprietate
Copil
imagine _id message imagini[0]
message _id convo messages[0]
user _id message from
2.2.3.9. Imagine
Documentele din această colecție reprezint ă referințe către imaginile
încărcate de către utilizatori. În cazul în care se șterge un document din baza de
date care conține referințe spre documente din această colecție, imaginile
respective vor fi șterse de pe disc.
Structura Relații
Câmp Tip
_id String Colecție
Părinte Prop.
PărinteColecție
Copil Proprietate
Copil
imagine _id anunt imagini[0]
APLICA ȚIE WEB DE PUBLICITATE
38
nume String
user ObjectId
createdAt Date
updatedAt Date
imagine _id messages imagini[0]
imagine _id user avatar
user _id imagine user
2.2.3.10 Judet
Structura Relații
Câmp Tip
_id String
nume String
Colecție
Părinte Prop.
PărinteColecție
Copil Proprietate
Copil
judet _id localitate judet
2.2.3.11 Localitate
Această colecție conține documente ce reprezint ă fiecare localitate din
România (13,749 de documente). Fiecare document conține câte o referință către
județul de care aparține localitate și un array special numit „coordonate”. Acest
array stocheaz ă longitudinea și latitudinea localității, sub formatul [longitudine,
latitudine], și pentru acest câmp se definește un index special, de tip
„2dsphere”. Valorile din cadrul acestui câmp sunt utilizate pentru stabilirea
automată a locației utilizatorului prin intermediul sintaxei „$near”.
APLICA ȚIE WEB DE PUBLICITATE
39
Structura Relații
Câmp Tip
_id String
nume String
judet ObjectId
coordonate Array
[0] Numeric
createdAt Date
updatedAt Date
Colecție
Părinte Prop.
PărinteColecție
Copil Proprietate
Copil
judet _id localitate judet
localitate _id anunt localitate
localitate _id user localitate
2.2.3.12 Role
Colecția conține documente ce reprezint ă rolurile pe care le poate avea
un utilizator. Un exemplu de rol ar fi cel de „Admin”. Pe baza acestor roluri se
face autorizarea utilizatorului în timp ce acesta navigheaz ă pe client, cât și
atunci când cere date de la API.
Structura Relații
Câmp Tip
_id String Colecție
Părinte Prop.
PărinteColecție
Copil Proprietate
Copil
role _id user roles[0]
APLICA ȚIE WEB DE PUBLICITATE
40
nume String
roleNumber Numeric
createdAt Date
updatedAt Date
2.2.3.13 Valuta
În această colecție se stocheaz ă documentele ce conțin valutele folosite în
cadrul aplicației. Fiecare valută are un nume (acronim de 3 litere) și un simbol,
folosit la afișarea prețului. Aceste documente sunt actualizate în fiecare zi ,
deoarece se aduce noul curs valutar, iar data ultimei actualizări se salvează în
proprietatea „ratesFetched”. Cursul de raportare este calculat pentru fiecare
valută din baza de date și se stocheaz ă în tabloului „rates”.
Structura Relații
Câmp Tip
_id String
nume String
simbol String
user ObjectId
ratesFetched Date
rates Array Colecție
Părinte Prop.
PărinteColecție
Copil Proprietate
Copil
user _id valuta user
valuta _id anunt valuta
APLICA ȚIE WEB DE PUBLICITATE
41
[0] Document
_id ObjectId
nume String
valoare Numeric
isActive Boolean
createdAt Date
updatedAt Date
APLICA ȚIE WEB DE PUBLICITATE
42
Capitolul 3
Aplicația
3.1. Autentificare, Autorizare și Înregistrare
Deoarece aplicația este una „single page” nu există sesiunea specifică
PHP iar din considerente de securitate nu se folosesc cookie‐uri. În schimb,
autentificarea se face folosind JSON Web Token.
La prima autentificare utilizatorul primește un token generat de către
server. Acest token este apoi stocat în storage‐ul local al browserului. La fiecare
accesare a aplicației se execută acțiunea SetSessionFromStorage. Funcția
reductor care corespunde acestei acțiuni verifică dacă există în storage‐ul local
al browserului obiectul „authData”. Acest obiect conține date despre
utilizatorul autentificat, precum: numele de utilizator, email, id‐ul din baza de
date și token‐ul primit de la server în momentul autentific ării. Dacă există acest
obiect, reductorul va actualiza starea „auth” din cadrul Store‐ului cu datele din
obiect.
De asemenea, aplicația va completă automat headerul Authorization,
pentru toate cererile trimise, cu valoarea tokenului. Acest lucru este realizat
prin intermediul unui interceptor HTTP de Angular. Dacă răspunsul de la
server este 401, deși tokenul a fost trimis, se execută automat acțiunea Logout
APLICA ȚIE WEB DE PUBLICITATE
43
iar funcția reductor care corespunde acestei acțiuni va șterge obiectul
„authData” din local storage, va actualiza corespunz ător starea de „auth” a
Store‐ului și va redirecționa utilizatorul către pagină de acasă cu un mesaj care
îl notifica de faptul că va trebui să se
autentifice din nou.
În cazul creării unui cont nou, dacă acest lucru se realizeaz ă cu succes
utilizatorul va fi nevoit să își confirme adresa de e‐mail înainte să se poată
autentifica.
Unele rute, precum cea de adăugare a unui anunț nou, necesită ca
utilizatorul să fie autentificat. Accesul la aceste rute este protejat printr‐un guard
Angular care verifică valoarea variabilei „isAuthenticated” din cadrul stării de
„auth”. Această măsura de restricționare a accesului ține doar de partea de
client. Fiind parte de client, utilizatorul are acces la codul sursă și ar putea obține
acces la rutele restricționate de către guard. În caz că obține acces la o pagină
restricționata el nu va putea modifică nimic deoarece pe server se face din nou
autorizarea utilizatorului, pe bază tokenului acestuia. În acest caz, dacă nu
există tokenul sau este invalid serverul va trimite înapoi un răspuns cu statusul
401 iar clientul va redirecționa utilizatorul, actualizând starea „auth” în mod
corespunz ător.
Rutele ce țin de modulul admin necesită că acesta să fie autentificat și să
aibă rolul de administrator. Această verificare se face însă diferit față de rutele
din modulul client, aplicația trimițând o cerere către server iar serverul
efectuând aceasta verificare de roluri. În funcție de răspunsul de la server
utilizatorului i se permite, sau nu, accesul la rute.
Rutele definite de modulul admin sunt protejate de un Guard. Acest
Guard trimite o cerere HTTP către server, având ca valoare pentru câmpil
APLICA ȚIE WEB DE PUBLICITATE
44
,,Authorization” tokenul utilizatorului autentificat. În server se stabilește dacă
utilizatorul cu tokenul respectiv are rolul de administrator și se trimite înapoi
un răspuns. Dacă utilizatorul este administrator, răspunsul va avea statusul 200,
în caz contrat răspunsul va avea statusul 401. Clientul așteaptă răspunsul
de la
server înainte de a realiză rutarea. Dacă răspunsul are statusul 200 se va realiza
rutarea către componenta corespunz ătoare din modulul Admin. În caz contrat,
routerul Angular va redirecționa utilizatorul către pagină de 404. Acesta
abortare, la fel ca și cea pentru modulul „client” ține doar de partea de client.
Toate rutele care au de a face cu operații CRUD pe resurse gestionate din
panoul de admin sunt protejate cu o funcție intermediara în server care verifică
dacă utilizatorul care încearcă să facă modificarea este într‐adevăr
administrator. Dacă utilizatorul nu este administrator, serverul va răspunde cu
statusul 401 cererii. Când răspunsul ajunge în client iar statusul este 401
utilizatorul va fi redirecționat imediat către pagină de 404, fiind nevoit să repete
procesul. De asemenea, cererea respectiva va fi stocata permanent în baza de
date, împreun ă cu datele de conexiune ale utilizatorului.
Figura 3.1.1 Obiectul „authData” din local storage pentru un utilizator autentificat
APLICA ȚIE WEB DE PUBLICITATE
45
Autentificarea cu JSON Web Token este considerat ă una dintre cele mai
sigure metode, fiind nevoie de manipularea serverului pentru a obține
privilegii.
3.2. Panoul de administrator
Panoul de administrator are rolul de a pune la dispoziția utilizatorilor cu
privilegiile corespunz ătoare unelte pentru gestiunea datelor aplicației.
3.2.1 Gestionarea resurselor
În cadrul acestui panou administratorii pot gestiona resurse precum:
• Categorii
• Subcategorii
◦ Atribute
◦ Detalii pentru atribute
• Valute
Adăugarea unei subcategorii se face folosind un „stepper” de Angular
Material. Acesta necesită completarea unei acțiuni înainte de a permite
avansarea la următorul pas. Primul pas
este alegerea unei categorii de care va
ține subcategoria. În acest pas ne sunt afișate toate categoriile din aplicație, sub
aceeași formă în care apăr în pagină de acasă. Pentru afișarea acestora se
folosește o component ă comună celor două module, component ă
CategoryCard.
APLICA ȚIE WEB DE PUBLICITATE
46
Figura 3.2.1.1 Primul pas din stepper
Odată ce administratorul a ales o categorie, acestuia i se permite accesul
la următorul pas, cel de alegere a unei subcategorii. În cadrul acestui pas
administratorul are posibilitatea de a alege o subcategorie deja existentă, în caz
că dorește să facă modificări. Dacă dorește, însă, să adauge o subcategorie el este
nevoit să apese pe butonul „Adaugă”. Pasul care afișează subcategoriile
existente folosește aceeași component ă CategoryCard ca și pasul care afișează
categoriile.
APLICA ȚIE WEB DE PUBLICITATE
47
Figura 3.2.1.2 Formularul de adăugare subcategorie
În cadrul formularului de adăugare subcategorie avem cinci câmpuri.
Toate aceste câmpuri sunt obligatorii, introducerea unei înregistrări fiind
blocată atunci când unul dintre câmpuri nu este completat. Primul câmp este
cel de selectarea a iconiței. Dacă utilizatorul dă click pe iconiță deja selectată se
va deschide un modal cu toate iconițele disponibile. Acest modal are un input
de filtrare. La selectarea unei iconițe din modal acesta se va închide. Modalul
este component ă IconPicker. Al doilea câmp este cel câmpul pentru numele
subcategoriei. Al treilea câmp este câmpul pentru selectarea culorii pe care o va
avea subcategoria. Acest câmp primește ca valoare direct un cod hexazecimal
de culoare dar utilizatorul poate da click pe cutia ce afișează culoarea curentă și
se va deschide un modal de selectarea a culorii. Următoarele două câmpuri sunt
cele care vor stabilii numărul maxim de poze pe care utilizatorul le va putea
încărca la adăugarea unui anunț nou pentru această subcategorie și durata de
validitate a anunțului, în zile.
APLICA ȚIE WEB DE PUBLICITATE
48
Serverul face verificări în fiecare zi prin intermediul unui „cron job” și va
actualiza automat câmpul „isActive” cu valoarea „false” pentru toate anunțurile
din baza de date a căror termen de valabilitate a expirat. Aceste anunțuri nu vor
mai apărea în căută
rile utilizatorilor până când utilizatorul care a adăugat
anunțul respectiv nu îl reactiveaz ă. Odată ce au trecut 15 zile de la dezactivarea
unui anunț, fără ca acesta să fie reactivat, serverul va șterse automat toate
înregistrările din baza de date care țin de acest anunț și imaginile de pe discul
serverului. Această operațiune este, de asemenea, automată și se realizeaz ă
folosind un „cron job” care se execută zilnic.
Odată ce administratorul a adăugat cu succes o subcategorie el va avea
acces la ultimul pas, cel de adăugare a atributelor pentru subcategoria selectată.
Figura 3.2.1.3 Pasul de adăugare a atributelor
Fiecărei subcategorii noi create i se adaugă automat atributele „Pret”,
„Pret de la” și „Pret pana la”. Atributele care se adaugă la acest pas se vor folosi
pentru filtrarea anunțurilor de către utilizatori și unele dintre ele se vor folosi în
cadrul formularului de adăugare anunț. Fiecare subcategorie va avea atribute
specifice ei. Există trei tipuri de atribut:
APLICA ȚIE WEB DE PUBLICITATE
49
• forValue, acest tip de atribut se folosește exclusiv în cadrul
formularului de adăugare anunț iar valoarea introdus ă de
utilizator pentru acest atribut se va putea sorta. Un exemplu este
atributul „pret”
• forSorting, acest tip de atribut este folosit la sortarea atributelor de
tip „forValue”.
El poate fi de două feluri:
◦ isMin, dacă detaliul selectat sau valoarea introdus ă manual
pentru acest atribut reprezint ă valoarea minimă
◦ isMax, dacă detaliul selectat sau valoarea introdus ă manual
pentru acest atribut reprezint ă valoarea maximă
• Pentru filtrare, acesta este tipul standard de atribut. El nu permite
introducerea manuală de valori ci doar selecția unui detaliu ce
aparține de el. Acest tip de atribut, însă, poate avea un părinte.
Dacă are un părinte detaliile lui vor depinde de detaliul selectat în
atributul părinte. Un exemplu ar fi atributele „Marca” și „Model”,
evident detaliile din cadrul atributului Model depind de Marca
selectată.
Pentru fiecare atribut există posibilitatea de a stabili dacă acesta este
obligatoriu sau nu. Această caracteristic ă este luată în considerare în cadrul
formularului de adăugare a unui anunț nou. Odată adăugat, administratorul
are posibilitatea de a adaugă detalii pentru atributul respectiv, doar dacă acesta
nu este de tip forValue. Dacă atributul pentru care se adaugă detaliile are un
atribut părinte atunci administratorul este nevoit să selecteze mai întâi un
detaliu dintre cele ale părintelui. Acestui detaliu i se vor asocia detaliile
APLICA ȚIE WEB DE PUBLICITATE
50
introduse pentru atributul copil. În cazul în care utilizatorul dorește să șteargă
o resursa care ține de acest pas i se afișează un modal de confirmare care
detaliază toate resurse care depind de resursa ce urmează să fie ștearsă.
Ștergerea se va efectuă doar dacă
administratorul confirmă.
Adăugarea, modificarea și ștergerea categoriilor se face în mod similar
cu cea a subcategoriilor. Diferențele dintre cele două fiind faptul ca în cazul
categoriilor nu există un „stepper” și categoriile nu au atribute.
La gestionarea valutelor avem un tabel de tip Data Table de la Angular
Material. Dacă se selecteaz ă o înregistrare din tabel se deschide modalul care
conține formularul completat cu detaliile înregistrării selectate. De aici avem
posibilitatea să facem modificări sau să ștergem înregistrarea. La ștergere se
afișează un modal de confirmare. În cadrul acestei pagini se regăsește și butonul
de actualizarea a cursului valutar. Actualizarea cursului valutar se face zilnic la
ora 4 dimineață, sau la prima pornire a serverului pe ziua respectiv ă. Serverul
aduce cursul valutar de pe un API de specialitate. Obiectul care vine ca răspuns
de la API conține cursul valutar pentru toate valutele din baza de date, raportat
la prima valută. Funcția care aduce cursul valutar calculeaz ă cursul raportat la
fiecare valuta din bază de date și le salvează în cadrul array‐ului „rates” pe
înregistrarea corespunz ătoare. De asemenea, actualizeaz ă valoarea proprietății
„ratesFetched” cu data la care s‐a realizat actualizarea cursului.
APLICA ȚIE WEB DE PUBLICITATE
51
3.3. Clientul
La accesarea rutei de bază a aplicației, routerul de Angular va încărca în
memorie modulul Client. În cadrul acestui modul se regăsesc mai multe
componente, precum:
• Home, această component ă conține componentele care alcătuiesc
pagina de acasă
• Search, care conține template ‐ul cu inputul de alegere a locației și
de introducere a cuvintelor cheie pentru căutare. În cadrul acestei
componente se află logica pentru typeahead ‐ul de locație.
• Anunțuri, care conține componentele de afișare a listei de anunțuri,
sidebar‐ul de filtrare, pagina de detalii anunț
• Add‐anunț, pagina de adăugare a unui anunț nou
• Atributes, componentele care se ocupă cu afișarea atributelor
folosite la filtrarea anunțurilor. În cadrul acestor componente se
află și logică care gestioneaz ă selectarea detaliilor.
• Paginile ce țin de administrarea contului:
◦ anunturi
◦ favorite
◦ setari
Aceste componente sunt încărcate în interiorul unui „router‐outlet” aflat
în componenta de baza, ClientComponent. Bara de navigare, reprezentat ă prin
elementul „app‐nav”, este creată o singura data la nivelul întregii aplicații. Ea
este prezentă în cadrul componentei AppComponent.
APLICA ȚIE WEB DE PUBLICITATE
52
3.3.1. Acas ă
Prima pagină este cea de acasă. În cadrul acestei componente se află o
secțiune de banner. Sub secțiune se află două rânduri de categorii, câte șase pe
rând. Aceste două rânduri sunt separate de SearchComponent, care conține
două câmpuri.
Figura 3.3.1.1 Pagina de Acasă
Unul dintre câmpuri se folosește pentru selectarea locației. Acest câmp
funcționează în stilul „typeahead”. Atunci când utilizatorul scrie în el se afișează
o listă cu sugestii de locații preluate din bază de date, care corespund cu ceea ce
a introdus.
În interior acestui input se află o iconiță de geo locație. Dacă utilizatorul
dă click pe această iconiță, clientul va trimite o cerere către un API de geo locație.
Acesta va stabili locația utilizatorului pe baza adresei IP și returneaz ă
APLICA ȚIE WEB DE PUBLICITATE
53
coordonate în format „[longitudine, latitudine]”. Aceste coordonate sunt
ulterior trimise în serverul aplicației. Acesta va caută în baza de date locația cea
mai apropiat ă. Folosirea sintaxei „$near” este posibilă deoarece câmpul
„coordonate” a colecției „localitate” a fost definit cu un index de tip „2dsphere”.
Figura 3.3.1.2 Typeahead ‐ul de locații
const result = await Localitate .find({
coordonate : {
$near : {
$geometry : {
type : "Point",
coordinates : [params.long, params.lat]
},
$maxDistance : params.acc
}
}
}).populate ('judet');
APLICA ȚIE WEB DE PUBLICITATE
54
Dacă utilizatorul dă click pe una dintre categorii, ambele rânduri vor fi
înlocuite cu subcategoriile categoriei selectate. În partea de dreapta sus a paginii
apare un buton pe care utilizatorul îl poate folosi pentru a reveni la categorii. La
selectarea unei subcategorii se afișează atributele pentru subcategoria
respectiv ă. Din această listă sunt excluse atributele forValue, fiind prezente doar
cele cu detalii și forSorting.
Figura 3.3.1.3 Atributele pentru subcategoria „Automobile
3.3.2. Anun ț Nou
Navigarea către pagina de adăugare a unui anunț nou se poate face
apăsând butonul Anunț Nou de pe bara de navigare. În cadrul acestei pagini se
regăsește un „stepper” similar cu cel folosit în pagina de gestiune a
subcategoriilor, doar ca acesta este dispus pe verticala. Parcurgerea acestuia se
face in mod liniar, ceea ce înseamnă ca utilizatorul este obligat sa completeze
anumiți pași înainte sa poată avansa prin el.
APLICA ȚIE WEB DE PUBLICITATE
55
Primul pas este cel de selecție a subcategoriei. În cadrul acestui pas sunt
afișate toate categoriile active sub acelas format ca și pe pagina de acasă.
Componenta folosită pentru afișarea lor este CategoryCardComponent.
După ce se selecteaz ă o categorie se avanseaz ă la cel de
al doilea pas. În
cadrul acestui pas se selecteaz ă subcategoria.
Figura 3.3.2.1 Primul pas pentru adăugarea unui anunț nou
APLICA ȚIE WEB DE PUBLICITATE
56
Figura 3.3.2.2 Al treilea pas al stepper‐ului de adăugare anunț
Odată ce au fost selectate categoria și subcategoria utilizatorul avanseaz ă
la cel de al treilea pas, cel de completare a detaliilor anunțului. În cadrul acestui
pas se afișează mai multe input‐uri și select‐uri, în cazul atributelor, încadrate
într‐o forma. Forma este de tip „reactiveForm” și are validatori în componenta
paginii. Avansarea la următorul pas este blocata atâta timp cât forma este
invalida.
În partea de sus a formularului se afla un input pentru titlu, acesta este
obligatoriu și are o limita de 70 de caractere. Sub input se afla un indicator care
afișează numărul de caractere ramase în timp real. De asemenea, în partea de
sus, se mai afla inputul de locație. Acest câmp este similar cu cel de pe pagina
de acasă, singura diferența fiind stilul. El este de tip „typeahead” și completarea
acestuia este obligatorie.
APLICA ȚIE WEB DE PUBLICITATE
57
Sub cele doua inputuri se afla atributele subcategoriei selectate. Dacă
atributele au detalii ele sunt afișate sub forma unui select, în caz contrar este
sunt inputuri de tip numeric. Cele definite ca fiind obligatorii vor preveni
avansarea prin „stepper” pana în momentul completării acestora. Dacă un
atribut are părinte este necesara selectarea unui detaliu pentru părinte înainte
de completarea lui.
Sub atribute se afla inputul pentru preț, un select pentru valuta și patru
căsuțe de selecție. Căsuța de preț este bifat implicit, ceea ce permite introducerea
unui preț. Dacă se bifează schimb input‐urile pentru preț și valuta vor fi
dezactivate, la fel și cele pentru „Negociabil” și „Gratuit”. Dacă se bifează căsuța
„Gratuit”, câmpurile vor fi din nou blocate iar prețul pentru anunț va fi 0.
Pasul trei se termina cu un câmp pentru descriere. Acesta are un contor
de caractere ramase și este limitat la 9000 de caractere.
Dacă toate inputurile din formular sunt valide utilizatorul poate avansa
la cel de al patrulea pas. În cadrul acestui pas, utilizatorul poate adaugă pozele
anunțului. Numărul de poze disponibile este diferit de la o subcategorie la alta.
Acesta se stabilește în momentul definirii subcategoriei. Adăugarea pozelor nu
este obligatorie, utilizatorul având posibilitatea de a avansa direct la ultimul
pas.
APLICA ȚIE WEB DE PUBLICITATE
58
Figura 3.3.2.3 Al patrulea pas al stepper‐ului de adăugare anunț
În cadrul ultimulului pas, utilizatorul are posibilitatea de a introduce date
de contact, dacă acesta nu este înregistrat. Dacă este înregistrat și are setate date
de contact implicite acestea se completeaz ă automat. De asemenea, acesta poate
alege, folosind un „checkbox”, dacă dorește să fie contactat prin mesagerie
pentru anunțul respectiv. De aici utilizatorul poate sa publice anunțul sau sa îl
pre vizualizeze. Dacă alege sa îl pre vizualizeze el va fi redirecționat către
pagina de detalii unde anunțul afișat este cel pe care dorește să‐l adauge. Dacă
considera ca este în regulă îl poate adaugă apăsând pe butonul Adăugare.
3.3.3. Anun țuri
Navigarea către pagina de anunțuri se face atunci când utilizatorul apăsa
pe iconiță de căutare din inputul „Caută”. Înainte de a realiză navigarea, se
compune URL‐ul paginii de anunțuri. URL‐ul paginii va conține ca parametri
APLICA ȚIE WEB DE PUBLICITATE
59
de query perechi cheie‐valoare pentru fiecare atribut selectat în timpul filtrării
inițiale din pagina de acasă. Cheia reprezint ă id‐ul atributului și valoare
reprezint ă ori id‐ul detaliului selectat, în cazul atributelor cu detalii, ori valoarea
introdus ă manual de utilizator, în cazul atributelor folosite pentru
sortare. Alți
parametri URL posibili sunt: id‐ul subcategoriei selectate, id‐ul valutei în care
se va face afișarea prețurilor anunțurilor și filtrarea după preț, id‐ul locației
selectate și textul de query introdus în inputul de căutare. De asemenea, înainte
de a se realiza navigarea se aduc anunțurile care corespund cu filtrele alese de
utilizator.
Filtrarea anunțurilor se realizeaz ă pe server în cadrul funcției
getAnunturi a controllerului de anunțuri. Această funcție tratează o cerere de
tip POST, care conține toate atributele selectate de utilizator în format JSON.
Figura 3.3.3.1 Exemplu de obiect trimis ca body pentru cererea de getAnunturi
În cadrul acestei funcții se construie ște un obiect de query MongoDB în funcție
de valorile din obiectul trimis că body. Valoarea atributelor din array‐ul de
atribute, care nu sunt forSorting, este adăugat în array‐ul care reprezint ă
valoarea cheii $and din query. Cheia $and îndepline ște un rol similar cu sintaxa
„WHERE AND” din limbajul SQL. După ce se construie ște obiectul de query se
APLICA ȚIE WEB DE PUBLICITATE
60
execută query‐ul. Valorile returnate sunt apoi filtrate încă o dată, de data această
cu atributele forSorting. În cazul în care atributul sortează prețul, valoarea
pentru atribut este mai întâi convertit ă în valută în care a fost adăugat anunțul
filtrat și apoi se face filtrarea.
Anunțurile rămase după această etapă de filtrare
sunt returnate în client unde sunt afișate în cadrul paginii de anunțuri.
Figura 3.3.3.2 Exemplu de rezultat a filtrării anunțurilor din subcategoria Automobile
după marca și preț
Componen ța AnuntiriComponent este componen ța de bază în cadrul
paginii de anunțuri. În stânga paginii se află un o bara care conține inputurile
din SearchComponent, două carduri de tip CategoryCardComponent, un select
cu valutele disponibile, cele două atribute de tip AtributeComponent folosite la
sortarea anunțurilor după preț, atributele care țin de categoria selectată și un
buton de filtrare, la apăsarea căruia se realizeaz ă filtrarea propriu‐zisă a
APLICA ȚIE WEB DE PUBLICITATE
61
anunțurilor. Câmpurile din SearchComponent funcționează la fel că și pe
pagină de acasă, câmpul pentru locație fiind un „typeahead” pentru locațiile
din baza de date. Cardurile de categorie și subcategorie funcționează ca afișaj
pentru categoria, respectiv subcategoria, selectată. Dacă utilizatorul ap
ăsa pe
cardul de categorie se deschide un modal de unde poate selecta una dintre
categoriile disponibile. Odată ce utilizatorul a selectat o categorie acesta are
acces la cardul ce reprezint ă subcategoria, funcționalitatea ce ține de selectare
fiind similară. După ce se selecteaz ă o subcategorie se vor afișa în bara din
stânga atributele ce țin de ea. Selectul de valută are ca valoare selectată implicită
valută „EUR”. Dacă utilizatorul schimbă valuta selectată se realizeaz ă conversia
prețurilor anunțurilor afișate în noua valută. Această conversie se face pe client.
Atributele pentru sortarea în funcție de preț sunt prezente permanent,
indiferent dacă s‐a selectat o subcategorie sau nu. Filtrarea anunțurilor în
funcție de preț se realizeaz ă în valuta selectată pe client, indiferent de valuta în
care s‐a creat anunțul. În cazul în care utilizatorul a selectat o subcategorie
acesta are acces la atributele ce țin de subcategoria respectiv ă. Aceste atribute
funcționează la fel ca și în pagină de acasă, fiind aceeași component ă.
Utilizatorul poate actualiza lista de anunțuri folosind butonul „Filtreaz ă”.
În partea stânga a componen ței AnunturiComponent avem anunțurile
propriu‐zise. Acestea sunt afișate în ordine crescătoare a datei la care au fost
adăugate. Pentru fiecare anunț sunt afișate: prima imagine, titlul, locația, dată
adăugării cu numărul de zile care au trecut de atunci, prețul și iconiță de
adăugare la favorite. Această secțiune funcționează cu principiul „infinite
scroll”. Cererea inițială aduce primele 50 de anunțuri care corespund cu filtrele
APLICA ȚIE WEB DE PUBLICITATE
62
introduse iar atunci când utilizatorul ajunge cu bara de scroll aproape de
capătul paginii se aduc următoarele 50 și așa mai departe.
În cazul în care utilizatorul acceseaz ă pagina de anunțuri direct cu URL,
acesta nerealizând navigarea de pe pagina de acasă, se
completeaz ă automat
categoria selectată și subcategoria selectă, dacă este cazul, valorile pentru
atribute și se aduc anunțurile corespunz ătoare.
Utilizatorul poate naviga spre pagina de detalii prin click pe titlul sau
poza unui anunț.
3.3.4. Detalii anun ț
Figura 3.3.4.1 Pagina de detaliu pentru un anunț
APLICA ȚIE WEB DE PUBLICITATE
63
URL‐ul pentru această pagină este compus din titlul anunțului și id‐ul
anunțului ca și parametru de query. Partea de titlu din URL este ignorată, ea
având rolul de a face URL‐ul mai lizibil. ID‐ul anunțului este obligatoriu iar
dacă se
încearcă navigarea folosind un id fals sau fără id utilizatorul este
redirecționat spre pagina de 404.
Pagina conține titlul anunțului și prețul, în partea de sus. De asemenea
sunt vizibile două butoane de navigare: „Anunțul precedent” și „Anunțul
Următor”. Aceste butoane sunt vizibile doar dacă utilizatorul a ajuns pe această
pagină din pagină de filtrare a anunțurilor. Anunțurile filtrate sunt stocate în
Store‐ul aplicației, în reductorul „anunț” și sunt disponibile în pagină de detalii.
Utilizatorul are posibilitatea de a naviga prin anunțurile deja încărcate, fără să
fie nevoit să se reîntoarc ă în pagină de filtrare. Dacă utilizatorul navigheaz ă prin
anunțuri folosind butonul „anunțul următor” și ajunge la penultimul anunț
încărcat, clientul va trimite automat o cerere către server, folosind același obiect
de query cu care a fost filtrat anunțul curent, pentru a aduce următoarele 100 de
anunțuri. Deoarece această navigare introduce înregistrări în istoricul
browserului, butonul de înapoi al acestuia nu va mai fi de folos utilizatorului în
cazul în care dorește să se întoarcă la pagină de filtrare a anunțurilor. De aceea
în partea de stânga sus, pe bară de navigare, apare un buton care îl
redirecționează spre acea pagină. Pagină va fi completat ă cu toate anunțurile
încărcate iar atributele vor fi completate automat, dacă este cazul. Acest lucru
este posibil doar dacă utilizatorul a ajuns pe pagină de detalii prin navigare din
pagina de filtrare. În caz contrat, obiectul de query și anunțurile pe care acesta
l‐a filtrat nu se află în Store‐ul aplicației.
APLICA ȚIE WEB DE PUBLICITATE
64
Sub titlul anunțului se află imaginile care au fost publicate de către
autorul anunțului în momentul adăugării acestuia. Partea stânga a paginii
continuă cu o secțiune care conține avatarul utilizatorului care a publicat
anunțul și numele acestuia. Lângă aceste elemente
se află data publicării
împreun ă cu numărul de zile de la aceasta. Partea stânga se încheie cu descrierea
anunțului și numărul de vizualizări.
În partea stânga se află prețul și două butoane: unul de contactarea
vânzătorului unul de adăugare la favorite anunțului. La apasarea butonului
de contact se deschide un modal din care se poate alege dintre doua metode de
contact: prin telefon sau prin mesagerie.
Figura 3.3.4.2 Modalul de contact pentru un anunț
APLICA ȚIE WEB DE PUBLICITATE
65
3.3.5. Mesageria Instant ă
Partea de client dispune de un sistem de mesagerie instantă intre
utilizatori. Dacă utilizatorul alege metoda de contact Mesaj din dialogul ce
contact pentru un anunț se deschide o fereastră în partea de dreapta jos a paginii
prin care acesta poate comunica cu utilizatorul care a adăugat anunțul.
Mesajele trimise de către utilizatorul activ sunt afișate în partea dreapta
iar cele primite în partea stânga. Dacă celălalt utilizator a văzut mesajul acest
lucru se afișează în fereastra.
Figura 3.3.5.1 Mesaj văzut
APLICA ȚIE WEB DE PUBLICITATE
66
Mesageria funcționează folosind un pachet numit socket.io. Prin
intermediul acestuia clientul comunica în permanenta cu serverul, prin
protocolul WebSocket. Acest socket funcționează prin evenimente. Atunci când
un utilizator se autentifica pe client se creează o conexiune cu serverul iar
serverul răspunde cu conversa țiile recente.
Aceste conversa ții sunt afișate într‐
un meniu disponibil pe toate paginile aplicației. Meniul se poate deschide
folosind iconița specifica de pe bara de navigație. Dacă utilizatorul da click pe
una dintre conversa ții se deschide fereastra de chat și se cer mesajele ce țin de
conversa ția respectiva de la server.
Figura 3.3.5.2 Meniul cu conversa ții recente
În momentul în care un utilizator trimite un mesaj, destinatarul va primii
mesajul respectiv prin intermediul socket‐ului și se va deschide automat
fereastra de chat, în cazul în care aceasta a fost închisă. Dacă utilizatorul da click
APLICA ȚIE WEB DE PUBLICITATE
67
pe fereastra de chat se emite evenimentul care tine de faptul ca acesta a văzut
mesajul iar utilizatorul care l‐a trimis va fi notificat de către socket ca mesajul a
fost văzut. Dacă în momentul primirii mesajului tabul în care este deschisă
aplicația nu
este activ se browser‐ul va reda un sunet de notificare iar titlul
tabului va afișa intermitent faptul ca utilizatorul a primit un mesaj nou.
3.3.6. Anun țurile Tale
În cadrul acestei pagini utilizatorul poate gestiona anunțurile pe care le‐
a adăugat, atât cele active cât și cele inactive. El poate modifica un anunț activ
sau îl poate șterge. De asemenea poate dezactiva un anunț activ sau poate
reactiva un anunț inactiv.
Figura 3.3.6.1 Pagina de Anunțurile Tale
APLICA ȚIE WEB DE PUBLICITATE
68
3.3.7. Set ări
În aceasta pagina utilizatorul își poate modifica setările contului. Acesta
își poate schimba numele de utilizator, locația și nr telefon. Dacă utilizatorul își
adaugă locația și numărul de telefon acestea vor fi completate automat în
momentul adăugării unui anunț nou.
De asemenea, în aceasta pagina utilizatorul își poate schimba adresa de
e‐mail sau parola. În ambele cazuri este necesara confirmarea schimbării prin
intermediul e‐mailului care se trimite automat.
APLICA ȚIE WEB DE PUBLICITATE
69
Capitolul 4
Concluzii
4.1 Dezvolt ări în viitor
Aplicația își atinge obiectivul, punând la dispoziția utilizatorilor o
platform ă modernă și dinamică, prin intermediul căreia aceștia pot publica cu
usurință și gratuit anunțuri de mică publicitate.
Pentru a pregăti aplicația de producție, însă, sunt necesare anumite
îmbunătățiri. Este necesară scrierea de teste automate, aplicația în formatul
actual fiind testată manual, ceea ce nu este optim. Baza de date ar mai putea fi
normalizat ă iar volumul de date tranzacționate între client și server redus.
Aceasta poate fi dezolvat ă prin adaugarea de funcționalitate nouă,
precum un sistem de reputație pentru utilizatori, metode mai complexe de
filtrare al anunțurilor și instrumente de administrare.
Deoarece serverul este facut dupa modelul REST API, îmi propun sa creez
versiuni mobile pentru Android și iOS, numărul utilizatorilor care folosesc
telefonul mobil pentru navigarea pe internet fiind în continuă creștere.
APLICA ȚIE WEB DE PUBLICITATE
70
Bibliografie
[1] N.Murray, A. LERNER , F.Coury și C.Taborda, Ng‐Book: The Complete Guide to
Angular , CreateSpace Independent Publishing Platform, 2018
[2] B. SYED, Beginning Node.js , Apress, 2014
[3] B. SYED, Typescript Deep Dive, ARTPOWER International PUB, 2017
[4] J.DICKEY , Write Modern Web apps with the MEAN stack: Mongo, Express,
AngularJS, and Node.js , Peachpit Press, 2014
[5] A.N AYAK , MongoDB Cookbook , Packt Publishing Ltd, 2014
Copyright Notice
© Licențiada.org respectă drepturile de proprietate intelectuală și așteaptă ca toți utilizatorii să facă același lucru. Dacă consideri că un conținut de pe site încalcă drepturile tale de autor, te rugăm să trimiți o notificare DMCA.
Acest articol: Lect. univ. dr. MARIETA GÂTA [611705] (ID: 611705)
Dacă considerați că acest conținut vă încalcă drepturile de autor, vă rugăm să depuneți o cerere pe pagina noastră Copyright Takedown.
