├── .gitignore
├── LICENSE
├── README.md
├── classes
├── 01-classes-intro.ts
├── 02-interfaces.ts
├── 03-singleton.ts
└── tsconfig.json
├── data
└── db-data.ts
├── decorators
├── 01-use-decorators.ts
├── 02-method-decorator.ts
├── 03-class-decorator.ts
├── 04-property-decorators.ts
└── tsconfig.json
├── fundamentals
├── index.html
├── package-lock.json
├── package.json
├── src
│ ├── 01-why-typescript.ts
│ ├── 02-primitive-types.ts
│ ├── 03-null-undefined-chaining.ts
│ ├── 04-arrays.ts
│ ├── 05-enums.ts
│ ├── 06-any-type.ts
│ ├── 07-02-non-null-assertion-operator.ts
│ ├── 07-union-types.ts
│ ├── 08-literal-types.ts
│ ├── 09-type-aliases.ts
│ ├── 10-interfaces.ts
│ ├── 11-type-assertions.ts
│ ├── 12-modules-exports.ts
│ ├── 12-modules-imports.ts
│ ├── 14-module-reexports
│ │ ├── course-model.ts
│ │ ├── feature-1.ts
│ │ ├── feature-2.ts
│ │ └── index.ts
│ ├── 15-default-exports.ts
│ ├── 16-arrow-functions.ts
│ ├── 17-default-function-arguments.ts
│ ├── 18-object-spread.ts
│ ├── 19-object-destructuring.ts
│ ├── 20-array-spread-destructuring.ts
│ ├── 21-rest-arguments.ts
│ ├── 22-object-creation-shorthand-notation.ts
│ ├── 23-introduction-to-functions.ts
│ ├── 24-function-types.ts
│ ├── 25-tuples.ts
│ ├── 26-unknown-type.ts
│ ├── 27-type-predicates.ts
│ ├── 28-never-type.ts
│ ├── 29-intersection-types.ts
│ ├── 30-tsconfig-lib.ts
│ └── 31-express.ts
└── tsconfig.json
├── generics
├── 01-common-examples.ts
├── 02-partial.ts
├── 03-readonly.ts
├── 04-generic-functions.ts
├── 05-merge-generic-function.ts
├── 06-keyof.ts
├── 07-generic-classes.ts
└── tsconfig.json
└── rest-api
├── .env
├── package-lock.json
├── package.json
├── src
├── data-source.ts
├── logger.ts
├── middlewares
│ ├── admin-only.middleware.ts
│ ├── authentication-middleware.ts
│ └── default-error-handler.ts
├── models
│ ├── course.ts
│ ├── db-data.ts
│ ├── delete-db.ts
│ ├── lesson.ts
│ ├── populate-db.ts
│ └── user.ts
├── routes
│ ├── create-course.ts
│ ├── create-user.ts
│ ├── delete-course.ts
│ ├── find-course-by-url.ts
│ ├── find-lessons-for-course.ts
│ ├── get-all-courses.ts
│ ├── login.ts
│ ├── root.ts
│ └── update-course.ts
├── server.ts
└── utils.ts
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | *.js
3 | node_modules
4 | dist
5 | .env
6 | rest-api/logs
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Angular University
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Typescript: The Ultimate Bootcamp
2 |
3 | This repository contains the code of the [Typescript: The Ultimate Bootcamp](https://angular-university.io/course/typescript-bootcamp) video course.
4 |
5 | 
6 |
7 | You can find the starting point of the course in the [1-start branch](https://github.com/angular-university/typescript-bootcamp/tree/1-start).
8 |
9 | This master branch contains the *final version of the course code*, that you can use as a reference if you choose to code along.
10 |
11 | # Installation pre-requisites
12 |
13 | IMPORTANT: Please install Node 16 LST (Long Term Support version).
14 |
15 | # Other Courses from the Angular University
16 |
17 | # Modern Angular With Signals
18 |
19 | If you are looking for the [Modern Angular With Signals Course](https://angular-university.io/course/angular-signals-course), the repo with the full code can be found here:
20 |
21 | 
22 |
23 | # Angular Forms In Depth
24 |
25 | If you are looking for the [Angular Forms In Depth](https://angular-university.io/course/angular-forms-course) course, the repo with the full code can be found here:
26 |
27 | 
28 |
29 | # Angular Router In Depth
30 |
31 | If you are looking for the [Angular Router In Depth](https://angular-university.io/course/angular-router-course) course, the repo with the full code can be found here:
32 |
33 | 
34 |
35 | # NgRx (with NgRx Data) - The Complete Guide
36 |
37 | If you are looking for the [Ngrx (with NgRx Data) - The Complete Guide](https://angular-university.io/course/ngrx-course), the repo with the full code can be found here:
38 |
39 | 
40 |
41 |
42 | # Angular Core Deep Dive Course
43 |
44 | If you are looking for the [Angular Core Deep Dive Course](https://angular-university.io/course/angular-course), the repo with the full code can be found here:
45 |
46 | 
47 |
48 | # RxJs In Practice
49 |
50 | If you are looking for the [RxJs In Practice](https://angular-university.io/course/rxjs-course), the repo with the full code can be found here:
51 |
52 | 
53 |
54 | # NestJs In Practice (with MongoDB)
55 |
56 | If you are looking for the [NestJs In Practice Course](https://angular-university.io/course/nestjs-course), the repo with the full code can be found here:
57 |
58 | 
59 |
60 | # Angular Testing Course
61 |
62 | If you are looking for the [Angular Testing Course](https://angular-university.io/course/angular-testing-course), the repo with the full code can be found here:
63 |
64 | 
65 |
66 | # Serverless Angular with Firebase Course
67 |
68 | If you are looking for the [Serverless Angular with Firebase Course](https://angular-university.io/course/firebase-course), the repo with the full code can be found here:
69 |
70 | 
71 |
72 | # Angular Universal Course
73 |
74 | If you are looking for the [Angular Universal Course](https://angular-university.io/course/angular-universal-course), the repo with the full code can be found here:
75 |
76 | 
77 |
78 | # Angular PWA Course
79 |
80 | If you are looking for the [Angular PWA Course](https://angular-university.io/course/angular-pwa-course), the repo with the full code can be found here:
81 |
82 | 
83 |
84 | # Angular Security Masterclass
85 |
86 | If you are looking for the [Angular Security Masterclass](https://angular-university.io/course/angular-security-course), the repo with the full code can be found here:
87 |
88 | [Angular Security Masterclass](https://github.com/angular-university/angular-security-course).
89 |
90 | 
91 |
92 | # Angular Advanced Library Laboratory Course
93 |
94 | If you are looking for the Angular Advanced Course, the repo with the full code can be found here:
95 |
96 | [Angular Advanced Library Laboratory Course: Build Your Own Library](https://angular-university.io/course/angular-advanced-course).
97 |
98 | 
99 |
100 |
101 | ## RxJs and Reactive Patterns Angular Architecture Course
102 |
103 | If you are looking for the RxJs and Reactive Patterns Angular Architecture Course code, the repo with the full code can be found here:
104 |
105 | [RxJs and Reactive Patterns Angular Architecture Course](https://angular-university.io/course/reactive-angular-architecture-course)
106 |
107 | 
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/classes/01-classes-intro.ts:
--------------------------------------------------------------------------------
1 | import {HasId, HasTitle} from "./02-interfaces";
2 | import {CoursesService} from "./03-singleton";
3 |
4 | abstract class Course implements HasTitle {
5 |
6 | private static TOTAL_COURSES = 0;
7 |
8 | static readonly TYPESCRIPT_TITLE = "Typescript Bootcamp";
9 |
10 | protected constructor(
11 | public id:string,
12 | protected _title:string,
13 | protected price:number,
14 | protected subtitle = "",
15 | protected creationDt = new Date(2000,1,1)
16 | ) {
17 |
18 | this.validate();
19 |
20 | const service = CoursesService.instance();
21 |
22 | Course.TOTAL_COURSES++;
23 |
24 | }
25 |
26 | printId() {
27 | console.log(`The course id is ${this.id}`);
28 | }
29 |
30 | protected abstract validate();
31 |
32 | static printTitle(course: Course) {
33 | console.log(`The title of the course ${course.title}`)
34 | }
35 |
36 | get title() {
37 | return this._title;
38 | }
39 |
40 | set title(newTitle:string) {
41 | if (!newTitle) {
42 | throw "Title cannot be empty";
43 | }
44 |
45 | this._title = newTitle;
46 | }
47 |
48 | get age() {
49 | const ageInMs = new Date().getTime() - this.creationDt.getTime();
50 |
51 | return Math.round(ageInMs / 1000 / 60 / 24);
52 | }
53 |
54 | }
55 |
56 | class FreeCourse extends Course {
57 |
58 | constructor( id:string,
59 | title:string,
60 | subtitle = "",
61 | creationDt = new Date(2000,1,1)) {
62 |
63 | super(id, title, 0, subtitle, creationDt);
64 |
65 | }
66 |
67 | protected validate() {
68 | console.log(`Called FreeCourse validate()`);
69 | }
70 |
71 | }
72 |
73 | //const typescript = new Course(Course.TYPESCRIPT_TITLE, 100);
74 |
75 | //console.log(typescript.title);
76 |
77 | const angular = new FreeCourse("1", "Angular For Beginners");
78 |
79 | CoursesService.instance();
80 |
81 | console.log(angular);
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/classes/02-interfaces.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface HasId {
3 | id:string;
4 | printId();
5 | }
6 |
7 | export interface HasTitle extends HasId {
8 | title:string;
9 | }
10 |
--------------------------------------------------------------------------------
/classes/03-singleton.ts:
--------------------------------------------------------------------------------
1 |
2 | export class CoursesService {
3 |
4 | private static INSTANCE: CoursesService;
5 |
6 | private constructor() {
7 | console.log(`The CoursesService was initialized.`);
8 | }
9 |
10 | static instance() {
11 | if (!CoursesService.INSTANCE) {
12 | CoursesService.INSTANCE = new CoursesService();
13 | }
14 | return CoursesService.INSTANCE;
15 | }
16 |
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/classes/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES5"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/data/db-data.ts:
--------------------------------------------------------------------------------
1 |
2 | export const COURSES: any = {
3 |
4 | 20: {
5 | id: 20,
6 | title: 'Typescript Bootcamp',
7 | longDescription: 'Learn in depth the Typescript language, build practical real-world projects',
8 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/typescript-bootcamp-2.jpg',
9 | category: 'BEGINNER',
10 | seqNo: 0,
11 | url: 'typescript-bootcamp',
12 | price: 50,
13 | lessons: [
14 |
15 | {
16 | id: 131,
17 | title: "Setting Up the Development Environment",
18 | 'duration': "0:44",
19 | 'seqNo': 1,
20 | longDescription: ""
21 | },
22 |
23 | {
24 | id: 132,
25 | title: "Why Typescript? Key Benefits of the Language",
26 | 'duration': "12:33",
27 | 'seqNo': 2,
28 | longDescription: ""
29 | },
30 |
31 | {
32 | id: 133,
33 | title: "Compiling Your First Typescript Program",
34 | 'duration': "05:18",
35 | 'seqNo': 3,
36 | longDescription: ""
37 | },
38 |
39 | {
40 | id: 134,
41 | title: "The Typescript compiler noEmitOnError flag",
42 | 'duration': "02:30",
43 | 'seqNo': 4,
44 | longDescription: ""
45 | },
46 |
47 | {
48 | id: 135,
49 | title: "Running a Typescript Program in a Browser",
50 | 'duration': "06:21",
51 | 'seqNo': 5,
52 | longDescription: ""
53 | },
54 |
55 | {
56 | id: 136,
57 | title: "Understanding the differences between const, let and var",
58 | 'duration': "06:23",
59 | 'seqNo': 6,
60 | longDescription: ""
61 | },
62 |
63 | {
64 | id: 137,
65 | title: "Typescript primitive types - numbers, strings and booleans",
66 | 'duration': "07:59",
67 | 'seqNo': 7,
68 | longDescription: ""
69 | },
70 |
71 | {
72 | id: 138,
73 | title: "Typescript Template Strings",
74 | 'duration': "03:48",
75 | 'seqNo': 8,
76 | longDescription: ""
77 | },
78 |
79 | {
80 | id: 139,
81 | title: "Understanding Type Inference",
82 | 'duration': "03:40",
83 | 'seqNo': 9,
84 | longDescription: ""
85 | },
86 |
87 | {
88 | id: 140,
89 | title: "When to use Typescript Type Annotations and Why",
90 | 'duration': "140",
91 | 'seqNo': 10,
92 | longDescription: ""
93 | }
94 | ],
95 | },
96 |
97 | 11: {
98 | id: 11,
99 | title: 'Angular Material Course',
100 | longDescription: 'Build Applications with the official Angular UI Widget Library',
101 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/angular-material-course-1.jpg',
102 | category: 'BEGINNER',
103 | seqNo: 1,
104 | url: 'angular-material-course',
105 | price: 50,
106 | lessons: [
107 |
108 | // Angular Material In Depth
109 | {
110 | id: 120,
111 | title: 'Introduction to Angular Material',
112 | 'duration': '4:17',
113 | 'seqNo': 1,
114 | longDescription: "A quick introduction to the Angular Material library."
115 | },
116 | {
117 | id: 121,
118 | title: 'Navigation and Containers',
119 | 'duration': '6:37',
120 | 'seqNo': 2,
121 | longDescription: "Guided tour of navigation elements and container."
122 | },
123 | {
124 | id: 122,
125 | title: 'Data Tables',
126 | 'duration': '8:03',
127 | 'seqNo': 3,
128 | longDescription: "Angular Material Data Tables in detail."
129 | },
130 | {
131 | id: 123,
132 | title: 'Dialogs',
133 | 'duration': '11:46',
134 | 'seqNo': 4,
135 | longDescription: "Modal elements and how to use them."
136 | },
137 | {
138 | id: 124,
139 | title: 'Commonly used Form Controls',
140 | 'duration': '7:17',
141 | 'seqNo': 5,
142 | longDescription: "All sorts of commonly needed form controls."
143 | },
144 | {
145 | id: 125,
146 | title: 'Drag and Drop',
147 | 'duration': '8:16',
148 | 'seqNo': 6,
149 | longDescription: "How to use drag and drop."
150 | },
151 | {
152 | id: 126,
153 | title: 'Responsive Design',
154 | 'duration': '7:28',
155 | 'seqNo': 7,
156 | longDescription: "Everything about making our screens responsive."
157 | },
158 | {
159 | id: 127,
160 | title: 'Tree Component',
161 | 'duration': '11:09',
162 | 'seqNo': 8,
163 | longDescription: "All about the Angular Material Tree component."
164 | },
165 | {
166 | id: 128,
167 | title: 'Virtual Scrolling',
168 | 'duration': '3:44',
169 | 'seqNo': 9,
170 | longDescription: "How to use virtual scrolling to handle large amounts of data."
171 | },
172 | {
173 | id: 129,
174 | title: 'Custom Themes',
175 | 'duration': '8:55',
176 | 'seqNo': 10,
177 | longDescription: "How to build your own custom Angular Material theme."
178 | },
179 | {
180 | id: 130,
181 | title: 'Changing Theme at Runtime',
182 | 'duration': '12:37',
183 | 'seqNo': 11,
184 | longDescription: ""
185 | }
186 |
187 |
188 | ],
189 | },
190 |
191 | 19: {
192 | id: 19,
193 | title: 'Angular Forms In Depth',
194 | longDescription: 'Build complex enterprise data forms with the powerful Angular Forms module',
195 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/angular-forms-course-small.jpg',
196 | category: 'BEGINNER',
197 | lessons: [],
198 | seqNo: 2,
199 | url: 'angular-forms-course',
200 | price: 50
201 | },
202 |
203 |
204 | 18: {
205 | id: 18,
206 | title: 'Angular Router In Depth',
207 | longDescription: 'Build large-scale Single Page Applications with the powerful Angular Router',
208 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/angular-router-course.jpg',
209 | category: 'BEGINNER',
210 | lessons: [
211 |
212 | // Angular Router Course
213 | {
214 | id: 90,
215 | title: 'What is a Single Page Application?',
216 | 'duration': '04:00',
217 | 'seqNo': 1,
218 | videoId: 'VES1eTNxi1s'
219 | },
220 | {
221 | id: 91,
222 | title: 'Setting Up The Development Environment',
223 | 'duration': '06:05',
224 | 'seqNo': 2,
225 | videoId: 'ANfplcxnl78'
226 | },
227 | {
228 | id: 92,
229 | title: 'Angular Router Setup',
230 | 'duration': '02:36',
231 | 'seqNo': 3,
232 | videoId: '9ez72LAd6mM'
233 | },
234 | {
235 | id: 93,
236 | title: 'Configuring a Home Route and Fallback Route',
237 | 'duration': '02:55',
238 | 'seqNo': 4,
239 | videoId: 'Clj-jZpl64w'
240 | },
241 | {
242 | id: 94,
243 | title: 'Styling Active Routes With The routerLinkActive And routerLinkActiveOptions',
244 | 'duration': '07:50',
245 | 'seqNo': 5,
246 | videoId: 'zcgnsmPVc30'
247 | },
248 | {
249 | id: 95,
250 | title: 'Child Routes - How To Setup a Master Detail Route',
251 | 'duration': '04:10',
252 | 'seqNo': 6,
253 | videoId: 'zcgnsmPVc30'
254 | },
255 | {
256 | id: 96,
257 | title: 'Programmatic Router Navigation via the Router API ',
258 | 'duration': '03:59',
259 | 'seqNo': 7,
260 | videoId: 'VES1eTNxi1s'
261 | },
262 | {
263 | id: 97,
264 | title: 'Relative And Absolute Router Navigation',
265 | 'duration': '04:58',
266 | 'seqNo': 8,
267 | videoId: 'MQl9Zs3QqGM'
268 | },
269 | {
270 | id: 98,
271 | title: 'Master Detail Navigation And Route Parameters',
272 | 'duration': '06:03',
273 | 'seqNo': 9,
274 | videoId: 'ANfplcxnl78'
275 | },
276 |
277 | {
278 | id: 99,
279 | title: 'The Route Parameters Observable',
280 | 'duration': '06:50',
281 | 'seqNo': 10,
282 | videoId: 'zcgnsmPVc30'
283 | },
284 | {
285 | id: 100,
286 | title: 'Optional Route Query Parameters',
287 | 'duration': '03:03',
288 | 'seqNo': 11,
289 | videoId: '0Qsg8fyKwO4'
290 | },
291 | {
292 | id: 101,
293 | title: 'The queryParams Directive and the Query Parameters Observable',
294 | 'duration': '07:50',
295 | 'seqNo': 12,
296 | videoId: 'VES1eTNxi1s'
297 | },
298 | {
299 | id: 102,
300 | title: 'Exiting an Angular Route - How To Prevent Memory Leaks',
301 | 'duration': '07:50',
302 | 'seqNo': 13,
303 | videoId: 'ANfplcxnl78'
304 | },
305 | {
306 | id: 103,
307 | title: 'CanDeactivate Route Guard',
308 | 'duration': '04:50',
309 | 'seqNo': 14,
310 | videoId: '9ez72LAd6mM'
311 | },
312 | {
313 | id: 104,
314 | title: 'CanActivate Route Guard - An Example of An Asynchronous Route Guard',
315 | 'duration': '03:32',
316 | 'seqNo': 15,
317 | videoId: 'Clj-jZpl64w'
318 | },
319 | {
320 | id: 105,
321 | title: 'Configure Auxiliary Routes in the Angular Router',
322 | 'duration': '05:16',
323 | 'seqNo': 16,
324 | videoId: 'zcgnsmPVc30'
325 | },
326 |
327 | {
328 | id: 106,
329 | title: 'Angular Auxiliary Routes - How To Pass Router Parameters',
330 | 'duration': '07:50',
331 | 'seqNo': 17,
332 | videoId: 'yjQUkNHb1Is'
333 | },
334 | {
335 | id: 107,
336 | title: 'Angular Router Redirects and Path Matching',
337 | 'duration': '02:59',
338 | 'seqNo': 18,
339 | videoId: 'VES1eTNxi1s'
340 | },
341 | {
342 | id: 108,
343 | title: 'Angular Router Hash Location Strategy',
344 | 'duration': '07:50',
345 | 'seqNo': 19,
346 | videoId: 'MQl9Zs3QqGM'
347 | },
348 | {
349 | id: 109,
350 | title: 'Angular Router Lazy Loading and Shared Modules',
351 | 'duration': '08:45',
352 | 'seqNo': 20,
353 | videoId: '0Qsg8fyKwO4'
354 | },
355 | {
356 | id: 110,
357 | title: 'Exercise - Implement a Widget Dashboard',
358 | 'duration': '07:50',
359 | 'seqNo': 21,
360 | videoId: 'VES1eTNxi1s'
361 | },
362 | {
363 | id: 111,
364 | title: 'Exercise Solution ',
365 | 'duration': '07:50',
366 | 'seqNo': 22,
367 | videoId: '0Qsg8fyKwO4'
368 | },
369 |
370 |
371 | ],
372 | seqNo: 3,
373 | url: 'angular-router-course',
374 | price: 50
375 | },
376 |
377 | 17: {
378 | id: 17,
379 | title: 'Reactive Angular Course',
380 | longDescription: 'How to build Angular applications in Reactive style using plain RxJs - Patterns and Anti-Patterns',
381 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/reactive-angular-course.jpg',
382 | category: 'BEGINNER',
383 | lessons: [
384 |
385 | // Reactive Angular Course
386 | {
387 | id: 80,
388 | title: 'Introduction to Reactive Programming',
389 | 'duration': '03:45',
390 | 'seqNo': 0,
391 | videoId: 'Df1QnesgB_s',
392 | },
393 | {
394 | id: 81,
395 | title: 'Introduction to RxJs',
396 | 'duration': '08:36',
397 | 'seqNo': 1,
398 | videoId: '8m5RrAtqlyw',
399 | },
400 | {
401 | id: 82,
402 | title: 'Setting up the development environment',
403 | 'duration': '09:10',
404 | 'seqNo': 2,
405 | videoId: '3fDbUB-nKqc',
406 | },
407 | {
408 | id: 83,
409 | title: 'Designing and building a Service Layer',
410 | 'duration': '07:20',
411 | 'seqNo': 3,
412 | videoId: '',
413 | },
414 | {
415 | id: 84,
416 | title: 'Stateless Observable Services',
417 | 'duration': '11:47',
418 | 'seqNo': 4,
419 | videoId: 'qvDPnRs_ZPA',
420 | },
421 | {
422 | id: 85,
423 | title: 'Smart vs Presentational Components',
424 | 'duration': '06:30',
425 | 'seqNo': 5,
426 | videoId: '5bsZJGAelFM',
427 | },
428 | {
429 | id: 86,
430 | title: 'Lightweight state management',
431 | 'duration': '4:13',
432 | 'seqNo': 6,
433 | videoId: '9m3_HHeP9Ko',
434 | },
435 | {
436 | id: 87,
437 | title: 'Event bubbling anti-pattern',
438 | 'duration': '05:47',
439 | 'seqNo': 7,
440 | videoId: 'PRQCAL_RMVo',
441 | },
442 | {
443 | id: 88,
444 | title: 'Master detail with cached master table',
445 | 'duration': '05:17',
446 | 'seqNo': 8,
447 | videoId: 'du4ib4jBUG0'
448 | },
449 | {
450 | id: 89,
451 | title: 'Error handling',
452 | 'duration': '07:50',
453 | 'seqNo': 9,
454 | videoId: '8m5RrAtqlyw'
455 | }
456 |
457 |
458 | ],
459 | seqNo: 4,
460 | url: 'reactive-angular-course',
461 | price: 50
462 |
463 | },
464 | 3: {
465 | id: 3,
466 | title: 'RxJs In Practice Course',
467 | longDescription: 'Understand the RxJs Observable pattern, learn the RxJs Operators via practical examples',
468 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/rxjs-in-practice-course.png',
469 | category: 'BEGINNER',
470 | lessons: [],
471 | seqNo: 5,
472 | url: 'rxjs-course',
473 | price: 50
474 | },
475 |
476 | 4: {
477 | id: 4,
478 | title: 'NgRx (with NgRx Data) - The Complete Guide',
479 | longDescription: 'Learn the modern Ngrx Ecosystem, including NgRx Data, Store, Effects, Router Store, Ngrx Entity, and Dev Tools.',
480 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/ngrx-v2.png',
481 | category: 'BEGINNER',
482 | lessons: [
483 |
484 | // Ngrx Course
485 | {
486 | id: 50,
487 | title: 'Welcome to the Angular Ngrx Course',
488 | 'duration': '6:53',
489 | 'seqNo': 1,
490 |
491 | },
492 | {
493 | id: 51,
494 | title: 'The Angular Ngrx Architecture Course - Helicopter View',
495 | 'duration': '5:52',
496 | 'seqNo': 2,
497 | },
498 | {
499 | id: 52,
500 | title: 'The Origins of Flux - Understanding the Famous Facebook Bug Problem',
501 | 'duration': '8:17',
502 | 'seqNo': 3,
503 | },
504 | {
505 | id: 53,
506 | title: 'Custom Global Events - Why Don\'t They Scale In Complexity?',
507 | 'duration': '7:47',
508 | 'seqNo': 4,
509 | },
510 | {
511 | id: 54,
512 | title: 'The Flux Architecture - How Does it Solve Facebook Counter Problem?',
513 | 'duration': '9:22',
514 | 'seqNo': 5,
515 | },
516 | {
517 | id: 55,
518 | title: 'Unidirectional Data Flow And The Angular Development Mode',
519 | 'duration': '7:07',
520 | 'seqNo': 6,
521 | },
522 |
523 | {
524 | id: 56,
525 | title: 'Dispatching an Action - Implementing the Login Component',
526 | 'duration': '4:39',
527 | 'seqNo': 7,
528 | },
529 | {
530 | id: 57,
531 | title: 'Setting Up the Ngrx DevTools - Demo',
532 | 'duration': '4:44',
533 | 'seqNo': 8,
534 | },
535 | {
536 | id: 58,
537 | title: 'Understanding Reducers - Writing Our First Reducer',
538 | 'duration': '9:10',
539 | 'seqNo': 9,
540 | },
541 | {
542 | id: 59,
543 | title: 'How To Define the Store Initial State',
544 | 'duration': '9:10',
545 | 'seqNo': 10,
546 | },
547 | ],
548 | seqNo: 6,
549 | url: 'ngrx-course',
550 | promo: false,
551 | price: 50
552 | },
553 |
554 |
555 | 2: {
556 | id: 2,
557 | title: 'Angular Core Deep Dive',
558 | longDescription: 'A detailed walk-through of the most important part of Angular - the Core and Common modules',
559 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/angular-core-in-depth-small.png',
560 | lessons: [],
561 | category: 'BEGINNER',
562 | seqNo: 7,
563 | url: 'angular-core-course',
564 | price: 50
565 | },
566 |
567 |
568 | 5: {
569 | id: 5,
570 | title: 'Angular for Beginners',
571 | longDescription: 'Establish a solid layer of fundamentals, learn what\'s under the hood of Angular',
572 | iconUrl: 'https://angular-academy.s3.amazonaws.com/thumbnails/angular2-for-beginners-small-v2.png',
573 | category: 'BEGINNER',
574 | seqNo: 8,
575 | url: 'angular-for-beginners',
576 | price: 50,
577 | lessons: [
578 |
579 | {
580 | id: 1,
581 | title: 'Angular Tutorial For Beginners - Build Your First App - Hello World Step By Step',
582 | 'duration': '4:17',
583 | 'seqNo': 1,
584 | },
585 | {
586 | id: 2,
587 | title: 'Building Your First Component - Component Composition',
588 | 'duration': '2:07',
589 | 'seqNo': 2,
590 | },
591 | {
592 | id: 3,
593 | title: 'Component @Input - How To Pass Input Data To an Component',
594 | 'duration': '2:33',
595 | 'seqNo': 3,
596 | },
597 | {
598 | id: 4,
599 | title: ' Component Events - Using @Output to create custom events',
600 | 'duration': '4:44',
601 | 'seqNo': 4,
602 | },
603 | {
604 | id: 5,
605 | title: ' Component Templates - Inline Vs External',
606 | 'duration': '2:55',
607 | 'seqNo': 5,
608 | },
609 | {
610 | id: 6,
611 | title: 'Styling Components - Learn About Component Style Isolation',
612 | 'duration': '3:27',
613 | 'seqNo': 6,
614 | },
615 | {
616 | id: 7,
617 | title: ' Component Interaction - Extended Components Example',
618 | 'duration': '9:22',
619 | 'seqNo': 7,
620 | },
621 | {
622 | id: 8,
623 | title: ' Components Tutorial For Beginners - Components Exercise !',
624 | 'duration': '1:26',
625 | 'seqNo': 8,
626 | },
627 | {
628 | id: 9,
629 | title: ' Components Tutorial For Beginners - Components Exercise Solution Inside',
630 | 'duration': '2:08',
631 | 'seqNo': 9,
632 | },
633 | {
634 | id: 10,
635 | title: ' Directives - Inputs, Output Event Emitters and How To Export Template References',
636 | 'duration': '4:01',
637 | 'seqNo': 10,
638 | },
639 |
640 |
641 | ]
642 | },
643 |
644 | 12: {
645 | id: 12,
646 | title: 'Angular Testing Course',
647 | longDescription: 'In-depth guide to Unit Testing and E2E Testing of Angular Applications',
648 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/angular-testing-small.png',
649 | category: 'BEGINNER',
650 | seqNo: 9,
651 | url: 'angular-testing-course',
652 | lessons: [
653 |
654 | // Angular Testing Course
655 | {
656 | id: 40,
657 | title: 'Angular Testing Course - Helicopter View',
658 | 'duration': '5:38',
659 | 'seqNo': 1,
660 | },
661 |
662 | {
663 | id: 41,
664 | title: 'Setting Up the Development Environment',
665 | 'duration': '5:12',
666 | 'seqNo': 2,
667 | },
668 |
669 | {
670 | id: 42,
671 | title: 'Introduction to Jasmine, Spies and specs',
672 | 'duration': '4:07',
673 | 'seqNo': 3,
674 | },
675 |
676 | {
677 | id: 43,
678 | title: 'Introduction to Service Testing',
679 | 'duration': '7:32',
680 | 'seqNo': 4,
681 | },
682 |
683 | {
684 | id: 44,
685 | title: 'Settting up the Angular TestBed',
686 | 'duration': '6:28',
687 | 'seqNo': 5,
688 | },
689 |
690 | {
691 | id: 45,
692 | title: 'Mocking Angular HTTP requests',
693 | 'duration': '4:38',
694 | 'seqNo': 6,
695 | },
696 |
697 | {
698 | id: 46,
699 | title: 'Simulating Failing HTTP Requests',
700 | 'duration': '7:54',
701 | 'seqNo': 7,
702 | },
703 |
704 | {
705 | id: 47,
706 | title: 'Introduction to Angular Component Testing',
707 | 'duration': '5:31',
708 | 'seqNo': 8,
709 | },
710 |
711 | {
712 | id: 48,
713 | title: 'Testing Angular Components without the DOM',
714 | 'duration': '8:19',
715 | 'seqNo': 9,
716 | },
717 |
718 | {
719 | id: 49,
720 | title: 'Testing Angular Components with the DOM',
721 | 'duration': '7:05',
722 | 'seqNo': 10,
723 | },
724 |
725 |
726 | ],
727 | promo: false,
728 | price: 50
729 | },
730 |
731 |
732 | 1: {
733 | id: 1,
734 | title: 'Serverless Angular with Firebase Course',
735 | longDescription: 'Serveless Angular with Firestore, Firebase Storage & Hosting, Firebase Cloud Functions & AngularFire',
736 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/serverless-angular-small.png',
737 | category: 'BEGINNER',
738 | seqNo: 10,
739 | url: 'serverless-angular',
740 | price: 50,
741 | lessons: [
742 | // Serverless Angular with Firebase Course
743 | {
744 | id: 30,
745 | title: 'Development Environment Setup',
746 | 'duration': '5:38',
747 | 'seqNo': 1,
748 | },
749 |
750 | {
751 | id: 31,
752 | title: 'Introduction to the Firebase Ecosystem',
753 | 'duration': '5:12',
754 | 'seqNo': 2,
755 | },
756 |
757 | {
758 | id: 32,
759 | title: 'Importing Data into Firestore',
760 | 'duration': '4:07',
761 | 'seqNo': 3,
762 | },
763 |
764 | {
765 | id: 33,
766 | title: 'Firestore Documents in Detail',
767 | 'duration': '7:32',
768 | 'seqNo': 4,
769 | },
770 |
771 | {
772 | id: 34,
773 | title: 'Firestore Collections in Detail',
774 | 'duration': '6:28',
775 | 'seqNo': 5,
776 | },
777 |
778 | {
779 | id: 35,
780 | title: 'Firestore Unique Identifiers',
781 | 'duration': '4:38',
782 | 'seqNo': 6,
783 | },
784 |
785 | {
786 | id: 36,
787 | title: 'Querying Firestore Collections',
788 | 'duration': '7:54',
789 | 'seqNo': 7,
790 | },
791 |
792 | {
793 | id: 37,
794 | title: 'Firebase Security Rules In Detail',
795 | 'duration': '5:31',
796 | 'seqNo': 8,
797 | },
798 |
799 | {
800 | id: 38,
801 | title: 'Firebase Cloud Functions In Detail',
802 | 'duration': '8:19',
803 | 'seqNo': 9
804 | },
805 |
806 | {
807 | id: 39,
808 | title: 'Firebase Storage In Detail',
809 | 'duration': '7:05',
810 | 'seqNo': 10
811 | },
812 |
813 |
814 | ]
815 | },
816 |
817 | 16: {
818 | id: 16,
819 | title: 'Stripe Payments In Practice',
820 | longDescription: 'Build your own ecommerce store & membership website with Firebase, Stripe and Express',
821 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/stripe-course.jpg',
822 | lessons: [
823 |
824 | // Stripe Course
825 | {
826 | id: 70,
827 | title: 'Introduction to Stripe Payments',
828 | 'duration': '03:45',
829 | 'seqNo': 0
830 | },
831 | {
832 | id: 71,
833 | title: 'The advantages of Stripe Checkout',
834 | 'duration': '08:36',
835 | 'seqNo': 1
836 | },
837 | {
838 | id: 72,
839 | title: 'Setting up the development environment',
840 | 'duration': '09:10',
841 | 'seqNo': 2
842 | },
843 | {
844 | id: 73,
845 | title: 'Creating a server Checkout Session',
846 | 'duration': '07:20',
847 | 'seqNo': 3
848 | },
849 | {
850 | id: 74,
851 | title: 'Redirecting to the Stripe Checkout page',
852 | 'duration': '11:47',
853 | 'seqNo': 4
854 | },
855 | {
856 | id: 75,
857 | title: 'Order fulfillment webhook',
858 | 'duration': '06:30',
859 | 'seqNo': 5
860 | },
861 | {
862 | id: 76,
863 | title: 'Installing the Stripe CLI',
864 | 'duration': '4:13',
865 | 'seqNo': 6
866 | },
867 | {
868 | id: 77,
869 | title: 'Firestore Security Rules for protecting Premium content',
870 | 'duration': '05:47',
871 | 'seqNo': 7
872 | },
873 | {
874 | id: 78,
875 | title: 'Stripe Subscriptions with Stripe Checkout',
876 | 'duration': '05:17',
877 | 'seqNo': 8
878 | },
879 | {
880 | id: 79,
881 | title: 'Stripe Subscription Fulfillment',
882 | 'duration': '07:50',
883 | 'seqNo': 9
884 | },
885 |
886 |
887 | ],
888 | category: 'BEGINNER',
889 | seqNo: 11,
890 | url: 'stripe-course',
891 | price: 50
892 | },
893 |
894 | 14: {
895 | id: 14,
896 | title: 'NestJs In Practice (with MongoDB)',
897 | longDescription: 'Build a modern REST backend using Typescript, MongoDB and the familiar Angular API.',
898 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/nestjs-v2.png',
899 | category: 'BEGINNER',
900 | lessons: [
901 |
902 | // NestJs Course
903 | {
904 | id: 60,
905 | title: 'Introduction to NestJs',
906 | 'duration': '4:29',
907 | 'seqNo': 1
908 | },
909 | {
910 | id: 61,
911 | title: 'Development Environment Setup',
912 | 'duration': '6:37',
913 | 'seqNo': 2
914 | },
915 | {
916 | id: 62,
917 | title: 'Setting up a MongoDB Database',
918 | 'duration': '6:38',
919 | 'seqNo': 3
920 | },
921 | {
922 | id: 63,
923 | title: 'CRUD with NestJs - Controllers and Repositories',
924 | 'duration': '12:12',
925 | 'seqNo': 4
926 | },
927 | {
928 | id: 64,
929 | title: 'First REST endpoint - Get All Courses',
930 | 'duration': '3:42',
931 | 'seqNo': 5
932 | },
933 | {
934 | id: 65,
935 | title: 'Error Handling',
936 | 'duration': '5:15',
937 | 'seqNo': 6
938 | },
939 | {
940 | id: 66,
941 | title: 'NestJs Middleware',
942 | 'duration': '7:08',
943 | 'seqNo': 7
944 | },
945 | {
946 | id: 67,
947 | title: 'Authentication in NestJs',
948 | 'duration': '13:22',
949 | 'seqNo': 8
950 | },
951 | {
952 | id: 68,
953 | title: 'Authorization in NestJs',
954 | 'duration': '6:43',
955 | 'seqNo': 9
956 | },
957 | {
958 | id: 69,
959 | title: 'Guards & Interceptors',
960 | 'duration': '8:16',
961 | 'seqNo': 10
962 | },
963 |
964 |
965 | ],
966 | seqNo: 12,
967 | url: 'nestjs-course',
968 | promo: false,
969 | price: 50
970 | },
971 |
972 | 6: {
973 | id: 6,
974 | title: 'Angular Security Course - Web Security Fundamentals',
975 | longDescription: 'Learn Web Security Fundamentals and apply them to defend an Angular / Node Application from multiple types of attacks.',
976 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/security-cover-small-v2.png',
977 | category: 'ADVANCED',
978 | seqNo: 13,
979 | url: 'angular-security-course',
980 | price: 50,
981 | lessons: [
982 |
983 | // Security Course
984 | {
985 | id: 11,
986 | title: 'Course Helicopter View',
987 | 'duration': '08:19',
988 | 'seqNo': 1
989 | },
990 |
991 | {
992 | id: 12,
993 | title: 'Installing Git, Node, NPM and Choosing an IDE',
994 | 'duration': '04:17',
995 | 'seqNo': 2
996 | },
997 |
998 | {
999 | id: 13,
1000 | title: 'Installing The Lessons Code - Learn Why Its Essential To Use NPM 5',
1001 | 'duration': '06:05',
1002 | 'seqNo': 3
1003 | },
1004 |
1005 | {
1006 | id: 14,
1007 | title: 'How To Run Node In TypeScript With Hot Reloading',
1008 | 'duration': '03:57',
1009 | 'seqNo': 4
1010 | },
1011 |
1012 | {
1013 | id: 15,
1014 | title: 'Guided Tour Of The Sample Application',
1015 | 'duration': '06:00',
1016 | 'seqNo': 5
1017 | },
1018 | {
1019 | id: 16,
1020 | title: 'Client Side Authentication Service - API Design',
1021 | 'duration': '04:53',
1022 | 'seqNo': 6
1023 | },
1024 | {
1025 | id: 17,
1026 | title: 'Client Authentication Service - Design and Implementation',
1027 | 'duration': '09:14',
1028 | 'seqNo': 7
1029 | },
1030 | {
1031 | id: 18,
1032 | title: 'The New Angular HTTP Client - Doing a POST Call To The Server',
1033 | 'duration': '06:08',
1034 | 'seqNo': 8
1035 | },
1036 | {
1037 | id: 19,
1038 | title: 'User Sign Up Server-Side Implementation in Express',
1039 | 'duration': '08:50',
1040 | 'seqNo': 9
1041 | },
1042 | {
1043 | id: 20,
1044 | title: 'Introduction To Cryptographic Hashes - A Running Demo',
1045 | 'duration': '05:46',
1046 | 'seqNo': 10
1047 | },
1048 | {
1049 | id: 21,
1050 | title: 'Some Interesting Properties Of Hashing Functions - Validating Passwords',
1051 | 'duration': '06:31',
1052 | 'seqNo': 11
1053 | },
1054 |
1055 |
1056 | ]
1057 | },
1058 |
1059 | 7: {
1060 | id: 7,
1061 | title: 'Angular PWA - Progressive Web Apps Course',
1062 | longDescription: 'Learn Angular Progressive Web Applications, build the future of the Web Today.',
1063 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/angular-pwa-course.png',
1064 | category: 'ADVANCED',
1065 | seqNo: 14,
1066 | url: 'angular-pwa-course',
1067 | price: 50,
1068 | lessons: [
1069 | // PWA course
1070 | {
1071 | id: 22,
1072 | title: 'Course Kick-Off - Install Node, NPM, IDE And Service Workers Section Code',
1073 | 'duration': '07:19',
1074 | 'seqNo': 1
1075 | },
1076 | {
1077 | id: 23,
1078 | title: 'Service Workers In a Nutshell - Service Worker Registration',
1079 | 'duration': '6:59',
1080 | 'seqNo': 2
1081 | },
1082 | {
1083 | id: 24,
1084 | title: 'Service Workers Hello World - Lifecycle Part 1 and PWA Chrome Dev Tools',
1085 | 'duration': '7:28',
1086 | 'seqNo': 3
1087 | },
1088 | {
1089 | id: 25,
1090 | title: 'Service Workers and Application Versioning - Install & Activate Lifecycle Phases',
1091 | 'duration': '10:17',
1092 | 'seqNo': 4
1093 | },
1094 |
1095 | {
1096 | id: 26,
1097 | title: 'Downloading The Offline Page - The Service Worker Installation Phase',
1098 | 'duration': '09:50',
1099 | 'seqNo': 5
1100 | },
1101 | {
1102 | id: 27,
1103 | title: 'Introduction to the Cache Storage PWA API',
1104 | 'duration': '04:44',
1105 | 'seqNo': 6
1106 | },
1107 | {
1108 | id: 28,
1109 | title: 'View Service Workers HTTP Interception Features In Action',
1110 | 'duration': '06:07',
1111 | 'seqNo': 7
1112 | },
1113 | {
1114 | id: 29,
1115 | title: 'Service Workers Error Handling - Serving The Offline Page',
1116 | 'duration': '5:38',
1117 | 'seqNo': 8
1118 | },
1119 |
1120 | ]
1121 | },
1122 |
1123 | 8: {
1124 | id: 8,
1125 | title: 'Angular Advanced Library Laboratory: Build Your Own Library',
1126 | longDescription: 'Learn Advanced Angular functionality typically used in Library Development. Advanced Components, Directives, Testing, Npm',
1127 | iconUrl: 'https://angular-academy.s3.amazonaws.com/thumbnails/advanced_angular-small-v3.png',
1128 | category: 'ADVANCED',
1129 | seqNo: 15,
1130 | url: 'angular-advanced-course',
1131 | price: 50,
1132 | lessons: []
1133 | }
1134 |
1135 | };
1136 |
1137 | export const USERS = {
1138 | 1: {
1139 | id: 1,
1140 | email: 'test@angular-university.io',
1141 | plainTextPassword: 'test',
1142 | passwordSalt: "o61TA7yaJIsa",
1143 | pictureUrl: 'https://angular-academy.s3.amazonaws.com/main-logo/main-page-logo-small-hat.png',
1144 | isAdmin: false
1145 | },
1146 | 2: {
1147 | id: 2,
1148 | email: 'admin@angular-university.io',
1149 | plainTextPassword: 'admin',
1150 | passwordSalt: "NydKRjIh4T4X",
1151 | pictureUrl: 'https://angular-academy.s3.amazonaws.com/main-logo/main-page-logo-small-hat.png',
1152 | isAdmin: true
1153 | }
1154 |
1155 | };
1156 |
--------------------------------------------------------------------------------
/decorators/01-use-decorators.ts:
--------------------------------------------------------------------------------
1 |
2 | import {Log, LoggingLevel, Perf} from "./02-method-decorator";
3 | import {SealClass} from "./03-class-decorator";
4 | import {DatabaseId} from "./04-property-decorators";
5 |
6 | @SealClass
7 | class DbService {
8 |
9 | @Perf()
10 | @Log(LoggingLevel.INFO)
11 | saveData(data:any) {
12 |
13 | console.log(`saving data in the database...`);
14 |
15 | }
16 |
17 | }
18 |
19 | const db = new DbService();
20 |
21 | // db.saveData({hello: "World"});
22 |
23 | /*
24 |
25 | Object.defineProperty(DbService, "sayHello",{
26 | value: () => {
27 | console.log("Hello World");
28 | }
29 | })
30 |
31 | */
32 |
33 |
34 | class Course {
35 |
36 | @DatabaseId()
37 | id:string;
38 |
39 | title:string;
40 |
41 | constructor(title:string) {
42 | this.title = title;
43 | }
44 |
45 | print(message:string) {
46 | console.log(`${message}, Course ${this.title}, id ${this.id}`);
47 | }
48 |
49 | }
50 |
51 | const course1 = new Course("Typescript Bootcamp");
52 |
53 | console.log(`Course 1 id: `, course1.id);
54 |
55 | const course2 = new Course("Angular Core In Depth");
56 |
57 | console.log(`Course 2 id: `, course2.id);
58 |
59 | console.log("Course 1", course1);
60 |
61 | console.log("Course 2", course2);
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/decorators/02-method-decorator.ts:
--------------------------------------------------------------------------------
1 |
2 | export enum LoggingLevel {
3 | ERROR,
4 | INFO,
5 | WARN,
6 | DEBUG,
7 | TRACE
8 | }
9 |
10 | const appMaxLoggingLevel = LoggingLevel.INFO;
11 |
12 | export function Log(level: LoggingLevel): MethodDecorator {
13 |
14 | console.log(`Applying @Log Decorator`);
15 |
16 | return (target: any, propertyKey: string,
17 | descriptor: PropertyDescriptor) => {
18 |
19 | const originalFunction = descriptor.value;
20 |
21 | descriptor.value = function(...args: any[]) {
22 |
23 | if (level <= appMaxLoggingLevel) {
24 | console.log(`>> Log: ${propertyKey}, ${JSON.stringify(args)}`);
25 | }
26 |
27 | originalFunction.apply(this, args);
28 | }
29 |
30 | }
31 | }
32 |
33 | export function Perf():MethodDecorator {
34 |
35 | return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
36 |
37 | const originalFunction:Function = descriptor.value;
38 |
39 | descriptor.value = function(...args:any[]) {
40 |
41 | console.log(`started at ${new Date().getTime()}`);
42 |
43 | originalFunction.apply(this,args);
44 |
45 | console.log(`ended at ${new Date().getTime()}`);
46 | };
47 |
48 | }
49 |
50 | }
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/decorators/03-class-decorator.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | export function SealClass(constructor: Function) {
4 | Object.seal(constructor);
5 | Object.seal(constructor.prototype);
6 | }
7 |
--------------------------------------------------------------------------------
/decorators/04-property-decorators.ts:
--------------------------------------------------------------------------------
1 |
2 | export function DatabaseId(): PropertyDecorator {
3 | return (classPrototype:any, propertyKey:string) => {
4 | Object.defineProperty(classPrototype, propertyKey, {
5 | get: function() {
6 | if (!this["_id"]) {
7 | this["_id"] =
8 | Date.now().toString(36) + Math.random().toString(36).slice(2);
9 | }
10 | return this["_id"];
11 | }
12 | });
13 |
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/decorators/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "experimentalDecorators": true
5 | }
6 | }
7 |
8 |
--------------------------------------------------------------------------------
/fundamentals/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Typescript: The Ultimate Bootcamp
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/fundamentals/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "typescript-fundamentals",
3 | "version": "1.0.0",
4 | "description": "",
5 | "scripts": {
6 | "start": "lite-server"
7 | },
8 | "author": "",
9 | "license": "ISC",
10 | "dependencies": {
11 | "express": "^4.19.2",
12 | "lite-server": "^2.6.1"
13 | },
14 | "devDependencies": {
15 | "@types/express": "^4.17.21"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/fundamentals/src/01-why-typescript.ts:
--------------------------------------------------------------------------------
1 |
2 | const courseName = "Typescript Bootcamp";
3 |
4 | debugger;
5 |
6 | //comment
7 |
8 | if (courseName) {
9 |
10 | const subtitle = "Learn the language fundamentals, build practical projects";
11 |
12 | printCourseName(courseName);
13 | }
14 |
15 | function printCourseName(name :string) {
16 |
17 | debugger;
18 |
19 | console.log("The course name is " + name.toUpperCase());
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/fundamentals/src/02-primitive-types.ts:
--------------------------------------------------------------------------------
1 |
2 | // primitive types: number
3 | const lessonsCount = 10;
4 |
5 | const total = lessonsCount + 10;
6 |
7 | console.log("total =", total);
8 |
9 | // primitive types: string
10 | let title = "Typescript Bootcamp";
11 |
12 | let subtitle = "Learn the language fundamentals, build practical projects";
13 |
14 | let fullTitle = `Full title:${title}: ${subtitle}`;
15 |
16 | console.log(`Full title: ${fullTitle}`);
17 |
18 | // primitive types: boolean
19 | const published = true;
20 |
21 | if (published) {
22 | console.log("The course is published.");
23 | }
24 |
25 | printCourse(title, subtitle, lessonsCount);
26 |
27 | function printCourse(title:string, subtitle:string, lessonsCount:number) {
28 |
29 | let fullTitle = title + subtitle;
30 |
31 | }
32 | /*
33 | author: {
34 | firstName: "Vasco",
35 | lastName: "Cavalheiro"
36 | }*/
37 |
38 |
39 | // primitive types: object
40 |
41 | let course = {
42 | title: "Typescript Bootcamp",
43 | subtitle: "Learn the language fundamentals, build practical projects",
44 | lessonsCount: 10
45 | };
46 |
47 | console.log("type of course is " + typeof course);
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/fundamentals/src/03-null-undefined-chaining.ts:
--------------------------------------------------------------------------------
1 |
2 | let course = {
3 | textFields: {
4 | title: "Typescript Bootcamp"
5 | }
6 | };
7 |
8 | const title = course?.textFields?.title ?? "No title found";
9 |
10 | logCourseTitle(course);
11 |
12 |
13 | function logCourseTitle(course) {
14 |
15 | if (!course?.textFields) {
16 | console.log("textFields not defined.");
17 | return;
18 | }
19 |
20 | if (course.textFields.title) {
21 | console.log(`The title is ${course.textFields.title}`);
22 | }
23 | }
24 |
25 |
--------------------------------------------------------------------------------
/fundamentals/src/04-arrays.ts:
--------------------------------------------------------------------------------
1 |
2 | // array
3 | const numbers = [1, 2 ,3];
4 |
5 | numbers.push(4);
6 |
7 | // numbers.push("Hello World");
8 |
--------------------------------------------------------------------------------
/fundamentals/src/05-enums.ts:
--------------------------------------------------------------------------------
1 |
2 | enum CourseType {
3 | FREE = "FREE",
4 | PREMIUM = "PREMIUM",
5 | PRIVATE = "PRIVATE",
6 | HIDDEN ="HIDDEN"
7 | }
8 |
9 | const course = {
10 | title: "Typescript Bootcamp",
11 | type: CourseType.HIDDEN
12 | };
13 |
14 | console.log(course);
15 |
16 |
--------------------------------------------------------------------------------
/fundamentals/src/06-any-type.ts:
--------------------------------------------------------------------------------
1 |
2 | let lessonsCount:any = 10;
3 |
4 | let numbers : any[] = [10, 20, "Hello", true];
5 |
6 | printCourse( "Typescript Bootcamp", 10);
7 |
8 | function printCourse(title:string, lessonsCount:number) {
9 |
10 | console.log(`Title: ${title}, lessons count: ${lessonsCount}`);
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/fundamentals/src/07-02-non-null-assertion-operator.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | let courseId : number | null;
4 |
5 | courseId!.toString();
6 |
7 |
8 |
--------------------------------------------------------------------------------
/fundamentals/src/07-union-types.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | let uniqueIdentifier: number | string = 1000;
5 |
6 | uniqueIdentifier = "201e72bb-49e3-40ef-8331-b7f3e7d947f8";
7 |
8 | const keys: (number | string) [] = [1000, "Hello"];
9 |
10 | let courseId: number | null = 1000;
11 |
12 | courseId = null;
13 |
14 |
15 |
--------------------------------------------------------------------------------
/fundamentals/src/08-literal-types.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | const title = "Typescript Bootcamp";
5 |
6 | const lessonsCount = 10;
7 |
8 | let pageSize: 10 | 15 | 20 = 10;
9 |
10 | let courseStatus: "draft" | "published" | "unpublished" |
11 | "archived" = "draft";
12 |
13 | courseStatus = "published";
14 |
--------------------------------------------------------------------------------
/fundamentals/src/09-type-aliases.ts:
--------------------------------------------------------------------------------
1 |
2 | type CourseStatus = "draft" | "published" | "unpublished" |
3 | "archived";
4 |
5 | let courseStatus:CourseStatus = "draft";
6 |
7 | let newStatus:CourseStatus = "published";
8 |
9 | type Course = {
10 | readonly title:string,
11 | subtitle:string,
12 | lessonsCount?:number
13 | };
14 |
15 | let course: Course = {
16 | title: "Typescript Bootcamp",
17 | subtitle: "Learn the language fundamentals, build practical projects",
18 | lessonsCount: 10
19 | };
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/fundamentals/src/10-interfaces.ts:
--------------------------------------------------------------------------------
1 |
2 | interface Course {
3 | readonly title:string;
4 | subtitle:string;
5 | }
6 |
7 | const course: Course = {
8 | title: "Typescript Bootcamp",
9 | subtitle: "Learn the language fundamentals, build practical projects",
10 | lessonsCount: 10
11 | };
12 |
13 | interface Course {
14 | lessonsCount?:number;
15 | }
16 |
17 | const otherCourse: Course = {
18 | title: "Typescript Bootcamp v2",
19 | subtitle: "Learn the language fundamentals, build practical projects",
20 | lessonsCount: 10
21 | };
22 |
23 |
--------------------------------------------------------------------------------
/fundamentals/src/11-type-assertions.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | const input =
4 | document.getElementById("input-field") as HTMLInputElement;
5 |
6 | input.value;
7 |
8 |
--------------------------------------------------------------------------------
/fundamentals/src/12-modules-exports.ts:
--------------------------------------------------------------------------------
1 |
2 | export const PAGE_SIZE = 100;
3 |
4 | const pageSize = PAGE_SIZE;
5 |
6 | export const COURSE = {
7 | title: "Typescript Bootcamp",
8 | subtitle: "Learn the language fundamentals, build practical projects",
9 | lessonsCount: 10
10 | };
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/fundamentals/src/12-modules-imports.ts:
--------------------------------------------------------------------------------
1 |
2 | import {PAGE_SIZE, COURSE} from "./12-modules-exports";
3 |
4 | const pageSize = PAGE_SIZE;
5 |
6 | import {HELLO_WORLD} from './32-plain-javascript'
7 |
8 | import {Course, loadAllCourses, saveCourse}
9 | from "14-module-reexports";
10 |
11 | import printCourse from "./15-default-exports";
12 |
13 | import * as constants from "./15-default-exports";
14 |
15 | printCourse({});
16 |
--------------------------------------------------------------------------------
/fundamentals/src/14-module-reexports/course-model.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | export type Course = {
4 | readonly title:string,
5 | subtitle:string,
6 | lessonsCount?:number
7 | };
8 |
--------------------------------------------------------------------------------
/fundamentals/src/14-module-reexports/feature-1.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | export function loadAllCourses() {
4 |
5 | }
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/fundamentals/src/14-module-reexports/feature-2.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | export function saveCourse() {
5 |
6 | }
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/fundamentals/src/14-module-reexports/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import {Course} from "./course-model";
3 | import {loadAllCourses} from "./feature-1";
4 | import {saveCourse} from "./feature-2";
5 |
6 | export {
7 | Course,
8 | loadAllCourses,
9 | saveCourse
10 | };
11 |
--------------------------------------------------------------------------------
/fundamentals/src/15-default-exports.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | export const COURSE_TOTAL = 20;
4 |
5 | export const TYPESCRIPT_COURSE = {
6 | title: "Typescript Bootcamp",
7 | subtitle: "Learn the language fundamentals, build practical projects",
8 | lessonsCount: 10
9 | };
10 |
11 | export default function printCourse(course) {
12 | console.log(`The course title is ${course.title}`);
13 | }
14 |
15 |
16 |
--------------------------------------------------------------------------------
/fundamentals/src/16-arrow-functions.ts:
--------------------------------------------------------------------------------
1 |
2 | function saveCourse(course, callback: Function) {
3 |
4 | this.course = course;
5 |
6 | setTimeout(() => {
7 |
8 | callback(this.course?.title ?? "unknown course");
9 |
10 | }, 1000);
11 |
12 | }
13 |
14 | const cb = (title:string) => console.log("Save successful.", title);
15 |
16 | saveCourse({title:"Typescript Bootcamp"}, cb);
17 |
--------------------------------------------------------------------------------
/fundamentals/src/17-default-function-arguments.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | function printCourse(title = "TITLE", subtitle = "SUBTITLE", lessonsCount = 0) {
7 |
8 | console.log(`Title: ${title}, Subtitle: ${subtitle} lessons count: ${lessonsCount}`);
9 |
10 | }
11 |
12 | printCourse(
13 | "Typescript Bootcamp",
14 | "Learn the language fundamentals, build practical projects",
15 | 10);
16 |
17 | printCourse(
18 | "Typescript Bootcamp",
19 | "Learn the language fundamentals, build practical projects"
20 | );
21 |
22 | printCourse();
23 |
--------------------------------------------------------------------------------
/fundamentals/src/18-object-spread.ts:
--------------------------------------------------------------------------------
1 |
2 | interface Course {
3 | title:string;
4 | subtitle:string;
5 | stats: {
6 | lessonsCount:number;
7 | }
8 | }
9 |
10 | let course: Course = {
11 | title: "Typescript Bootcamp",
12 | subtitle: "Learn the language fundamentals, build practical projects",
13 | stats: {
14 | lessonsCount: 10
15 | }
16 | };
17 |
18 | const newCourse = {...course};
19 |
20 | console.log(newCourse);
21 |
22 | course.stats.lessonsCount = 100;
23 |
24 | console.log(newCourse);
25 |
26 |
--------------------------------------------------------------------------------
/fundamentals/src/19-object-destructuring.ts:
--------------------------------------------------------------------------------
1 |
2 | interface Course {
3 | title:string;
4 | subtitle:string;
5 | lessonsCount:number;
6 | }
7 |
8 | let course: Course = {
9 | title: "Typescript Bootcamp",
10 | subtitle: "Learn the language fundamentals, build practical projects",
11 | lessonsCount: 10
12 | };
13 |
14 | printCourse(course);
15 |
16 | function printCourse(course:Course) {
17 |
18 | const {title, ...other} = course;
19 |
20 | console.log(`Title: ${title}, Subtitle: ${other.subtitle} lessons count: ${other.lessonsCount}`);
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/fundamentals/src/20-array-spread-destructuring.ts:
--------------------------------------------------------------------------------
1 |
2 | const numbers = [1, 2 ,3];
3 |
4 | const moreNumbers = [...numbers, 4, 5, 6];
5 |
6 | console.log(moreNumbers);
7 |
8 | const [first, second, third] = moreNumbers;
9 |
10 | console.log(first, second, third);
11 |
--------------------------------------------------------------------------------
/fundamentals/src/21-rest-arguments.ts:
--------------------------------------------------------------------------------
1 |
2 | interface Course {
3 | title:string;
4 | lessonsCount:number;
5 | }
6 |
7 | const course1:Course = {
8 | title: "Typescript Bootcamp",
9 | lessonsCount: 100
10 | };
11 |
12 | const course2: Course = {
13 | title: "Angular For Beginners",
14 | lessonsCount: 20
15 | };
16 |
17 | function printCourses(message:string, ...courses: Course[]) {
18 |
19 | console.log(message);
20 |
21 | for (let course of courses) {
22 | console.log(course.title);
23 | }
24 |
25 | }
26 |
27 | // printCourses("Welcome to the Angular University", [course1, course2]);
28 |
29 | printCourses("Welcome to the Angular University", course1, course2);
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/fundamentals/src/22-object-creation-shorthand-notation.ts:
--------------------------------------------------------------------------------
1 |
2 | interface Course {
3 | title:string;
4 | subtitle: string;
5 | lessonsCount:number;
6 | }
7 |
8 | const title = "Typescript Bootcamp",
9 | subtitle = "Learn the language fundamentals, build practical projects",
10 | lessonsCount = 10;
11 |
12 | const course:Course = {
13 | title,
14 | subtitle,
15 | lessonsCount
16 | };
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/fundamentals/src/23-introduction-to-functions.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | interface Course {
4 | title:string;
5 | subtitle: string;
6 | lessonsCount:number;
7 | }
8 |
9 | function createCourse(title:string, subtitle:string,
10 | lessonsCount:number) :Course {
11 |
12 | console.log(` Creating course with Title: ${title},
13 | Subtitle: ${subtitle} lessons count: ${lessonsCount}`);
14 |
15 | return {
16 | title,
17 | subtitle,
18 | lessonsCount
19 | };
20 | }
21 |
22 | const result = createCourse("Typescript Bootcamp", "Learn the language fundamentals", 100);
23 |
24 | console.log(typeof createCourse);
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/fundamentals/src/24-function-types.ts:
--------------------------------------------------------------------------------
1 |
2 | interface Course {
3 | title:string;
4 | subtitle: string;
5 | lessonsCount:number;
6 | }
7 |
8 | type CreateCourse = (title:string, subtitle:string, lessonsCount:number) => Course;
9 |
10 | type OnCourseCreated = (course: Course) => void;
11 |
12 | const createCourse = (title:string, subtitle:string,
13 | lessonsCount:number, callback: OnCourseCreated) => {
14 |
15 | console.log(` Creating course with Title: ${title},
16 | Subtitle: ${subtitle} lessons count: ${lessonsCount}`);
17 |
18 | const course = {
19 | title,
20 | subtitle,
21 | lessonsCount
22 | };
23 |
24 | callback(course);
25 |
26 | return course;
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/fundamentals/src/25-tuples.ts:
--------------------------------------------------------------------------------
1 |
2 | interface Course {
3 | title:string;
4 | subtitle: string;
5 | lessonsCount:number;
6 | }
7 |
8 | type CourseRecord = [string, string, number];
9 |
10 | const courseRecord: CourseRecord =
11 | ["Typescript Bootcamp","Learn the language fundamentals", 100];
12 |
13 | function createCourse(title:string, subtitle:string): CourseRecord {
14 |
15 | console.log(` Creating course with Title: ${title}, Subtitle: ${subtitle} `);
16 |
17 | return [title, subtitle, 100];
18 | }
19 |
20 |
21 |
--------------------------------------------------------------------------------
/fundamentals/src/26-unknown-type.ts:
--------------------------------------------------------------------------------
1 |
2 | let anyValue: any;
3 | anyValue = true;
4 | anyValue = 10;
5 | anyValue = "Hello World";
6 | anyValue = [];
7 | anyValue = {};
8 | anyValue = null;
9 | anyValue = undefined;
10 |
11 | let value1: unknown = anyValue;
12 | let value2: any = anyValue;
13 | let value3: boolean = anyValue;
14 | let value4: number = anyValue;
15 | let value5: string = anyValue;
16 | let value6: object = anyValue;
17 | let value7: any[] = anyValue;
18 | let value8: Function = anyValue;
19 |
20 |
21 | let unknownValue: unknown;
22 | unknownValue = true;
23 | unknownValue = 10;
24 | unknownValue = "Hello World";
25 | unknownValue = [];
26 | unknownValue = {};
27 | unknownValue = null;
28 | unknownValue = undefined;
29 |
30 | let value10: unknown = unknownValue;
31 | let value11: any = unknownValue;
32 | //let value12: boolean = unknownValue;
33 | //let value13: number = unknownValue;
34 |
35 | if (typeof unknownValue == "string") {
36 |
37 | let value14: string = unknownValue;
38 | }
39 |
40 | // let value14: string = unknownValue;
41 | // let value15: object = unknownValue;
42 | // let value16: any[] = unknownValue;
43 | // let value17: Function = unknownValue;
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/fundamentals/src/27-type-predicates.ts:
--------------------------------------------------------------------------------
1 |
2 | interface Course {
3 | readonly title:string,
4 | subtitle:string,
5 | lessonsCount?:number
6 | }
7 |
8 | const course: unknown = {
9 | title: "Typescript Bootcamp",
10 | subtitle: "Learn the language fundamentals, build practical projects",
11 | lessonsCount: 10
12 | };
13 |
14 | if (isCourse(course)) {
15 |
16 | }
17 |
18 | function isCourse(value: unknown): value is Course {
19 |
20 | const course = value as Course;
21 |
22 | return course?.title != null && course?.subtitle != null;
23 | }
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/fundamentals/src/28-never-type.ts:
--------------------------------------------------------------------------------
1 |
2 | let anyValue:any;
3 |
4 | //let neverValue : never = undefined;
5 |
6 | //neverValue["property"] = 10;
7 |
8 | type CourseStatus = "draft" | "published" | "unpublished";
9 |
10 | let courseStatus : CourseStatus;
11 |
12 | if (courseStatus == "draft") {
13 |
14 | }
15 | else if (courseStatus == "published") {
16 |
17 | }
18 | else if (courseStatus == "unpublished") {
19 |
20 | }
21 | else {
22 | unexpectedError(courseStatus);
23 | }
24 |
25 | function unexpectedError(value:never) {
26 | throw new Error(`Unexpected value: ${value}`);
27 | }
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/fundamentals/src/29-intersection-types.ts:
--------------------------------------------------------------------------------
1 |
2 | interface HasId {
3 | id:string;
4 | }
5 | interface HasTitle {
6 | title:string;
7 | description:string;
8 | }
9 |
10 | type Course = HasId & HasTitle;
11 |
12 |
--------------------------------------------------------------------------------
/fundamentals/src/30-tsconfig-lib.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | document.addEventListener('click', () =>{
4 |
5 | console.log(`The button was clicked`);
6 |
7 | });
8 |
--------------------------------------------------------------------------------
/fundamentals/src/31-express.ts:
--------------------------------------------------------------------------------
1 |
2 | const express = require('express');
3 |
4 | import {Request,Response} from "express";
5 |
6 | let req : Request;
7 |
8 |
9 |
--------------------------------------------------------------------------------
/fundamentals/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "rootDir": "src",
5 | "outDir": "dist",
6 | "module": "CommonJS",
7 | "baseUrl": "src",
8 | "skipLibCheck": true,
9 | "sourceMap": true,
10 | "noEmitOnError": true,
11 | "strictNullChecks": true,
12 | "removeComments": true
13 |
14 | },
15 | "files": [
16 | "src/01-why-typescript.ts"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/generics/01-common-examples.ts:
--------------------------------------------------------------------------------
1 |
2 | const numbers = new Array();
3 |
4 | numbers.push(10);
5 |
6 | const promise = new Promise((resolve, reject) => {
7 |
8 | resolve("Hello World");
9 |
10 | });
11 |
12 | promise.then(val => {
13 |
14 |
15 | })
16 |
--------------------------------------------------------------------------------
/generics/02-partial.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface Course {
3 | title:string;
4 | subtitle:string;
5 | lessonsCount: number;
6 | }
7 |
8 | export function updateCourse(
9 | courseId:string, update: Partial) {
10 |
11 | }
12 |
13 | updateCourse("1", {
14 | title: "New version of title"
15 | });
16 |
17 | updateCourse("1", {
18 | subtitle: "New version of subtitle"
19 | });
20 |
21 | updateCourse("1", {
22 | title: "New version of title",
23 | lessonsCount: 100
24 | });
25 |
26 |
--------------------------------------------------------------------------------
/generics/03-readonly.ts:
--------------------------------------------------------------------------------
1 |
2 | interface Course {
3 | title:string;
4 | subtitle:string;
5 | lessonsCount: number;
6 | }
7 |
8 | function freezeCourse(course:Course): Readonly {
9 | return Object.freeze(course);
10 | }
11 |
12 | const frozen = freezeCourse({
13 | title: "Typescript Bootcamp",
14 | subtitle: "Learn the language, build practical projects",
15 | lessonsCount: 100
16 | });
17 |
18 | //frozen.title = "";
19 |
--------------------------------------------------------------------------------
/generics/04-generic-functions.ts:
--------------------------------------------------------------------------------
1 |
2 | interface Course {
3 | title:string;
4 | subtitle:string;
5 | lessonsCount: number;
6 | }
7 |
8 | export function freezeCourse(course:Course): Readonly {
9 | return Object.freeze(course);
10 | }
11 |
12 | function freezeLesson(lesson:Lesson): Readonly {
13 | return Object.freeze(lesson);
14 | }
15 |
16 | function freeze(input: T): Readonly {
17 | return Object.freeze(input);
18 | }
19 |
20 | const course: Course = {
21 | title: "Typescript Bootcamp",
22 | subtitle: "Learn the language, build practical projects",
23 | lessonsCount: 100
24 | }
25 |
26 | const frozenCourse = freeze(course);
27 |
28 | // const frozenNumber = freeze("10");
29 |
30 | //frozenCourse.title = "";
31 |
32 | interface Lesson {
33 | title:string;
34 | seqNo:number;
35 | }
36 |
37 | const frozenLesson = freeze({
38 | title: "Lesson Title",
39 | seqNo: 10
40 | })
41 |
--------------------------------------------------------------------------------
/generics/05-merge-generic-function.ts:
--------------------------------------------------------------------------------
1 |
2 | const someData = {
3 | title: "Typescript Bootcamp",
4 | subtitle: "Learn the language, build practical projects",
5 | lessonsCount: 100
6 | }
7 |
8 | const moreData = {
9 | seqNo: 10,
10 | price: 100
11 | }
12 |
13 | export function merge(obj1: T, obj2: U) {
14 | return Object.assign(obj1, obj2) as (T & U);
15 | }
16 |
17 | const merged = merge(someData, moreData);
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/generics/06-keyof.ts:
--------------------------------------------------------------------------------
1 |
2 | const course: Course = {
3 | title: "Typescript Bootcamp",
4 | subtitle: "Learn the language, build practical projects",
5 | lessonsCount: 100
6 | }
7 |
8 | type CourseKeys = keyof Course;
9 |
10 | export function extractProperty(data: T, property:K) {
11 | return data[property];
12 | }
13 |
14 | const val = extractProperty(course, "lessonsCount");
15 |
--------------------------------------------------------------------------------
/generics/07-generic-classes.ts:
--------------------------------------------------------------------------------
1 |
2 | class KeyValue {
3 |
4 | constructor(
5 | public readonly key: K,
6 | public readonly value: V) {
7 | }
8 |
9 | print() {
10 | console.log(`key = ${this.key} value = ${this.value}`);
11 | }
12 | }
13 |
14 | const p1 = new KeyValue("1", 10);
15 |
16 | const val1 = p1.value;
17 |
18 | const p2 = new KeyValue(2, "Hello World");
19 |
20 | const val2 = p2.value;
21 |
22 | const course: Course = {
23 | title: "Typescript Bootcamp",
24 | subtitle: "Learn the language, build practical projects",
25 | lessonsCount: 100
26 | }
27 |
28 | const p3 = new KeyValue("3", course);
29 |
30 | const val3 = p3.value;
31 |
--------------------------------------------------------------------------------
/generics/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [
4 | "ES2015",
5 | "DOM"
6 | ]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/rest-api/.env:
--------------------------------------------------------------------------------
1 |
2 | NODE_ENV=development
3 | #PORT=9003
4 | LOGGER_LEVEL=debug
5 |
6 | DB_HOST=ec2-34-252-35-249.eu-west-1.compute.amazonaws.com
7 | DB_PORT=5432
8 | DB_USERNAME=weqmmdzigxaexg
9 | DB_PASSWORD=67d8818d8d1aef20a1c19ce4b3dd209832d56d0c9fc59dd111abda3f43f57c22
10 | DB_NAME=d6s4priqjfa63v
11 |
12 | JWT_SECRET=42de8b5c36d7e58a65edfa9de6ade2b759ba09bf9803090412539be1c2086b53
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/rest-api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rest-api",
3 | "version": "1.0.0",
4 | "description": "REST API project with Express and TypeORM",
5 | "scripts": {
6 | "clean": "rimraf dist",
7 | "build": "tsc",
8 | "start-server": "node dist/server.js",
9 | "start-dev-server": "tsc-watch --onSuccess \"node dist/server.js\"",
10 | "dev": "npm-run-all clean build start-dev-server",
11 | "populate-db": "npm-run-all clean build run-populate-db-script",
12 | "run-populate-db-script": "node dist/models/populate-db.js",
13 | "delete-db": "npm-run-all clean build run-delete-db-script",
14 | "run-delete-db-script": "node dist/models/delete-db.js"
15 | },
16 | "author": "",
17 | "license": "MIT",
18 | "devDependencies": {
19 | "@types/express": "^4.17.21",
20 | "@types/node": "^20.12.13",
21 | "npm-run-all": "^4.1.5",
22 | "tsc-watch": "^6.2.0",
23 | "typescript": "^5.4.5"
24 | },
25 | "dependencies": {
26 | "body-parser": "^1.20.2",
27 | "cors": "^2.8.5",
28 | "dotenv": "^16.4.5",
29 | "express": "^4.19.2",
30 | "jsonwebtoken": "^9.0.2",
31 | "pg": "^8.11.5",
32 | "reflect-metadata": "^0.2.2",
33 | "rimraf": "^5.0.7",
34 | "typeorm": "^0.3.20",
35 | "winston": "^3.13.0"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/rest-api/src/data-source.ts:
--------------------------------------------------------------------------------
1 | import {DataSource} from "typeorm";
2 | import {Course} from "./models/course";
3 | import {Lesson} from "./models/lesson";
4 | import {User} from "./models/user";
5 |
6 |
7 | export const AppDataSource = new DataSource({
8 | type: "postgres",
9 | host: process.env.DB_HOST,
10 | username: process.env.DB_USERNAME,
11 | password: process.env.DB_PASSWORD,
12 | port: parseInt(process.env.DB_PORT),
13 | database: process.env.DB_NAME,
14 | ssl: true,
15 | extra: {
16 | ssl : {
17 | rejectUnauthorized:false
18 | }
19 | },
20 | entities: [
21 | Course,
22 | Lesson,
23 | User
24 | ],
25 | synchronize: true,
26 | logging:true
27 | })
28 |
--------------------------------------------------------------------------------
/rest-api/src/logger.ts:
--------------------------------------------------------------------------------
1 |
2 | import * as winston from "winston";
3 |
4 | export const logger = winston.createLogger({
5 | level: process.env.LOGGER_LEVEL,
6 | format: winston.format.json({
7 | space: 4
8 | }),
9 | transports: [
10 | new winston.transports.File({
11 | filename: "logs/all.log"
12 | }),
13 | new winston.transports.File({
14 | filename: "logs/error.log",
15 | level: "error"
16 | })
17 | ]
18 | });
19 |
20 | if (process.env.NODE_ENV != "production") {
21 | logger.add(new winston.transports.Console({
22 | format: winston.format.simple()
23 | }));
24 | }
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/rest-api/src/middlewares/admin-only.middleware.ts:
--------------------------------------------------------------------------------
1 | import {NextFunction, Request, Response} from "express";
2 | import {logger} from "../logger";
3 |
4 |
5 | export function checkIfAdmin(request: Request, response: Response, next:NextFunction) {
6 |
7 | const user = request["user"];
8 |
9 | if (!user?.isAdmin) {
10 | logger.error(`The user is not an admin, access denied`);
11 | response.sendStatus(403);
12 | return;
13 | }
14 |
15 | logger.debug(`The user is a valid admin, granting access.`);
16 |
17 | next();
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/rest-api/src/middlewares/authentication-middleware.ts:
--------------------------------------------------------------------------------
1 | import {NextFunction, Request, Response} from "express";
2 | import {logger} from "../logger";
3 | const JWT_SECRET = process.env.JWT_SECRET;
4 | const jwt = require("jsonwebtoken");
5 |
6 | export function checkIfAuthenticated(
7 | request: Request, response: Response, next:NextFunction) {
8 |
9 | const authJwtToken = request.headers.authorization;
10 |
11 | if (!authJwtToken) {
12 | logger.info(`The authentication JWT is not present, access denied.`);
13 | response.sendStatus(403);
14 | return;
15 | }
16 |
17 | checkJwtValidity(authJwtToken)
18 | .then(user => {
19 |
20 | logger.info(`Authentication JWT successfully decoded:`, user);
21 | request["user"] = user;
22 |
23 | next();
24 | })
25 | .catch(err => {
26 | logger.error(`Could not validate the authentication JWT, access denied.`, err);
27 | response.sendStatus(403);
28 | });
29 | }
30 |
31 | async function checkJwtValidity(authJwtToken:string) {
32 |
33 | const user = await jwt.verify(authJwtToken, JWT_SECRET);
34 |
35 | logger.info("Found user details in JWT:", user);
36 |
37 | return user;
38 | }
39 |
--------------------------------------------------------------------------------
/rest-api/src/middlewares/default-error-handler.ts:
--------------------------------------------------------------------------------
1 | import {NextFunction, Request, Response} from "express";
2 | import {logger} from "../logger";
3 |
4 |
5 | export function defaultErrorHandler(
6 | err, request: Request, response: Response, next:NextFunction) {
7 |
8 | logger.error(`Default error handler triggered; reason: `, err);
9 |
10 | if (response.headersSent) {
11 | logger.error(`Response was already being written, delegating to built-in Express error handler.`);
12 | return next(err);
13 | }
14 |
15 | response.status(500).json({
16 | status: "error",
17 | message: "Default error handling triggered, check logs."
18 | });
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/rest-api/src/models/course.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Column, CreateDateColumn, Entity, OneToMany,
3 | PrimaryGeneratedColumn, UpdateDateColumn
4 | } from "typeorm";
5 | import {Lesson} from "./lesson";
6 |
7 | @Entity({
8 | name: "COURSES"
9 | })
10 | export class Course {
11 |
12 | @PrimaryGeneratedColumn()
13 | id:number;
14 |
15 | @Column()
16 | seqNo:number;
17 |
18 | @Column()
19 | url:string;
20 |
21 | @Column()
22 | title:string;
23 |
24 | @Column()
25 | iconUrl:string;
26 |
27 | @Column()
28 | longDescription:string;
29 |
30 | @Column()
31 | category: string;
32 |
33 | @OneToMany(() => Lesson, lesson => lesson.course)
34 | lessons: Lesson[];
35 |
36 | @CreateDateColumn()
37 | createdAt: Date;
38 |
39 | @UpdateDateColumn()
40 | lastUpdatedAt: Date;
41 | }
42 |
--------------------------------------------------------------------------------
/rest-api/src/models/db-data.ts:
--------------------------------------------------------------------------------
1 |
2 | export const COURSES: any = {
3 |
4 | 20: {
5 | id: 20,
6 | title: 'Typescript Bootcamp',
7 | longDescription: 'Learn in depth the Typescript language, build practical real-world projects',
8 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/typescript-bootcamp-2.jpg',
9 | category: 'BEGINNER',
10 | seqNo: 0,
11 | url: 'typescript-bootcamp',
12 | price: 50,
13 | lessons: [
14 |
15 | {
16 | id: 131,
17 | title: "Setting Up the Development Environment",
18 | 'duration': "0:44",
19 | 'seqNo': 1,
20 | longDescription: ""
21 | },
22 |
23 | {
24 | id: 132,
25 | title: "Why Typescript? Key Benefits of the Language",
26 | 'duration': "12:33",
27 | 'seqNo': 2,
28 | longDescription: ""
29 | },
30 |
31 | {
32 | id: 133,
33 | title: "Compiling Your First Typescript Program",
34 | 'duration': "05:18",
35 | 'seqNo': 3,
36 | longDescription: ""
37 | },
38 |
39 | {
40 | id: 134,
41 | title: "The Typescript compiler noEmitOnError flag",
42 | 'duration': "02:30",
43 | 'seqNo': 4,
44 | longDescription: ""
45 | },
46 |
47 | {
48 | id: 135,
49 | title: "Running a Typescript Program in a Browser",
50 | 'duration': "06:21",
51 | 'seqNo': 5,
52 | longDescription: ""
53 | },
54 |
55 | {
56 | id: 136,
57 | title: "Understanding the differences between const, let and var",
58 | 'duration': "06:23",
59 | 'seqNo': 6,
60 | longDescription: ""
61 | },
62 |
63 | {
64 | id: 137,
65 | title: "Typescript primitive types - numbers, strings and booleans",
66 | 'duration': "07:59",
67 | 'seqNo': 7,
68 | longDescription: ""
69 | },
70 |
71 | {
72 | id: 138,
73 | title: "Typescript Template Strings",
74 | 'duration': "03:48",
75 | 'seqNo': 8,
76 | longDescription: ""
77 | },
78 |
79 | {
80 | id: 139,
81 | title: "Understanding Type Inference",
82 | 'duration': "03:40",
83 | 'seqNo': 9,
84 | longDescription: ""
85 | },
86 |
87 | {
88 | id: 140,
89 | title: "When to use Typescript Type Annotations and Why",
90 | 'duration': "140",
91 | 'seqNo': 10,
92 | longDescription: ""
93 | }
94 | ],
95 | },
96 |
97 | 11: {
98 | id: 11,
99 | title: 'Angular Material Course',
100 | longDescription: 'Build Applications with the official Angular UI Widget Library',
101 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/angular-material-course-1.jpg',
102 | category: 'BEGINNER',
103 | seqNo: 1,
104 | url: 'angular-material-course',
105 | price: 50,
106 | lessons: [
107 |
108 | // Angular Material In Depth
109 | {
110 | id: 120,
111 | title: 'Introduction to Angular Material',
112 | 'duration': '4:17',
113 | 'seqNo': 1,
114 | longDescription: "A quick introduction to the Angular Material library."
115 | },
116 | {
117 | id: 121,
118 | title: 'Navigation and Containers',
119 | 'duration': '6:37',
120 | 'seqNo': 2,
121 | longDescription: "Guided tour of navigation elements and container."
122 | },
123 | {
124 | id: 122,
125 | title: 'Data Tables',
126 | 'duration': '8:03',
127 | 'seqNo': 3,
128 | longDescription: "Angular Material Data Tables in detail."
129 | },
130 | {
131 | id: 123,
132 | title: 'Dialogs',
133 | 'duration': '11:46',
134 | 'seqNo': 4,
135 | longDescription: "Modal elements and how to use them."
136 | },
137 | {
138 | id: 124,
139 | title: 'Commonly used Form Controls',
140 | 'duration': '7:17',
141 | 'seqNo': 5,
142 | longDescription: "All sorts of commonly needed form controls."
143 | },
144 | {
145 | id: 125,
146 | title: 'Drag and Drop',
147 | 'duration': '8:16',
148 | 'seqNo': 6,
149 | longDescription: "How to use drag and drop."
150 | },
151 | {
152 | id: 126,
153 | title: 'Responsive Design',
154 | 'duration': '7:28',
155 | 'seqNo': 7,
156 | longDescription: "Everything about making our screens responsive."
157 | },
158 | {
159 | id: 127,
160 | title: 'Tree Component',
161 | 'duration': '11:09',
162 | 'seqNo': 8,
163 | longDescription: "All about the Angular Material Tree component."
164 | },
165 | {
166 | id: 128,
167 | title: 'Virtual Scrolling',
168 | 'duration': '3:44',
169 | 'seqNo': 9,
170 | longDescription: "How to use virtual scrolling to handle large amounts of data."
171 | },
172 | {
173 | id: 129,
174 | title: 'Custom Themes',
175 | 'duration': '8:55',
176 | 'seqNo': 10,
177 | longDescription: "How to build your own custom Angular Material theme."
178 | },
179 | {
180 | id: 130,
181 | title: 'Changing Theme at Runtime',
182 | 'duration': '12:37',
183 | 'seqNo': 11,
184 | longDescription: ""
185 | }
186 |
187 |
188 | ],
189 | },
190 |
191 | 19: {
192 | id: 19,
193 | title: 'Angular Forms In Depth',
194 | longDescription: 'Build complex enterprise data forms with the powerful Angular Forms module',
195 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/angular-forms-course-small.jpg',
196 | category: 'BEGINNER',
197 | lessons: [],
198 | seqNo: 2,
199 | url: 'angular-forms-course',
200 | price: 50
201 | },
202 |
203 |
204 | 18: {
205 | id: 18,
206 | title: 'Angular Router In Depth',
207 | longDescription: 'Build large-scale Single Page Applications with the powerful Angular Router',
208 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/angular-router-course.jpg',
209 | category: 'BEGINNER',
210 | lessons: [
211 |
212 | // Angular Router Course
213 | {
214 | id: 90,
215 | title: 'What is a Single Page Application?',
216 | 'duration': '04:00',
217 | 'seqNo': 1,
218 | videoId: 'VES1eTNxi1s'
219 | },
220 | {
221 | id: 91,
222 | title: 'Setting Up The Development Environment',
223 | 'duration': '06:05',
224 | 'seqNo': 2,
225 | videoId: 'ANfplcxnl78'
226 | },
227 | {
228 | id: 92,
229 | title: 'Angular Router Setup',
230 | 'duration': '02:36',
231 | 'seqNo': 3,
232 | videoId: '9ez72LAd6mM'
233 | },
234 | {
235 | id: 93,
236 | title: 'Configuring a Home Route and Fallback Route',
237 | 'duration': '02:55',
238 | 'seqNo': 4,
239 | videoId: 'Clj-jZpl64w'
240 | },
241 | {
242 | id: 94,
243 | title: 'Styling Active Routes With The routerLinkActive And routerLinkActiveOptions',
244 | 'duration': '07:50',
245 | 'seqNo': 5,
246 | videoId: 'zcgnsmPVc30'
247 | },
248 | {
249 | id: 95,
250 | title: 'Child Routes - How To Setup a Master Detail Route',
251 | 'duration': '04:10',
252 | 'seqNo': 6,
253 | videoId: 'zcgnsmPVc30'
254 | },
255 | {
256 | id: 96,
257 | title: 'Programmatic Router Navigation via the Router API ',
258 | 'duration': '03:59',
259 | 'seqNo': 7,
260 | videoId: 'VES1eTNxi1s'
261 | },
262 | {
263 | id: 97,
264 | title: 'Relative And Absolute Router Navigation',
265 | 'duration': '04:58',
266 | 'seqNo': 8,
267 | videoId: 'MQl9Zs3QqGM'
268 | },
269 | {
270 | id: 98,
271 | title: 'Master Detail Navigation And Route Parameters',
272 | 'duration': '06:03',
273 | 'seqNo': 9,
274 | videoId: 'ANfplcxnl78'
275 | },
276 |
277 | {
278 | id: 99,
279 | title: 'The Route Parameters Observable',
280 | 'duration': '06:50',
281 | 'seqNo': 10,
282 | videoId: 'zcgnsmPVc30'
283 | },
284 | {
285 | id: 100,
286 | title: 'Optional Route Query Parameters',
287 | 'duration': '03:03',
288 | 'seqNo': 11,
289 | videoId: '0Qsg8fyKwO4'
290 | },
291 | {
292 | id: 101,
293 | title: 'The queryParams Directive and the Query Parameters Observable',
294 | 'duration': '07:50',
295 | 'seqNo': 12,
296 | videoId: 'VES1eTNxi1s'
297 | },
298 | {
299 | id: 102,
300 | title: 'Exiting an Angular Route - How To Prevent Memory Leaks',
301 | 'duration': '07:50',
302 | 'seqNo': 13,
303 | videoId: 'ANfplcxnl78'
304 | },
305 | {
306 | id: 103,
307 | title: 'CanDeactivate Route Guard',
308 | 'duration': '04:50',
309 | 'seqNo': 14,
310 | videoId: '9ez72LAd6mM'
311 | },
312 | {
313 | id: 104,
314 | title: 'CanActivate Route Guard - An Example of An Asynchronous Route Guard',
315 | 'duration': '03:32',
316 | 'seqNo': 15,
317 | videoId: 'Clj-jZpl64w'
318 | },
319 | {
320 | id: 105,
321 | title: 'Configure Auxiliary Routes in the Angular Router',
322 | 'duration': '05:16',
323 | 'seqNo': 16,
324 | videoId: 'zcgnsmPVc30'
325 | },
326 |
327 | {
328 | id: 106,
329 | title: 'Angular Auxiliary Routes - How To Pass Router Parameters',
330 | 'duration': '07:50',
331 | 'seqNo': 17,
332 | videoId: 'yjQUkNHb1Is'
333 | },
334 | {
335 | id: 107,
336 | title: 'Angular Router Redirects and Path Matching',
337 | 'duration': '02:59',
338 | 'seqNo': 18,
339 | videoId: 'VES1eTNxi1s'
340 | },
341 | {
342 | id: 108,
343 | title: 'Angular Router Hash Location Strategy',
344 | 'duration': '07:50',
345 | 'seqNo': 19,
346 | videoId: 'MQl9Zs3QqGM'
347 | },
348 | {
349 | id: 109,
350 | title: 'Angular Router Lazy Loading and Shared Modules',
351 | 'duration': '08:45',
352 | 'seqNo': 20,
353 | videoId: '0Qsg8fyKwO4'
354 | },
355 | {
356 | id: 110,
357 | title: 'Exercise - Implement a Widget Dashboard',
358 | 'duration': '07:50',
359 | 'seqNo': 21,
360 | videoId: 'VES1eTNxi1s'
361 | },
362 | {
363 | id: 111,
364 | title: 'Exercise Solution ',
365 | 'duration': '07:50',
366 | 'seqNo': 22,
367 | videoId: '0Qsg8fyKwO4'
368 | },
369 |
370 |
371 | ],
372 | seqNo: 3,
373 | url: 'angular-router-course',
374 | price: 50
375 | },
376 |
377 | 17: {
378 | id: 17,
379 | title: 'Reactive Angular Course',
380 | longDescription: 'How to build Angular applications in Reactive style using plain RxJs - Patterns and Anti-Patterns',
381 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/reactive-angular-course.jpg',
382 | category: 'BEGINNER',
383 | lessons: [
384 |
385 | // Reactive Angular Course
386 | {
387 | id: 80,
388 | title: 'Introduction to Reactive Programming',
389 | 'duration': '03:45',
390 | 'seqNo': 0,
391 | videoId: 'Df1QnesgB_s',
392 | },
393 | {
394 | id: 81,
395 | title: 'Introduction to RxJs',
396 | 'duration': '08:36',
397 | 'seqNo': 1,
398 | videoId: '8m5RrAtqlyw',
399 | },
400 | {
401 | id: 82,
402 | title: 'Setting up the development environment',
403 | 'duration': '09:10',
404 | 'seqNo': 2,
405 | videoId: '3fDbUB-nKqc',
406 | },
407 | {
408 | id: 83,
409 | title: 'Designing and building a Service Layer',
410 | 'duration': '07:20',
411 | 'seqNo': 3,
412 | videoId: '',
413 | },
414 | {
415 | id: 84,
416 | title: 'Stateless Observable Services',
417 | 'duration': '11:47',
418 | 'seqNo': 4,
419 | videoId: 'qvDPnRs_ZPA',
420 | },
421 | {
422 | id: 85,
423 | title: 'Smart vs Presentational Components',
424 | 'duration': '06:30',
425 | 'seqNo': 5,
426 | videoId: '5bsZJGAelFM',
427 | },
428 | {
429 | id: 86,
430 | title: 'Lightweight state management',
431 | 'duration': '4:13',
432 | 'seqNo': 6,
433 | videoId: '9m3_HHeP9Ko',
434 | },
435 | {
436 | id: 87,
437 | title: 'Event bubbling anti-pattern',
438 | 'duration': '05:47',
439 | 'seqNo': 7,
440 | videoId: 'PRQCAL_RMVo',
441 | },
442 | {
443 | id: 88,
444 | title: 'Master detail with cached master table',
445 | 'duration': '05:17',
446 | 'seqNo': 8,
447 | videoId: 'du4ib4jBUG0'
448 | },
449 | {
450 | id: 89,
451 | title: 'Error handling',
452 | 'duration': '07:50',
453 | 'seqNo': 9,
454 | videoId: '8m5RrAtqlyw'
455 | }
456 |
457 |
458 | ],
459 | seqNo: 4,
460 | url: 'reactive-angular-course',
461 | price: 50
462 |
463 | },
464 | 3: {
465 | id: 3,
466 | title: 'RxJs In Practice Course',
467 | longDescription: 'Understand the RxJs Observable pattern, learn the RxJs Operators via practical examples',
468 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/rxjs-in-practice-course.png',
469 | category: 'BEGINNER',
470 | lessons: [],
471 | seqNo: 5,
472 | url: 'rxjs-course',
473 | price: 50
474 | },
475 |
476 | 4: {
477 | id: 4,
478 | title: 'NgRx (with NgRx Data) - The Complete Guide',
479 | longDescription: 'Learn the modern Ngrx Ecosystem, including NgRx Data, Store, Effects, Router Store, Ngrx Entity, and Dev Tools.',
480 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/ngrx-v2.png',
481 | category: 'BEGINNER',
482 | lessons: [
483 |
484 | // Ngrx Course
485 | {
486 | id: 50,
487 | title: 'Welcome to the Angular Ngrx Course',
488 | 'duration': '6:53',
489 | 'seqNo': 1,
490 |
491 | },
492 | {
493 | id: 51,
494 | title: 'The Angular Ngrx Architecture Course - Helicopter View',
495 | 'duration': '5:52',
496 | 'seqNo': 2,
497 | },
498 | {
499 | id: 52,
500 | title: 'The Origins of Flux - Understanding the Famous Facebook Bug Problem',
501 | 'duration': '8:17',
502 | 'seqNo': 3,
503 | },
504 | {
505 | id: 53,
506 | title: 'Custom Global Events - Why Don\'t They Scale In Complexity?',
507 | 'duration': '7:47',
508 | 'seqNo': 4,
509 | },
510 | {
511 | id: 54,
512 | title: 'The Flux Architecture - How Does it Solve Facebook Counter Problem?',
513 | 'duration': '9:22',
514 | 'seqNo': 5,
515 | },
516 | {
517 | id: 55,
518 | title: 'Unidirectional Data Flow And The Angular Development Mode',
519 | 'duration': '7:07',
520 | 'seqNo': 6,
521 | },
522 |
523 | {
524 | id: 56,
525 | title: 'Dispatching an Action - Implementing the Login Component',
526 | 'duration': '4:39',
527 | 'seqNo': 7,
528 | },
529 | {
530 | id: 57,
531 | title: 'Setting Up the Ngrx DevTools - Demo',
532 | 'duration': '4:44',
533 | 'seqNo': 8,
534 | },
535 | {
536 | id: 58,
537 | title: 'Understanding Reducers - Writing Our First Reducer',
538 | 'duration': '9:10',
539 | 'seqNo': 9,
540 | },
541 | {
542 | id: 59,
543 | title: 'How To Define the Store Initial State',
544 | 'duration': '9:10',
545 | 'seqNo': 10,
546 | },
547 | ],
548 | seqNo: 6,
549 | url: 'ngrx-course',
550 | promo: false,
551 | price: 50
552 | },
553 |
554 |
555 | 2: {
556 | id: 2,
557 | title: 'Angular Core Deep Dive',
558 | longDescription: 'A detailed walk-through of the most important part of Angular - the Core and Common modules',
559 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/angular-core-in-depth-small.png',
560 | lessons: [],
561 | category: 'BEGINNER',
562 | seqNo: 7,
563 | url: 'angular-core-course',
564 | price: 50
565 | },
566 |
567 |
568 | 5: {
569 | id: 5,
570 | title: 'Angular for Beginners',
571 | longDescription: 'Establish a solid layer of fundamentals, learn what\'s under the hood of Angular',
572 | iconUrl: 'https://angular-academy.s3.amazonaws.com/thumbnails/angular2-for-beginners-small-v2.png',
573 | category: 'BEGINNER',
574 | seqNo: 8,
575 | url: 'angular-for-beginners',
576 | price: 50,
577 | lessons: [
578 |
579 | {
580 | id: 1,
581 | title: 'Angular Tutorial For Beginners - Build Your First App - Hello World Step By Step',
582 | 'duration': '4:17',
583 | 'seqNo': 1,
584 | },
585 | {
586 | id: 2,
587 | title: 'Building Your First Component - Component Composition',
588 | 'duration': '2:07',
589 | 'seqNo': 2,
590 | },
591 | {
592 | id: 3,
593 | title: 'Component @Input - How To Pass Input Data To an Component',
594 | 'duration': '2:33',
595 | 'seqNo': 3,
596 | },
597 | {
598 | id: 4,
599 | title: ' Component Events - Using @Output to create custom events',
600 | 'duration': '4:44',
601 | 'seqNo': 4,
602 | },
603 | {
604 | id: 5,
605 | title: ' Component Templates - Inline Vs External',
606 | 'duration': '2:55',
607 | 'seqNo': 5,
608 | },
609 | {
610 | id: 6,
611 | title: 'Styling Components - Learn About Component Style Isolation',
612 | 'duration': '3:27',
613 | 'seqNo': 6,
614 | },
615 | {
616 | id: 7,
617 | title: ' Component Interaction - Extended Components Example',
618 | 'duration': '9:22',
619 | 'seqNo': 7,
620 | },
621 | {
622 | id: 8,
623 | title: ' Components Tutorial For Beginners - Components Exercise !',
624 | 'duration': '1:26',
625 | 'seqNo': 8,
626 | },
627 | {
628 | id: 9,
629 | title: ' Components Tutorial For Beginners - Components Exercise Solution Inside',
630 | 'duration': '2:08',
631 | 'seqNo': 9,
632 | },
633 | {
634 | id: 10,
635 | title: ' Directives - Inputs, Output Event Emitters and How To Export Template References',
636 | 'duration': '4:01',
637 | 'seqNo': 10,
638 | },
639 |
640 |
641 | ]
642 | },
643 |
644 | 12: {
645 | id: 12,
646 | title: 'Angular Testing Course',
647 | longDescription: 'In-depth guide to Unit Testing and E2E Testing of Angular Applications',
648 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/angular-testing-small.png',
649 | category: 'BEGINNER',
650 | seqNo: 9,
651 | url: 'angular-testing-course',
652 | lessons: [
653 |
654 | // Angular Testing Course
655 | {
656 | id: 40,
657 | title: 'Angular Testing Course - Helicopter View',
658 | 'duration': '5:38',
659 | 'seqNo': 1,
660 | },
661 |
662 | {
663 | id: 41,
664 | title: 'Setting Up the Development Environment',
665 | 'duration': '5:12',
666 | 'seqNo': 2,
667 | },
668 |
669 | {
670 | id: 42,
671 | title: 'Introduction to Jasmine, Spies and specs',
672 | 'duration': '4:07',
673 | 'seqNo': 3,
674 | },
675 |
676 | {
677 | id: 43,
678 | title: 'Introduction to Service Testing',
679 | 'duration': '7:32',
680 | 'seqNo': 4,
681 | },
682 |
683 | {
684 | id: 44,
685 | title: 'Settting up the Angular TestBed',
686 | 'duration': '6:28',
687 | 'seqNo': 5,
688 | },
689 |
690 | {
691 | id: 45,
692 | title: 'Mocking Angular HTTP requests',
693 | 'duration': '4:38',
694 | 'seqNo': 6,
695 | },
696 |
697 | {
698 | id: 46,
699 | title: 'Simulating Failing HTTP Requests',
700 | 'duration': '7:54',
701 | 'seqNo': 7,
702 | },
703 |
704 | {
705 | id: 47,
706 | title: 'Introduction to Angular Component Testing',
707 | 'duration': '5:31',
708 | 'seqNo': 8,
709 | },
710 |
711 | {
712 | id: 48,
713 | title: 'Testing Angular Components without the DOM',
714 | 'duration': '8:19',
715 | 'seqNo': 9,
716 | },
717 |
718 | {
719 | id: 49,
720 | title: 'Testing Angular Components with the DOM',
721 | 'duration': '7:05',
722 | 'seqNo': 10,
723 | },
724 |
725 |
726 | ],
727 | promo: false,
728 | price: 50
729 | },
730 |
731 |
732 | 1: {
733 | id: 1,
734 | title: 'Serverless Angular with Firebase Course',
735 | longDescription: 'Serveless Angular with Firestore, Firebase Storage & Hosting, Firebase Cloud Functions & AngularFire',
736 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/serverless-angular-small.png',
737 | category: 'BEGINNER',
738 | seqNo: 10,
739 | url: 'serverless-angular',
740 | price: 50,
741 | lessons: [
742 | // Serverless Angular with Firebase Course
743 | {
744 | id: 30,
745 | title: 'Development Environment Setup',
746 | 'duration': '5:38',
747 | 'seqNo': 1,
748 | },
749 |
750 | {
751 | id: 31,
752 | title: 'Introduction to the Firebase Ecosystem',
753 | 'duration': '5:12',
754 | 'seqNo': 2,
755 | },
756 |
757 | {
758 | id: 32,
759 | title: 'Importing Data into Firestore',
760 | 'duration': '4:07',
761 | 'seqNo': 3,
762 | },
763 |
764 | {
765 | id: 33,
766 | title: 'Firestore Documents in Detail',
767 | 'duration': '7:32',
768 | 'seqNo': 4,
769 | },
770 |
771 | {
772 | id: 34,
773 | title: 'Firestore Collections in Detail',
774 | 'duration': '6:28',
775 | 'seqNo': 5,
776 | },
777 |
778 | {
779 | id: 35,
780 | title: 'Firestore Unique Identifiers',
781 | 'duration': '4:38',
782 | 'seqNo': 6,
783 | },
784 |
785 | {
786 | id: 36,
787 | title: 'Querying Firestore Collections',
788 | 'duration': '7:54',
789 | 'seqNo': 7,
790 | },
791 |
792 | {
793 | id: 37,
794 | title: 'Firebase Security Rules In Detail',
795 | 'duration': '5:31',
796 | 'seqNo': 8,
797 | },
798 |
799 | {
800 | id: 38,
801 | title: 'Firebase Cloud Functions In Detail',
802 | 'duration': '8:19',
803 | 'seqNo': 9
804 | },
805 |
806 | {
807 | id: 39,
808 | title: 'Firebase Storage In Detail',
809 | 'duration': '7:05',
810 | 'seqNo': 10
811 | },
812 |
813 |
814 | ]
815 | },
816 |
817 | 16: {
818 | id: 16,
819 | title: 'Stripe Payments In Practice',
820 | longDescription: 'Build your own ecommerce store & membership website with Firebase, Stripe and Express',
821 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/stripe-course.jpg',
822 | lessons: [
823 |
824 | // Stripe Course
825 | {
826 | id: 70,
827 | title: 'Introduction to Stripe Payments',
828 | 'duration': '03:45',
829 | 'seqNo': 0
830 | },
831 | {
832 | id: 71,
833 | title: 'The advantages of Stripe Checkout',
834 | 'duration': '08:36',
835 | 'seqNo': 1
836 | },
837 | {
838 | id: 72,
839 | title: 'Setting up the development environment',
840 | 'duration': '09:10',
841 | 'seqNo': 2
842 | },
843 | {
844 | id: 73,
845 | title: 'Creating a server Checkout Session',
846 | 'duration': '07:20',
847 | 'seqNo': 3
848 | },
849 | {
850 | id: 74,
851 | title: 'Redirecting to the Stripe Checkout page',
852 | 'duration': '11:47',
853 | 'seqNo': 4
854 | },
855 | {
856 | id: 75,
857 | title: 'Order fulfillment webhook',
858 | 'duration': '06:30',
859 | 'seqNo': 5
860 | },
861 | {
862 | id: 76,
863 | title: 'Installing the Stripe CLI',
864 | 'duration': '4:13',
865 | 'seqNo': 6
866 | },
867 | {
868 | id: 77,
869 | title: 'Firestore Security Rules for protecting Premium content',
870 | 'duration': '05:47',
871 | 'seqNo': 7
872 | },
873 | {
874 | id: 78,
875 | title: 'Stripe Subscriptions with Stripe Checkout',
876 | 'duration': '05:17',
877 | 'seqNo': 8
878 | },
879 | {
880 | id: 79,
881 | title: 'Stripe Subscription Fulfillment',
882 | 'duration': '07:50',
883 | 'seqNo': 9
884 | },
885 |
886 |
887 | ],
888 | category: 'BEGINNER',
889 | seqNo: 11,
890 | url: 'stripe-course',
891 | price: 50
892 | },
893 |
894 | 14: {
895 | id: 14,
896 | title: 'NestJs In Practice (with MongoDB)',
897 | longDescription: 'Build a modern REST backend using Typescript, MongoDB and the familiar Angular API.',
898 | iconUrl: 'https://angular-university.s3-us-west-1.amazonaws.com/course-images/nestjs-v2.png',
899 | category: 'BEGINNER',
900 | lessons: [
901 |
902 | // NestJs Course
903 | {
904 | id: 60,
905 | title: 'Introduction to NestJs',
906 | 'duration': '4:29',
907 | 'seqNo': 1
908 | },
909 | {
910 | id: 61,
911 | title: 'Development Environment Setup',
912 | 'duration': '6:37',
913 | 'seqNo': 2
914 | },
915 | {
916 | id: 62,
917 | title: 'Setting up a MongoDB Database',
918 | 'duration': '6:38',
919 | 'seqNo': 3
920 | },
921 | {
922 | id: 63,
923 | title: 'CRUD with NestJs - Controllers and Repositories',
924 | 'duration': '12:12',
925 | 'seqNo': 4
926 | },
927 | {
928 | id: 64,
929 | title: 'First REST endpoint - Get All Courses',
930 | 'duration': '3:42',
931 | 'seqNo': 5
932 | },
933 | {
934 | id: 65,
935 | title: 'Error Handling',
936 | 'duration': '5:15',
937 | 'seqNo': 6
938 | },
939 | {
940 | id: 66,
941 | title: 'NestJs Middleware',
942 | 'duration': '7:08',
943 | 'seqNo': 7
944 | },
945 | {
946 | id: 67,
947 | title: 'Authentication in NestJs',
948 | 'duration': '13:22',
949 | 'seqNo': 8
950 | },
951 | {
952 | id: 68,
953 | title: 'Authorization in NestJs',
954 | 'duration': '6:43',
955 | 'seqNo': 9
956 | },
957 | {
958 | id: 69,
959 | title: 'Guards & Interceptors',
960 | 'duration': '8:16',
961 | 'seqNo': 10
962 | },
963 |
964 |
965 | ],
966 | seqNo: 12,
967 | url: 'nestjs-course',
968 | promo: false,
969 | price: 50
970 | },
971 |
972 | 6: {
973 | id: 6,
974 | title: 'Angular Security Course - Web Security Fundamentals',
975 | longDescription: 'Learn Web Security Fundamentals and apply them to defend an Angular / Node Application from multiple types of attacks.',
976 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/security-cover-small-v2.png',
977 | category: 'ADVANCED',
978 | seqNo: 13,
979 | url: 'angular-security-course',
980 | price: 50,
981 | lessons: [
982 |
983 | // Security Course
984 | {
985 | id: 11,
986 | title: 'Course Helicopter View',
987 | 'duration': '08:19',
988 | 'seqNo': 1
989 | },
990 |
991 | {
992 | id: 12,
993 | title: 'Installing Git, Node, NPM and Choosing an IDE',
994 | 'duration': '04:17',
995 | 'seqNo': 2
996 | },
997 |
998 | {
999 | id: 13,
1000 | title: 'Installing The Lessons Code - Learn Why Its Essential To Use NPM 5',
1001 | 'duration': '06:05',
1002 | 'seqNo': 3
1003 | },
1004 |
1005 | {
1006 | id: 14,
1007 | title: 'How To Run Node In TypeScript With Hot Reloading',
1008 | 'duration': '03:57',
1009 | 'seqNo': 4
1010 | },
1011 |
1012 | {
1013 | id: 15,
1014 | title: 'Guided Tour Of The Sample Application',
1015 | 'duration': '06:00',
1016 | 'seqNo': 5
1017 | },
1018 | {
1019 | id: 16,
1020 | title: 'Client Side Authentication Service - API Design',
1021 | 'duration': '04:53',
1022 | 'seqNo': 6
1023 | },
1024 | {
1025 | id: 17,
1026 | title: 'Client Authentication Service - Design and Implementation',
1027 | 'duration': '09:14',
1028 | 'seqNo': 7
1029 | },
1030 | {
1031 | id: 18,
1032 | title: 'The New Angular HTTP Client - Doing a POST Call To The Server',
1033 | 'duration': '06:08',
1034 | 'seqNo': 8
1035 | },
1036 | {
1037 | id: 19,
1038 | title: 'User Sign Up Server-Side Implementation in Express',
1039 | 'duration': '08:50',
1040 | 'seqNo': 9
1041 | },
1042 | {
1043 | id: 20,
1044 | title: 'Introduction To Cryptographic Hashes - A Running Demo',
1045 | 'duration': '05:46',
1046 | 'seqNo': 10
1047 | },
1048 | {
1049 | id: 21,
1050 | title: 'Some Interesting Properties Of Hashing Functions - Validating Passwords',
1051 | 'duration': '06:31',
1052 | 'seqNo': 11
1053 | },
1054 |
1055 |
1056 | ]
1057 | },
1058 |
1059 | 7: {
1060 | id: 7,
1061 | title: 'Angular PWA - Progressive Web Apps Course',
1062 | longDescription: 'Learn Angular Progressive Web Applications, build the future of the Web Today.',
1063 | iconUrl: 'https://s3-us-west-1.amazonaws.com/angular-university/course-images/angular-pwa-course.png',
1064 | category: 'ADVANCED',
1065 | seqNo: 14,
1066 | url: 'angular-pwa-course',
1067 | price: 50,
1068 | lessons: [
1069 | // PWA course
1070 | {
1071 | id: 22,
1072 | title: 'Course Kick-Off - Install Node, NPM, IDE And Service Workers Section Code',
1073 | 'duration': '07:19',
1074 | 'seqNo': 1
1075 | },
1076 | {
1077 | id: 23,
1078 | title: 'Service Workers In a Nutshell - Service Worker Registration',
1079 | 'duration': '6:59',
1080 | 'seqNo': 2
1081 | },
1082 | {
1083 | id: 24,
1084 | title: 'Service Workers Hello World - Lifecycle Part 1 and PWA Chrome Dev Tools',
1085 | 'duration': '7:28',
1086 | 'seqNo': 3
1087 | },
1088 | {
1089 | id: 25,
1090 | title: 'Service Workers and Application Versioning - Install & Activate Lifecycle Phases',
1091 | 'duration': '10:17',
1092 | 'seqNo': 4
1093 | },
1094 |
1095 | {
1096 | id: 26,
1097 | title: 'Downloading The Offline Page - The Service Worker Installation Phase',
1098 | 'duration': '09:50',
1099 | 'seqNo': 5
1100 | },
1101 | {
1102 | id: 27,
1103 | title: 'Introduction to the Cache Storage PWA API',
1104 | 'duration': '04:44',
1105 | 'seqNo': 6
1106 | },
1107 | {
1108 | id: 28,
1109 | title: 'View Service Workers HTTP Interception Features In Action',
1110 | 'duration': '06:07',
1111 | 'seqNo': 7
1112 | },
1113 | {
1114 | id: 29,
1115 | title: 'Service Workers Error Handling - Serving The Offline Page',
1116 | 'duration': '5:38',
1117 | 'seqNo': 8
1118 | },
1119 |
1120 | ]
1121 | },
1122 |
1123 | 8: {
1124 | id: 8,
1125 | title: 'Angular Advanced Library Laboratory: Build Your Own Library',
1126 | longDescription: 'Learn Advanced Angular functionality typically used in Library Development. Advanced Components, Directives, Testing, Npm',
1127 | iconUrl: 'https://angular-academy.s3.amazonaws.com/thumbnails/advanced_angular-small-v3.png',
1128 | category: 'ADVANCED',
1129 | seqNo: 15,
1130 | url: 'angular-advanced-course',
1131 | price: 50,
1132 | lessons: []
1133 | }
1134 |
1135 | };
1136 |
1137 | export const USERS = {
1138 | 1: {
1139 | id: 1,
1140 | email: 'test@angular-university.io',
1141 | plainTextPassword: 'test',
1142 | passwordSalt: "o61TA7yaJIsa",
1143 | pictureUrl: 'https://angular-academy.s3.amazonaws.com/main-logo/main-page-logo-small-hat.png',
1144 | isAdmin: false
1145 | },
1146 | 2: {
1147 | id: 2,
1148 | email: 'admin@angular-university.io',
1149 | plainTextPassword: 'admin',
1150 | passwordSalt: "NydKRjIh4T4X",
1151 | pictureUrl: 'https://angular-academy.s3.amazonaws.com/main-logo/main-page-logo-small-hat.png',
1152 | isAdmin: true
1153 | }
1154 |
1155 | };
1156 |
--------------------------------------------------------------------------------
/rest-api/src/models/delete-db.ts:
--------------------------------------------------------------------------------
1 | import * as dotenv from "dotenv";
2 |
3 | const result = dotenv.config();
4 |
5 | import "reflect-metadata";
6 | import {AppDataSource} from "../data-source";
7 | import {Lesson} from "./lesson";
8 | import {Course} from "./course";
9 | import {User} from "./user";
10 |
11 | async function deleteDb() {
12 |
13 | await AppDataSource.initialize();
14 |
15 | console.log(`Database connection ready.`);
16 |
17 | console.log(`Clearing LESSONS table.`);
18 |
19 | await AppDataSource.getRepository(Lesson).delete({});
20 |
21 | console.log(`Clearing COURSES table.`);
22 |
23 | await AppDataSource.getRepository(Course).delete({});
24 |
25 | console.log(`Clearing USERS table.`);
26 |
27 | await AppDataSource.getRepository(User).delete({});
28 |
29 | }
30 |
31 |
32 | deleteDb()
33 | .then(() => {
34 | console.log(`Finished deleting database, exiting!`);
35 | process.exit(0);
36 | })
37 | .catch(err => {
38 | console.error(`Error deleting database.`, err);
39 | });
40 |
--------------------------------------------------------------------------------
/rest-api/src/models/lesson.ts:
--------------------------------------------------------------------------------
1 | import {Column, CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn} from "typeorm";
2 | import { Course } from "./course";
3 | import {JoinColumn} from "typeorm";
4 |
5 | @Entity({
6 | name: "LESSONS"
7 | })
8 | export class Lesson {
9 |
10 | @PrimaryGeneratedColumn()
11 | id:number;
12 |
13 | @Column()
14 | title:string;
15 |
16 | @Column()
17 | duration:string;
18 |
19 | @Column()
20 | seqNo: number;
21 |
22 | @ManyToOne(() => Course, course => course.lessons)
23 | @JoinColumn({
24 | name: "courseId"
25 | })
26 | course: Course;
27 |
28 | @CreateDateColumn()
29 | createdAt: Date;
30 |
31 | @UpdateDateColumn()
32 | lastUpdatedAt: Date;
33 | }
34 |
--------------------------------------------------------------------------------
/rest-api/src/models/populate-db.ts:
--------------------------------------------------------------------------------
1 |
2 | import * as dotenv from "dotenv";
3 |
4 | const result = dotenv.config();
5 |
6 | import "reflect-metadata";
7 |
8 | import {COURSES, USERS} from "./db-data";
9 | import {AppDataSource} from "../data-source";
10 | import { Course } from "./course";
11 | import {DeepPartial} from "typeorm";
12 | import {Lesson} from "./lesson";
13 | import {User} from "./user";
14 | import {calculatePasswordHash} from "../utils";
15 |
16 | async function populateDb() {
17 |
18 | await AppDataSource.initialize();
19 |
20 | console.log(`Database connection ready.`);
21 |
22 | const courses = Object.values(COURSES) as DeepPartial[];
23 |
24 | const courseRepository = AppDataSource.getRepository(Course);
25 |
26 | const lessonsRepository = AppDataSource.getRepository(Lesson);
27 |
28 | for (let courseData of courses) {
29 |
30 | console.log(`Inserting course ${courseData.title}`);
31 |
32 | const course = courseRepository.create(courseData);
33 |
34 | await courseRepository.save(course);
35 |
36 | for (let lessonData of courseData.lessons) {
37 |
38 | console.log(`Inserting lesson ${lessonData.title}`);
39 |
40 | const lesson = lessonsRepository.create(lessonData);
41 |
42 | lesson.course = course;
43 |
44 | await lessonsRepository.save(lesson);
45 | }
46 |
47 | }
48 |
49 | const users = Object.values(USERS) as any[];
50 |
51 | for (let userData of users) {
52 |
53 | console.log(`Inserting user: ${userData}`);
54 |
55 | const {email, pictureUrl, isAdmin, passwordSalt, plainTextPassword} = userData;
56 |
57 | const user = AppDataSource
58 | .getRepository(User)
59 | .create({
60 | email,
61 | pictureUrl,
62 | isAdmin,
63 | passwordSalt,
64 | passwordHash: await calculatePasswordHash(
65 | plainTextPassword, passwordSalt)
66 | });
67 |
68 | await AppDataSource.manager.save(user);
69 |
70 | }
71 |
72 | const totalCourses = await courseRepository
73 | .createQueryBuilder()
74 | .getCount();
75 |
76 | const totalLessons = await lessonsRepository
77 | .createQueryBuilder()
78 | .getCount();
79 |
80 | console.log(` Data Inserted - courses ${totalCourses}, lessons ${totalLessons}`);
81 |
82 | }
83 |
84 | populateDb()
85 | .then(() => {
86 | console.log(`Finished populating database, exiting!`);
87 | process.exit(0);
88 | })
89 | .catch(err => {
90 | console.error(`Error populating database.`, err);
91 | });
92 |
93 |
--------------------------------------------------------------------------------
/rest-api/src/models/user.ts:
--------------------------------------------------------------------------------
1 | import {Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn} from "typeorm";
2 |
3 |
4 | @Entity({
5 | name: "USERS"
6 | })
7 | export class User {
8 |
9 | @PrimaryGeneratedColumn()
10 | id:number;
11 |
12 | @Column()
13 | email:string;
14 |
15 | @Column()
16 | passwordHash:string;
17 |
18 | @Column()
19 | passwordSalt:string;
20 |
21 | @Column()
22 | pictureUrl:string;
23 |
24 | @Column()
25 | isAdmin:boolean;
26 |
27 | @CreateDateColumn()
28 | createdAt: Date;
29 |
30 | @UpdateDateColumn()
31 | lastUpdatedAt: Date;
32 | }
33 |
--------------------------------------------------------------------------------
/rest-api/src/routes/create-course.ts:
--------------------------------------------------------------------------------
1 | import {NextFunction, Request, Response} from "express";
2 | import {logger} from "../logger";
3 | import {AppDataSource} from "../data-source";
4 | import {Course} from "../models/course";
5 |
6 | /*
7 | *
8 | * curl -X POST http://localhost:9000/api/courses -H "Content-Type:application/json" -d '{"url": "firebase-bootcamp", "title": "Firebase Bootcamp", "iconUrl": "https://angular-university.s3-us-west-1.amazonaws.com/course-images/firebase-course-1.jpg","longDescription": "Complete guided tour to the Firebase ecosystem.", "category": "BEGINNER"}'
9 | *
10 | * {"url": "firebase-bootcamp", "title": "Firebase Bootcamp", "iconUrl": "https://angular-university.s3-us-west-1.amazonaws.com/course-images/firebase-course-1.jpg","longDescription": "Complete guided tour to the Firebase ecosystem.", "category": "BEGINNER"}
11 | *
12 | * */
13 |
14 | export async function createCourse(
15 | request: Request, response: Response, next:NextFunction) {
16 |
17 | try {
18 |
19 | logger.debug(`Called createCourse()`);
20 |
21 | const data = request.body;
22 |
23 | if (!data) {
24 | throw `No data available, cannot save course.`;
25 | }
26 |
27 | const course = await AppDataSource.manager.transaction(
28 | "REPEATABLE READ",
29 | async (transactionalEntityManager) => {
30 |
31 | const repository = transactionalEntityManager.getRepository(Course);
32 |
33 | const result = await repository
34 | .createQueryBuilder("courses")
35 | .select("MAX(courses.seqNo)", "max")
36 | .getRawOne();
37 |
38 | const course = repository
39 | .create({
40 | ...data,
41 | seqNo: ( result?.max ?? 0 ) + 1
42 | });
43 |
44 | await repository.save(course);
45 |
46 | return course;
47 | }
48 | );
49 |
50 | response.status(200).json({course});
51 |
52 | }
53 | catch(error) {
54 | logger.error(`Error calling createCourse()`);
55 | return next(error);
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/rest-api/src/routes/create-user.ts:
--------------------------------------------------------------------------------
1 | import {NextFunction, Request, Response} from "express";
2 | import {logger} from "../logger";
3 | import {AppDataSource} from "../data-source";
4 | import {User} from "../models/user";
5 | import {calculatePasswordHash} from "../utils";
6 | const crypto = require("crypto");
7 |
8 | /**
9 | *
10 | * curl -X POST http://localhost:9000/api/users -H "Content-Type:application/json" -d '{"email": "new-user@angular-university.io", "pictureUrl":"https://avatars.githubusercontent.com/u/5454709", "password": "test123", "isAdmin": false}'
11 | *
12 | */
13 | export async function createUser(
14 | request: Request, response: Response, next:NextFunction) {
15 |
16 | try {
17 |
18 | logger.debug(`Called createUser()`);
19 |
20 | const {email, pictureUrl, password, isAdmin} = request.body;
21 |
22 | if (!email) {
23 | throw "Could not extract the email from the request, aborting.";
24 | }
25 |
26 | if (!password) {
27 | throw "Could not extract the plain text password from the request, aborting."
28 | }
29 |
30 | const repository = AppDataSource.getRepository(User);
31 |
32 | const user = await repository.createQueryBuilder("users")
33 | .where("email = :email", {email})
34 | .getOne();
35 |
36 | if (user) {
37 | const message = `User with email ${email} already exists, aborting.`;
38 | logger.error(message);
39 | response.status(500).json({message});
40 | return;
41 | }
42 |
43 | const passwordSalt = crypto.randomBytes(64).toString('hex');
44 |
45 | const passwordHash = await calculatePasswordHash(password, passwordSalt);
46 |
47 | const newUser = repository.create({
48 | email,
49 | pictureUrl,
50 | isAdmin,
51 | passwordHash,
52 | passwordSalt
53 | });
54 |
55 | await AppDataSource.manager.save(newUser);
56 |
57 | logger.info(`User ${email} has been created.`);
58 |
59 | response.status(200).json({
60 | email,
61 | pictureUrl,
62 | isAdmin
63 | });
64 |
65 | }
66 | catch (error) {
67 | logger.error(`Error calling createUser()`);
68 | return next(error);
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/rest-api/src/routes/delete-course.ts:
--------------------------------------------------------------------------------
1 | import {NextFunction, Request, Response} from "express";
2 | import {logger} from "../logger";
3 | import {isInteger} from "../utils";
4 | import {AppDataSource} from "../data-source";
5 | import {Lesson} from "../models/lesson";
6 | import {Course} from "../models/course";
7 |
8 |
9 | export async function deleteCourseAndLessons(
10 | request: Request, response: Response, next:NextFunction) {
11 |
12 | try {
13 |
14 | logger.debug(`Called deleteCourseAndLessons()`);
15 |
16 | const courseId = request.params.courseId;
17 |
18 | if (!isInteger(courseId)) {
19 | throw `Invalid courseId ${courseId}`;
20 | }
21 |
22 | await AppDataSource.manager.transaction(
23 | async (transactionalEntityManager) => {
24 |
25 | await transactionalEntityManager
26 | .createQueryBuilder()
27 | .delete()
28 | .from(Lesson)
29 | .where("courseId = :courseId", {courseId})
30 | .execute();
31 |
32 | await transactionalEntityManager
33 | .createQueryBuilder()
34 | .delete()
35 | .from(Course)
36 | .where("id = :courseId",{courseId})
37 | .execute();
38 | }
39 | );
40 |
41 | response.status(200).json({
42 | message: `Course deleted successfully ${courseId}`
43 | });
44 |
45 | }
46 | catch(error) {
47 | logger.error(`Error calling deleteCourseAndLessons()`);
48 | return next(error);
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/rest-api/src/routes/find-course-by-url.ts:
--------------------------------------------------------------------------------
1 | import {NextFunction, Request, Response} from "express";
2 | import {logger} from "../logger";
3 | import {AppDataSource} from "../data-source";
4 | import {Course} from "../models/course";
5 | import {Lesson} from "../models/lesson";
6 |
7 |
8 | export async function findCourseByUrl(
9 | request: Request, response: Response, next:NextFunction) {
10 |
11 | try {
12 |
13 | logger.debug(`Called findCourseByUrl()`);
14 |
15 | const courseUrl = request.params.courseUrl;
16 |
17 | if (!courseUrl) {
18 | throw `Could not extract the course url from the request.`;
19 | }
20 |
21 | const course = await AppDataSource
22 | .getRepository(Course)
23 | .findOneBy({
24 | url: courseUrl
25 | });
26 |
27 | if (!course) {
28 | const message = `Could not find a course with url ${courseUrl}`;
29 | logger.error(message);
30 | response.status(404).json({message});
31 | return;
32 | }
33 |
34 | const totalLessons = await AppDataSource
35 | .getRepository(Lesson)
36 | .createQueryBuilder("lessons")
37 | .where("lessons.courseId = :courseId", {
38 | courseId: course.id
39 | })
40 | .getCount()
41 |
42 | response.status(200).json({
43 | course,
44 | totalLessons
45 | });
46 |
47 |
48 | }
49 | catch (error) {
50 | logger.error(`Error calling findCourseByUrl()`);
51 | return next(error);
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/rest-api/src/routes/find-lessons-for-course.ts:
--------------------------------------------------------------------------------
1 | import {NextFunction, Request, Response} from "express";
2 | import {logger} from "../logger";
3 | import {isInteger} from "../utils";
4 | import {AppDataSource} from "../data-source";
5 | import {Lesson} from "../models/lesson";
6 |
7 |
8 | export async function findLessonsForCourse(
9 | request: Request, response: Response, next:NextFunction) {
10 |
11 | try {
12 |
13 | logger.debug(`Called findLessonsForCourse()`);
14 |
15 | const courseId = request.params.courseId,
16 | query = request.query as any,
17 | pageNumber = query?.pageNumber ?? "0",
18 | pageSize = query?.pageSize ?? "3";
19 |
20 | if (!isInteger(courseId)) {
21 | throw `Invalid course id ${courseId}`;
22 | }
23 |
24 | if (!isInteger(pageNumber)) {
25 | throw `Invalid pageNumber ${pageNumber}`;
26 | }
27 |
28 | if (!isInteger(pageSize)) {
29 | throw `Invalid pageSize ${pageSize}`;
30 | }
31 |
32 | const lessons = await AppDataSource
33 | .getRepository(Lesson)
34 | .createQueryBuilder("lessons")
35 | .where("lessons.courseId = :courseId", {courseId})
36 | .orderBy("lessons.seqNo")
37 | .skip(pageNumber * pageSize)
38 | .take(pageSize)
39 | .getMany();
40 |
41 | response.status(200).json({lessons});
42 |
43 | }
44 | catch(error) {
45 | logger.error(`Error calling findLessonsForCourse()`);
46 | return next(error);
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/rest-api/src/routes/get-all-courses.ts:
--------------------------------------------------------------------------------
1 | import {Response, Request, NextFunction} from "express";
2 | import {logger} from "../logger";
3 | import {AppDataSource} from "../data-source";
4 | import {Course} from "../models/course";
5 |
6 | export async function getAllCourses(
7 | request: Request, response: Response, next:NextFunction) {
8 |
9 | try {
10 |
11 | logger.debug(`Called getAllCourses()`, request["user"]);
12 |
13 | const courses = await AppDataSource
14 | .getRepository(Course)
15 | .createQueryBuilder("courses")
16 | .orderBy("courses.seqNo")
17 | .getMany();
18 |
19 | response.status(200).json({courses});
20 |
21 | }
22 | catch (error) {
23 | logger.error(`Error calling getAllCourses()`);
24 | return next(error);
25 | }
26 |
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/rest-api/src/routes/login.ts:
--------------------------------------------------------------------------------
1 | import {NextFunction, Request, Response} from "express";
2 | import {logger} from "../logger";
3 | import {AppDataSource} from "../data-source";
4 | import {User} from "../models/user";
5 | import {calculatePasswordHash} from "../utils";
6 | const JWT_SECRET = process.env.JWT_SECRET;
7 | const jwt = require("jsonwebtoken");
8 |
9 | /**
10 | *
11 | * curl -X POST http://localhost:9000/api/login -H "Content-Type:application/json" -d '{"email": "test@angular-university.io", "password":"test"}'
12 | *
13 | */
14 |
15 | export async function login(request: Request, response: Response, next:NextFunction) {
16 |
17 | try {
18 |
19 | logger.debug(`Called login()`);
20 |
21 | const {email, password} = request.body;
22 |
23 | if (!email) {
24 | throw `Could not extract the email from the request, aborting.`;
25 | }
26 |
27 | if (!password) {
28 | throw `Could not extract the plain text password from the request, aborting.`;
29 | }
30 |
31 | const user = await AppDataSource
32 | .getRepository(User)
33 | .createQueryBuilder("users")
34 | .where("email = :email", {email})
35 | .getOne();
36 |
37 | if (!user) {
38 | const message = `Login denied.`;
39 | logger.info(`${message} - ${email}`);
40 | response.status(403).json({message});
41 | return;
42 | }
43 |
44 | const passwordHash = await calculatePasswordHash(password, user.passwordSalt);
45 |
46 | if (passwordHash != user.passwordHash) {
47 | const message = `Login denied.`;
48 | logger.info(`${message} - user with ${email} has entered the wrong password.`);
49 | response.status(403).json({message});
50 | return;
51 | }
52 |
53 | logger.info(`User ${email} has now logged in.`);
54 |
55 | const {pictureUrl, isAdmin} = user;
56 |
57 | const authJwt = {
58 | userId: user.id,
59 | email,
60 | isAdmin
61 | };
62 |
63 | const authJwtToken = await jwt.sign(authJwt, JWT_SECRET);
64 |
65 | response.status(200).json({
66 | user: {
67 | email,
68 | pictureUrl,
69 | isAdmin
70 | },
71 | authJwtToken
72 | });
73 |
74 | }
75 | catch(error) {
76 | logger.error(`Error calling login()`);
77 | return next(error);
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/rest-api/src/routes/root.ts:
--------------------------------------------------------------------------------
1 |
2 | import {Response, Request} from "express";
3 |
4 | export function root(request: Request, response: Response) {
5 |
6 | response.status(200).send("Express server is up and running.
");
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/rest-api/src/routes/update-course.ts:
--------------------------------------------------------------------------------
1 | import {NextFunction, Request, Response} from "express";
2 | import {logger} from "../logger";
3 | import {isInteger} from "../utils";
4 | import {AppDataSource} from "../data-source";
5 | import {Course} from "../models/course";
6 |
7 | /*
8 | *
9 | * curl -X PATCH http://localhost:9000/api/courses/76 -H "Content-Type:application/json" -d '{"title":"Typescript Bootcamp v2"}'
10 | *
11 | **/
12 |
13 | export async function updateCourse(
14 | request: Request, response: Response, next:NextFunction) {
15 |
16 | try {
17 |
18 | logger.debug(`Called updateCourse()`);
19 |
20 | const courseId = request.params.courseId,
21 | changes = request.body;
22 |
23 | if (!isInteger(courseId)) {
24 | throw `Invalid course id ${courseId}`;
25 | }
26 |
27 | await AppDataSource
28 | .createQueryBuilder()
29 | .update(Course)
30 | .set(changes)
31 | .where("id = :courseId", {courseId})
32 | .execute();
33 |
34 | response.status(200).json({
35 | message: `Course ${courseId} was updated successfully.`
36 | });
37 |
38 | }
39 | catch (error) {
40 | logger.error(`Error calling updateCourse()`);
41 | return next(error);
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/rest-api/src/server.ts:
--------------------------------------------------------------------------------
1 | import * as dotenv from "dotenv";
2 |
3 | const result = dotenv.config();
4 |
5 | if (result.error) {
6 | console.log(`Error loading environment variables, aborting.`);
7 | process.exit(1);
8 | }
9 |
10 | import "reflect-metadata";
11 | import * as express from 'express';
12 | import {root} from "./routes/root";
13 | import {isInteger} from "./utils";
14 | import {logger} from "./logger";
15 | import {AppDataSource} from "./data-source";
16 | import {getAllCourses} from "./routes/get-all-courses";
17 | import {defaultErrorHandler} from "./middlewares/default-error-handler";
18 | import {findCourseByUrl} from "./routes/find-course-by-url";
19 | import {findLessonsForCourse} from "./routes/find-lessons-for-course";
20 | import {updateCourse} from "./routes/update-course";
21 | import {createCourse} from "./routes/create-course";
22 | import {deleteCourseAndLessons} from "./routes/delete-course";
23 | import {createUser} from "./routes/create-user";
24 | import {login} from "./routes/login";
25 | import {checkIfAuthenticated} from "./middlewares/authentication-middleware";
26 | import {checkIfAdmin} from "./middlewares/admin-only.middleware";
27 |
28 | const cors = require("cors");
29 |
30 | const bodyParser = require("body-parser");
31 |
32 | const app = express();
33 |
34 |
35 | function setupExpress() {
36 |
37 | app.use(cors({origin:true}));
38 |
39 | app.use(bodyParser.json());
40 |
41 | app.route("/").get(root);
42 |
43 | app.route("/api/courses").get(checkIfAuthenticated, getAllCourses);
44 |
45 | app.route("/api/courses/:courseUrl").get(checkIfAuthenticated, findCourseByUrl);
46 |
47 | app.route("/api/courses/:courseId/lessons").get(checkIfAuthenticated, findLessonsForCourse);
48 |
49 | app.route("/api/courses/:courseId").patch(checkIfAuthenticated, updateCourse);
50 |
51 | app.route("/api/courses").post(checkIfAuthenticated, createCourse);
52 |
53 | app.route("/api/courses/:courseId").delete(checkIfAuthenticated, deleteCourseAndLessons);
54 |
55 | app.route("/api/users").post(checkIfAuthenticated, checkIfAdmin, createUser);
56 |
57 | app.route("/api/login").post(login);
58 |
59 | app.use(defaultErrorHandler);
60 |
61 | }
62 |
63 | function startServer() {
64 |
65 | let port: number;
66 |
67 | const portEnv = process.env.PORT,
68 | portArg = process.argv[2];
69 |
70 | if (isInteger(portEnv)) {
71 | port = parseInt(portEnv);
72 | }
73 |
74 | if (!port && isInteger(portArg)) {
75 | port = parseInt(portArg);
76 | }
77 |
78 | if (!port) {
79 | port = 9000;
80 | }
81 |
82 | app.listen(port, () => {
83 |
84 | logger.info(`HTTP REST API Server is now running at http://localhost:${port}`);
85 |
86 | });
87 |
88 | }
89 |
90 |
91 |
92 | AppDataSource.initialize()
93 | .then(() => {
94 | logger.info(`The datasource has been initialized successfully.`);
95 | setupExpress();
96 | startServer();
97 | })
98 | .catch(err => {
99 | logger.error(`Error during datasource initialization.`, err);
100 | process.exit(1);
101 | })
102 |
--------------------------------------------------------------------------------
/rest-api/src/utils.ts:
--------------------------------------------------------------------------------
1 |
2 | const crypto = require("crypto");
3 | const util = require("util");
4 |
5 | const hashPassword = util.promisify(crypto.pbkdf2);
6 |
7 | export function isInteger(input:string) {
8 | return input?.match(/^\d+$/) ?? false;
9 | }
10 |
11 |
12 | export async function calculatePasswordHash(
13 | plainTextPassword:string,
14 | passwordSalt:string) {
15 |
16 | const passwordHash = await hashPassword(
17 | plainTextPassword,
18 | passwordSalt,
19 | 1000,
20 | 64,
21 | "sha512");
22 |
23 | return passwordHash.toString("hex");
24 | }
25 |
--------------------------------------------------------------------------------
/rest-api/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES5",
4 | "outDir": "dist",
5 | "experimentalDecorators": true,
6 | "emitDecoratorMetadata": true
7 | }
8 | }
9 |
--------------------------------------------------------------------------------