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

1

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

2
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ț Resceanu

Iulie, 2017
CRAIOVA

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

4 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] ,

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

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

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 în JavaScript, AngularJS, NodeJS ,
SPA și Bootstrap
2. Proiectarea bazei de date
3. Prezentarea aplicației
4. Arhitectura sistemului. Realizare practică
5. 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. Marius MARIAN

Data eliberării temei :
05.11 .2016

Termenul estimat de predare a
proiectului :
04.07.2017

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

6
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:
Specializarea: [Denumirea oficială a specializării absolvite de candidat ]
Titlul proiectului : [Titlul lucrării ]
Locația în care s -a realizat practica de
documentare (se bifează una sau mai
multe din opțiunile din dreapta): În facultate □
În producț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
□ Compl exă

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ă

Implementarea Insuficientă Satisfăcătoare Bună Foarte bună

7 □ □ □ □
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,

8 REZUMATUL PROIECTULUI

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

Diferenț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 înregistrați având posibilitatea de a le edita
datele personale, de a le șterge anumite seriale preferate, pr ecum și posibilitatea de ștergere definitivă
a acestora.
Primul capitol al lu crării prezintă scopul acestei aplicații precum și motivația de a crea o asftel
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 dezavantajele
folosirii unei astfel de aplicații . Ultimul subcapitol conține o scurtă prezentare a ceea ce înseamnă
Bootstrap.
Cel de -al treile capitol al lucrării descrie o bază de date de tip NoSQL, arhitectura bazei de
date precum și o scurtă prezentare a programului 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 meniurile ș i submeniurile specifice, și modul
de interacțiune al utilizatorului cu aplicația.

9 Î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
acestia, concluzii care sunt confirmarea rezumatului și a teme i tratate în lucrare.

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

10 CUPRINSUL
1 INTRODUCERE ………………………….. ………………………….. ………………………….. ………………………….. ………… 4
1.1 SCOPUL ………………………….. ………………………….. ………………………….. ………………………….. ………………. 4
1.2 MOTIVAȚIA ………………………….. ………………………….. ………………………….. ………………………….. ………… 4
2 INTRODUCERE ÎN JAVASC RIPT, ANGULARJS , NODEJS , SPA ȘI B OOTSTRAP ………………………….. ……………………….. 4
1.1. Limbajul Javascript ………………………….. ………………………….. …………………………. ………………………….. 4
1.1.1. Caracterizare ………………………….. ………………………….. ………………………….. ………………………….. 4
1.1.2 . JavaScript și Java ………………………….. ………………………….. ………………………….. ……………………. 4
1.1.3 . Structura unui program JavaScript ………………………….. ………………………….. ………………………… 5
1.2. Introducere în AngularJS ………………………….. ………………………….. ………………………….. …………………… 5
1.2.1. Prezentare generală ………………………….. ………………………….. ………………………….. …………………. 5
1.2.2. Arhitectura MVC ………………………….. ………………………….. ………………………….. ……………………. 6
1.3. Introducere în NodeJ S ………………………….. ………………………….. ………………………….. ……………………….. 7
1.3.1. Prezentare generală ………………………….. ………………………….. ………………………….. …………………. 7
1.3.2. Comparație între NodeJS și PHP ………………………….. ………………………….. ………………………….. . 8
1.4. Aplicație structurată pe o singură pagină (Single Page Application) ………………………….. …………………. 9
1.4.1. Prezentare generală ………………………….. ………………………….. ………………………….. …………………. 9
1.4.2. Avantajele și dezavantajele unei SPA ………………………….. ………………………….. ……………………. 9
1.5. Bootstrap ………………………….. ………………………….. ………………………….. ………………………….. …………… 11
1.5.1. Descrier e generală ………………………….. ………………………….. ………………………….. ………………… 11
1.5.2. Avantajele și dezavantajeșe Bootstrap -ului ………………………….. ………………………….. …………… 11

CAPITOLUL II ………………………….. ………………………….. …………………… ERROR! BOOKMARK NOT DEFINED.
2. PROIECTAREA BAZEI DE DATE ………………………….. ………………………….. ………………………….. ………………………. 12
2.1. Baze de date NoSQL ………………………….. ………………………….. ………………………….. ……………………….. 12
2.2. MongoDB ………………………….. ………………………….. ………………………….. ………………………….. …………. 12
2.2.1. Descriere ………………………….. ………………………….. ………………………….. ………………………….. …. 12
2.2.2. Operații de tip CRUD (Create, Read, Update, Delete) ………………………….. ………………………… 13
2.2.3. MongoDB Compass ………………………….. ………………………….. ………………………….. ……………… 14
2.3. Crearea colecțiilor bazei de date ………………………….. ………………………….. ………………………….. ……….. 14
2.4. Structura internă a bazei de date ………………………….. ………………………….. ………………………….. ……….. 16

CAPITOLUL III ………………………….. ………………………….. ………………………….. ………………………….. …………….. 19
3. Prezentar ea aplicației ………………………….. ………………………….. ………………………….. …………………………. 19
3.1. Prezentare generală ………………………….. ………………………….. ………………………….. ………………….. 19
3.2. Fereastră de autentificare ………………………….. ………………………….. ………………………….. ………….. 20
3.3. Fereastră principală ………………………….. ………………………….. ………………………….. …………………. 21

11 3.3.1. Secțiunea – Meniuri ………………………….. ………………………….. ………………………….. ……….. 22
3.3.1.1. Meniul Acasă – Căutare ………………………….. ………………………….. ……………………. 23
3.3.1.2. Meniul Show -uri personale ………………………….. ………………………….. ………………. 23
3.3.1.3. Meniul Show -uri în desfășurare ………………………….. ………………………….. ………… 24
3.3.1.4. Meniul Calendar ………………………….. ………………………….. ………………………….. …. 25
3.3.1.5. Meniul Adaugă ………………………….. ………………………….. ………………………….. …… 26
3.3.1.6. Zona de administrare ………………………….. ………………………….. ……………………….. 26
3.3.1.7. Meniul Setări personale ………………………….. ………………………….. ……………………. 28
3.4. Fereastră Detalii show ………………………….. ………………………….. ………………………….. ……………… 29
3.5. Fereastră Show personal ………………………….. ………………………….. ………………………….. …………… 30

CAPITOLUL IV ………………………….. ………………………….. ………………………….. ………………………….. …………….. 33
4. Arhitectura siste mului. Realizare practică ………………………….. ………………………….. ………………………….. 33
4.1. Structura aplicației ………………………….. ………………………….. ………………………….. …………………… 33
4.2. Analiza rutelor aplicației ………………………….. ………………………….. ………………………….. ………….. 34
4.2.1. Ruta "login" ………………………….. ………………………….. ………………………….. …………………. 35
4.2.2. Ruta "signup" ………………………….. ………………………….. ………………………….. ……………….. 37
4.2.3. Ruta principală ………………………….. ………………………….. ………………………….. ……………… 39
4.2.4. Ruta "myshows" ………………………….. ………………………….. ………………………….. ……………. 39
4.2.5. Ruta "upcoming" ………………………….. ………………………….. ………………………….. …………… 40
4.2.6. Ruta "calendar" ………………………….. ………………………….. ………………………….. …………….. 40
4.2.7. Ruta " settings" ………………………….. ………………………….. ………………………….. ……………… 41
4.2.8. Ruta "admin" ………………………….. ………………………….. ………………………….. ………………… 42
4.2.9. Ruta "add" ………………………….. ………………………….. ………………………….. ……………………. 43
4.2.10. Ruta "shows/id" ………………………….. ………………………….. ………………………….. ………….. 44
4.2.11. Ruta "result/id" ………………………….. ………………………….. ………………………….. ……………. 45
4.3. Avertizare e -mail ………………………….. ………………………….. ………………………….. …………………….. 47

CONCLUZII ………………………….. ………………………….. ………………………….. ………………………….. ………………….. 49
BIBLIOGRAFIE ………………………….. ………………………….. ………………………….. ………………………….. ……………. 50

12 LISTA FIGURILOR
FIGURA 1. SELECTAREA PRIN CLICK DREAPT A A OPȚIUNII „UPDATE FIELD ” …. ERROR ! BOOKMARK NOT DEFINED .
FIGURA 2. ACTUALIZAREA ÎNTREGUL UI TABEL ………………………….. …………… ERROR ! BOOKMARK NOT DEFINED .

13 Capitolul I
– Introducere –

1.1 Scopul
Această aplicație a fost con cepută 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ă 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ă marcheze episoadele văzute .
Fiecare serial pe care un utilizator l -a înregistrat în secțiunea anterioară 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 user nu
dorește să piardă șirul episoadelor vizionate sau să fie înștiințat cu privire la episoade le care urmează
să apară .

1.2 Motivația
Am ales această temă din dorinț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țiile MongoDB –
ului, care este o baza 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 defavo area
unei aplicații de tip MPA(Multi Page Application).

14 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 (orientat pe obiecte) , care se bazea ză 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, JavaScript mai este folosit și pent ru 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 Java

Cele două limbaje de programar e 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 server.
JavaScript a apărut inițial sub denumirea de Live Sc ript î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
pogramare .
 JavaScript poat e fi executat doar în cadrul browser -ului, pe când în Java pot fi create
aplicații atât pentru browser cât si pentru Mașina Virtuală.
 Codul limbajului Java trebuie să fie compilat, pe când cel din JavaScript este prezentat
sub formă de text.
 Sunt dependete de plugin -uri diferite.

15 2.1.3. Structura unui program JavaScript

Pentru a crea un program în limbajul JavaScript nu este nevoie decât de unde editor pentru
text, cum ar fi NotePad++, SublimeText, Atom. Depistarea erorilor se face direct in browser, fară 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 World” , care apare în toate cărtile de programare are
următoarea structură:
var mesaj_intampinare = „Hello World”;
console.log(mesaj_intampinare);

2.2. Introducere in AngularJS

2.2.1. Prezentare generală
AngularJS este un framework dezvoltat la început de către Misko Hever y și Adam Abrons
fiind menț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(Aplicație pe o singură pagină), dar și pentru aplicații le
mobile deoarece se combină foarte ușor cu Bootstrap, rezultatul fiind o aplicație fluidă, rapidă dar ș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 mare 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 important deoarece sunt folosite din ce in ce mai des
dispozitivele mobile(tablete, telefoane) care folosesc un timp relativ superior de încărcare față de
conexiunil e clasice.

16 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 administrarea datelor aplicației.
 View – afișează fizic datelor unui utilizator
 Controller – controlea ză interacțiunea dintre Model și View

Această arhitectură este foarte populară 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șat e.

Figura 2.1. Arhitectura MVC

În AngularJs, modelul constă în toate datele primitive cum ar fi Integer, String și Boolean și
tipuri complexe sub formă de obiecte. Pe scurt, modelul este un simplu obiecte JavaScript. Însă acesta
poate fi co nstruit folosind orice baza de date, precum Sql Server sau MySql sau chiar din JSON.
$scope.customer = {
”Nume”: ”Robert”
}
Eveniment
Controller
View
Model

17 În exemplul anterior, $scope este un obiect iar customer este o variabilă care este atașată de
scopul obiectului.
În Angul arJs, 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 AngularJs, controller -ul oferă un control asupra view -ului și modelului, protejând d atele
în funcție de request. Cel mai important lucru este că modelul trăies te in 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 serve r. A fost creat de Ryan Dahl
alături de alți programatori în 2009. În 2011 a fost lansat Node Package Manager (NPM) care are
scopul de a facilita instalarea altor module JavaScript dezvoltate de către comunitatea NodeJs.
NodeJS -ul a contribuit la creșterea și evoluția JavaScript -ului, utilizatorii realizând că
JavaScript -ul pe server nu este așa de rău ca pe browser.

Figura 2.2. Schema NodeJS

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

18 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 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
asincronă, introducând promisiun ile si callback -urile. Un alt avantaj al folosirii NodeJs -ului îl
constituie faptul că este folosit același limbaj atât pe partea de server cât și pe partea de client.

Figura 2.3. PHP vs NodeJS

În comparație cu PHP, care este un limbaj de programare, NodeJs este m ai de grabă un
intepretor pentru cod de JavaScript. Pentru a executa un cod de PHP pe server, trebuie instalat PHP
Package, pe când NodeJs necesită Node.js installer.

Față de NodeJs , PHP conține puține variabile și funcții care pot fi mai ușor de folosit.U n alt
aspect de luat in 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 NodeJs poate interacționa cu SQL doar prin
intermediul unei librării.
Totuși, aplicațiile dezvo ltate cu NodeJs sunt mult mai rapide, însă PHP -ul dispune de unelte
mai dezvoltate

19 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 w eb care își
propune să ofere utilizatorului experiența unei aplicații desktop. Într -o astfel de aplicaț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 nece sitătile utilizatorului.
Interacțiunea unei SPA cu serverul se face prin fișiere de tip JSON (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ținutu l aplicației, fiind numit View.
Deoarece SPA descarcă în avans structura paginii web, nu mai este nevoie de un request
pentru conținut adiț ional.Acest lucru este similar ca și in cazul unei aplicații desktop.

2.4.2. Avantajele și dezavantajele unei SPA
Avantaje:
o rapiditatea , toate resursele fiind încărcate 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ă c rearea unei aplicații mobile datorită codului 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 offline
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 corespunzător.
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 securiz ată
o este greu de încărcat datorită framework -urilor de dimensiuni relativ mari.

20
Dferențel a între modurile 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.4.3.

Figura 2.4. Single Pag e Application și Multi Page Application

1
2.5. Bootst rap
2.5.1. Descriere generală
Bootstrap este un framework folosit pentru a proiecta siteuri și aplicații web, dezvoltat inițial
de Twitter, ca și framework pentru munca internă din companie. Acesta este în momentul de față cel
mai utilizat framework pentru inte rfeț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 procentuale sunt egale, fiind extrem de
fiabil, iar clasele responsive sunt controlate în fun cț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, breadcrumbs ș i altele. Pe lângă acestea, mai conține și de o serie de
stiluri pentru elemen te 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 f i realizat un
design în Bootstrap
 are în componența elemente responsive
 rapiditatea finalizării proiectelor – folosind elementele predefinite din Bootstrap, se
poate salva mult timp
 documentație bogată și sprijinul comunității
 suport pentru majoritatea bro wserelor
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

2 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 mu lți operatori prin intermediul unui mecanism de
redirectare IO (intrare -ieșire).
Principalele caracteristici:
– stochează obiecte (documente), reducând nevoia 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ă suport pentru indexare
– suportă atât operații de actualizare atomice dar și operațiile tradiționale
– posibilitatea stocării unor fișiere mari fară să complice stiva de date

Dezavantaje ale NoSQL -ului:
– nu exista standarde, pre cum 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ă de date, 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 dis pun de scheme dinamice.

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

Figura 3.1. Operația Cr eate

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

Figura 3.2. Operația Read

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

Figura 3.3. Operația Update

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

Figura 3.4. Operația Delete

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

Figura 3.5. 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ân d
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

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

 showSchema
showSchema = new mongoose.Schema({
showId: {
type: Number,
unique: true
},
name: String,
firstAired: Date,
overview: String,
rating: Number,
status: String,
poster: String,
banner: String,
creatorName: String,
creatorProfile: String,
network: String,
episodesNumber: Number,
episodeDuration: [Number],

6 genre: String,
owners: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}],
subscribers: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}],
seasons: [{
seasonId: Number,
seasonNumber: Number,
episodeNumber: Number,
episodeName: String,
airDate: Date,
seasonPoster: String,
episodes: []
}],
});

Figura 3.6. Baza de date a aplicației

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

7

Figura 3.7. Structură Bază de date – MongoDB

8 Un utili zator poate aveam în secțiunilea personalShows un dicționar de ObjectId -uri, care fac
referire la un anumit serial.
Un serial conține atât în secțiunea de owners cât și în secțiunea de subscribers câte un
dicționar de ObjectId -uri care fac referire la un anu mit 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 3.8. Structură Bază de date – MySQL

9 Capitolul I V
-Prezentarea aplicației –

4.1. Prezentarea generală

Această aplic aț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ă 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ă adăugarea în această secțiune,
ultizatorul poate să marcheze episoadele deja vizionate precum și să primească email -uri de fiecare
dată când un episod nou o să apară.
Aplicația conține două tipur i de utilizatori:
 utilizatorul normal
 administratorul

Pentru a putea utiliza aplicația, este nevoie de crearea unui cont. Există un singur
administrator de sistem , restul utilizatorilor făcând partea din categoria utilizatorilor normali.
Acest lucru pres upune completarea unu 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 e ste utilizată adresa.
Formularul mai dispune și de un sistem de înștiințare cu privire la complexitatea parolei aleasă.

Figura 4.1. Schema înregistrare

10 Dacă unul dintre câmpurile enumerate mai sus nu este completat, utilizatorul nu poate crea
acel co nt.
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 toate datele utilizatorului

Figura 4.2. Schemă utilizator

După crearea contului, utilizatorul este redirecți onat către pagina de Autentificare, unde
trebuie să -și introducă datele. Dacă adresa de email și parola au fost introduse corect, utilizatorul va
avea acces la fereastra principală.
În partea stânga , se găsește meniul la care utilizatorul are acces. Cu aj utorul acestora,
utilizatorul, poate accesa conținutul de seriale din baza de date precum și serialele personale, poate
vizualiza diferite 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 4.3. Fereastră de autentificare

11 Fereastra de autentificare este alcătuita din două Text Input -uri și un buton de „Autentificare ”.
Modul de funcționare
Utilizatorul se au tentifică 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 verifica dacă în baza de date exista acea înregistrare.
Dacă adre sa de e -mail și parola 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.

4.3. Fereastra p rincipală
Următoare a fereastră este deschisă dacă au fost completate în mod corespunzător câmpurile
din secțiunea de autentificare.

Figura 4.4. Fereastră Principală
Fereastra principală este alcătuită din două view -uri: unul pentru meniul din stânga, și unul
pentru conțin utul 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.

12 4.4. Secțiunea – Meniuri
Meniul este alcătui din subcategorii:
o Meniul Principal
 Acasă
 Show -uri Personale
 Show-uri În Desfașurare
 Calendar
 Adaugă
o Setări
 Setările Utilizatorului
 Zona de Administrare (vizibilă doar pentru administratorul de rețea)
o Opțiunea de deconectare

Fig 4.5. Meniu Principal

13
4.4.1. Meniul Acasă – Căutare
Meniul Acasă este meniul care va fi selectat în mod implicit atunci când utilizatorul se
conectează la aplicație. Acesta va afișa in fereastra principală cele mai bune 18 seriale ordonate după
notele acestora din baza de date.
Tot din această fereastră, utilizatorul poate căuta printre s erialele aflate în baza de date,
folosind caseta 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ța sa acel grup de litere, îndrum ându -l
astfel și spre alte seriale .

Figura 4.6. Meniu Acasă – Căutare

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

14 Figura 4.7. Meniu Show -uri personale

4.4.3 . Meniu l Show -uri în d esfășurare
Accesând acest meniu, utilizatorul poate accesa doar serialele care se află în desfăș urare, care
urmeză să apară în continuare. Alături de numele acestora , utilizatorul mai poate viziona numărul
sezonului și numărul episodului care urmează să apară precum și numărul de zile rămase până la
apariția acestuia.
Selectând numele unui serial, u tilizatorul va fi redirecționat către o pagină de detalii cu privire
la sezoanele și episoadele acestuia.

Figura 4.8. Meniu Show -uri în desfășurare

15 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 la ziua de azi.
o trei butoane pen tru afișarea calendarului în funcție de lună, săptămână, sau zi.

Figura 4.9. Meniu Calendar

16 4.4.5. 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 serialelor preferate.

Figura 4.10. Meniu Adaugă

4.4.6. Zona de administrare
În acest meniu, un singur uti lizator are acces , fiind vorba de administratorul de rețea.
Meniul nu va fi vizibil pentru un utilizator normal.Acesta are acces la întrea ga aplicație, mai
puțin la acesta .
Zona de Administrare este compusă din trei secțiuni:
 Secțiunea Utilizatori
 Secțiu nea Setări utilizatori
 Secțiunea Administrare seriale utilizatori

Figura 4.11. Zona de administrare

17  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

Figura 4.12 . Secțiune utilizatori

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

 Secțiunea Setări utilizatori
Din această secțiune, pot fi modificate datele personale ale unui utilizator. Selectând unul din
utilizatorii aflați în Secțiunea Utilizatori, administratorul poate de exemplu să -i modifi ce parola
acestuia.

Figura 4.13. Secțiunea Setări utilizator

18
 Secțiunea Administrare seriale utilizatori

Figura 4.14 . Administrare seriale utilizatori
În această secțiune, administratorul are acces la toate serialele adăugate de un anumit user,
având posibilitatea, dacă acesta a încălcat vreo re gulă, să elimine o parte din acestea.

4.4.7. Meniul Setări Personale
Accesând acest meniu, un utilizator, dacă doreș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 pro ces de
criptare, astfel, ea devenind mai puternică și mai greu de descifrat.
Pentru criptare, am folosit un pachet, “bcrypt” , pentru NodeJs, care genereză un salt de 10
poziții, parola fiind apoi hășuită, în funcție de acel salt.

Figura 4.15. Meniu Setăr i personale

19 Câmpurile de nume, e -mail și parolă actuală nu sunt disponibile pentru editare, ele 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.

Figura 4.16. Alertă schimbare parolă
4.5. Fereastră Detalii show
În momentul în care utilizatorul 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 momentul de față

Figura 4.17. Fereastră D etalii 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 din 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 ”.

20 Figura 4.18. Fereastră Detalii eliminare show

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

Figura 4.19. Fereastră Show personal

21 Detalii despre show
În această se cț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, precum și o imagine
sugestivă.

Figura 4.20. Detalii despre show

Sezoane și detalii des pre 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.

Figura 4.21. Sezoane ș i detalii despre acestea

22 Tabelul este alcătuit din cinci coloane:
– număr episod
– nume episod
– data la care acesta a fost lansat
– un buton de „ Vizionare”
– 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 detalii despre ce se va întâmpla în episodul respectiv, precum și o imagine din acel episod.

Figura 4.2 2. Figură vizionare episod

23 CAPITOLUL V
-Arhitectura sistemului.Realizare practică –

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

Figura 5. 1. Structura View -uri

View -ul de sidebar este prezent în toat e 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>

24 Procesul tipic pentru a implementa o astfel de arhitectură presupune divizarea aplicației
pentru o orgaziare 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 JavaScript pentru acțiunile de la nivelul serverului

Inspectând acest folder, observăm că el conține la rândul său în alte foldere, după cum
urmează:
– controllers
– directives
– filters
– services
– styleshe ets
– 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'
})

25 .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/showres ult.html' ,
controller: 'ShowResultCtrl' ,
})
.when('/shows/:id/:seasonId' , {
templateUrl: 'views/seasondetail.html' ,
controller: 'SeasonDetailCtrl' ,
})
.when('/calendar' , {
templateUrl: 'views/calendar.html' ,
controller: 'CalendarCtrl' ,
})
.otherwise({
redirectTo: '/',
});

Când o anumită rută este accesată ,utilizatorul va fi direcționat către un view care are în spate
un controller responsabil pent ru 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 default ( / ).

5.2.1. Ruta “login”
Atunci când utilizatorul accesează adresa aplicației, el va fi redirecționa t către
localhost:4000/login, obligându -l pe acesta să se autentifice pentru a putea folosit aplicația.
Formularul de autentificare , la nivel de HTML, are urmă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>

26 <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: $s cope.email,
password: $scope.password
});
var user = {
email: $scope.email,
password: $scope.password
}
};

Am folosit un serviciu de autentificare, modelul din arhitectura MVC, pentru a putea
trans mite datele între view -uri. Când acesta este apelat, fișierul auth.js preia datele introduce î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( '/');
$alert({
title: 'Felicitări!' ,
content: 'Ai fost autentificat cu succes.' ,
animation: 'fadeZoomFadeDown' ,
type: 'material' ,
duration: 3
});
})
.error(function () {
delete $window.localStorage.token;
$alert({
title: 'Error!' ,
content: 'Adresă de email/parolă invalidă.' ,
animation: 'fadeZoomFadeDown' ,
type: 'material' ,
duration: 3

27 });
});
},

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

app.post( '/auth/logi n', 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 localhost:4000/signup.
$scope.signup = function () {
Auth.signu p({
name: $scope.displayName,
email: $scope.email,
password: $scope.password,
photo: $scope.defaultImage
});
};

Ca și în cazul 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({

28 title: Eroare!',
content: response.data,
animation: 'fadeZoomFadeDown' ,
type: 'material' ,
duration: 3
});
});
}

În cadrul fișierului server.js, se re alizează 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
radoi.robert94 @gmail .com , nivelul acestuia va fi 1 -administrator, alt fel 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 === radoi.robert94 @gmail.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 creat 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, e lement, attrs, ngModel) {
element.bind( 'blur', function () {

29
if (ngModel.$modelValue) {
$http.get( '/api/users' , { params: { email: ngModel.$modelValue }
})
.success( function (data) {
ngModel.$setValidity( 'unique' , data.availab le);
});
}
});
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 fiecărui serial, se realizează un request către api-ul bazei de date,
deaorece 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 di v sepa rat.

30

5.2.5. Ruta “ upcoming ”
Când utilizatorul selectează opțiunea “ upcoming ”, din bara de navigare, va fi redirecționat
către această rută.

La nivelul controller -ului am realizat o prelucrare suplimentară a datelor , pentru afișare
numărului de zi le 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 într -o variabilă
– iterarea prin ultimul sezon, comparând data de apariție a fie că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_date;
shows[i].nextEpisode.number = lastSeasonEpisodes[j].episo de_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
episoad ele din serialele persoanale care urmează să apară.
De această dată, în controller vom genera câte un eveniment pentru fiecare episod.

31 for (var i = 0; i < lastSeasonEpisodes.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);
}
}

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 ap elată.
$scope.saveSettings = function (argument) {
Auth.changeSettings({
name: $scope.currentUserName,
email: $scope.currentUserEmail,
password: $scope.currentPassword,
newPassword: $scope.newPassword
});
}
Și de această data am folosit același serviciu de autentificare.
changeSettings: function (user) {
return $http.post( '/auth/changesettings' , user)
.success( function () {
$alert({
title: 'Felicitări!' ,
content: 'Parola a fost actualizată.' ,
animation: 'fadeZoomFadeUp' ,
type: 'material' ,
duration: 3
});
})
.error(function (response) {
$alert({
title: 'Eroare!' ,
content: response.data,
animation: 'fadeZoomFadeUp' ,
type: 'material' ,
duration: 3
});
});
}
La nivel de server, se face o cautare în baza de date a utilizatorului, în funcție de adresa
acestuia de email. Dacă aceasta este validă, se va realiza o operație de update, înlocuindu -se vechea
parolă cu cea nouă.

32
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 “a dmin”
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 adm in, am definit o funcție, necesară pentru a extrage toți
utilizatorii din baza de date.
(function getUsers() {
Subscription.getUsers().success( function (userList) {
$scope.userList = userList;
});
})();
Pentru a accesa datele, am folosit serviciul Subscription , necesar pentru a realiza un api call
către server.
getUsers: function () {
return $http.get( '/api/userlist' , {});
} ;
La nivelul server -ului se face o interogare în baza de date, returnându -se 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(userM ap);
});
});

33 Acțiunile realizate î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).success( function () {
$scope.isDeleted = true;
})
}

$scope.deleteUserShow = function (show) {
Subscription.deleteUserShow(show, $rootScope.currentUser)
.success( function () {
})
}
5.2.9. Ruta “add”
Această rută poate 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 acest uia în listă.
La nivel de server, sunt realizate următoarele operații asyncrone:
 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
 fiecare poster este encodat la base64, pentru a putea fi salvat local
 serialul va fi adăugat în listă, iar dacă acesta deja există, un mesaj de atenționare va fi
afișat

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.get( 'https://api.them oviedb.org/3/tv/' + seriesId + '?api_key='
+ apyKey, function (error, response, body) … );
},

function (show, callback) {
var url = "http://image.tmdb.org/t/p/w185//" + show.poster;
request({url: url, encodi ng: null
}, function (error, response, body) {

34 show.poster = 'data:' + response.headers[ 'content -type'] +';base64,'
+ body.toString( 'base64' );
callback(error, 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 + ' există deja .'
});
}
return next(err);
}
});
User.findOne({
'email': userEmail
}, function (err, user) {
user.personalShows.push(show);
user.save( function (err) {
user.populate( 'persona lShows', function (err) {
if (err) return next(err);
res.json(200);
})
});

5.2.10. Ruta „shows/id”

După adăugare unui serial în secțiunea „Show -uri personale ”, utilizatorul poate accesa detalii
despre acel seri al î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 () {
var index = $scope.currentUser.episodesWatched.indexOf(episode.id);
$scope.currentUser.episodes Watched.splice(index, 1);
});}

35 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.episodesWatched.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;
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 episode ca și văzut, se face o căutare în baza de date a
utilizatorului, introducându -se în dicționarul de episoade văzute, id -ul episodului, iar apoi se
realiz ează 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 verifică id -ul episodului aflat deja in lista de
episoade v ăzute. După identificarea id-ului, el este eliminat din listă, efectuându -se update -ul pentru
utilizator.

5.2.11 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 scruta descriere, dar și de posibilitatea adăugării la Show -uri
personale.

36 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 f i 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="addToMyS hows()">
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();
var lastSeason = show.seasons[show.se asons.length – 1];
var upcomingEpisode = lastSeason.episodes.filter( function (episode) {
return new Date(episode.air_date) > new Date();
})[0];
console.log(upcomingEpisode);
if (upcomingEpisode !== undefined) {
agenda
.schedule(upcomingEpisode.air_date, 'send email alert' ,
show.name)
.repeatEvery( '1 week' );
}
})
User.findById(req.body.currentUser._id, function (err, user) {
if (err) return next(err);
user.personalShows.push(s howId);
user.save( function (err) {
if (err) return next(err);
res.send(200);
});
})

});

37 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;
}
agenda.emit( 'ready');
agenda.start();
}
});
var nodemailer = requi re('nodemailer' );

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

Figura 5.1 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) {
if (show.owners.length > 0) {
var emails = show.owners.map (function (user) {
return user.email;
});
}

38 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 Rob ert',
to: emails.join( ','),
subject: show.name + ' începe curând!' ,
text: show.name + ' începe în mai puțin de 2 ore ' +

'Episodul ' + upcomingEpisode.episode_number + '
Descriere \n\n' + upcomingEpisode.ov erview
};

smtpTransport.sendMail(mailOpti ons, function (error, info)
{
});
}

});
});

39 CONCLUZII

Am creat aceast ă aplicație cu scopul de a ajuta persoanele care doresc să fie la curent cu
ultimele seriale apărute precum și să -și țină o evid entă a serialelor preferate. Administratorul acestei
aplicații, prin meniul special de care dispune, poate controla toată activi tatea 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 arhitecturii, ea
poate fi împarțită în :
Model – este responsabil pentru administrarea datelor aplicației.
View – afișează fizic datelor unui utilizator
Controller – controlea ză 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 apl icații

40 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

41 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.wikiped ia.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.techstrikers.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

42 ANEXĂ COD

1. Controllere
 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();
$alert({
content: 'TV show has been added.' ,
animation: 'fadeZoomFadeDown' ,
type: 'material' ,
duration: 3
});
})
.catch(function (response) {
$scope.showName = '';
$scope.addForm.$setPristine();
$alert({
content: response.data.message,
animation: 'fadeZoomFadeDown' ,
type: 'material' ,
duration: 3
});
});
};
});

 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;
$scope.headingTitle = 'Admin Zone' ;

(function getUsers() {
Subscription.getUse rs().success( function (userList) {
$scope.userList = userList;
});
})()

43
$scope.userDetails = function (userInfo) {
$scope.isClicked = true;
var user = User.get({
userId: userInfo._id
}, function (user, err) {
$scope.shows = Show.qu ery({
_id: user.personalShows
});
});
}

$scope.deleteUser = function (user) {
Subscription.removeUser(user).success( function () {
$scope.isDeleted = true;
})
}

$scope.deleteUserShow = function (show) {
Subscription.deleteUserShow(show, $rootScope.currentUser)
.success( function (){
})
}
})

 Calendar
angular.module( 'MyApp')
.controller( 'CalendarCtrl' , function ($scope, $alert, 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, showI ndex, 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();
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);

44 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,agendaDay'
},
defaultView: 'month',
displayE ventTime: false,
allDay: true,

});

});

 Detail

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

function getEpisodes(season) {
$http.get( 'https://api.themoviedb.org/3/tv/' + $scope.show.showId +
'/season/' + season.seasonNumber +
'?api_key=9f67cd8600bf766fe003 d2a5462a94c7&language=en -US')
.then(function (response) {
if ($scope.seasonEpisodes.length > 0) {
$scope.seasonEpisodes = [];
}
for (var i = 0; i < response.data.episodes.length; i++) {
$scope.se asonEpisodes.push(response.data.episodes[i]);
}
})
}

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

Show.get({
showId: $routeParams.id
}, function (show, cb) {
$scope.show = show;

if (show.creatorProfile == null) {
show.creatorProfile = 'utils/img/nodirector.jpg'
} else {
show.creatorProfile = "http://image.tmdb.org/t/p/w 185" +
show.creatorProfile;
}

45 //remove season 0(special) from all shows
for (var i = 0; i < $scope.show.seasons.length; i++) {
if ($scope.show.seasons[i].seasonNumber == 0) {
$scope.show.seasons.splice($scope.show.seaso ns[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.seasons[i].season Poster = 'utils/img/noimg.jpg' ;
}
}

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

$scope.isWatched = function (episode) {
return this.currentUser.episodesWatched.indexOf(episode.id) != -1;
};

$scope.subscribe = function () {
Subscription.subscribe(show, $rootScope.currentUser).success( function ()
{
$scope.show.subscribers.push($rootScope.currentUser._id);
});
};

$scope.unsubscribe = function () {
Subscription.unsubscribe(show,
$rootScope.currentUser).success( function () {
var index =
$scope.show.subscribers.indexOf($rootScope.currentUser._id);
$scope.show.subscribers.sp lice(index, 1);
});
};

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

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

$scope.unWatchEpisode = function (episode) {
Subscription.removeWatchedEpisodes(episode,
$rootScope.currentUser ).success( function () {
var index = $scope.currentUser.episodesWatched.indexOf(episode.id);
$scope.currentUser.episodesWatched.splice(index, 1);
})
}

$scope.getDetail = function (episode) {
this.episode = episode;
this.episodeImage = episode.still_path;
}.bind($scope)

46
});
});

 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
}
};
});

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

 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();

})

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

47
$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 = [];
}

});
})

 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.level;
}

$scope.logout = function () {
Auth.logout();
};

}]);

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

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

var user = User.get({
userId: $scope.currentUser._id
}, function (user, err) {
$scope.shows = Show.query({
_id: user.temporarySh ows
})
});
});

48  Season detail
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 = [];

});

 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(localStorageService.get( 'currentUser' ));

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

$scope.saveSettings = function (argument) {
Auth.changeSettings({
name: $scope.currentUserName,
email: $scope.currentUs erEmail,
password: $scope.currentPassword,
newPassword: $scope.newPassword
});
}
});

 Show result

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

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

$scope.isAddedToMyShows = function () {
return $scope.show.owners.indexOf($scope.currentUser._id) !== -1;
}

$scope.addToMyShows = function () {
Subscription.addToMyShows(show,
$rootScope.currentUser).success( function () {

49 $alert({
content: 'TV show will be added to your personal shows' ,
placement: 'top-right',
type: 'material' ,
duration: 3
});
$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);
})
}
})
});

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

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

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

 Upcoming
angular.modul e('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);
var todayYear = todayDate.getFullYear();

50 var todayDateText = todayYear + "-" + todayMonth + "-" + todayDay;

return todayDateText;
}

$scope.currentDate = $scope.formatCu rrentDate();
var user = 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_da te > 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].nextEpisode.poster = lastSeasonEpisodes[j].still_path;
shows[i].nextEpisode.rate = lastSeasonEpisodes[j].vote_average;
}
}
}
}.bind($scope));
} else {
$scope.shows = [ ];
}
}.bind($scope));

})

2. Directive
 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];
var weak = dots.slice( -2);
var strong = dots.slice( -3);
var strongest = dots.slice( -4);

element.after(indicator);

element.bind( 'keyup', function () {

51 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.backgroundColor =
'#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>'
};
});

 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);
});
}
});
element.bind( 'keyup', function () {
ngModel.$setValidity( 'unique' , true);
});
}
};
});

3. Filtre
 fromNow
angular.module( 'MyApp').
filter('fromNow' , function() {
return function (date) {
return moment(date).fromNow();
}
});

52 4. Servicii
 Auth
angular.module( 'MyApp')
.factory( 'Auth', function ($http, $location, $rootScope, $alert, $window,
localStorageService) {
//localStorageService.clearAll();
var user;
var token = localStorageService.get( 'token');
if (token) {
var payload = JSON.parse($window.atob(token.split( '.')[1]));
localStorageService.set( 'currentUser' , JSON.stringify(payload.user));
$rootScope.currentUser = payload. user;
}
return {
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($window.atob(data.token.split( '.')[1]));
localStorageService.set( 'currentUser' ,
JSON.stringify(payload.user));
$rootScope.currentUser =
JSON.parse(localStorageService.get( 'currentUser' ));
$location.path( '/');
$alert({
title: 'Cheers!' ,
content: 'You have successfully logged in.' ,
animation: 'fadeZoomFadeDown' ,
type: 'material' ,
duration: 3
});
})
.error(function () {
delete $window.localStorage.token;
$alert({
title: 'Error!' ,
content: 'Invalid username or password.' ,
animation: 'fadeZoomFadeDown' ,
type: 'material' ,
duration: 3
});
});
},
signup: function (user) {
return $http.post( '/auth/signup' , user)
.success( function () {
$location.path( '/login' );
$alert({
title: 'Congratulations!' ,
content: 'Your account has been created.' ,
animation: 'fadeZoomFadeDown' ,
type: 'material' ,
duration: 3
});
})
.error(function (response) {
$alert({

53 title: 'Error!' ,
content: response.data,
animation: 'fadeZoomFadeDown' ,
type: 'material' ,
duration: 3
});
});
},
logout: function () {
localStorageService.clearAll();
$rootScope.currentUser = null;
$alert({
content: 'You have been logged out.' ,
animation: 'fadeZoomFadeDown' ,
type: 'material' ,
duration: 3
});
},
isLogin: function (user) {
return (user) ? user : false;
},
changeSettings: function (user) {
return $http.post( '/auth/changesettings' , 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
});
});
}
};
});

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

 Subscription
angular.module( 'MyApp')
.factory( 'Subscription' , function ($http) {
return {

54 subscribe: function (show, user) {
return $http.post( '/api/subscribe' , {
mongooseShowId: show._id,
currentUser: user
});
},
unsubscribe: function (show, user) {
return $http.post( '/api/unsubscribe' , {
showId: show._id,
currentUser: user
});
},
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 (episode, user) {
return $http.post( '/api/saveepisode' , {
currentUser: user,
episode: episode
})
},
removeWatchedEpisodes: function (episode, user) {
return $http.post( '/api/removeepisode' , {
currentUser: user,
episode: episode
})
},
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
})
},
};
});

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

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

$routeProvider
.when('/admin' , {
template Url: 'views/admin.html' ,
controller: 'AdminCtrl' ,
})
.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' ,
controller: 'LoginCtrl' ,
})
.when('/signup' , {
templateUrl: 'views/signup.html' ,
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({

56 redirectTo: '/',
});
})

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

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

});
}])

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

var bcrypt = requi re('bcryptjs' );
var mongoose = require( 'mongoose' );

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;
}
agenda.emit( 'ready');
agenda.start();
}
});
var sugar = require( 'sugar');
var nodemailer = require( 'nodemailer' );
var _ = require( 'lodash' );

var tokenSecret = 'your unique secret' ;

//mongoose Show Schema
var showSchema = new mongoose.Schema({
showId: {
type: Number,
unique: true

57 },
name: String,
firstAired: Date,
overview: String,
rating: Number,
status: String,
poster: String,
banner: String,
creatorName: String,
creatorProfile: String,
network: String,
episodesNumber: Number,
episodeDuration: [Number],
genre: String,
owners: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}],
subscribers: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Show'
}],
seasons: [{
seasonId: Number,
seasonNumber: Number,
episodeNumber: Number,
episodeName: String,
airDate: Date,
seasonPoster: String,
episodes: []
}],
});

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

//Hash the password before to be saved in UserSchema
userSchema.pre( 'save', function (next) {
var user = this;
if (!user.isModified( 'password' )) return next();
bcrypt.genSalt(10, function (err, salt) {

58 if (err) return next(err);
bcrypt.hash(user.password, salt, function (err, hash) {
if (err) return next(err);
user.password = hash;
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/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);
});
});
});

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.episodesWatched.push(episodeId);
user.save( function (err) {
if (err) return next(err);
res.send(200);

59 });
})
})

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;
var index = user.episodesWatched.indexOf(episodeId);
user.episodesWatched.splice(index, 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(e rr);
res.send(200);
})
})
})

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

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

var level;
if (req.body.email === 'rradoi@w -systems.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

60 });
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 = createJwtToken(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.findByI d(req.params.id, function (err, user) {
if (err) return next(err);
res.send(user);
});
});

//first load
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);
});
} 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);

61 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/userlist' , 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 temporary = req.body.temporary;
var foundTemporary = 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, body) {
if (error) return next(error);
if (response.body == '') {
return res.send(400, {
message: seriesName + ' was not found.'
});
}
var parsedBody = JSON.parse(body);
//sau inca ceva
var seriesId = parsedBody.results[0].id;
callback( null, seriesId);
});
},

function (seriesId, callback) {
request.get( 'https://api.themoviedb.org/3/tv/' + seriesId + '?api_key='
+ apyKey, function (error, response, body) {
if (error) return next(error);
var parsedResponse = (JSON.parse(body));
var seasons = parsedResponse.seasons;
var show = new Show({
showId: parsedResponse.id,
name: parsedResponse.name,
firstAired: parsedResponse.first_air_date,

62 genre: parsedResponse.genres,
overview: parsedResponse.overview,
rating: parsedRespons e.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,
isTemporary: 1,
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 url = "http://image.tmdb.org/t/p/w185//" + show.poster;
request({
url: url,
encoding: null
}, function (error, response, body) {
show.poster = 'data:' + response.headers[ 'content -type'] +
';base64,' + body.toString( 'base64' );
callback(error, 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 + ' already exists.'
});
}
return next(err);
}

res.send(200);
});

if (temporary == false) {
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);
})

63
});

})
} else {

User.findOne({
'email': userEmail
}, function (err, user) {
user.temporaryShows.pop();
user.temporaryShows.push(show._id);
user.save( function (err) {
if (err) return next(err);
});

})

};
});

});

app.post( '/api/subscribe' , function (req, res, next) {
Show.findById(req.body.mongooseShowId, function (err, show) {
if (err) return next(err);
show.subscribers.push(req.body.currentUser._id);
show.save( function (err) {
if (err) return next(err);
res.send(200);
});

});
});

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

Show.findById(req.body.showId, function (err, show) {
if (err) return next(err);
var index = show .subscribers.indexOf(req.body.currentUser._id);
show.subscribers.splice(index, 1);
show.save( function (err) {
if (err) return next(err);
res.send(200);
});
});
});

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();
var lastSeason = show.seasons[show.seasons.length – 1];
var upcomingEpisode = lastSeason.episodes.filter( function (episode) {
return new Date(episode.air_date) > new Date();
})[0];
console.log(upcomingEpisode);
if (upcomingEpisode !== undefined) {
agenda.schedule(upcomingEp isode.air_date, 'send email alert' ,
show.name).repeatEvery( '1 week' );
}
})
User.findById(req.body.currentUser._id, function (err, user) {
if (err) return next(err);

64 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.currentUser._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);
res.send(200);
});
})
});

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

app.use( function (err, req, res, next) {
// console.error(err.stack);
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) {
// console.log(job.attrs);
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'
}
});

65
var mailOptions = {
tls: {
rejectUnauthorized: false
},
from: 'Radoi Robert' ,
to: emails.join( ','),
subject: show.name + ' începe cur ând!',
text: show.name + ' începe în mai pu țin de 2 ore ' +

'Episodul ' + upcomingEpisode.episode_number + ' Descriere \n\n'
+ upcomingEpisode.overview
};

smtpTransport.sendMail(mailOptions, function(error, info) {
});
}

});
});

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

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);
});

Similar Posts