├── .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 | ![Typescript: The Ultimate Bootcamp](https://angular-university.s3-us-west-1.amazonaws.com/course-images/typescript-bootcamp-2.jpg) 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 | ![Modern Angular With Signals Course](https://d3vigmphadbn9b.cloudfront.net/course-images/large-images/angular-signals-course.jpg) 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 | ![Angular Forms In Depth](https://angular-university.s3-us-west-1.amazonaws.com/course-images/angular-forms-course-small.jpg) 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 | ![Angular Router In Depth](https://angular-university.s3-us-west-1.amazonaws.com/course-images/angular-router-course.jpg) 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 | ![Ngrx (with NgRx Data) - The Complete Guide](https://angular-university.s3-us-west-1.amazonaws.com/course-images/ngrx-v2.png) 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 | ![Angular Core Deep Dive](https://s3-us-west-1.amazonaws.com/angular-university/course-images/angular-core-in-depth-small.png) 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 | ![RxJs In Practice Course](https://s3-us-west-1.amazonaws.com/angular-university/course-images/rxjs-in-practice-course.png) 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 | ![NestJs In Practice Course](https://angular-university.s3-us-west-1.amazonaws.com/course-images/nestjs-v2.png) 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 | ![Angular Testing Course](https://s3-us-west-1.amazonaws.com/angular-university/course-images/angular-testing-small.png) 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 | ![Serverless Angular with Firebase Course](https://s3-us-west-1.amazonaws.com/angular-university/course-images/serverless-angular-small.png) 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 | ![Angular Universal Course](https://s3-us-west-1.amazonaws.com/angular-university/course-images/angular-universal-small.png) 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 | ![Angular PWA Course - Build the future of the Web Today](https://s3-us-west-1.amazonaws.com/angular-university/course-images/angular-pwa-course.png) 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 | ![Angular Security Masterclass](https://s3-us-west-1.amazonaws.com/angular-university/course-images/security-cover-small-v2.png) 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 | ![Angular Advanced Library Laboratory Course: Build Your Own Library](https://angular-academy.s3.amazonaws.com/thumbnails/advanced_angular-small-v3.png) 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 | ![RxJs and Reactive Patterns Angular Architecture Course](https://s3-us-west-1.amazonaws.com/angular-academy/blog/images/rxjs-reactive-patterns-small.png) 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 | --------------------------------------------------------------------------------