Șef lucrări dr. Ing. Ionuț Cristian Resceanu Iulie, 2017 CRAIOVA ii UNIVERSITATEA DIN CRAIOVA FACULTATEA DE AUTOMATICĂ, CALCULATOARE ȘI ELECTRONICĂ… [627127]

i

UNIVERSITATEA DIN CRAIOVA
FACULTATEA DE AUTOMATICĂ, CALCULATOARE ȘI
ELECTRONICĂ

DEPARTAMENTUL DE AUTOMATICĂ ȘI ELECTRONICĂ

PROIECT DE DIPLOMĂ
Robert Gabriel Rădoi

COORDONATOR ȘTIINȚIFIC
Șef lucrări dr. Ing. Ionuț Cristian Resceanu

Iulie, 2017
CRAIOVA

ii
UNIVERSITATEA DIN CRAIOVA
FACULTATEA DE AUTOMATICĂ, CALCULATOARE ȘI
ELECTRONICĂ

DEPARTAMENTUL DE AUTOMATICĂ ȘI ELECTRONICĂ

APLICAȚIE PENTRU GESTIONARE CONȚINUT AUDIO -VIDEO
FOLOSIND NODEJS, MONGODB, ANGULARJS, BOOTSTRAP
Robert G abriel Rădoi

COORDONATOR ȘTIINȚIFIC
Șef lucrări dr. Ing. Ionuț Cristian Resceanu

Iulie, 2017
CRAIOVA

iii „Învățătura este o comoară care își urmează stăpânul pretutindeni .”
Proverb popular

iv DECLARAȚIE DE ORIGINALITATE

Subsemnatul RĂDOI ROB ERT GABRIEL , student: [anonimizat], Calculatoare și Electronică a Universit ății din
Craiova, certific prin prezenta că am luat la cunoșt ință de cele prezentate mai jos și că î mi asum, î n
acest context, originalita tea proiectului meu de licență :
 cu titlul APLICAȚIE PENTRU GESTIONARE CONȚINUT AUDIO -VIDEO FOLOSIND
NODEJS , MONGODB, ANGULARJS, BOOTSTRAP
 coordonată de ȘEF LUCRĂRI DR. ING. IONUȚ CRISTIAN RESCEANU ,
 prezentată în sesiunea IULI E 2017 .
La elaborarea proiectului de licență, se consideră plagiat una dintre următoarele acțiuni:
 reproducerea exactă a cuvintelor unui alt autor, dintr -o altă lucrare, în limba română sau prin
traducere dintr -o altă limbă, dacă se omit ghilimele și refe rința precisă,
 redarea cu alte cuvinte, reformularea prin cuvinte proprii sau rezumarea ideilor din alte
lucrări , dacă nu se indică sursa bibliografică,
 prezentarea unor date experimentale obținute sau a unor aplicații realizate de alți autori fără
menți onarea corectă a acestor surse,
 însușirea totală sau parțială a unei lucrări în care regulile de mai sus sunt respectate, dar care
are alt autor.
Pentru evitarea acest or situații neplăcute se recomandă:
 plasarea într e ghilimele a citatelor directe și indicarea referinței într -o listă corespunzătoare la
sfărșitul lucrării,
 indicarea în text a reformulării unei idei, opinii sau teorii și corespunzător în lista de referințe
a sursei originale de la care s -a făcut preluarea,
 precizarea sursei de la care s -au preluat date experimentale, descrieri tehnice, figuri, imagini,
statistici, tabele et caetera ,
 precizarea referințelor poate fi omisă dacă se folosesc informații sau teorii arhicunoscute, a
căror paternitate este unanim cunoscută și acceptată.
Data , Semnătura candidat: [anonimizat] ,

v
UNIVERSITATEA DIN CRAIOVA
Facultatea de Automatică, Calculatoare și Electronică

Departamentul de Automatică și Electronică
Aprobat la data de
…………………
Șef de departament,
Prof. dr. ing.
Emil PETRE

PROIECTUL DE DIPLOMĂ

Numele și prenumele
student: [anonimizat]/ -ei:
Rădoi Robert Gabriel

Enunțul temei:

Aplicație pentru gestionare conținut audio -video folosind
NodeJS, MongoDB, AngularJS, Bootstrap

Datele de pornire:

NodeJS, AngularJS, MongoDB, Bootstrap , SPA

Conținutul proiectului :

1. Introducere
2. Introducere în JavaScript, AngularJS, NodeJS ,
SPA și Bootstrap
3. Proiectarea bazei de date
4. Prezentarea aplicației
5. Arhitectura sistemului. Realizare practică
6. Concluzii

Material grafic obligatoriu:
Imagini, capturi de ecran

Consultații:
Periodice
Conducătorul științific
(titlul, nume și prenume,
semnătura): Șef lucrări dr. ing. Resceanu Ionuț Cristian

Data eliberării temei :
05.11 .2016

Termenul estimat de predare a
proiectului :
04.07.2017

Data predării proiectului de către
student și semnătura acestuia:

vi
UNIVERSITATEA DIN CRAIOVA
Facultatea de Automatică, Calculatoare și Electronică

Departamentul de Automatică și Electronică

REFERATUL CONDUCĂTORULUI ȘTIINȚIFIC

Numele și prenumele candida tului/ -ei: Rădoi Robert Gabriel
Specializarea: Ingineria Sistemelor Multime dia
Titlul proiectului : Aplicație pentru gestionare conținut audio -video folosind
NodeJS, MongoDB, AngularJS, Bootstrap
Locația în care s -a realizat practica de
documentare (se bifează una sau mai
multe din opțiunile din dreapta): În facultate □
În pr oducție □
În cercetare □
Altă locație: [se detaliază ]

În urma analizei lucrării candidatului au fost constatate următoarele:
Nivelul documentării Insuficient
□ Satisfăcător
□ Bine
□ Foarte bine

Tipul proiectului Cercetare
□ Proiectare
□ Realizare
practică □ Altul
[se detaliază ]
Aparatul matematic utilizat Simplu
□ Mediu
□ Complex
□ Absent

Utilitate Contract de
cercetare □ Cercetare
internă □ Utilare
□ Altul
[se detaliază ]
Redactarea lucrării Insuficient
□ Satisfăcător
□ Bine
□ Foarte bine

Partea grafică, desene Insuficient ă
□ Satisfăcătoare
□ Bună
□ Foarte bună

Realizarea
practică Contribuția autorului Insuficientă
□ Satisfăcătoare
□ Mare
□ Foarte mare

Complexitatea
temei Simplă
□ Medie
□ Mare
□ Complex ă

Analiza cerințelor Insuficient
□ Satisfăcător
□ Bine
□ Foarte bine

Arhitectura Simplă
□ Medie
□ Mare
□ Complexă

Întocmirea
specificațiilor
funcționale Insuficientă
□ Satisfăcătoare
□ Bună
□ Foarte bună

vii Implementarea Insufi cientă
□ Satisfăcătoare
□ Bună
□ Foarte bună

Testarea Insuficientă
□ Satisfăcătoare
□ Bună
□ Foarte bună

Funcționarea Da
□ Parțială
□ Nu

Rezultate experimentale Experiment propriu
□ Preluare din bibliografie

Bibliografie Cărți
Reviste
Articole
Referințe web

Comentarii
și
observații

În concluzie, se propune:

ADMITEREA PROIECTULUI
□ RESPINGEREA PROIECTULUI

Data, Semnătura conducătorului științific,

viii
CUPRINS
CAPITOLUL I ………………………….. ………………………….. ………………………….. ………………………….. ………………. 14
1.1 SCOPUL ………………………….. ………………………….. ………………………….. ………………………….. ……………… 14
1.2 MOTIVAȚIA ………………………….. ………………………….. ………………………….. ………………………….. …………. 14
CAPITOLUL II ………………………….. ………………………….. ………………………….. ………………………….. ……………… 15
2.1 LIMBAJUL JAVASCRIPT ………………………….. ………………………….. ………………………….. …………………………. 15
2.1.1 Caracterizare ………………………….. ………………………….. ………………………….. …………………….. 15
2.1.2 JavaScript și Java ………………………….. ………………………….. ………………………….. ……………….. 15
2.1.3 Structura unui program JavaScript ………………………….. ………………………….. …………………… 16
2.2 INTRODUCERE IN ANGULAR JS ………………………….. ………………………….. ………………………….. ………………… 16
2.2.1 Prezentare generală ………………………….. ………………………….. ………………………….. …………… 16
2.2.2 Arhitectura MVC ………………………….. ………………………….. ………………………….. ………………… 17
2.3 INTRODUCERE ÎN NODEJS ………………………….. ………………………….. ………………………….. …………………….. 18
2.3.1 Prezentare generală ………………………….. ………………………….. ………………………….. …………… 18
2.3.2 Comparație între NodeJS și PHP ………………………….. ………………………….. ……………………….. 19
2.4 APLICAȚIE STRUCTURATĂ PE O SINGURĂ PAGINĂ (SINGLE PAGE APPLICATION ) ………………………….. …………………. 20
2.4.1 Prezentare generală ………………………….. ………………………….. ………………………….. …………… 20
2.4.2 Avantajele și dezavantajele unei SPA ………………………….. ………………………….. ………………… 20
2.5 BOOTSTRAP ………………………….. ………………………….. ………………………….. ………………………….. ………… 22
2.5.1 Descriere generală ………………………….. ………………………….. ………………………….. …………….. 22
2.5.2 Avantajele și dezavant ajele Bootstrap -ului ………………………….. ………………………….. ………… 22
CAPITOLUL III ………………………….. ………………………….. ………………………….. ………………………….. …………….. 23
3.1 BAZE DE DATE NOSQL ………………………….. ………………………….. ………………………….. ………………………… 23
3.1.1 Prezentare generală ………………………….. ………………………….. ………………………….. …………… 23
3.1.2 MongoDB ………………………….. ………………………….. ………………………….. …………………………. 23
3.1.2.1. Descriere ………………………….. ………………………….. ………………………….. ………………………….. ….. 23
3.1.2.2. Operații de tip CRUD (Create, Read, Update, Delete) ………………………….. …………………………. 24
3.1.2.3. MongoDB Compass ………………………….. ………………………….. ………………………….. ………………. 25
3.2. CREAREA COLECȚIILOR B AZEI DE DATE ………………………….. ………………………….. ………………………….. ……….. 25
3.3. STRUCTURA INTERNĂ A B AZEI DE DATE ………………………….. ………………………….. ………………………….. ………. 27
CAPITOLUL IV ………………………….. ………………………….. ………………………….. ………………………….. …………….. 30
4.1. PREZENTARE GENERALĂ ………………………….. ………………………….. ………………………….. ……………………….. 30
4.2. FEREASTRĂ DE AUTENTIF ICARE ………………………….. ………………………….. ………………………….. ……………….. 31

ix 4.3. FEREASTRA PRINCIPALĂ ………………………….. ………………………….. ………………………….. ……………………….. 33
4.4. SECȚIUNEA – MENIURI ………………………….. ………………………….. ………………………….. ………………………… 33
4.4.1. Meniul Acasă – Căutare ………………………….. ………………………….. ………………………….. …… 34
4.4.2. Meniul Show -uri personale ………………………….. ………………………….. ………………………….. 35
4.4.3. Meniul Show -uri în desfășurare ………………………….. ………………………….. ……………………. 36
4.4.4. Meniul Calendar ………………………….. ………………………….. ………………………….. …………….. 36
4.4.1. Meniul Adaugă ………………………….. ………………………….. ………………………….. ……………… 37
4.4.2. Meniul Statistici ………………………….. ………………………….. ………………………….. …………….. 38
4.4.3. Zona de administrare ………………………….. ………………………….. ………………………….. ……… 39
4.4.4. Meniul Setări personale ………………………….. ………………………….. ………………………….. ….. 41
4.5. FEREASTRĂ DETALII SHOW ………………………….. ………………………….. ………………………….. ……………………. 42
4.6. FEREASTRĂ SHOW PERSONAL ………………………….. ………………………….. ………………………….. …………………. 43
CAPITOLUL V ………………………….. ………………………….. ………………………….. ………………………….. ……………… 46
5.1. STRUCTURA APLICAȚIEI ………………………….. ………………………….. ………………………….. ………………………… 46
5.2. ANALIZA RUTELOR APLIC AȚIEI………………………….. ………………………….. ………………………….. …………………. 47
5.2.1. Ruta “login” ………………………….. ………………………….. ………………………….. ………………….. 48
5.2.2. Ruta “signup” ………………………….. ………………………….. ………………………….. ………………… 50
5.2.3. Ruta principală ………………………….. ………………………….. ………………………….. ………………. 52
5.2.4. Ruta “ myshows ” ………………………….. ………………………….. ………………………….. ……………. 52
5.2.5. Ruta “ upcoming ” ………………………….. ………………………….. ………………………….. …………… 53
5.2.6. Ruta “calendar” ………………………….. ………………………….. ………………………….. …………….. 53
5.2.7. Ruta “ settings ” ………………………….. ………………………….. ………………………….. ………………. 54
5.2.8. Ruta “admin” ………………………….. ………………………….. ………………………….. ………………… 54
5.2.9. Ruta “ add” ………………………….. ………………………….. ………………………….. ……………………. 56
5.2.10. Ruta “shows/id” ………………………….. ………………………….. ………………………….. …………….. 57
5.2.11. Ruta “statistics” ………………………….. ………………………….. ………………………….. …………….. 58
5.2.12. Ruta „result/id” ………………………….. ………………………….. ………………………….. ……………… 59
5.3. AVERTIZARE E -MAIL ………………………….. ………………………….. ………………………….. ………………………….. .. 60
CAPITOLUL V I ………………………….. ………………………….. ………………………….. ………………………….. …………….. 62
BIBLIOGRAFIE ………………………….. ………………………….. ………………………….. ………………………….. ……………. 63
REFERINȚE WEB ………………………….. ………………………….. ………………………….. ………………………….. …………. 64
ANEXĂ COD ………………………….. ………………………….. ………………………….. ………………………….. ………………. 65
INDEX ………………………….. ………………………….. ………………………….. ………………………….. ………………………. 91

x LISTA FIGURILOR
Figura 1 Arhitectur a MVC ………………………….. ………………………….. ………………………….. ………………………….. ….. 17
Figura 2 Schema NodeJS ………………………….. ………………………….. ………………………….. ………………………….. ……. 18
Figura 3 PHP vs NodeJS ………………………….. ………………………….. ………………………….. ………………………….. ……… 19
Figura 4 Single Page Application și Multi Page Application ………………………….. ………………………….. …………….. 21
Figura 5 Operația Create ………………………….. ………………………….. ………………………….. ………………………….. …… 24
Figura 6 Operația Read ………………………….. ………………………….. ………………………….. ………………………….. ……… 24
Figura 7 Operația Update ………………………….. ………………………….. ………………………….. ………………………….. ….. 24
Figura 8 Operația Delete ………………………….. ………………………….. ………………………….. ………………………….. …… 24
Figura 9 Fereastră MongoDB Co mpass ………………………….. ………………………….. ………………………….. ……………. 25
Figura 10 Baza de date a aplicației ………………………….. ………………………….. ………………………….. ………………….. 27
Figura 11 Structură Bază de date – MongoDB ………………………….. ………………………….. ………………………….. …… 28
Figura 12 S tructură Bază de date – MySQL ………………………….. ………………………….. ………………………….. ……….. 29
Figura 13 Schema înregistrare ………………………….. ………………………….. ………………………….. ………………………… 30
Figura 14 Schemă utilizator ………………………….. ………………………….. ………………………….. ………………………….. .. 31
Figura 15 Fereastră de autentificare ………………………….. ………………………….. ………………………….. ……………….. 31
Figura 16 Autentificare folosind Facebook ………………………….. ………………………….. ………………………….. ……….. 32
Figura 17 Fereastră Principală ………………………….. ………………………….. ………………………….. ………………………… 33
Figura 18 Meniu Princip al ………………………….. ………………………….. ………………………….. ………………………….. ….. 34
Figura 19 Meniu Acasă – Căutare ………………………….. ………………………….. ………………………….. ……………………. 35
Figura 20 Meniu Show -uri personale ………………………….. ………………………….. ………………………….. ……………….. 35
Figura 21 Meniu Sho w-uri în desfășurare ………………………….. ………………………….. ………………………….. …………. 36
Figura 22 Meniu Calendar ………………………….. ………………………….. ………………………….. ………………………….. …. 37
Figură 23 Meniu Adaugă ………………………….. ………………………….. ………………………….. ………………………….. …… 37
Figura 24 Fereastră Statistici ………………………….. ………………………….. ………………………….. ………………………….. 38
Figura 25 Zona de administrare ………………………….. ………………………….. ………………………….. ………………………. 39
Figura 26 Secțiune utilizatori ………………………….. ………………………….. ………………………….. ………………………….. 40
Figura 27 Secțiunea Setări utilizator ………………………….. ………………………….. ………………………….. ………………… 40
Figura 28 Administrare seriale utilizatori ………………………….. ………………………….. ………………………….. …………. 41
Figura 29 Meniu Setări personale ………………………….. ………………………….. ………………………….. ……………………. 41
Figura 30 Fereastră D etalii adăugare show ………………………….. ………………………….. ………………………….. ………. 42
Figură 31 Fereastră Detalii eliminare show ………………………….. ………………………….. ………………………….. ………. 43
Figura 32 Fereastră Show personal ………………………….. ………………………….. ………………………….. …………………. 43
Figura 33 Detalii despre show ………………………….. ………………………….. ………………………….. ………………………… 44
Figura 34 Sezoane și detalii despre acestea ………………………….. ………………………….. ………………………….. ……… 44
Figura 35 Fereastră vizionare episod ………………………….. ………………………….. ………………………….. ……………….. 45
Figura 36 Structura View -uri ………………………….. ………………………….. ………………………….. ………………………….. . 46

xi Figura 37 Avertizare e -mail ………………………….. ………………………….. ………………………….. ………………………….. .. 60
Figura 38 E -mail Episod nou ………………………….. ………………………….. ………………………….. ………………………….. . 61

xii REZUMATUL PROIECTULUI

Această aplicație oferă posibilitatea căutarii prin serialele aflate în baza de date și adăugarea
acestora în categoria serialelor personale ale unui utilizator.
Aplicația conține două tipuri de utilizatori:
 utilizatorul normal
 administratorul

Difere nța dintre cele două tipuri de utilizatori este următoarea: administratorul are aceleași
posibilități ca și utilizatorul normal (de a adăuga și de a șterge serialele din secțiunea „ Seriale
Personale” însă, în plus, el poate administra utilizatorii înregist rați având posibilitatea de a le edita
datele personale, de a le șterge anumite seriale preferate, precum și posibilitatea de ștergere definitivă
a acestora.
Primul capitol al lucr ării prezintă scopul acestei aplicații precum și motivația de a crea o asfte l
de aplicație.
Al doilea capitol al lucră rii este alcătuit din cinci subcapitole, ast fel: primul subcapitol
prezintă o scurtă descriere a JavaScript -ului, precum și o comparație între acesta și limbajul Java . Cel
de-al doilea, prezintă o scurtă descriere a ceea ce înseamnă AngularJS alături de o arhitectură MVC.
În cel de -al treilea subcapitol este prezentat NodeJS -ul și o c omparație între acesta și PHP . Cel de -al
patrulea subcapitol conține informații referitoare la o SPA, precum și avantajele și dezavan tajele
folosirii unei astfel de aplicații . Ultimul subcapitol conține o scurtă prezentare a ceea ce înseamnă
Bootstrap.
Cel de -al treile a capitol al lucrării descrie o bază de date de tip NoSQL, arhitectura bazei de
date precum și o scurtă prezentare a pro gramului MongoDB Compass, program utilizat pentru
realizarea acesteia dintr -o interfață grafică.
Capitolul patru conține prezentarea aplicației, componentele din care aceasta este alcătuită, și
modul de funcționare a acestora. Tot aici sunt prezentate meni urile ș i submeniurile specifice, și modul
de interacțiune al utilizatorului cu aplicația.

xiii În capitolul cinci este prezentată ahitectura de la baza aplicației și realizarea practică a
acesteia. Aplicația funcționează pe baza arhitecturii MVC. Pentru fiecare fereastră sunt exemplificate
secvențe de cod corespunzătoare.
Lucrarea se încheie prin prezentarea concluziilor care reiau o serie de idei din cuprinsul
acest eia, concluzii care sunt confirmarea rezumatului și a temei tratate în lucrare.

Termenii cheie : AngularJS, NodeJS, Bootstrap, MongoDB, MVC .

14
CAPITOLUL I

1.1 Scopul
Această aplicație a fost concepută cu scopul de a ajuta utilizatorii să își gestioneze serialele
preferate, precu m și să fie la cur ent cu ultimele episoade apărute.
Aplicația oferă posibilitatea căutarii în câteva secunde printre serialele aflate în baza de date și
adăugarea acestora într -o secțiune de „Seriale Personale”. După a dăugarea în această secțiune,
utilizatorul poate să -și marc heze episoadele vizionate .
Fiecare serial pe care un utilizator l -a înregistrat în secțiunea anterioară , și care se află în
desfășurare, va crea în baza de date un job, trimițând un e-mail de fiecare dată când un episod nou o
să apară.
Prin utilizarea ei, o astfel de aplicație devine folositoare în momentele în care un utilizator nu
dorește să piardă șirul episoadelor vizionate sau dorește să fie înștiințat cu privire la episoade le care
urmează să apară .

1.2 Motivația
Am ales această temă din d orința de a învăța să proiectez o aplicație de tip SPA( Single Page
Application) folosind un framework nou, cum ar fi AngularJS.
Înlocuirea tabelelor unei baze de date relaționale , cum ar fi MySQL , cu colecțiil e MongoDB –
ului, care este o bază de date de tip NoSQL a reprezentat o provocare.
Am considerat o astfel de temă ca fiind una utilă datorită faptului că în viitor , tot mai multe
site-uri web vor lua în considerare această abordare, de a crea o aplicație de tip SPA în defavoarea
unei aplicații de tip MPA (Mult i Page Application).

15 CAPITOLUL I I
– Introducere în JavaScript, AngularJS, NodeJS și Bootstrap –

2.1 Limbajul JavaScript
2.1.1 Caracterizare
JavaScript este un limbaj de programare OOP (ori entat pe obiecte) , care se bazează pe
conceptul prototipurilor. Acesta este foarte cunoscut, fiind folosit la construirea site -urilor web, însă
este folosit și în scopul accesării obiectelor încastrate (embedded) în alte aplicații.
Pe lângă mediul web, Ja vaScript mai este folosit și pentru widget -uri desktop sau pentru
documente PDF.
JavaScript deține o colecție de obiecte standard, precum Array, Math, Da te dar și elemente de
limbaj, cum ar fi structuri de control, operatori, declarații.

2.1.2 JavaScript și Jav a

Cele două limbaje de programare sunt fundamental diferite , fiind deseori confundate. Java a
apărut în anul 1995 și a introdus conceptul de VM (Virtual Machine), fiind folosit pentru a realiza
aplicații legate de partea de serv er.
JavaScript a apărut inițial sub denumirea de Live Script în anul 1995. Datorită succesului
major de care s -a bucurat Java, limbajul LiveScript a fost redenumit.
Diferențele dintre cele două limbaje:
 JavaScript este un limbaj OOP de scripting, în timp ce Java este un limbaj OOP de
programare
 JavaScript poate fi executat doar în cadrul browser -ului, pe când în Java pot fi create
aplicații atât pentru browser cât ș i pentru Mașina Virtuală
 codul limbajului Java trebuie să fie compilat, pe când cel din J avaScript e ste prezentat
sub formă de text
 sunt depende nte de plugin -uri diferite

16 2.1.3 Structura unui program JavaScript

Pentru a crea un program în limbajul JavaScr ipt nu este nevoie decât de un editor pent ru text,
cum ar fi NotePad++, SublimeText , Atom etc. Depi starea erorilor se face direct î n browser, fă ră a mai
fi nevoie de instalarea unui depanator pentru codul aplicației. Acest lucru constituie un avantaj față de
alte limbaje de programare, cum ar fi .NET, care are nevoie de Visual C# .NET sau Visual Basic
.NET.

O aplicație simplă, de tipul „ Hello W orld” , care apare în toate cărț ile de programare are
următoarea structură:
var mesaj_intampinare = „Hello World”;
console.log(mesaj_intampinare);

2.2 Introducere in Angula rJS
2.2.1 Prezentare generală
AngularJS este un framework dezvoltat la început de către Misko Hevery și Adam Abrons
fiind deținut în momentul de față de către cei de la Google. Acesta a fost construit în principal pentru
dezvoltarea ușoară a aplicațiilor Web.
Este folosit în special pentru SPA -uri (Single Page Application ), dar și pentru aplicațiile
mobile deoarece se combină foarte ușor cu Bootstrap, rezultatul fiind o aplicație fluidă, rapidă d ar și
care răspunde ușor , pornind de la o rezoluție mică, a unui dispozitiv mobil , până la o rezoluție mare
corespunzătoare unui browser web.
În comparație cu alte platforme open -source ale JavaScript -ului, AngularJS, deși ca
dimensiune (40kb) este mai mar e de exemplu decât BackboneJS (6.5kb), acesta din urmă necesită ș i
descărcarea unor librării suplimentare (jQuery și Underscore), ajungând la un număr mai mare de kb,
deci o încărcare mai lentă a paginii web.
Timpul de încărcare joacă un rol foarte import ant deoarece sunt folosite din ce in ce mai des
dispozitivele mobile (tablete, telefoane) care folosesc un timp relativ superior de încărcare față de
conexiunile clasice.

17 2.2.2 Arhitectura MVC
O arhitectură de tip MVC ( Model – View – Controller ) este alcătuită din trei părți:
 Model – este responsabil pentru a dministrarea datelor aplicației
 View – afișează fizic datelor unui utilizator
 Controller – controlează interacțiunea dintre Model și View

Această arhitectură este foarte p opulară deoarece izolează partea logică a aplicației de partea
vizuală. Controller – ul primește cerințele aplicației, lucrând alături de model pentru a pregăti datele ce
vor fi afișate.

În AngularJS , modelul constă în toate datel e primitive , cum ar fi : Integer, String și Boolean și
tipuri complexe sub formă de obiecte. Pe scurt , modelul este un simplu obiect JavaScript. Însă acesta
poate fi construit folosind orice bază de date, precum Sql Server sau MySql sau chiar din JSON.
$scope.customer = {
”Nume”: ”Robert”
}
Eveniment
Controller
View
Model
Figura 1 Arhitectura MVC

18 În exemplul anterior, $scope este un obiect iar customer este o variabilă care este atașată de
scopul obiectului.
În AngularJS , view -ul este un element de DOM folosit pentru a afișa datele. Afișarea datelor
dintr -un controller se face folosind expresii în view.
<p> {{customer.nume}} </p>
În AngularJ S, controller -ul oferă un control asupra view -ului și modelului, protejând datele
în funcție de request. Cel mai import ant lucru este că modelul trăieș te în interiorul controller -ului.
function realCustomer($scope) {} ;

2.3 Introducere în NodeJS
2.3.1 Prezentare generală
NodeJS reprezintă JavaScript -ul ce lucrează pe partea de server. A fost creat de Ryan Dahl
alături de a lți programatori în 2009. În 2011 a fost lansat Node Package Manager (NPM) care are
scopul de a facilita instalarea altor module JavaScript dezvol tate de către comunitatea NodeJS .
NodeJS -ul a contribuit la creșterea și evoluția JavaScript -ului, utilizatori i realizând că
JavaScript -ul pe server nu este așa de rău ca pe browser.

Figura 2 Schema NodeJS

Un alt avantaj important al NodeJS -ului îl reprezintă faptul că are un singur fir de execuție și
faptul că este o arhitectură asincr onă (un bloc de comandă este executat numai după ce blocul anterior
a fost executat complet).

19 Aplicațiile NodeJS pot rula atât pe Windows, cât și pe Linux, macOS, sau servere Unix. Ele
pot fi scrise folosind CoffeeScript (o alternativă a JavaScript -ului), Dart, Microsoft TypeScript , sau
oricare alt limbaj care poate fi compilat la nivel de JavaScript.

2.3.2 Comparație între NodeJS și PHP
În timp ce în PHP funcțiile sunt executate secvențial, în NodeJS este adoptată procedura
asinc ronă, introducând promisiunile ș i callback -urile. Un alt avantaj al folosirii NodeJ S-ului îl
constituie faptul că este folosit același limbaj atât pe partea de server cât și pe partea de client.

Figura 3 PHP vs NodeJS

În compara ție cu PHP, care este un limbaj de programare, NodeJ S este mai de grabă un
intepretor pentru cod de JavaScript. Pentru a executa un cod de PHP pe server, trebuie inst alat PHP
Package, pe când NodeJS necesită Node.js installer.

Față de NodeJS , PHP conține p uține variabile și funcții , care pot fi mai ușor de folosit.Un alt
aspect de luat î n considerare îl reprezintă baza de date. PHP a fost creat să existe alături de MySQL și
variantele acestuia cum ar fi MariaDB, pe când NodeJ S poate interacționa cu SQL doar prin
intermediul unei librării.
Totuși, aplicațiile dezvoltate cu NodeJ S sunt mult mai rapide, însă PHP -ul dispune de unelte
mai dezvoltate

20 2.4 Aplicație structurată pe o singură pagină (Single Page Application)
2.4.1 Prezentare generală
O astfel de aplicație (Single Page Application) este o aplicație web, sau un site web care își
propune să ofere utilizatorului experiența unei aplicații desktop. Într -o astfe l de apli cație, toate
resursele necesare: HTML, JavaScript și CSS sunt încărcate o singură dată, iar apoi celelalte resurse
sunt adăugate dinamic , în funcție de necesităț ile utilizatorului.
Interacțiunea unei SPA cu serverul se face prin fișiere de tip JSO N (JavaScript Object
Notation), în timp ce paginile nu sunt în mod tradițional pagini. Când utilizatorul navighează,
conținutul încărcat este independent de conținutul aplicației, fiind numit View.
Deoarece SPA descarcă în avans structura paginii web, nu m ai este nevoie de un request
pentru conținut adiț ional. Acest lucru este similar ca și î n cazul unei aplicații desktop.

2.4.2 Avantajele și dezavantajele unei SPA
Avantaje:
o rapiditatea , toate resursele fiind încărcat e o singură dată
o ușurința depistării erorilor folosind Google Chrome, cu ajutorul căruia pot fi monitorizate
operațiile de la nivel de server, poate fi investigată pagina și datele asociate acesteia
o este simplă crearea unei aplicații mobile datorită codulu i folosit la nivel de server , acesta
putând fi utilizat atât pentru aplicații web dar și pentru aplicații mobile
o poate cache -ui orice local storage eficient. O aplicație trimite un singur request, stochează
toate datele și le poate folosi chiar și offlin e
o nu necesită cod suplimentar pentru a da render la o pagină pe server

Dezavantaje:
o necesită prezența și disponibilitatea JavaScript -ului. Dacă un utilizator blochează
JavaScript -ul în browser, este posibil ca aplicația să nu mai funcționeze corespu nzăto r
o este dificilă optimizarea SEO, deoarece conținutul este încărcat prin intermediul AJAX –
ului, datele fiind încărcate fără refresh la pagină
o comparativ cu o aplicație web tradițională, SPA este mai puțin securizată
o este greu de încărcat datorită framework -urilor de dimensiuni relativ mari

21
Diferența între modu l de funcționare ale unei aplicații de tip SPA și modul de funcționare a
unei aplicații de tip MPA este descrisă în următoarea figură:

2.1.1.

Figura 4 Single Page Application și Multi Page Application

22 2.5 Bootstrap
2.5.1 Descriere generală
Bootstrap este un framework folosit pentru a proiecta site -uri și aplicații web, dezvoltat inițial
de Twitter, ca și framework pentru munca internă di n companie. Acesta este în momentul de față cel
mai utilizat framework pentru interfețele web, el devenind un standard în crearea de template -uri
pentru sisteme cum ar fi WordPress și Joomla.
Acesta împarte pagina în 12 coloane ale căror valori procentual e sunt egale, fiind extrem de
fiabil, iar clasele responsive sunt controlate în funcție de lățimea de care dispune dispozitivul.
Printre c ompomente le de care dispune Bootstrap -ul se regăsește meniu l de navigare , care
include dropdown, carousel -ul, breadcru mbs ș i altele. Pe lângă acestea, mai conține și o serie de stiluri
pentru elemente de bază, cum ar fi: butoane, tabele, formulare.

2.5.2 Avantajele și dezavantaje le Bootstrap -ului
Avantaje :
 este open source – popularitatea acestei platforme vine din faptul că este gratuită
 ușurința folosirii – cunoscând bazele HTML -ului și CSS -ului, poate fi realizat un
design în Bootstrap
 are în componență elemente responsive
 rapiditatea finalizării proiectelor – folosind elemen tele predefinite din Bootstrap, se
poate salva mult timp
 documentație bogată și sprijinul comunității
 suport pentru majoritatea browserelor

Dezavantaje:
 dacă nu sunt customizate stilurile și culorile, site -ul poate începe să arate ca majoritatea
site-urilor existente
 poate necesita multe suprascrieri ale stilului dacă utilizatorul are în plan multe
customizări sau dorește să se abată de la structura Bootstrap -ului

23 CAPITOLUL II I
-Proiectarea bazei de date –

3.1 Baze de date NoSQL
3.1.1 Prezentare generală
NoSQL reprezintă o derivare a unui sistem de baze relaționale. Fiecare fișier dintr -o astfel de
bază de date este trimis către unul sau mai mulți operatori prin intermediul un ui mecanism de
redirectare IO (intrare -ieșire).
Principalele caracteristici:
– stochează obiecte (documente), reducând nev oia de join -uri ca în bazele de date
relaționale
– limbaj de interogare îmbună tățit, care însă păstrează principiile C++ și SQL
– prezintă s uport pentru indexare
– suportă atât operații de actualizare atomice dar și operațiile tradiționale
– posibilitat ea stocării unor fișiere mari fă ră să complice stiva de date

Dezavantaje ale NoSQL -ului:
– nu există standarde precum există un standard SQL pentru bazele de date relaționale
– nu sunt metode performante pentru a proteja datele
– posibilități limitate de interogare
– numărul mic de dezvoltatori pentru NoSQL

3.1.2 MongoDB
3.1.2.1. Descriere
MongoDB este o bază de date de tip NoSQL, fii nd orientată pe documente. Principala
diferență este legată de stocarea datelor: într-o astfel de bază , datele nu sunt stocate folosind tabele,
ca în cazul unei baze de date relațională. În MongoDB, datele sunt stocate ca și documente JSON care
dispun de scheme dinamice.

24 3.1.2.2. Operații de tip CRUD (Create, Read, Update, Delete)
Create – Operația de creare sau de inserare adaugă u n document nou la colecție. Dacă acea
colecție nu există, operația de insert va crea una nouă.

Figura 5 Operația Create

Read – Operația de citire primește documente dintr -o colecție.

Figura 6 Operația Read

Update – Această operație modifică un document existe nt dintr-o colecție.

Figura 7 Operația Update

Delete – Operația de ștergere elimină un document dintr -o colecție.

Figura 8 Operația Delete

25 3.1.2.3. MongoDB Compass
MongoDB Compass permite , prin intermediul unei interfețe, vizualizarea și explorarea printre
colecțiile din baza de date.

Figura 9 Fereastră MongoDB Compass

3.2. Crearea colecțiilor bazei de date
O bază de date, este o modalitate de a stoca date și informații pe un suport extern, având
posibilitatea extinderii și regăsirii rapide a acestora.
În cazul nostru, am folosit pachetul Mongoose, care permite crearea unor scheme dinamice în
baza de date la nivel de NodeJS .

Cele două scheme, utilizate în aplicație sunt următoarele:
– showSchema
– userSchema

 userSchema
userSchema = new mongoose.Schema({
name: {
type: String,
trim: true,
required: true

26 },
email: {
type: String,
unique: true,
lowercase: true,
trim: true
},
facebook: {
id: String,
email: String,
photo: String
},
personalShows: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Show',
progress : []
}],
facebookId: String,
episodesWatched: [],
password: String,
level: Number,
personalPhoto: String
});

 showSchema
showSchema = new mongoose.Schema({
showId: {
type: Number,
unique: true
},
name: St ring,
firstAired: Date,
overview: String,
rating: Number,
status: String,
poster: String,

27 banner: String,
creatorName: String,
creatorProfile: String,
network: String,
episodesNumber: Number,
episodeDuration: [Nu mber],
genre: [{}],
owners: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}],
seasons: [{
seasonId: Number,
seasonNumber: Number,
episodeNumber: Number,
episodeName: String,
airDate: Date,
seasonPoster: String,
episodes: [],
}]
});

Figura 10 Baza de date a aplicației
3.3. Structura internă a bazei de date
Baza de date a aplicației noastre este al cătuită din două colecții. În următoarea schema este
prezentată baza de date, după ce s -a realizat proiectarea acesteia, a colecțiilor și a relațiilor dintre ele.

28

Figura 11 Structură Bază de date – MongoDB

29 Un utili zator poate av eam în secțiun ea personalShows un dicționar de ObjectId -uri, care fac
referire la un anumit serial.
Un serial conține în secțiunea de owners un dicționar de ObjectId -uri care fac referire la un
anumit utilizator.
Dacă am fi folosit în locul bazei de date MongoDB, de exemplu, MySQL, cele două scheme,
cea de User și cea de Show ar fi arătat astfel:

Figura 12 Structură Bază de date – MySQL

30 CAPITOLUL I V
-Prezentarea aplicației –

4.1. Prezentare generală

Această aplicație a fost concepută cu scopul de a ajuta utilizatorii să își gestioneze serialele
preferate, precum și să fie la curent cu ultimele episoade apărute.
Aplicația oferă posibi litatea căutarii în câteva secunde printre serialele aflate în baza de date și
adăugarea acestora într -o secțiune de „Seriale Personale”. După adăugarea în această secțiune,
utilizatorul poate să marcheze episoadele deja vizionate precum și să primească em ail-uri de fiecare
dată când un episod nou o să apară.
Aplicația conține două tipuri de utilizatori:
 utilizatorul normal
 administratorul

Pentru a putea utiliza aplicația, este nevoie de crearea unui cont. E xistă un singur
administrator , restul u tilizatorilor făcând partea din categoria utilizatorilor normali.
Acest lucru presupune completarea unu i formular de înregistrare în care sunt necesare:
numele, adresa de e -mail, dar și o par olă. Nu pot fi înregistrați mai mulți utilizatori folosind aceea și
adresă de e -mail, aceștia din urmă fiind informați cu privire la faptul că deja este utilizată adresa.
Formularul mai dispune și de un sistem de înștiințare cu privire la complexitatea parolei aleasă.

Figura 13 Schema înregis trare

31
Dacă unul dintre câmpurile enumerate mai sus nu este completat, utilizatorul nu poate crea
acel cont.
După apăsarea butonului de „Înregistrare ”, dacă toate cerințele au fost îndeplinite, în baza de
date va fi creat un JSON care conține datel e utilizatorului .

Figura 14 Schemă utilizator

După crearea contului, utilizatorul este redirecționat către pagina de Autentificare, unde
trebuie să -și introducă datele. Dacă adresa de email și parola au fost introduse corect, ut ilizatorul va
avea acces la fereastra principală.
În partea stânga , se găsește meniul la care utilizatoru l are acces. Cu ajutorul acestui a, el poate
accesa conținutul de seriale din baza de date precum și serialele personale, poate vizualiza di ferite
statistici, precum și un calendar cu episoadele serialelor care urmează să apară.
În cazul administratorului, acesta dispune de un submeniu suplimentar.

4.2. Fereastră de autentificare

Figura 15 Fereastră de autentificare

32
Fereastra de autenti ficare este alcătuit ă din două text i nput-uri și două butoane : Autentificare
și Facebook.
Autentificare folosind contul creat
Utilizatorul se autentifică prin introducerea adresei de e -mail și a parolei. Adresa de e -mail
este unică , pentru fiecare utilizator în parte. După introducerea acestora și apăsarea butonului,
aplicația va veri fica dacă în baza de date există acea înregistrare.
Dacă adresa de e -mail și p arola au fost introduse greșit, utilizatorul va fi avertizat, primind un
mesaj de eroare.
Dacă adresa de e -mail și parola au fost introduse corect, utilizatorului îi este permis accesul la
aplicație.

Autentificare folosind contul de Facebook
Utilizatorul mai poate accesa aplicația folosind contul personal de Facebook. Prin acceptarea
condițiilor, acesta este autentificat automat, fără a mai fi necesar un proces de creare a unui cont.

Figura 16 Autentificare folosind Facebook

33 4.3. Fereastra p rincipală
Următoarea fere astră este deschisă dacă utilizatorul a reușit să se autentifice cu succes
folosind una din cele două metode .

Figura 17 Fereastră Principală
Fereastra principală este alcătuită din două view -uri: unul pentru meniul din stânga, și unul
pentru conținutul ce va fi încărcat dinamic.
Din fereastra principală, utilizatorul poate căuta un serial anume introducându -i numele î n
caseta de căutare.
4.4. Secțiunea – Meniuri
Meniul este alcătui t din subcategorii:
o Meniul Principal
 Acasă
 Show -uri personale
 Show -uri în d esfășurare
 Calendar
 Statistici
 Adaugă
o Setări
 Setările u tilizatorului
 Zona de a dministrare (v izibil ă doar pentru administrator )
o Opțiunea de deconectare

34

Figura 18 Meniu Principal

4.4.1. Meniul Acasă – Căutare
Meniul Acasă este meniul care va fi selectat în mod implicit atunci când ut ilizatorul se
conectează la aplicație. Acesta va afișa î n fereastra principală cele mai bune seriale ordonate după
notele acestora din baza de date.
Tot din această fereastră, utilizatorul poate căuta printre serialele aflate în baza de date,
folosind c aseta de căutare .
Dacă de exemplu , un utilizator dorește să caute un serial care începe cu grupul de litere “mi”
acestuia îi vor fi returnate toate seriale care au în compone nța sa acel grup de litere, îndrum ându -l
astfel și spre alte seriale.

35

Figura 19 Meniu Acasă – Căutare

4.4.2. Meniu l Show -uri personale
Accesând acest meniu, utilizatorului îi sunt afișate doar serialele pe care acesta le -a adăugat la
„Show -uri p ersonale ”. Vor fi afișate imagini sug estive pentru fiecare serial în parte, alături de numele
și numărul de episoade corespunzător.
Pentru a putea elimina unul din serialele adăugate în această categorie, utilizatorul este nevoit
să acceseze meniul anterior (Acasă).

Figura 20 Meniu Show -uri personale

36 4.4.3. Meniu l Show -uri în d esfășurare
Accesând acest meniu, utilizatorului îi sunt returnate doar serialele care se află în desfășurare .
Alături d e numele acestora , mai pot fi viziona te numărul sezonului și al episodului care urmează să
apară precum și numărul de zile rămase până la apariția acestuia.
Selectând numele unui serial, utilizatorul va fi redirecționat către o pagină de detalii cu pr ivire
la sezoanele și episoadele acestuia.

Figura 21 Meniu Show -uri în desfășurare

4.4.4. Meniu l Calendar
În acest meniu, utiliz atorul poate accesa un calendar, în interiorul căruia sa vor afla episoadele
din serialele adăugate în categoria Seriale personale.
Calendarul va conține numai episoadele serialelor care se află în desfășurare, marcate
conform zilei de apariție al următorului episod.
Calendarul dispune de următoarele butoane:
o un buton pentru accesarea zilei precendete
o un buton pentru accesarea zilei următoare
o un buton care -l duce pe utilizator la ziua curentă, în cazul în care acesta caută până la
o anumită dată și dorește să se întoarcă rapid
o trei butoane pentru afișarea calendarului în funcție de lun ă, săptămână, sau zi

37

Figura 22 Meniu Calendar

4.4.1. Meniul Adaugă
Acest meniu, este disponibil în cazul în care un utilizator este interesat de un anumit serial
care nu se află deja în baza de date locală. Prin introducerea numelui în caseta de input, serialul va fi
adăugat în baza de date a aplicației, putând fi mai apoi adăugat în secțiunea personal ă.

Figură 23 Meniu Adaugă

38 4.4.2. Meniul Statistici
Fereastra care se va deschide accesând meniul de statistici este următoarea :

Figura 24 Fereastră Statistici

Ea este alc ătuita din două grafice :
 un grafic care oferă detalii cu privire la numărul serialelor adăugate în secțiunea ce lor
personale în funcție de genul acestora
 un grafic care oferă informații cu privire la numărul episoadelor vizionate, numărul
episoadelor rămase, și numărul total de episoade ale serialelor personale

39 4.4.3. Zona de a dministrare
În acest meniu, un singur utilizator are acces , fiind vorba de administrator .
Meniul nu va fi vizibil pentru un utilizator normal.Acesta are acces la întrea ga aplicație, mai
puțin la zona acesta .
Zona de administrare este compusă din trei secțiuni:
 Secțiune a Utilizatori
 Secțiu nea Setări u tilizatori
 Secțiunea Administrare seriale u tilizatori

Figura 25 Zona de administrare

 Secțiunea Utilizatori

În această secțiune, administratorul poate vizualiza numărul de utilizatorii înregistrați, precum
și date despre aceștia, cum ar fi:
– adresa de e -mail
– numărul de seriale adăugate în categoria serialelor personale

40

Figura 26 Secțiune utilizatori

Tot din această secțiune, adminis tratorul, apăsând butonul de ștergere asociat fiecărui
utilizator are posibilitatea să -l șteargă din baza de date.

 Secțiunea Setări u tilizatori
Din această secțiune, pot fi modificate datele personale ale unui utiliza tor. Selectând unul din
utilizatorii aflați î n Secțiunea u tilizatori, administratorul poate să-i modifi ce parola acestuia.
Caseta pentru modificare nu este disponibilă în cazul utilizatorilor care s -au înregistrat
folosind contul personal de Fac ebook, aceștia neavând o parolă asociată .

Figura 27 Secțiunea Setări utilizator

41  Secțiunea Administrare seriale u tilizatori
În această secțiune, administratorul are acces la toate serialele adăugate de un anumit
utilizator , având posibilitatea, dacă acesta a încălcat vreo regulă, să elimine o parte din ele.

Figura 28 Administrare seriale utilizatori

4.4.4. Meniul Setări personale
Accesând acest meniu, un utilizator, dacă dor ește, are posibilitatea de a -și modifica parola.
Noua parolă, la fel ca și cea inițială, va fi salvată în baza de date, trecând printr -un proces de criptare,
astfel, ea devenind mai puternică și mai greu de descifrat.
Pentru criptare, am folosit pachetul “bcrypt” , pentru NodeJ S, care genereză un salt de 10
poziții, parola fiind apoi hășuită, în funcție de acel salt.

Figura 29 Meniu Setări personale

42 Câmpurile de nume, e -mail și parolă actuală nu sunt disponibile pentru editare, el e fiind
modale. După apăsarea butonului de “Salvați modificările ”, utilizatorul va primi un mesaj de
confirmare, dacă parola a fost modificată cu succes, sau un mesaj de eroare în caz contrar .

4.5. Fereastră Detalii show
În momentul în care uti lizatorul va da click pe unul dintre serialele aflate în meniul “Acasă”,
acestuia îi va fi încărcată o fereastră, care conține următoarele elemente:
– scurtă descriere a ceea ce se întâmpla în acel serial
– numărul de sezoane apărute
– starea serialului, în mome ntul de față

Figura 30 Fereastră Detalii adăugare show

Apăsând pe butonul „Adaugă la show -uri personale ”, serialul respectiv va f i adăugat in
colecția de seriale ale utilizatorului.
Singura modalitate de a elimina un serial di n acea colecție, este de a accesa din nou pagina de
detalii a serialului respectiv și apăsând butonul “ Elimină de la show -uri personale ”.

43

Figură 31 Fereastră Detalii eliminare show

4.6. Fereastră Show personal
Această fereastră conține următoarele componente:
o detalii despre show
o toate sezoanele alături de imagini sugestive pentru fiecare
o detalii despre fiecare sezon

Figur a 32 Fereastră Show personal

44 Detalii despre show
În această secțiune se vor afla informații mai detaliate despre acel serial. Utilizatorul are acces
la o descriere mai amplă cu privire la ce se întâmplă pe parcursul episoadelor .

Figura 33 Detalii despre show

Sezoane și detalii de spre acestea
Accesul printre sezoane poate fi făcut prin apăsarea pe una dintre imaginile corespunzătoare
acestuia.Astfel, când un sezon este selectat, un tabel cu episoadele acestuia este încărcat.

Figur a 34 Sezoane și detalii despre acestea

45 Tabelul este alcătuit din cinci coloane:
– număr episod
– nume episod
– data la care acesta a fost lansat
– un buton de v izionare
– un buton de detalii

Prin apăsarea butonului de „ Vizionare”, aflat pe linia unui episod, acesta, va fi marcat ca și
episod văzut.
Prin apăsarea butonului de “Detalii”, aflat pe linia unui episod, utilizatorul va obține mai
multe informații despre ce se va întâmpla în episodul respectiv, precum și o imagine din acel episod.

Figura 35 Fereas tră vizionare episod

46 CAPITOLUL V
-Arhitectura sistemului.Realizare practică –

5.1. Structura aplicației
Această aplicație este construită a vând o arhitectură de tip MVC, acronimul venind de la
Model – View – Controller .
La nivel de structură, aplicația conține două view -uri:
 sidebar
 main content

Figura 36 Structura View -uri

View -ul de sidebar este prezent în toate ferestrele aplicației, mai puțin în cele de Înregistrare
și Autentificare, pe când view -ul de main content este folosit pentru a insera dinamic datele.
<div class="page-container">
<div ng-show ="isLogged" ng-include="'views/sidebar.html'"></ div>
<div ng-view class="{{mainClass}}"></ div>
</div>

47 Proce sul tipic pentru a implementa o astfel de arhitectură presupune divizarea aplicației
pentru o organizare cât mai eficientă și cât mai productivă.
Aplicția pentru gestioarea conținutui audio -video este structurată astfel:
– un folder “public ”
– un fișier JavaS cript pentru acțiunile de la nivelul serverului

Inspectând acest folder, observă m că el conține la rândul său alte foldere, după cum urmează:
– controllers
– directives
– filters
– services
– stylesheets
– utils
– vendor
– views
5.2. Analiza rutelor aplicației
Aplicația conține mai multe end -pointuri, reprezentate prin rutele definite în fisierul „app.js ”.
Pentru a specifica fiecare rută, am folosit $routeProvider, un serviciu care aparține AngularJS -ului.
$routeProvider
.when('/admin', {
templateUrl: 'views/admin.html' ,
controller: 'AdminCtrl' ,
})
.when('/', {
templateUrl: 'views/home.html' ,
controller: 'MainCtrl' ,
})
.when('/shows/:id' , {
templateUrl: 'views/detail.html' ,
controller: 'DetailCtrl' ,
})
.when('/login' , {
templateUrl: 'views/login.html' ,
controller: 'LoginCtrl' ,
})
.when('/signup' , {
templateUrl: 'views/signup.html' ,
controller: 'SignupCtrl'
})
.when('/add', {
templateUrl: 'views/add.html' ,
controller: 'AddCtrl' ,
})

48 .when('/settings' , {
templateUrl: 'views/settings.html' ,
controller: 'SettingsCtrl' ,
})
.when('/myshows' , {
templateUrl: 'views/myshows.html' ,
controller: 'MyShowsCtrl' ,
})
.when('/statistics , {
templateUrl: 'views/diagram.html',
controller: DiagramCtrl ,
})
.when('/upcoming' , {
templateUrl: 'views/upcoming.html' ,
controller: 'UpcomingCtrl' ,
})
.when('/result' , {
templateUrl: 'views/result.html' ,
controller: 'ResultC trl',
})
.when('/result/:id' , {
templateUrl: 'views/showresult.html' ,
controller: 'ShowResultCtrl' ,
})
.when('/shows/:id/:seasonId' , {
templateUrl: 'views/seasondetail.html' ,
controlle r: 'SeasonDetailCtrl' ,
})
.when('/calendar' , {
templateUrl: 'views/calendar.html' ,
controller: 'CalendarCtrl' ,
})
.otherwise({
redirectTo: '/',
});

Când o anumită rută este accesată , utiliza torul va fi direcționat către un view care are în spate
un controller responsabil pentru partea logică. Dacă se încearcă accesarea altor rute decât cele care se
află în lista de mai sus, utilizatorul va fi redirecționat către ruta principală ( / ).

5.2.1. Ruta “ login ”
Atunci când utilizatorul accesează adresa aplicației, el va fi redirecționat către
localhost:4000/login, obligându -l pe acesta să se autentifice înainte de a folosi aplicația.
Formularul de autentificare , la nivel de HTML, are u rmătorul cod:
<form method="post" ng-submit="login()" name="loginForm" class="form-
horizontal">
<div class="form-group">
<div class="col-md-12">
<input class="form-control" type="text" name="email" ng-model="email"
placeholder ="Email" required autofocus >
</div>
</div>

49 <div class="form-group">
<div class="col-md-12">
<input class="form-control" type="password" name="password" ng-
model="password" placeholder ="Parolă" required >
</div>
</div>
<div class="form-group">
<div class="col-md-6">
<button type="submit" ng-disabled ="loginForm.$invalid" class="btn btn –
info btn -block">Autentificare </button>
</div>
</div>
</form>

După cum se observă, atunci când este completat formularul de autentificare, funcția login()
este apelată. Ea este definiă în interiorul controller -ului acestui view, iar pentru a o putea folosi în
acest fel a fost nevoie de atașarea acesteia la $scope.

$scope.login = function (user) {
Auth.login({
email: $scope.email,
password: $scope.password
});
var user = {
email: $scope.email,
password: $scope.password
}
};

Am folo sit un serviciu de autentificare, modelul din arhitectura MVC, pentru a putea
transmite datele între view -uri. Când acesta este apelat, fișier ul auth.js preia datele introdus e în
formular, și le folosește pentru a face un api call către server .
login: function (user) {
return $http.post( '/auth/login' , user)
.success( function (data) {
localStorageService.set( 'token', data.token);
var payload = JSON.parse($window.atob(data.token.split( '.')[1]));
localStorageService.set( 'currentUser' , JSON.stringify(payload.user));
$location.path( '/');
})
.error(function () {
delete $window.localStorage.token;
$alert({
title: 'Error!' ,
content: 'Adresă de email/parolă invalidă.' ,
animation: 'fadeZoomFadeDown' ,
type: 'material' ,
duration: 3
});
});
},

50 La nivelul server -ului, se realizează o căutare în baza de date , în funcție de adresa de email,
care este unică.

app.post( '/auth/login' , function (req, res, next) {
User.findOne({
email: req.body.email
}, function (err, user) {
if (!user) return res.send(401, 'User-ul nu există în baza de date' );
user.comparePassword(req.body.password, function (err, isMatch) {
if (!isMatch)
return res.send(401, 'Adresă de e -mail/parolă invalidă' );
var token = createJwtToken(user);
res.send({
token: token
});
});
});
});

5.2.2. Ruta “signup ”
Accesul la conținutul aplicației este dat prin crearea unui utilizator. Acest lucru se poate
realiza completând formularul corespunzător rutei lo calhost:4000/signup.
$scope.signup = function () {
Auth.signup({
name: $scope.displayName,
email: $scope.email,
password: $scope.password,
photo: $scope.defaultImage
});
};

Ca și în ca zul rutei de înregistrare, serviciul de autentificare preia datele din formular,
realizând un api call către server.
signup: function (user) {
return $http.post( '/auth/signup' , user)
.success( function () {
$location.path ('/login' );
$alert({
title: Felicitări !',
content: 'Contul a fost creat .',
animation: 'fadeZoomFadeDown' ,
type: 'material' ,
duration: 3
});
})
.error(function (response) {
$alert({
title: Eroare!',
content: response.data,
animation: 'fadeZoomFadeDown' ,
type: 'material' ,
duration: 3
});

51 });
}

În cadrul fișierului server.js, se realizează o operație de tip POST. Înainte de a fi creat un
utilizator este setat nivelul acestuia. Dacă adresa de e -mail introdusă în formular este
roberttestemailadr ess@gmail.com , nivelul acestuia va fi 1 -administrator, altfel nivelul va avea
valoarea 0 -utilizator.

app.post( '/auth/signup' , function (req, res, next) {

//set the level:
//0 – normal user
//1 – administrator

var level;
if (req.body. email === roberttestemailadress .com') {
level = 1;
} else {
level = 0;
}
var user = new User({
name: req.body.name,
email: req.body.email,
password: req.body.password,
level: level,
personalPhoto: req.body.photo
});
user.save( function (err) {
if (err) return next(err);
res.send(200);
});
});

Adresa de email introdusă este verificată în baza de date. Dacă nu mai există niciun utilizator
având acea adresă, va fi cr eat unul nou. Daca exista deja adresa de email, va fi afișată o alertă.

Pentru a ne asigura că adresa de email este unică, am create o directivă, uniqueEmail.
angular.module( 'MyApp')
.directive( 'uniqueEmail' , function ($http) {
return {
restrict: 'A',
require: 'ngModel' ,
link: function (scope, element, attrs, ngModel) {
element.bind( 'blur', function () {
if (ngModel.$modelValue) {
$http.get( '/api/users' , { params: { email: ngModel.$modelValue }
})
.success( function (data) {
ngModel.$setValidity( 'unique' , data.available);
});
}
});

52 element.bind( 'keyup', function () {
ngModel.$setValidity( 'unique' , true);
});
}
};
});

5.2.3. Ruta principal ă
După autentificare, utilizatorul este redirecționat către ruta principală a aplicației.
Aici, la nivel de controller sunt încărcate într -o variabilă “shows ”, toate serialele aflate în
baza de date.
$scope.shows = Show.query();

Afișarea acestora se face iterând prin toate rezultatele, și construind un < div > pentru fiecare.
<div class="col-xs-6 col-md-2" ng-repeat="show in shows | orderBy:'rating':true
| limitTo:18 | filter: showName">
</div>

Pentru afișarea posterului fi ecărui serial, se realizează un request către api-ul bazei de date,
deoarece acesta nu este încărcat la nivel local.

5.2.4. Ruta “myshows ”
Accesând această rută, în view -ul principal vor fi încărcate toate serialele personale ale unui
utilizator.
var user = User.get({
userId: $scope.currentUser._id
}, function (user, err) {
if (user.personalShows.length > 0) {
$scope.shows = Show.query({
_id: user.personalShows
});
} else {
$scope.shows = [];
}
});

Acestea vor vi afișate la fel ca și în cazul rutei principale, într -un <div> separat.

53 5.2.5. Ruta “ upcoming ”
Când utilizatorul selectează opțiunea “ upcoming ”, din bara de navigare, va accesa această
rută.
La nivelul controller -ului am re alizat o prelucrare suplimentară a datelor , pentru afișare a
numărului de zile rămase până la apariția urm ătorului episod:
– încărcarea tuturor serialelor personale ale utilizatorului
– iterarea prin toate sezoanele fiecărui serial și păstrarea ultimul sezon în tr-o variabilă
– iterarea prin ultimul sezon, comparând data de apariție a fiecărui episod cu data
actuală

User.get({
userId: userID
}, function (user, err) {
if (user.personalShows.length > 0) {
$scope.shows = Show.query({
_id: user.personalShows
}, function (shows, err) {
for (var i = 0; i < shows.length; i++) {
seasons = shows[i].seasons;
lastSeasonEpisodes = seasons[seasons.length – 1].episodes;
for (var j = 0; j < lastSeasonEpisodes.length; j++) {
if (lastSeasonEpisodes[j].air_date > this.currentDate) {
shows[i].nextEpisode.name = lastSeasonEpisodes[j].name;
shows[i].nextEpisode.air_date = lastSeasonEpisodes[j].air_dat e;
shows[i].nextEpisode.number = lastSeasonEpisodes[j].episode_number;
shows[i].nextEpisode.overview = lastSeasonEpisodes[j].overview;
shows[i].nextEpisode.nrsez = lastSeasonEpisodes[j].season_number;
shows[i].nextEpisode.poster = lastSeasonEpisodes[j].still_path;
shows[i].nextEpisode.rate = lastSeasonEpisodes[j].vote_average;
}
}
}
}.bind($scope));
5.2.6. Ruta “calendar”
Accesând această rută, utilizatorul va avea acces la un calendar în care se vor regăsi
episoadele din serialele persoanale care urmează să apară.
De această dată, în controller vom genera câte un eveniment pentru fiecare episod.
for (var i = 0; i < lastSea sonEpisodes.length; i++) {
if (lastSeasonEpisodes[i].air_date > currentDate) {
var newEvent = new Object();
newEvent.title = showInfo.name + " S" + lastSeasonEpisodes[i].season_number
+ "E" + lastSeasonEpisodes[i].episode_number;
newEvent.start = new Date(lastSeasonEpisodes[i].air_date);
$('#calendar' ).fullCalendar( 'renderEvent' , newEvent, true);
}
}

54 5.2.7. Ruta “ settings ”
Pentru a -și schimba parola personală, un utilizator trebuie sa acceseze ruta settings.
Completând caseta de “ parolă nouă ” și apăsând pe buton, funcția saveSettings() va fi apelată.
$scope.saveSettings = function (argument) {
Auth.changeSettings({
name: $scope.currentUserName,
email: $scope.currentUserEmail,
password: $scope.currentPassword,
newPassword: $scope.newPassword
});
}
Și de această dată am folosit același serviciu de autentificare.
changeSettings: function (user) {
return $http.post( '/auth/changesettings' , user)
.success( function () {
})
.error(function (response) {
});
}
La nivel de server, se face o cautare în baza de date a utilizatorului, în funcție de adresa
acestuia d e email. Dacă aceasta este validă, se va realiza o operație de update, înlocuindu -se vechea
parolă cu cea nouă.
app.post( '/auth/changesettings' , function (req, res, next) {
var password = req.body.newPassword;
User.findOne({
email: req.body.email
}, function (err, user) {
user.password = password;
user.save( function (err) {
if (err) return next(err);
res.send(200);
});
});
});
5.2.8. Ruta “admin”
Ruta este accesibilă numai pentru administrator. Prin accesarea acesteia, el are posibilitatea să
administreze utilizatorii înregistrați precum și serialele pe care aceștia le au în secțiunea de „Show -uri
personale ”.
În controller -ul view -ului de admin, am definit o funcție, necesară pentru a extrage toți
utilizatorii din baza de date.

55 (function getUsers() {
Subscription.getUsers().success( function (userList) {
$scope.userList = userList;
});
})();
Pentru a accesa datele, am folosit ser viciul Subscription , necesar pentru a realiza un api call
către server.
getUsers: function () {
return $http.get( '/api/userlist' , {});
} ;
În urma acestuia se va returna o listă cu toți utilizatorii înregistrați.
app.get( '/api/userlist' , function (req, res, next) {
User.find({}, function (err, users) {
var userMap = {};
users.forEach( function (user) {
userMap[user._id] = user;
});
res.send(userMap);
});
});
Acțiunile rea lizate în cadrul view -ului, acelea de „Ștergere utilizator ” sau “Ștergere serial”,
presupun apelarea altor api call -uri, acționate tot prin intermediul serviciul de Subscription.
$scope.deleteUser = function (user) {
Subscription.removeUser(user).succes s(function () {
$scope.isDeleted = true;
})
Subscription.getUsers().success( function (userList) {
$scope.userList = userList;
});
}

$scope.deleteUserShow = function (show) {
Subscription.deleteUserShow(show, $rootScope.currentUser)
.success( function () {
var user = arguments[0];
if (user.personalShows.length == 0) {
$scope.shows = [];
} else {
$scope.shows = Show.query({
_id: user.personalShows
});
}
})
Subscription.removeFromMyShows(show, $scope.selectedUser).success( function () {
var index = $scope.show.owners.indexOf($rootScope.currentUser._id);
$scope.show.owners.splice(index, 1);
})

}

56 5.2.9. Ruta “add”
Această rută p oate fi accesată dacă se dorește adăugare la “Show -uri personale”, a unui serial
care nu se află în baza de date locală a aplicației.
La nivel de HTML, este vorba de o casetă de input, în care este introdus numele serialului, și
un buton pentru adăugarea a cestuia în listă.
La nivel de server, sunt re alizate următoarele operații asi ncrone:
 se realizează un request pe site -ul care conține toată baza de date online, în urma
căruia se obține id -ul serialului respectiv
 se realizează un alt request, folosind id -ul anterior și obținându -se toate informațiile
despre serialul respectiv
 dacă nu există, serialul va fi adăugat în listă, iar dacă acesta deja există, un mesaj de
atenționare va fi afișa t

async.waterfall([
function (callback) {
request.get( 'https://api.themoviedb.org/3/search/tv?api_key=' + apyKey +
'&query=' + seriesName, function (error, response, body) … );
},

function (seriesId, callback) {
request.g et('https://api.themoviedb.org/3/tv/' + seriesId + '?api_key='
+ apyKey, function (error, response, body) … );
},

], function (err, show) {
if (err) return next(err);
show.save( function (err) {
if (err) {
if (err.code == 11000) {
return res.send(409, {
message: show.name + ' există deja .'
});
}
return next(err);
}
});
User.findOne({
'email': userEmail
}, function (err, user) {
user.personalShows.push(show);
user.save( function (err) {
user.populate( 'personalShows' , function (err) {
if (err) return next(err);
res.json(200);
})
});

57

5.2.10. Ruta “shows/id”

După adăugare unui serial în secțiunea „Show -uri personale ”, utilizatorul poate accesa detalii
despre acel serial în momentul în care va da click pe el.
View -ul de detalii este compus din mai multe elemente, cum ar fi butoane:vizionat, detalii,
precum și din liste și tabele.
La nivelul controller -ului, funcțiile apelate prin apăsarea butoanelor au următorul cod:

$scope.watchEpisode = function (episode) {
Subscription.addToWatchedEpisode(episode, $rootScope.currentUser)
.success( function() {
$scope.currentUser.episodesWatched.push(episode.id);
})
}

$scope.unWatchEpisode = function (episode) {
Subscription.removeWatchedEpisodes(episode, $rootScope.currentUser)
.success( function () {
for (var i = 0; i < $scope.currentUser.episodesWatched.length; i++) {
if ($scope.currentUser.episodesWatched[i].id == episode.id) {
var index = $scope.currentUser.episodesWatched[i];
$scope.curr entUser.episodesWatched.splice(index, 1);
}
}
});}

Toate aceste funcții sunt apelate prin intermediul serviciului de Subscription, la nivelul cărui
se realizează api call -urile către server.
app.post( '/api/saveepisode' , function (req, res, next) {
User.findOne({
'email': req.body.currentUser.email
}, function (err, user) {
if (err) return next(err);
var episodeId = req.body.episode.id;
user.episodesWatche d.push(episodeId);
user.save( function (err) {
if (err) return next(err);
res.send(200);
});
})
})

app.post( '/api/removeepisode' , function (req, res, next) {
User.findOne({
'email': req.body.currentUser.email
}, function (err, user) {
if (err) return next(err);
var episodeId = req.body.episode.id;

58 var index = user.episodesWatched.indexOf(episodeId);
user.episodesWatched.splice(index, 1);
user.save( function (err) {
if (err) return next(err);
res.send(200);
});
})
})

De exemplu, pentru marcarea unui episod ca și văzut, se face o căutare în baza de date a
utilizatorului, introducându -se în dicționaru l de episoade văzute, episodul respectiv , iar apoi se
realizează o operație de actualizare a acestuia.
Pentru revenirea la statutul de „ nevăzut”, se realizează din nou căutarea utilizatorului în baza
de date în funcție de adresa de e -mail a acestuia, și se veri fică dacă episodului se află deja in lista de
episoade văzute. După identificare , el este eliminat din listă, efectuându -se update -ul pentru utilizator.

5.2.11. Ruta “statistics ”
Prin accesarea acestei rute, un utilizator are acces la statistici cu privire la ep isoadele
vizioanate sau cu privire la serialele din categoria “Seriale personale” grupate în funcție de gen.
Pentru realizarea diagramelor este nevoie de o căutare în baza de date a tuturor serialelor
personale ale utilizatorului, și a episoadelor vizionat e de acesta.
$scope.shows = Show.query({
_id: user.personalShows
}, function (shows) {
var totalEpisodes = 0;
for (var i = 0; i < shows.length; i++) {
totalEpisodes += shows[i].episodesNumber;
}
var episodesRemained = totalEpisodes – this.episodesWatched;
new Chart(ctx2, {
type: "bar",
responsive: true,
data: {
labels: [ "Show-uri personale" ],
datasets: [{
label: 'Episoade văzute' ,
fillColor: '#382765' ,
data: [this.episodesWatched]
}, {
label: 'Episoade rămase' ,
fillColor: '#7BC225' ,
data: [episodesRemained]
}, {
label: 'Total episoade' ,
fillColor: '#7BC225' ,
data: [totalEpisodes]
}]
},
});

59 5.2.12. Ruta „result/id ”
Dacă un utilizator va da click pe un serial aflat în secțiunea Acasă, acesta va fi încărcat în
view -ul de content, alături de o scurtă descriere, dar și de posibilitatea adăugării la Show -uri
personale.

Pentru afișarea butonului “Adaugă la show -uri personale” se face o verificare la nivelul
utilizatorului. Dacă id-ul serialului nu se află în dicționarul de owners, butonul va fi vizibil. Dacă
serialul îi aparține deja, butonul care afișat va fi cel de “ Elimină de la show -uri personale ”.

<div class="row" ng-show="!isAddedToMyShows()">
<div class="col-lg-12">
<button class="btn btn -block btn -success" ng-click="addToMyShows()">
Adaugă la show -uri personale
</button>
</div>
</div>
<div class="row" ng-show="isAddedToMyShows()">
<div class="col-lg-12">
<button class="btn btn -block btn -danger" ng-click="removeFromMyShows()">
Elimină de la show -uri personale
</button>
</div>
</div>

Prin acționarea celor două butoane, prin intermediul serviciului Subscription, se realizează api
call-uri către server.
De exemplu, pentru adăugarea unui serial la Show -uri personale, la nivelul serverului avem
următorul cod:
app.post( '/api/addtomyshows' , function (req, res, next) {
var showId = req.body.showId;
Show.findById(showId, function (err, show) {
show.owners.push(req.body.currentUser._id);
show.save( function (err, show) {
var lastSeason = show.seasons[show.seasons.length – 1];
var upcomingEpisode = lastSeason.episodes.filter( function (episode) {
return new Date(episode.air_date) > new Date();
})[0];
var currentDate = moment().format();
console.log(upcomingEpisode);
if (upcomingEpisode) {
var targetDate = moment(upcomingEpisode.air_date).format();
var _duration =
moment.durat ion(moment(upcomingEpisode.air_date).diff(currentDate));
var _days = Math.floor(_duration.asDays());
}
if (upcomingEpisode !== undefined) {
agenda.schedule( 'in ' + _days + 'days', 'send email alert' ,
show.name).repeatEvery( '1 week' ).save()
}
})
User.findById(req.body.currentUser._id, function (err, user) {
if (err) return next(err);

60 user.personalShows.push(showId);
user.save( function (err) {
if (err) return next(err);
res.send(200);
});
})

});

5.3. Avertizare e -mail
În momentul în care un utilizator alege să adauge un serial la Show -uri personale, id -ul
acestuia este introdus în dicționarul “owners ” al serialului respectiv.
Atunci când un episod nou urmează să apară, un e -mail va fi trimis către toți utilizatorii din
acel dicționar. Pentru realizarea acestei operații am folosit nodemailer și agenda.

var agenda = require( 'agenda' )({
db: {address: 'mongodb://localhost/test' },
function (err) {
if (err) {
console.log(err);
throw err;
}
}
});
var nodemailer = require( 'nodemailer' );

Modul de funcționare este următorul: dacă serialul care umează sa fie adăug at la Show -uri
personale nu este deja încheiat, se va crea în baza de date un job cu episodul care umează sa apară.

Figura 37 Avertizare e -mail

agenda.define( 'send email alert' , function (job, done) {
Show.findOne({
name: job.attrs.data
}, function (err, show) {}).populate( 'owners' ).exec(function (err, show) {

61 var mailOptions = {
from: 'Radoi Robert' ,
to: emails.join( ','),
subject: 'Mâine apare un episod nou din ' + show.name,

text: 'Episodul ' + upcomingEpisode.episode_number + ' urmează să
apară\n\n' + 'Iată ce se va întampla în acest episod \n\n' +
upcomingEpisode.overview
};
smtpTransport.sendMail(mailOptions, function (error, info) {
smtpTransport.close();
});

});
});

Figura 38 E-mail Episod nou

62 CAPITOLUL VI
-Concluzii –

Am creat această aplicație cu scopul de a ajuta persoanele care doresc să fie la curent cu
ultimele seriale apărut e precum și să -și țină o evidenț ă a serialelor preferate. Administratorul acestei
aplicații, prin meniul special de care dispune, poate controla toată activitatea din aplicație, cum ar fi
serialele dar și utilizatorii.

Fiind vo rba de o aplicație de tip SPA, codul acesteia poate fi foarte ușor folosit la nivel de
server, putând fi utilizat și pentru aplicații mobile.

Aplicația folosește o arhitectură MVC. După cum este precizat și în numele arhitecturi i, ea
poate fi împarțită în :
Model – este responsabil pentru ad ministrarea datelor aplicației
View – afișează fizic datelor unui utilizator
Controller – controlează interacțiunea dintre Model și View

În urma realizării acestei aplicații mi -am îmbogățit cunoștințele cu privire la JavaScript, în
special AngularJS și NodeJS, dar și cele cu privire la MongoDB, o bază de date de tip NoSQL,
cunoștințe care -mi vor fi necesare pe viitor în realizarea altor aplicații

63

BIBLIOGRAFIE

 Node.js Design Patterns Kindle Edition, Mario Casciaro, 2014
 SPA Design and Architecture Understanding Single Page Web Applications 1st Edition,
Emmit Scott , 2016
 AngularJS Essentials, Rodrigo Branas, 2014
 AngularJS by Example, Chandermani, 2015

64

REFERINȚE WEB

 https://docs.angularjs.org/guide/introduction
 https://www.tutorialspoint.com/angularjs/angularjs_mvc_architecture.htm
 https://en.wikipedia.org/wiki/Single -page_application
 https://en.wikipedia.org/wiki/JavaScript
 https://medium.com/unexpected -token/10 -weeks -of-node -js-after-10-years -of-php-
a352042c0c11
 https://en.wikipedia.org/wiki/Node.js
 https://neoteric.eu/single -page -application -vs-multiple -page -application
 https://en.wikipedia.org/wiki/Bootstrap_(front -end_framework)
 https://ro.wikipedia.org/wiki/MongoDB
 https://ro.wikipedia.org/wiki/Baz%C4%83_de_date
 http://www.rusu.coneural.org/teaching/MLR5027/2014.BD.Curs.14.pdf
 https://xfactorapp.com/twitter -bootstrap -platforma -de-care-ne-am-indragostit -cu-totii/
 https: //www.quora.com/What -are-the-pros-and-cons-of-using -Bootstrap -in-web-development
 https://docs.mongodb.com/manual
 http://www.tech strikers.com/AngularJS/angularjs -mvc-pattern.php
 http://vizteck.com/blog/node -js-vs-php-find-a-useful -comparison -for-your-next-startup/
 http://www.scriptol.com/javascript/nodejs.php

65

ANEXĂ COD
1. Controllere
1.1. Add
angular.module( 'MyApp')
.controller( 'AddCtrl' , function ($scope, $alert, Show, $rootScope) {
$scope.mainClass = 'page-content' ;
$rootScope.isLogged = true;
$scope.addShow = function () {
Show.save({
showName: $scope.showName,
currentUser: $scope.currentUser,
temporary: false,
}).$promise
.then(function () {
$scope.showName = '';
$scope.addForm.$setPristine();
})
.catch(function (response) {
$scope.showName = '';
$scope.addForm.$setPristine();
});
};
});

1.2. Admin
angular.module( 'MyApp')
.controller('AdminCtrl' , function (Subscription, $scope, $alert, Show,
$rootScope, $http, User, $location) {
$scope.mainClass = 'page-content' ;
$rootScope.isLogged = true;
$scope.isDeleted = false;

(function getUsers() {
Subscription.getUsers().success( function(userList) {
$scope.userList = userList;
});
})()
$scope.userDetails = function (userInfo) {
$scope.isClicked = true;
var user = User.get({
userId: userInfo._id
}, function (user, err) {
$scope.c urrentUserName = user.name;
$scope.currentUserEmail = user.email;
$scope.currentPassword = user.password;
$scope.facebook = user.facebook;
if(user.personalShows == undefined ||
user.personalShows.length == 0){
$scope.shows = [];

66 } else {
$scope.shows = Show.query({
_id: user.personalShows
});
}
$scope.selectedUser = user;
});
}

$scope.deleteUser = function (user) {
Subscription.removeUser(user).success( function () {})
Subscription.getUsers().success( function (userList) {
$scope.userList = userList;
});

}

$scope.deleteUserShow = function (show) {

Subscription.deleteUserShow(show,$scope.selectedUser).success( function ()
{
var user = ar guments[0];
if (user.personalShows.length == 0) {
$scope.shows = [];
} else {
$scope.shows = Show.query({
_id: user.personalShows
});
}
})
Subscription.removeFromMyShows(show, $scop e.selectedUser)
.success( function () {
var index =
$scope.show.owners.indexOf($rootScope.currentUser._id);
$scope.show.owners.splice(index, 1);
})
}

1.3. Calendar
angular.module( 'MyApp')
.controller( 'CalendarCtrl' , function ($scope, $al ert, Show, $rootScope, User,
$http) {
$scope.mainClass = 'page-content' ;
$rootScope.isLogged = true;

User.get({
userId: $rootScope.currentUser._id
}, function (user, err) {
Show.query({
_id: user.personalShows
}, function(show) {
_.each(show, function (showInfo, showIndex, allShows) {
var currentDate = $scope.formatCurrentDate();
var seasons = showInfo.seasons;
var lastSeasonEpisodes = seasons[seasons.length – 1].episodes;
for (var i = 0; i < lastSeasonEpisodes.length; i++) {
if (lastSeasonEpisodes[i].air_date > currentDate) {
var newEvent = new Object();

67 newEvent.title = showInfo.name + " S" +
lastSeasonEpisodes[i].season_number + "E" +
lastSeasonEpisodes[i].episode_number;
newEvent.start = new Date(lastSeasonEpisodes[i].air_date);
$('#calendar' ).fullCalendar( 'renderEvent' , newEvent, true);
}
}
})
});
});

$scope.formatCurrentDate = function () {
var todayDate = new Date();
var todayMonth = ( "0" + (todayDate.getMonth() + 1)).slice( -2);
var todayDay = ( "0" + todayDate.getDate()).slice( -2);
var todayYear = todayDate.getFullYear();
var todayDateText = todayYear + "-" + todayMonth + "-" + todayDay;
return todayDateText;
}

var calendar = $( '#calendar' ).fullCalendar({
header: {
left: 'prev,next today' ,
center: 'title',
right: 'month,agendaWeek,ag endaDay'
},
defaultView: 'month',
displayEventTime: false,
allDay: true,

});

});

1.4. Detail

angular.module( 'MyApp')
.controller( 'DetailCtrl' , function ($http, $location, $window, $scope,
$rootScope, $routeParams, User, Show, Subscription, localStorageService,
$route) {
$scope.mainClass = 'page-content' ;
$rootScope.isLogged = true;
$scope.seasonEpisodes = [];

function getEpisodes(season) {
if ($scope.seasonEpisodes.length > 0) {
$scope.seasonEpis odes = [];
}
for (var i = 0; i < season.episodes.length; i++) {
$scope.seasonEpisodes.push(season.episodes[i]);
}
}

User.get({
userId: $rootScope.currentUser._id
}, function (user) {
this.currentUser = user ;
}.bind($rootScope));

Show.get({
showId: $routeParams.id

68 }, function (show, cb) {
$scope.show = show;
if (show.creatorProfile == null) {
debugger ;
show.creatorProfile = 'utils/img/nodirector.jpg'
} else {
show.creatorProfile = "http://image.tmdb.org/t/p/w185" +
show.creatorProfile;
}
for (var i = 0; i < $scope.show.seasons.length; i++) {
if ($scope.show.seasons[i].seasonNumber == 0) {
$scope.show.seasons.splice($scope .show.seasons[i], 1);
}
if ($scope.show.seasons[i].seasonPoster != null) {
$scope.show.seasons[i].seasonPoster =
"http://image.tmdb.org/t/p/w185" + $scope.show.seasons[i].seasonPoster;
} else {
$scope.show.season s[i].seasonPoster = 'utils/img/noimg.jpg' ;
}
}

$scope.isSubscribed = function () {
return $scope.show.subscribers.indexOf($rootScope.currentUser._id) !==
-1;
};

$scope.isWatched = function (episode) {
var isWatched = false;
for (var i = 0; i < this.currentUser.episodesWatched.length; i++) {
if (this.currentUser.episodesWatched[i].id == episode.id) {
isWatched = true;
}
}
return isWatched;
};

$scope.selectSeason = function (season) {
$scope.currentSeason = season;
getEpisodes(season);
}

$scope.watchEpisode = function (episode) {
Subscription.addToWatchedEpisode($scope.show._id, episode,
$rootScope.currentUser) .success( function () {
$scope.currentUser.episodesWatched.push(episode);
})
}

$scope.unWatchEpisode = function (episode) {

Subscription.removeWatchedEpisodes($scope.show._id, episode,
$rootScope.currentUser).success( function() {
for (var i = 0; i < $scope.currentUser.episodesWatched.length; i++) {
if ($scope.currentUser.episodesWatched[i].id == episode.id) {
var index = $scope.currentUser.episodesWatched[i];
$scope.curre ntUser.episodesWatched.splice(index, 1);
}
}
})
}

$scope.getDetail = function (episode) {

69 this.episode = episode;
if (episode.still_path !== null) {
this.episodeImage = "http://image.tmdb. org/t/p/w185" +
episode.still_path;

} else {
this.episodeImage = 'utils/img/nodirector.jpg' ;
}
}.bind($scope)
});
});

1.5. Login
angular.module( 'MyApp')
.controller( 'LoginCtrl' , function ($scope, Auth, $rootScope) {
$rootScope.isLogged = false;
$scope.mainClass = 'page-container' ;

$scope.login = function (user) {
Auth.login({
email: $scope.email,
password: $scope.password
});
var user = {
email: $scope.email,
password: $scope.password
}
};
$scope.facebookLogin = function () {
Auth.facebookLogin();
};
});

1.6. Logout
angular.module( 'MyApp')
.controller( 'LogoutCtrl' , ['$scope' , 'Auth','$rootScope' ,'$location' ,
function ($scope, Auth, $rootS cope, $location) {
$scope.logout = function () {
Auth.logout();
};
}]);

1.7. Main
angular.module( 'MyApp')
.controller( 'MainCtrl' , function ($scope, $alert, Show, $rootScope, $http,
Subscription) {
$scope.mainClass = 'page-content' ;
$rootScope.isLogged = true;

$scope.headingTitle = 'Cele mai bune seriale' ;
$scope.shows = Show.query();

70 })

1.8. MyShows
angular.module( 'MyApp')
.controller( 'MyShowsCtrl' , function ($scope, User, Show, $rootScope) {
$scope.headingTitle = 'Show-uri personale' ;

$scope.mainClass = 'page-content' ;
$rootScope.isLogged = true;
var user = User.get({
userId: $scope.currentUser._id
}, function (user, err) {
if (user.personalShows.length > 0) {
$scope.shows = Show.query({
_id: user.personalShows
});

} else {
$scope.shows = [];
}
});
})

1.9. Navbar
angular.module( 'MyApp')
.controller( 'NavbarCtrl' , ['$route' , 'localStorageService' , '$scope' , '
$alert', 'Show', '$rootScope' , '$location' , '$window' , 'User',
function ($route, localStorageService, $scope, $alert, Show, $rootScope,
$location, $window, User) {
$scope.mainClass = 'page-content' ;
$rootScope.isLogged = true;

$scope.isAdmin = function () {
return $scope.currentUser.lev el;
}
}]);

1.10. Result
angular.module( 'MyApp')
.controller( 'ResultCtrl' , function ($scope, User, Show, $rootScope) {
$scope.headingTitle = 'Result' ;

$scope.mainClass = 'page-content' ;
$rootScope.isLogged = true;

var user = Us er.get({
userId: $scope.currentUser._id
}, function (user, err) {
$scope.shows = Show.query({
_id: user.temporaryShows
})

71 });
});

1.11. Seasond etail
angular.module( 'MyApp')
.controller( 'SeasonDetailCtrl' , function($http, $location, $window, $scope,
$rootScope, $routeParams, User, Show, Subscription, localStorageService) {
$scope.mainClass = 'page-content' ;
$rootScope.isLogged = true;
$scope.seasonEpisodes = [];

});

1.12. Settings
angular.module( 'MyApp')
.controller( 'SettingsCtrl' , function ($scope, Auth, $rootScope,
localStorageService, Auth) {
$scope.mainClass = 'page-content' ;
$rootScope.isLogged = true;
$scope.headingTitle = 'Setări Personale' ;

var user = JSON.parse(local StorageService.get( 'currentUser' ));

$scope.currentUserName = user.name;
$scope.currentUserEmail = user.email;
$scope.currentPassword = user.password;

$scope.saveSettings = function (argument) {
Auth.changeSettings({
name: $scope.c urrentUserName,
email: $scope.currentUserEmail,
password: $scope.currentPassword,
newPassword: $scope.newPassword
});
}
});

1.13. Show result

angular.module( 'MyApp')
.controller( 'ShowResultCtrl' , function (localStorageService, $locatio n,
$alert, $scope, User, Show, $rootScope, Subscription, $routeParams) {
$scope.headingTitle = 'Result' ;

$scope.mainClass = 'page-content' ;
$rootScope.isLogged = true;
Show.get({
showId: $routeParams.id
}, function (show, cb) {
$scope.show = show;

72 $scope.isAddedToMyShows = function () {
return $scope.show.owners.indexOf($scope.currentUser._id) !== -1;
}
$scope.addToMyShows = function () {
Subscription.addToMyShows(show,
$rootScope.currentUser) .success( function () {

$scope.show.owners.push($rootScope.currentUser._id);
})
}

$scope.removeFromMyShows = function () {
Subscription.removeFromMyShows(show,
$rootScope.currentUser).success( function (){
var index = $scope.show.owners.indexOf($rootScope.currentUser._id);
$scope.show.owners.splice(index, 1);
})
}
})
});

1.14. Signup
angular.module( 'MyApp')
.controller( 'SignupCtrl' , function ($scope, Auth, $rootScope) {

$scope.mai nClass = 'page-container' ;
$rootScope.isLogged = false;
$scope.defaultImage = 'utils/img/user.png' ;

$scope.signup = function () {
Auth.signup({
name: $scope.displayName,
email: $scope.email,
password: $scope.passwo rd,
photo: $scope.defaultImage
});
};
$scope.pageClass = 'fadeZoom'
});

1.15. Upcoming
angular.module( 'MyApp')
.controller( 'UpcomingCtrl' , function ($scope, User, Show, localStorageService,
$rootScope) {

$scope.headingTitle = 'Show-uri în desfășurare' ;
$scope.mainClass = 'page-content' ;
$rootScope.isLogged = true;

var userID = JSON.parse(localStorageService.get( 'currentUser' ))._id;

$scope.formatCurrentDate = function () {
var todayDate = new Date();
var todayMonth = ( "0" + (todayDate.getMonth() + 1)).slice( -2);
var todayDay = ( "0" + todayDate.getDate()).slice( -2);

73 var todayYear = todayDate.getFullYear();
var todayDateText = todayYear + "-" + todayMonth + "-" + todayDay;

return todayDateText;
}

$scope.currentDate = $scope.formatCurrentDate();
var user = User.get({
userId: userID
}, function (user, err) {
if (user.personalShows.length > 0) {
$scope.shows = Show.query({
_id: user.personal Shows
}, function (shows, err) {
for (var i = 0; i < shows.length; i++) {
seasons = shows[i].seasons;
lastSeasonEpisodes = seasons[seasons.length – 1].episodes;
for (var j = 0; j < lastSeasonEpisodes.len gth; j++) {
if (lastSeasonEpisodes[j].air_date > this.currentDate) {
shows[i].nextEpisode.name = lastSeasonEpisodes[j].name;
shows[i].nextEpisode.air_date = lastSeasonEpisodes[j].air_date;
shows[i].nextEpisode.number =
lastSeasonEpisodes[j].episode_number;
shows[i].nextEpisode.overview = lastSeasonEpisodes[j].overview;
shows[i].nextEpisode.nrsez =
lastSeasonEpisodes[j].season_number;
shows[i].nextEp isode.poster = lastSeasonEpisodes[j].still_path;
shows[i].nextEpisode.rate = lastSeasonEpisodes[j].vote_average;
} else if (lastSeasonEpisodes.length == 0) {
shows[i].nextEpisode.number = 1;
shows[i].nextEpisode.nrsez = seasons.length;
} else {
shows[i].nextEpisode.number = 1;
shows[i].nextEpisode.nrsez = seasons.length;
}
}
}
}.bind($scope));
} else {
$scope.shows = [];
}
}.bind($scope));

})

2. Directive
2.1. passwordStrength
angular.module( 'MyApp')
.directive( 'passwordStrength' , function () {
return {
restrict: 'A',
require: 'ngModel' ,
link: function (scope, element , attrs, ngModel) {
var indicator = element.children();
var dots = Array.prototype.slice.call(indicator.children());
var weakest = dots.slice( -1)[0];

74 var weak = dots.slice( -2);
var strong = dots.slice( -3);
var strongest = dots.slice( -4);

element.after(indicator);

element.bind( 'keyup', function () {
angular.forEach(dots, function (el) { el.style.backgroundColor =
'#ebeef1' ; });
if (ngModel.$modelValue) {
if (ngModel.$modelValue.length > 8) {
angular.forEach(strongest, function (el) {
el.style.backgroundColor = '#008cdd' ; });
} else if (ngModel.$modelValue.length > 5) {
angular.forEach(strong, function (el) { el.style.background Color =
'#6ead09' ; });
} else if (ngModel.$modelValue.length > 3) {
angular.forEach(weak, function (el) { el.style.backgroundColor =
'#e09115' ; });
} else {
weakest.style.backgroundColor = '#e01414' ;
}
}
});
},
template: '<span class="password -strength –
indicator"><span></span><span></span><span></span><span></span></span>'
};
});

2.2. uniqueEmail
angular.module( 'MyApp')
.directive( 'uniqueEmail' , function ($http) {
return {
restrict: 'A',
require: 'ngModel' ,
link: function (scope, element, attrs, ngModel) {
element.bind( 'blur', function () {
if (ngModel.$modelValue) {
$http.get( '/api/users' , { params: { email: ngMod el.$modelValue }
}).success( function (data) {
ngModel.$setValidity( 'unique' , data.available);
});
}
});
element.bind( 'keyup', function () {
ngModel.$setValidity( 'unique' , true);
});
}
};
});

3. Filtre
3.1. fromNow
angular.module( 'MyApp').

75 filter('fromNow' , function () {
return function (date) {
return moment(date).fromNow();
}
});

4. Servicii
4.1. Auth
angular.module( 'MyApp')
.factory( 'Auth', function ($http, $location, $root Scope, $alert, $window,
localStorageService, User) {
var user;
var token = localStorageService.get( 'token');
if (token) {
var payload = JSON.parse($window.atob(token.split( '.')[1]));
localStorageService.set( 'currentUser' , JSON.strin gify(payload.user));
$rootScope.currentUser = payload.user;
}

// Asynchronously initialize Facebook SDK
window.fbAsyncInit = function () {
FB.init({
appId: '854488264726746' ,
cookie: true,
xfbml: true,
version: 'v2.8'
});
FB.AppEvents.logPageView();
};

(function (d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {
return;
}
js = d.createElement(s);
js.id = id;
js.src = "//connect.facebook.net/en_US/sdk.js" ;
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script' , 'facebook -jssdk'));

return {
facebookLogin: function () {
FB.login( function (response) {
FB.api('/me?fields= id,name,email,permissions' , function (profile) {
var data = {
signedRequest: response.authResponse.signedRequest,
profile: profile
};
$http.post( '/auth/facebook' , data).success( function (token) {
var payload = JSON.parse($window.atob(token.split( '.')[1]));
localStorageService.set( 'token', token);
localStorageService.set( 'currentUser' ,
JSON.stringify(payload.user));
$rootScope.currentUser =
JSON.parse(localStorageService.get( 'currentUser' ));
$rootScope.currentUser = payload.user;

76 $location.path( '/');
});
});
}, {
scope: 'email, public_profile'
});
},

login: function (user) {
return $http.post( '/auth/login' , user)
.success( function (data) {
localStorageService.set( 'token', data.token);
//$window.localStorage.token = data.token;
var payload = JSON.parse($win dow.atob(data.token.split( '.')[1]));
localStorageService.set( 'currentUser' ,
JSON.stringify(payload.user));
$rootScope.currentUser =
JSON.parse(localStorageService.get( 'currentUser' ));
$location.path( '/');
})
.error(function () {
delete $window.localStorage.token;
$alert({
title: 'Error!' ,
content: 'Nume de utilizator/parolă greșite' ,
animation: 'fadeZoomFadeDown' ,
type: 'material',
duration: 3
});
});
},
signup: function (user) {
return $http.post( '/auth/signup' , user)
})
.error(function (response) {
$alert({
title: 'Error!' ,
content: response.data,
animation: 'fadeZoomFadeDown' ,
type: 'material' ,
duration: 3
});
});
},
logout: function () {
if ($rootScope.currentUser.facebook) {
FB.logout( function (response) {});
localStorageService.clearAll();
}
localStorageService.clearAll();
$rootScope.currentUser = null;
$alert({
content: 'Ai fost deconectat cu succes' ,
animation: 'fadeZoomFadeDown' ,
type: 'material' ,
duration: 3
});
},
isLogin: function (user) {
return (user) ? user : false;
},
changeSettings: function (user) {

77 return $http.post( '/auth/changese ttings', user)
.success( function () {
$alert({
title: 'Felicitări!' ,
content: 'Parola a fost schimbată cu succes.' ,
animation: 'fadeZoomFadeUp' ,
type: 'material' ,
duration: 3
});
})
.error(function (response) {
$alert({
title: 'Error!' ,
content: response.data,
animation: 'fadeZoomFadeUp' ,
type: 'material' ,
duration: 3
});
});
}
};
});

4.2. Show
angular.module( 'MyApp')
.factory( 'Show', function ($resource) {
return $resource( 'api/shows/:showId' );
});

4.3. Subscription
angular.module( 'MyApp')
.factory( 'Subscription' , function ($http) {
return {
addToMyShows: function (show, user) {
return $http.post( '/api/addtomyshows' , {
showId: show._id,
currentUser: user
})
},
removeFromMyShows: function (show, user) {
return $http.post('/api/removefrommyshows' , {
showId: show._id,
currentUser: user
})
},
addToWatchedEpisode: function (showId, episode, user) {
return $http.post( '/api/saveepisode' , {
currentUser: user,
episode: episode,
showId: showId
})
},
removeWatchedEpisodes: function (showId, episode, user) {
return $http.post( '/api/removeepisode' , {
currentUser: user,

78 episode: episode,
showId: show Id
})
},
getPersonalShows: function (user) {
return $http.post( '/api/getpersonalshows' , {
currentUser: user,
})
},
getUsers: function () {
return $http.get( '/api/userlist' , {})
},
removeUser: function (user) {
return $http.post( '/api/removeuser' , {
user: user
})
},
deleteUserShow: function (show, user) {
return $http.post( '/api/removespecificshow' , {
user: user,
showId: show._id
})
},
};
});

4.4. User
angular.module( 'MyApp')
.factory( 'User', function ($resource) {
return $resource( 'api/users/:userId' );
});

5. app.js
angular.module( 'MyApp', ['ngResource' ,'angularMoment' , 'ngMessages' , 'ngRoute' ,
'ngAnimat e', 'mgcrea.ngStrap' , 'LocalStorageModule' ])
.config( function ($routeProvider, $locationProvider, ) {
$locationProvider.html5Mode( true);

$routeProvider
.when('/admin' , {
templateUrl: 'views/admin.html' ,
controller: 'AdminCtr l',
})
.when('/', {
templateUrl: 'views/home.html' ,
controller: 'MainCtrl' ,
})
.when('/shows/:id' , {
templateUrl: 'views/detail.html' ,
controller: 'DetailCtrl' ,
})
.when('/statistics' , {
templateUrl: 'views/diagram.html' ,
controller: 'DiagramCtrl' ,
})
.when('/login' , {
templateUrl: 'views/login.html' ,

79 controller: 'LoginCtrl' ,
})
.when('/signup' , {
templateUrl: 'views/signup.htm l',
controller: 'SignupCtrl'
})
.when('/add', {
templateUrl: 'views/add.html' ,
controller: 'AddCtrl' ,
})
.when('/settings' , {
templateUrl: 'views/settings.html' ,
controller: 'SettingsCtrl' ,
})
.when('/myshows' , {
templateUrl: 'views/myshows.html' ,
controller: 'MyShowsCtrl' ,
})
.when('/upcoming' , {
templateUrl: 'views/upcoming.html' ,
controller: 'UpcomingCtrl' ,
})
.when('/result ', {
templateUrl: 'views/result.html' ,
controller: 'ResultCtrl' ,
})
.when('/result/:id' , {
templateUrl: 'views/showresult.html' ,
controller: 'ShowResultCtrl' ,
})
.when('/shows/:id/:seasonId' , {
templateUrl: 'views/seasondetail.html' ,
controller: 'SeasonDetailCtrl' ,
})
.when('/calendar' , {
templateUrl: 'views/calendar.html' ,
controller: 'CalendarCtrl' ,
})
.otherwise({
redirectTo: '/',
});
})

.config([ 'localStorageServiceProvider' , function (localStorageServiceProvider) {
localStorageServiceProvider.setPrefix( 'Serials' );
}])

.run(['$rootScope' , '$location' , 'Auth', function ($rootScope, $location, auth)
{
$rootScope.$on( '$routeC hangeStart' , function (event, next, current) {
var user = event.currentScope.currentUser;
if (!auth.isLogin(user) && $location.url() !== "/signup" ) {
event.preventDefault();
$location.path( '/login' );
}

});

}])

80

6. server .js
var path = require( 'path');
var express = require( 'express' );
var bodyParser = require( 'body-parser');
var logger = require( 'morgan' );

var bcrypt = require( 'bcryptjs' );
var mongoose = require( 'mongoose' );
var crypto = require( 'crypto' );

var jwt = require( 'jwt-simple');
var moment = require( 'moment' );

var async = require( 'async');
var request = require( 'request' );

var agenda = require( 'agenda' )({
db: {
address: 'mongodb://localhost/test'
},
function (err) {
if (err) {
console.log(err);
throw err;
}
}
});
var sugar = require( 'sugar');
var nodemailer = require( 'nodemailer' );
var _ = require( 'lodash' );

var tokenSecret = 'your unique secret' ;

//mongoose Show Schema
var showSchema = new mongoose.S chema({
showId: {
type: Number,
unique: true
},
name: String,
firstAired: Date,
overview: String,
rating: Number,
status: String,
poster: String,
banner: String,
creatorName: String,
creatorProfil e: String,
network: String,
episodesNumber: Number,
episodeDuration: [Number],
genre: [{}],

81 owners: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}],
seasons: [{
seasonId: Number,
seasonNum ber: Number,
episodeNumber: Number,
episodeName: String,
airDate: Date,
seasonPoster: String,
episodes: [],
}],
nextEpisode: [{
episode_name: String,
episode_air_date: Date,
episode_nu mber: Number,
episode_season_number: Number,
episode_overview: String,
episode_poster: String,
episode_rate: Number
}]
});

//mongoose User Schema
var userSchema = new mongoose.Schema({
name: {
type: String,
trim: true,
required: true
},
email: {
type: String,
unique: true,
lowercase: true,
trim: true
},
facebook: {
id: String,
email: String,
photo: String
},
personalShows: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Show',
progress: []
}],
facebookId: String,
episodesWatched: [],
password: String,
level: Number,
personalPhoto: String
});

userSchema.pre( 'save', function(next) {
var user = this;
if (!user.isModified( 'password' )) return next();
bcrypt.genSalt(10, function (err, salt) {
if (err) return next(err);
bcrypt.hash(user.password, salt, function (err, hash) {
if (err) return next(err);
user.password = hash;

82 next();
});
});
});

userSchema.methods.comparePassword = function (candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, function (err, isMatch) {
if (err) return cb(err);
cb(null, isMatch);
});
};

var User = mongoose.model( 'User', userSchema);
var Show = mongoose.model( 'Show', showSchema);

mongoose.Promise = global.Promise;
mongoose.connect( 'localhost:27017/test' );

var app = express();

app.set('port', process.env.PORT || 4000);
app.use(logger( 'dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: false
}));
app.use(express.static(path.join(__dirname, 'public' )));

function createJwtToken(user) {
var payload = {
user: user,
iat: new Date().getTime(),
exp: moment().add( 'days', 7).valueOf()
};

return jwt.encode(payload, tokenSecret);
}

app.post( '/auth/facebook' , function (req, res, next) {
var profile = req.body.profile;
var signedRequest = req.body.signedRequest;
var encodedSignature = signedRequest.split( '.')[0];
var payload = signedRequest.split( '.')[1];
var appSecret = '1cffa7590dcbabf72c9915e2817baf70' ;

var expectedSignature = crypto.createHmac( 'sha256',
appSecret).update(payload).digest( 'base64' );
expectedSignature = expectedSignature.replace(/ \+/g, '-').replace(/ \//g,
'_').replace(/=+$/, '');

if (encodedSignature !== expectedSignature) {
return res.send(400, 'Invalid Request Signature ');
}
User.findOne({
facebookId: profile.id
}, function (err, existingUser) {
if (existingUser) {
var token = createJwtToken(existingUser);
return res.send(token);
}
var level;

83 if (profile.email == 'roberttestemailadress@gmail.com' ) {
level = 1;
} else {
level = 0;
}
var user = new User({
name: profile.name,
facebookId: profile.id,
email: profile.emai l,
level: level,
personalPhoto: '//graph.facebook.com/' + profile.id +
'/picture?width=100&height=100' ,
facebook: {
id: profile.id,
email: profile.email,
}
});
user.save( function (err) {
if (err) return next(err);
var token = createJwtToken(user);
res.send(token);
});
});
});

app.post( '/auth/changesettings' , function (req, res, next) {
var password = req.body.ne wPassword;
User.findOne({
email: req.body.email
}, function (err, user) {
user.password = password;
user.save( function (err) {
if (err) return next(err);
res.send(200);
});
});
});

app.post('/api/getpersonalshows' , function (req, res, next) {
var user = req.body.currentUser;
console.log(user);
User.findOne({
email: user.email
})
.populate( 'personalShows' )
.exec(function (err, user) {
res.send(user.personalShows);
})
});

app.post( '/api/saveepisode' , function (req, res, next) {
User.findOne({
'email': req.body.currentUser.email
}, function (err, user) {
if (err) return next(err);
var episode = req .body.episode;
user.episodesWatched.push(episode);
user.save( function (err) {
if (err) return next(err);

84 res.send(200);
});
})
})

app.post( '/api/removeepisode' , function (req, res, next) {
User.findOn e({
'email': req.body.currentUser.email
}, function (err, user) {
if (err) return next(err);
var episodeId = req.body.episode.id;
var index = user.episodesWatched.indexOf(episodeId);
user.episodesWatched.splice(in dex, 1);
user.save( function (err) {
if (err) return next(err);
res.send(200);
});
})
})

app.post( '/api/removeuser' , function (req, res, next) {
User.findOne({
'email': req.body.user.email
}, function(err, user) {
if (err) return next(err);
user.remove( function (err) {
if (err) return next(err);
res.send(200);
})
})
})

app.post( '/api/removespecificshow' , function (req, res, next) {
var showId = req.body.showId;
User.findOne({
'email': req.body.user.email
}, function (err, user) {
var indexShow = user.personalShows.indexOf(showId);
user.personalShows.splice(indexShow, 1);
user.save( function (err) {
if (err) next(err);
res.send(user);
})
})
})

app.post( '/auth/signup' , function (req, res, next) {

//set the level:
//0 – normal user
//1 – administrator

var level;
if (req.body.email === 'roberttestemailadre ss@gmail.com' ) {
level = 1;
} else {
level = 0;
}

85 var user = new User({
name: req.body.name,
email: req.body.email,
password: req.body.password,
level: level,
personalPhoto: req.body.photo ,
facebookId: '-'
});
user.save( function (err) {
if (err) return next(err);
res.send(200);
});
});

app.post( '/auth/login' , function (req, res, next) {
User.findOne({
email: req.body.email
}, function (err, user) {
if (!user) return res.send(401, 'User does not exist' );
user.comparePassword(req.body.password, function (err, isMatch) {
if (!isMatch) return res.send(401, 'Invalid email and/or
password' );
var token = creat eJwtToken(user);
res.send({
token: token
});
});
});
});

app.get( '/api/users' , function (req, res, next) {
if (!req.query.email) {
return res.send(400, {
message: 'Email parameter is required.'
});
}
User.findOne({
email: req.query.email
}, function (err, user) {
if (err) return next(err);
res.send({
available: !user
});
});
});

app.get( '/api/users/:id' , function (req, res, next) {
User.findById(req.params.id, function (err, user) {
if (err) return next(err);
res.send(user);
});
});

app.get( '/api/shows' , function (req, res, next) {
if (Object.keys(req.query).length == 0) {
var query = Show.find({}, 'name _id poster rating' , function (err,
shows) {
res.send(shows);

86 });
} else {
var query = Show.find();
var personalShowsIDs = req.query._id;
if (personalShowsIDs == undefined) {
personalShowsIDs = [];
}
if (personalShowsIDs) {
query.where( '_id').in(personalShowsIDs);
} else {
query.limit(12);
}
query.exec( function (err, shows) {
if (err) return next(err);
res.send(shows);
})

}

});

app.get( '/api/shows/:id' , function (req, res, next) {
Show.findById(req.params.id, function (err, show) {
if (err) return next(err);
res.send(show);
});
});

app.get( '/api/userli st', function (req, res, next) {
User.find({}, function (err, users) {
var userMap = {};
users.forEach( function (user) {
userMap[user._id] = user;
});
res.send(userMap);
});
});

app.post( '/api/shows' , function(req, res, next) {
var seriesName = req.body.showName
.toLowerCase()
.replace(/ /g, '-')
.replace(/[^ \w-]+/g, '');
var userId = req.body.currentUser._id;
var userEmail = req.body.currentUser.email;
var foundTempo rary = false;
var apyKey = "9f67cd8600bf766fe003d2a5462a94c7" ;
async.waterfall([
function (callback) {

request.get( 'https://api.themoviedb.org/3/search/tv?api_key=' + apyKey +
'&query=' + seriesName, function (error, response, bod y) {
if (error) return next(error);
if (response.body == '') {
return res.send(400, {
message: seriesName + ' was not found.'
});
}
var parsedBody = JSON.parse(body);

87 var seriesId = parsedBody.results[0].id;
callback( null, seriesId);
});
},

function (seriesId, callback) {
request.get( 'https://api.themoviedb.org /3/tv/' + seriesId +
'?api_key=' + apyKey + '&language=en -US', function (error, response, body) {
if (error) return next(error);
var parsedResponse = (JSON.parse(body));
console.log(parsedResponse);
if (parsedResponse.created_by.length == 0) {
parsedResponse.created_by.push({
name: "Necunoscut" ,
profile_path: null
})
}
var seasons = parsedResponse.seasons;
var show = new Show({
showId: parsedResponse.id,
name: parsedResponse.name,
firstAired: parsedResponse.first_air_date,
genre: pars edResponse.genres[0],
overview: parsedResponse.overview,
rating: parsedResponse.vote_average,
status: parsedResponse.status,
banner: parsedResponse.backdrop_path,
poster: parsedResponse.poster_path,
creatorName: parsedResponse.created_by[0].name,
creatorProfile: parsedResponse.created_by[0].profile_path,
network: parsedResponse.networks[0].name,
nextEpisode: {
name: '',
number: '',
overview: '',
poster: '',
nrsez: '',
air_date: '',
rate: ''
},
episodesNumber: parsedResponse.number_of_episodes,
episodeDuration: parsedResponse.episode_run_time,
seasons: []
});
_.each(seasons, function (season) {
show.seasons.push({
seasonId: season.id,
seasonNumber: season.season_number,
episodeNumber: season.episode_count,
episodeName: season.name,
seasonPoster: season.poster_path,
airDate: season.air_date,
episodes: [],
});
});
callback( null, show);
});
},

function (show, callback) {
var showId = show.showId;
var showName = show.name;

88 var nrPrimulSezon;
var nrTotalSezoane;
if (show.seasons.length == 0) {
callback( null, show);
return;
}
if (show.seasons[0].seasonNumber == 0) {
nrPrimulSezon = 1;
} else {
nrPrimulSezon = show.seasons[0].seasonNumber;
}
if (show.seasons.length == 1) {
nrTotalSezoane = show.seasons.length + 1;
} else {
nrTotalSezoane = show.seasons.length;
}
async.whilst(
function () {
return nrPrimulSezon < nrTotalSezoane;
},
function (whilstCb) {
request.get( 'https://api.themoviedb.org/3/tv/' +
showId + '/season/' + nrPrimulSezon +
'?api_key=9f67cd8600bf766fe003d2a5462a94c 7&language=en -US',
function (error, response, body) {
if (error) return next(error);
var seasonEpisodes = JSON.parse(body).episodes;

if (show.seasons.length == 1 && nrPrimulSezon == 1) {

show.seasons[nrPrimulSezon – 1].episodes =
easonEpisodes
_.each(show.seasons[nrPrimulSezon – 1].episodes,
function (episode) {
episode.showName = showName;
})
} else {

show.seasons[nrPrimulSezon].episodes = seasonEpisodes;
_.each(show.seasons[nrPrimulSezon – 1].episodes,
function (episode) {
episode.showName = showName;
})
}
nrPrimulSezon++;
whilstCb( null);
})
},
function (x) {
callback( null, show);
}
)
}
], function (err, show) {
if (err) return next(err);
show.save( function (err) {
if (err) {
if (err.code == 11000) {
return res.send(409, {
message: show.name + ' deja există.'
});
}

89 return next(err);
}
res.send(200);
});

})
});

app.post( '/api/addtomyshows' , function (req, res, next) {

var showId = req.bod y.showId;

Show.findById(showId, function (err, show) {

show.owners.push(req.body.currentUser._id);
show.save( function (err, show) {
var lastSeason = show.seasons[show.seasons.length – 1];
var upcomingEpisode = la stSeason.episodes.filter( function (episode)
{
return new Date(episode.air_date) > new Date();
})[0];
var currentDate = moment().format();
console.log(upcomingEpisode);
if (upcomingEpisode) {
var targetDate = moment(upcomingEpisode.air_date).format();
var _duration =
moment.duration(moment(upcomingEpisode.air_date).diff(currentDate));
var _days = Math.floor(_duration.asDays());
}
if (upcomingEpisode !== undefined) {
agenda.schedule( 'in ' + _days + 'days', 'send email alert' ,
show.name).repeatEvery( '1 week' ).save()

}
});
})
User.findById(req.body.currentUser._id, function (err, user) {
if (err) return next(err);
user.personalShows.push(showId);
user.save( function (err) {
if (err) return next(err);
res.send(200);
});
})

});

app.post( '/api/removefrommyshows' , function (req, res , next) {
var showId = req.body.showId;
Show.findById(showId, function (err, show) {
var index = show.owners.indexOf(req.body.currentUser._id);
show.owners.splice(index, 1);
show.save();
})
User.findById(req.body.curr entUser._id, function (err, user) {
if (err) return next(err);
var index = user.personalShows.indexOf(showId);
user.personalShows.splice(index, 1);
user.save( function (err) {
if (err) return next(err);

90 res.send(200);
});
})
});

app.get( '*', function (req, res) {
res.redirect( '/#' + req.originalUrl);
});

app.use( function (err, req, res, next) {
res.send(500, {
message: err.message
});
});

app.listen(app.get( 'port'), function() {
console.log( 'Express server listening on port ' + app.get( 'port'));
});

agenda.define( 'send email alert' , function (job, done) {
Show.findOne({
name: job.attrs.data
}, function (err, show) {}).populate( 'owners' ).exec(function (err, show) {
if (show.owners !== null) {
var emails = show.owners.map( function (user) {
return user.email;
});
}
var lastSeason = show.seasons[show.seasons.length – 1];
var upcomingEpisode = lastSeason.episodes.filter( function (episode) {
return new Date(episode.air_date) > new Date();
})[0];
if (upcomingEpisode !== undefined && emails.length > 0) {
var smtpTransport = nodemailer.createTransport({
service: 'Gmail',
auth: {
user: 'roberttestemailadress@gmail.com' ,
pass: 'parolatest'
}
});
var mailOptions = {
tls: {
rejectUnauthorized: false
},
from: 'Radoi Robert' ,
to: emails.join( ','),
subject: 'Mâine apare un episod nou din ' + show.name,
text: 'Episodul ' + upcomingEpisode.episo de_number + ' urmează
să apară \n\n' + 'Iată ce se va întampla în acest episod \n\n' +
upcomingEpisode.overview
};
smtpTransport.sendMail(mailOptions, function (error, info) {
smtpTransport.close();
});
}
});
});

agenda.on( 'ready', function () {
agenda.start();
});

91
agenda.on( 'start', function (job) {
console.log( "Job %s starting" , job.attrs.name);
});

agenda.on( 'complete' , function (job) {
console.log( "Job %s finished" , job.attrs.name) ;
});
INDEX
A
Analiza rutelor aplicației ………………………….. …… 46
ANEXĂ COD ………………………….. …………………….. 64
Aplicație structurată pe o singură pagină (Single
Page Application) ………………………….. ………… 19
Arhitectura MVC ………………………….. ……………… 16
Autentificare folosind contul creat ………………… 31
Autentificare folosind contul de Facebook …….. 31
Avantajele și dezavantajele Bootstrap -ului ……… 21
Avantajele și dezavantajele unei SPA ……………… 19
Avertizare e -mail ………………………….. …………….. 59
B
Baze de date NoSQL ………………………….. ………… 22
BIBLIOGRAFIE ………………………….. ………………….. 62
Bootstrap ………………………….. ……………………….. 21
C
Capitolul I ………………………….. ……………………….. 13
Capitolul II ………………………….. ………………………. 14
Capitolul IV ………………………….. …………………….. 29
CAPITOLUL V ………………………….. …………………… 45
Caracterizare ………………………….. …………………… 14
Comparație între NodeJS și PHP …………………….. 18
CONCLUZII ………………………….. ……………………… 61
Crearea colecțiilor bazei de date ……………………. 24
CUPRINSUL ………………………….. …………………….. 13
D
Descriere ………………………….. ………………………… 22
Descriere generală ………………………….. …………… 21 F
Fereastră de autentificare ………………………….. …. 30
Fereastră Detalii show ………………………….. ……… 41
Fereastra principală ………………………….. …………. 32
Fereastră Show personal ………………………….. ….. 42
I
Introducer e in AngularJS ………………………….. …… 15
Introducere în NodeJS ………………………….. ………. 17
J
JavaScript și Java ………………………….. ……………… 14
L
Limbajul JavaScript ………………………….. …………… 14
M
Meniul Acasă – Căutare ………………………….. …….. 33
Meniul Adaugă ………………………….. ………………… 36
Meniul Calendar ………………………….. ………………. 35
Meniul Setări Personale ………………………….. ……. 40
Meniul Show -uri în desfășurare ……………………… 35
Meniul Show -uri personale ………………………….. .. 34
Meniul Sta tistici ………………………….. ………………. 37
MongoDB ………………………….. ……………………….. 22
MongoDB Compass ………………………….. …………. 24
Motivația ………………………….. ………………………… 13
O
Operații de tip CRUD (Create, Read, Update,
Delete) ………………………….. ……………………….. 23

92 P
Prezentare generală ……………………… 15, 17, 19, 22
Prezentarea aplicației ………………………….. ……… 29
Prezentarea generală ………………………….. ……….. 29
Proiectarea bazei de date ………………………….. … 22
R
REFERINȚE WEB ………………………….. ………………. 63
Ruta “ add” ………………………….. ……………………… 55
Ruta “admin” ………………………….. ………………….. 53
Ruta “calendar” ………………………….. ………………. 52
Ruta “login ………………………….. ……………………… 47
Ruta “ myshows ” ………………………….. ……………… 51
Ruta “ settings ” ………………………….. ………………… 53
Ruta “signup" ………………………….. ………………….. 49 Ruta “ upcoming ” ………………………….. ……………… 52
Ruta „result/id” ………………………….. ……………….. 58
Ruta „shows/id ” ………………………….. ………………. 56
Ruta principală ………………………….. ………………… 51
S
Secțiunea – Meniuri ………………………….. …………. 32
Secțiunea Setări utilizatori ………………………….. .. 39
Secțiunea Util izatori ………………………….. ………… 38
Structura aplicației ………………………….. …………… 45
Structura internă a bazei de date …………………… 26
Structura unui program JavaScript …………………. 15
Z
Zona de administrare ………………………….. ……….. 38

Similar Posts