├── .editorconfig
├── .gitignore
├── .vscode
├── extensions.json
├── launch.json
└── tasks.json
├── README.md
├── angular.json
├── example.env
├── netlify.toml
├── netlify
└── functions
│ ├── bookmarks
│ ├── delete.ts
│ ├── get.ts
│ ├── index.ts
│ ├── post.ts
│ └── put.ts
│ ├── posts
│ ├── helper.ts
│ └── index.ts
│ ├── projects
│ ├── delete.ts
│ ├── get.ts
│ ├── index.ts
│ ├── post.ts
│ └── put.ts
│ └── tsconfig.json
├── package-lock.json
├── package.json
├── src
├── _redirects
├── app
│ ├── app-routing.module.ts
│ ├── app.component.html
│ ├── app.component.spec.ts
│ ├── app.component.ts
│ ├── app.module.spec.ts
│ ├── app.module.ts
│ ├── core
│ │ ├── contracts
│ │ │ ├── mapper.contract.ts
│ │ │ └── usecase.contract.ts
│ │ ├── params
│ │ │ ├── no-param.paylod.ts
│ │ │ └── param.payload.ts
│ │ └── types
│ │ │ └── types.ts
│ ├── data
│ │ ├── data.ioc.ts
│ │ ├── data.module.ts
│ │ ├── datasources
│ │ │ ├── local
│ │ │ │ └── .gitKeep
│ │ │ └── remote
│ │ │ │ ├── .gitKeep
│ │ │ │ └── repo-implementations
│ │ │ │ ├── bookmark
│ │ │ │ ├── bookmark.repository.spec.ts
│ │ │ │ └── bookmark.repository.ts
│ │ │ │ ├── post
│ │ │ │ ├── post.repository.spec.ts
│ │ │ │ └── post.repository.ts
│ │ │ │ └── project
│ │ │ │ ├── project.repository.spec.ts
│ │ │ │ └── project.repository.ts
│ │ ├── interactors
│ │ │ ├── contracts
│ │ │ │ ├── ibookmark.interactor.ts
│ │ │ │ ├── ipost.interactor.ts
│ │ │ │ └── iproject.interactor.ts
│ │ │ └── implementations
│ │ │ │ ├── bookmark
│ │ │ │ ├── bookmark.interactor.spec.ts
│ │ │ │ └── bookmark.interactor.ts
│ │ │ │ ├── post
│ │ │ │ ├── post.interactor.spec.ts
│ │ │ │ └── post.interactor.ts
│ │ │ │ └── project
│ │ │ │ ├── project.interactor.spec.ts
│ │ │ │ └── project.interactor.ts
│ │ └── requests
│ │ │ ├── bookmark.request.ts
│ │ │ ├── posts.request.ts
│ │ │ └── project.request.ts
│ ├── domain
│ │ ├── domain.module.ts
│ │ ├── entities
│ │ │ ├── bookmark.entity.ts
│ │ │ ├── index.ts
│ │ │ ├── post.entity.ts
│ │ │ └── project.entity.ts
│ │ ├── repositories
│ │ │ ├── ibookmark.repository.ts
│ │ │ ├── ipost.repository.ts
│ │ │ └── iproject.repository.ts
│ │ └── usecases
│ │ │ ├── bookmark-usecases
│ │ │ ├── create-usecase
│ │ │ │ ├── create-bookmark.usecase.spec.ts
│ │ │ │ └── create-bookmark.usecase.ts
│ │ │ ├── get-many-usecase
│ │ │ │ ├── get-many-bookmarks.uscecase.spec.ts
│ │ │ │ └── get-many-bookmarks.usecase.ts
│ │ │ ├── get-one-usecase
│ │ │ │ ├── get-one-bookmark.usecase.spec.ts
│ │ │ │ └── get-one-bookmark.usecase.ts
│ │ │ ├── remove-usecase
│ │ │ │ ├── remove-bookmark.usecase.spec.ts
│ │ │ │ └── remove-bookmark.usecase.ts
│ │ │ └── update-usecase
│ │ │ │ ├── update-bookmark.usecase.spec.ts
│ │ │ │ └── update-bookmark.usecase.ts
│ │ │ ├── posts-usecases
│ │ │ ├── create-usecase
│ │ │ │ ├── create-post.usecase.spec.ts
│ │ │ │ └── create-post.usecase.ts
│ │ │ ├── get-many-usecase
│ │ │ │ ├── get-many-posts.usecase.spec.ts
│ │ │ │ └── get-many-posts.usecase.ts
│ │ │ ├── get-one-usecase
│ │ │ │ ├── get-one-post.usecase.spec.ts
│ │ │ │ └── get-one-post.usecase.ts
│ │ │ ├── remove-usecase
│ │ │ │ ├── remove-post.usecase.spec.ts
│ │ │ │ └── remove-post.usecase.ts
│ │ │ └── update-usecase
│ │ │ │ ├── update-post.usecase.spec.ts
│ │ │ │ └── update-post.usecase.ts
│ │ │ └── projects-usecases
│ │ │ ├── create-usecase
│ │ │ ├── create-project.usecase.spec.ts
│ │ │ └── create-project.usecase.ts
│ │ │ ├── get-many-usecase
│ │ │ ├── get-many-project.usecase.spec.ts
│ │ │ └── get-many-project.usecase.ts
│ │ │ ├── get-one-usecase
│ │ │ ├── get-one-project.usecase.spec.ts
│ │ │ └── get-one-project.usecase.ts
│ │ │ ├── remove-usecase
│ │ │ ├── remove-project.usecase.spec.ts
│ │ │ └── remove-project.usecase.ts
│ │ │ └── update-usecase
│ │ │ ├── update-project.usecase.spec.ts
│ │ │ └── update-project.usecase.ts
│ └── presenter
│ │ ├── components
│ │ ├── components.module.spec.ts
│ │ ├── components.module.ts
│ │ ├── layouts
│ │ │ ├── editor-header
│ │ │ │ ├── editor-header.component.html
│ │ │ │ ├── editor-header.component.spec.ts
│ │ │ │ └── editor-header.component.ts
│ │ │ ├── editor-layout
│ │ │ │ ├── editor-layout.component.html
│ │ │ │ ├── editor-layout.component.spec.ts
│ │ │ │ └── editor-layout.component.ts
│ │ │ ├── footer
│ │ │ │ ├── footer.component.html
│ │ │ │ ├── footer.component.spec.ts
│ │ │ │ └── footer.component.ts
│ │ │ └── header
│ │ │ │ ├── header.component.html
│ │ │ │ ├── header.component.spec.ts
│ │ │ │ └── header.component.ts
│ │ └── shared
│ │ │ ├── button
│ │ │ ├── button.component.html
│ │ │ ├── button.component.spec.ts
│ │ │ └── button.component.ts
│ │ │ ├── form-field
│ │ │ ├── form-field.component.html
│ │ │ ├── form-field.component.spec.ts
│ │ │ └── form-field.component.ts
│ │ │ ├── link
│ │ │ ├── link.component.html
│ │ │ ├── link.component.spec.ts
│ │ │ └── link.component.ts
│ │ │ ├── modal
│ │ │ ├── modal.component.html
│ │ │ ├── modal.component.spec.ts
│ │ │ ├── modal.component.ts
│ │ │ └── modal.service.ts
│ │ │ ├── not-found
│ │ │ ├── not-found.component.html
│ │ │ ├── not-found.component.spec.ts
│ │ │ └── not-found.component.ts
│ │ │ ├── skeleton-loader
│ │ │ ├── blog-skeleton
│ │ │ │ ├── blog-skeleton.component.html
│ │ │ │ ├── blog-skeleton.component.spec.ts
│ │ │ │ └── blog-skeleton.component.ts
│ │ │ ├── bookmarks-skeleton
│ │ │ │ ├── bookmarks-skeleton.component.html
│ │ │ │ ├── bookmarks-skeleton.component.spec.ts
│ │ │ │ └── bookmarks-skeleton.component.ts
│ │ │ ├── projects-skeleton
│ │ │ │ ├── projects-skeleton.component.html
│ │ │ │ ├── projects-skeleton.component.spec.ts
│ │ │ │ └── projects-skeleton.component.ts
│ │ │ ├── skeleton-loader.component.html
│ │ │ ├── skeleton-loader.component.spec.ts
│ │ │ └── skeleton-loader.component.ts
│ │ │ ├── table
│ │ │ ├── table.component.html
│ │ │ ├── table.component.spec.ts
│ │ │ └── table.component.ts
│ │ │ ├── tag
│ │ │ ├── tag.component.spec.ts
│ │ │ └── tag.component.ts
│ │ │ ├── theme-switcher
│ │ │ ├── dark-theme-switch
│ │ │ │ └── dark-theme-switch.component.ts
│ │ │ ├── light-theme-switch
│ │ │ │ └── light-theme-switch.component.ts
│ │ │ ├── theme-switcher.component.html
│ │ │ ├── theme-switcher.component.spec.ts
│ │ │ ├── theme-switcher.component.ts
│ │ │ └── theme-switcher.service.ts
│ │ │ └── toast
│ │ │ ├── toast.component.html
│ │ │ ├── toast.component.spec.ts
│ │ │ ├── toast.component.ts
│ │ │ └── toast.service.ts
│ │ ├── pages
│ │ ├── blog
│ │ │ ├── blog.component.html
│ │ │ ├── blog.component.spec.ts
│ │ │ ├── blog.component.ts
│ │ │ └── post
│ │ │ │ ├── post.component.html
│ │ │ │ ├── post.component.spec.ts
│ │ │ │ └── post.component.ts
│ │ ├── bookmarks
│ │ │ ├── bookmark
│ │ │ │ ├── bookmark.component.html
│ │ │ │ ├── bookmark.component.spec.ts
│ │ │ │ └── bookmark.component.ts
│ │ │ ├── bookmarks.component.html
│ │ │ ├── bookmarks.component.spec.ts
│ │ │ └── bookmarks.component.ts
│ │ ├── editor
│ │ │ ├── components
│ │ │ │ ├── bookmark-form
│ │ │ │ │ ├── bookmark-form.component.html
│ │ │ │ │ ├── bookmark-form.component.spec.ts
│ │ │ │ │ └── bookmark-form.component.ts
│ │ │ │ ├── post-form
│ │ │ │ │ ├── post-form.component.html
│ │ │ │ │ ├── post-form.component.spec.ts
│ │ │ │ │ └── post-form.component.ts
│ │ │ │ └── project-form
│ │ │ │ │ ├── project-form.component.html
│ │ │ │ │ ├── project-form.component.spec.ts
│ │ │ │ │ └── project-form.component.ts
│ │ │ ├── editor-routing.module.ts
│ │ │ ├── editor.component.html
│ │ │ ├── editor.component.spec.ts
│ │ │ ├── editor.component.ts
│ │ │ ├── editor.functions.ts
│ │ │ ├── editor.module.ts
│ │ │ └── pages
│ │ │ │ ├── editor-bookmarks
│ │ │ │ ├── editor-bookmarks.component.html
│ │ │ │ ├── editor-bookmarks.component.spec.ts
│ │ │ │ └── editor-bookmarks.component.ts
│ │ │ │ ├── editor-home
│ │ │ │ ├── editor-home.component.html
│ │ │ │ ├── editor-home.component.spec.ts
│ │ │ │ └── editor-home.component.ts
│ │ │ │ ├── editor-posts
│ │ │ │ ├── editor-posts.component.html
│ │ │ │ ├── editor-posts.component.spec.ts
│ │ │ │ └── editor-posts.component.ts
│ │ │ │ └── editor-projects
│ │ │ │ ├── editor-projects.component.html
│ │ │ │ ├── editor-projects.component.spec.ts
│ │ │ │ └── editor-projects.component.ts
│ │ ├── home
│ │ │ ├── home.component.html
│ │ │ ├── home.component.spec.ts
│ │ │ └── home.component.ts
│ │ ├── page-seo.service.ts
│ │ ├── pages.module.ts
│ │ ├── post-detail
│ │ │ ├── post-detail.component.html
│ │ │ ├── post-detail.component.spec.ts
│ │ │ └── post-detail.component.ts
│ │ └── projects
│ │ │ ├── project
│ │ │ ├── project.component.html
│ │ │ ├── project.component.spec.ts
│ │ │ └── project.component.ts
│ │ │ ├── projects.component.html
│ │ │ ├── projects.component.spec.ts
│ │ │ └── projects.component.ts
│ │ ├── presenter.module.spec.ts
│ │ └── presenter.module.ts
├── assets
│ ├── .gitkeep
│ └── img
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-512x512.png
│ │ ├── apple-touch-icon.png
│ │ ├── blog-thumbnail.jpg
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── jamie.png
│ │ ├── projects
│ │ └── paymed-login.svg
│ │ └── site.webmanifest
├── favicon.ico
├── index.html
├── main.ts
└── styles.css
├── tailwind.config.js
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 4
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | .history/*
30 |
31 | # Miscellaneous
32 | /.angular/cache
33 | .sass-cache/
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | testem.log
38 | /typings
39 |
40 | # System files
41 | .DS_Store
42 | Thumbs.db
43 |
44 | # Netlify
45 | .netlify
46 |
47 | # Env
48 | .env
49 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | },
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
3 | "version": "2.0.0",
4 | "tasks": [
5 | {
6 | "type": "npm",
7 | "script": "start",
8 | "isBackground": true,
9 | "problemMatcher": {
10 | "owner": "typescript",
11 | "pattern": "$tsc",
12 | "background": {
13 | "activeOnStart": true,
14 | "beginsPattern": {
15 | "regexp": "(.*?)"
16 | },
17 | "endsPattern": {
18 | "regexp": "bundle generation complete"
19 | }
20 | }
21 | }
22 | },
23 | {
24 | "type": "npm",
25 | "script": "test",
26 | "isBackground": true,
27 | "problemMatcher": {
28 | "owner": "typescript",
29 | "pattern": "$tsc",
30 | "background": {
31 | "activeOnStart": true,
32 | "beginsPattern": {
33 | "regexp": "(.*?)"
34 | },
35 | "endsPattern": {
36 | "regexp": "bundle generation complete"
37 | }
38 | }
39 | }
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/example.env:
--------------------------------------------------------------------------------
1 | MONGODB_URL=
2 | MONGODB_NAME=
3 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | # netlify toml configuration
2 | [build]
3 | base = "./"
4 | publish = "./dist/angular-clean-architecture-serverless/"
5 | command = "ng build"
6 |
7 | [functions]
8 | directory = "/netlify/functions/"
9 | external_node_modules = ["mongodb", "netlify"]
10 |
--------------------------------------------------------------------------------
/netlify/functions/bookmarks/delete.ts:
--------------------------------------------------------------------------------
1 | import { MongoClient, ObjectId } from 'mongodb';
2 |
3 | const remove = async (bookmarkId: string) => {
4 | if (!bookmarkId) throw new Error('Please provide a book to remove');
5 | const client = new MongoClient(process.env['MONGODB_URL']);
6 |
7 | try {
8 | const database = client.db(process.env['MONGODB_NAME']);
9 | const result = await database.collection("bookmarks")
10 | .deleteOne({ "_id": new ObjectId(bookmarkId) });
11 | return result;
12 |
13 | } catch (error) {
14 |
15 | console.log(error);
16 | throw new Error(error.message);
17 | }
18 | finally {
19 | await client.close();
20 | }
21 | }
22 |
23 |
24 | export { remove };
25 |
--------------------------------------------------------------------------------
/netlify/functions/bookmarks/get.ts:
--------------------------------------------------------------------------------
1 | import { HandlerEvent } from "@netlify/functions";
2 | import { MongoClient, ObjectId } from 'mongodb'
3 |
4 | const get = async (event: HandlerEvent) => {
5 | if (Object.keys(event.queryStringParameters).length > 0) {
6 | return await getOne(event);
7 | }
8 | return await getAll(event);
9 | }
10 |
11 | const getAll = async (event: HandlerEvent) => {
12 | const client = new MongoClient(process.env['MONGODB_URL']);
13 | try {
14 | await client.connect();
15 | const db = client.db(process.env['MONGODB_NAME']);
16 | return await db.collection('bookmarks').find({}).sort({ created_at: -1 }).toArray();
17 | } catch (err) {
18 | throw new Error(`${err.message}`);
19 | } finally {
20 | await client.close();
21 | }
22 | }
23 |
24 | const getOne = async (event: HandlerEvent) => {
25 | const id = event.queryStringParameters.id;
26 |
27 | const client = new MongoClient(process.env['MONGODB_URL']);
28 | try {
29 | await client.connect();
30 | const db = client.db(process.env['MONGODB_NAME']);
31 | return await db.collection('bookmarks').findOne({ "_id": new ObjectId(id) });
32 | } catch (err) {
33 | console.log(err);
34 | throw new Error(`${err.message}`);
35 | } finally {
36 | await client.close();
37 | }
38 | }
39 |
40 | export { get };
41 |
--------------------------------------------------------------------------------
/netlify/functions/bookmarks/index.ts:
--------------------------------------------------------------------------------
1 | import { Handler } from '@netlify/functions';
2 |
3 | import { remove } from './delete';
4 | import { get } from './get';
5 | import { post } from './post';
6 | import { put } from './put';
7 |
8 | const handler: Handler = async (event, context) => {
9 | let body;
10 | try {
11 | switch (event.httpMethod) {
12 | case 'GET':
13 | body = await get(event);
14 | break;
15 |
16 | case 'POST':
17 | body = await post(event);
18 | break;
19 |
20 | case 'PUT':
21 | body = await put(event);
22 | break;
23 |
24 | case 'DELETE':
25 | body = await remove(event.queryStringParameters.bookmarkId);
26 | break;
27 |
28 | default:
29 | return {
30 | statusCode: 405,
31 | body: JSON.stringify({ message: 'Method not supported' }),
32 | };
33 | }
34 |
35 | return {
36 | statusCode: 200,
37 | body: JSON.stringify(body),
38 | };
39 | } catch (err: any) {
40 | return {
41 | statusCode: 500,
42 | body: JSON.stringify({ message: err.toString() }),
43 | };
44 | }
45 | };
46 |
47 | export { handler };
48 |
--------------------------------------------------------------------------------
/netlify/functions/bookmarks/post.ts:
--------------------------------------------------------------------------------
1 | import { HandlerEvent } from "@netlify/functions";
2 | import { MongoClient, ObjectId } from 'mongodb'
3 |
4 | const post = async (event: HandlerEvent) => {
5 | if (!event.body) throw new Error('Please provide a book to save');
6 |
7 | const client = new MongoClient(process.env['MONGODB_URL']);
8 |
9 | try {
10 | const database = client.db(process.env['MONGODB_NAME']);
11 | const body = JSON.parse(event.body);
12 | return await database.collection("bookmarks").insertOne(
13 | {
14 | ...body,
15 | created_at: new Date()
16 | }
17 | );
18 |
19 | } catch (error) {
20 | console.log(error);
21 | throw new Error(error.message);
22 | }
23 | finally {
24 | await client.close();
25 | }
26 | }
27 |
28 | export { post };
29 |
--------------------------------------------------------------------------------
/netlify/functions/bookmarks/put.ts:
--------------------------------------------------------------------------------
1 | import { HandlerEvent } from "@netlify/functions";
2 | import { MongoClient, ObjectId } from 'mongodb'
3 |
4 |
5 | const put = async (event: HandlerEvent) => {
6 | if (!event.body) throw new Error('Please provide a bookmark to save');
7 |
8 | const client = new MongoClient(process.env['MONGODB_URL']);
9 | const bookmark: any = JSON.parse(event.body);
10 |
11 | try {
12 | const { _id, ...restBookmark } = bookmark;
13 | const database = client.db(process.env['MONGODB_NAME']);
14 | const result = await database.collection("bookmarks").updateOne(
15 | { _id: new ObjectId(_id) },
16 | { $set: restBookmark, },
17 | { upsert: false }
18 | );
19 |
20 | return result;
21 |
22 | } catch (error) {
23 | console.log(error);
24 | throw new Error(error.message);
25 | }
26 | finally {
27 | await client.close();
28 | }
29 | }
30 |
31 | export { put };
32 |
--------------------------------------------------------------------------------
/netlify/functions/posts/index.ts:
--------------------------------------------------------------------------------
1 | import { Handler } from '@netlify/functions';
2 |
3 | import { remove, post, put, get } from '../posts/helper';
4 |
5 | const handler: Handler = async (event, context) => {
6 |
7 | let body;
8 | try {
9 | switch (event.httpMethod) {
10 | case 'GET':
11 | body = await get(event);
12 | break;
13 |
14 | case 'POST':
15 | body = await post(event);
16 | break;
17 |
18 | case 'PUT':
19 | body = await put(event);
20 | break;
21 |
22 | case 'DELETE':
23 | body = await remove(event.queryStringParameters.postId);
24 | break;
25 |
26 | default:
27 | return {
28 | statusCode: 405,
29 | body: JSON.stringify({ message: 'Method not supported' })
30 | }
31 | }
32 |
33 | return {
34 | statusCode: 200,
35 | body: JSON.stringify(body)
36 | };
37 |
38 | } catch (err: any) {
39 |
40 | return {
41 | statusCode: 500,
42 | body: JSON.stringify({ message: err.toString() })
43 | }
44 | }
45 |
46 | }
47 |
48 | export { handler };
49 |
--------------------------------------------------------------------------------
/netlify/functions/projects/delete.ts:
--------------------------------------------------------------------------------
1 | import { MongoClient, ObjectId } from 'mongodb'
2 |
3 | const remove = async (projectId: string) => {
4 | if (!projectId) throw new Error('Please provide a project to remove');
5 | const client = new MongoClient(process.env['MONGODB_URL']);
6 |
7 | try {
8 | const database = client.db(process.env['MONGODB_NAME']);
9 | const result = await database.collection("projects")
10 | .deleteOne({ "_id": new ObjectId(projectId) });
11 | return result;
12 |
13 | } catch (error) {
14 |
15 | console.log(error);
16 | throw new Error(error.message);
17 | }
18 | finally {
19 | await client.close();
20 | }
21 | }
22 |
23 |
24 | export { remove };
25 |
--------------------------------------------------------------------------------
/netlify/functions/projects/get.ts:
--------------------------------------------------------------------------------
1 | import { HandlerEvent } from "@netlify/functions";
2 | import { MongoClient, ObjectId } from 'mongodb'
3 |
4 | const get = async (event: HandlerEvent) => {
5 | if (Object.keys(event.queryStringParameters).length > 0) {
6 | return await getOne(event);
7 | }
8 | return await getAll(event);
9 | }
10 |
11 | const getAll = async (event: HandlerEvent) => {
12 | const client = new MongoClient(process.env['MONGODB_URL']);
13 | try {
14 | await client.connect();
15 | const db = client.db(process.env['MONGODB_NAME']);
16 | return await db.collection('projects').find({}).sort({ created_at: -1 }).toArray();
17 |
18 | } catch (err) {
19 | throw new Error(`${err.message}`);
20 | } finally {
21 | await client.close();
22 | }
23 | }
24 |
25 | const getOne = async (event: HandlerEvent) => {
26 | const id = event.queryStringParameters.id;
27 |
28 | const client = new MongoClient(process.env['MONGODB_URL']);
29 | try {
30 | await client.connect();
31 | const db = client.db(process.env['MONGODB_NAME']);
32 | return await db.collection('projects').findOne({ "_id": new ObjectId(id) });
33 | } catch (err) {
34 | console.log(err);
35 | throw new Error(`${err.message}`);
36 | } finally {
37 | await client.close();
38 | }
39 | }
40 |
41 | export { get };
42 |
--------------------------------------------------------------------------------
/netlify/functions/projects/index.ts:
--------------------------------------------------------------------------------
1 | import { Handler } from '@netlify/functions';
2 | import { get } from '../../functions/projects/get';
3 | import { post } from '../../functions/projects/post';
4 | import { put } from '../../functions/projects/put';
5 | import { remove } from '../../functions/projects/delete';
6 |
7 |
8 | const handler: Handler = async (event, context) => {
9 | let body;
10 | try {
11 | switch (event.httpMethod) {
12 | case 'GET':
13 | body = await get(event);
14 | break;
15 |
16 | case 'POST':
17 | body = await post(event);
18 | break;
19 |
20 | case 'PUT':
21 | body = await put(event);
22 | break;
23 |
24 | case 'DELETE':
25 | body = await remove(event.queryStringParameters.projectId);
26 | break;
27 |
28 | default:
29 | return {
30 | statusCode: 405,
31 | body: JSON.stringify({ message: 'Method not supported' })
32 | }
33 | }
34 |
35 | return {
36 | statusCode: 200,
37 | body: JSON.stringify(body)
38 | };
39 |
40 | } catch (err: any) {
41 |
42 | return {
43 | statusCode: 500,
44 | body: JSON.stringify({ message: err.toString() })
45 | }
46 | }
47 | }
48 |
49 | export { handler };
50 |
--------------------------------------------------------------------------------
/netlify/functions/projects/post.ts:
--------------------------------------------------------------------------------
1 | import { HandlerEvent } from "@netlify/functions";
2 | import { MongoClient, ObjectId } from 'mongodb'
3 |
4 | const post = async (event: HandlerEvent) => {
5 | if (!event.body) throw new Error('Please provide a project to save');
6 |
7 | const client = new MongoClient(process.env['MONGODB_URL']);
8 |
9 | try {
10 | const database = client.db(process.env['MONGODB_NAME']);
11 | const body = JSON.parse(event.body);
12 | return await database.collection("projects").insertOne(
13 | {
14 | ...body,
15 | created_at: new Date()
16 | }
17 | );
18 |
19 | } catch (error) {
20 | console.log(error);
21 | throw new Error(error.message);
22 | }
23 | finally {
24 | await client.close();
25 | }
26 | }
27 |
28 | export { post };
29 |
--------------------------------------------------------------------------------
/netlify/functions/projects/put.ts:
--------------------------------------------------------------------------------
1 | import { HandlerEvent } from "@netlify/functions";
2 | import { MongoClient, ObjectId } from 'mongodb'
3 |
4 |
5 | const put = async (event: HandlerEvent) => {
6 | if (!event.body) throw new Error('Please provide a project to update');
7 |
8 | const client = new MongoClient(process.env['MONGODB_URL']);
9 | const project: any = JSON.parse(event.body);
10 |
11 | try {
12 | const { _id, ...restProject } = project;
13 | const database = client.db(process.env['MONGODB_NAME']);
14 | const result = await database.collection("projects").updateOne(
15 | { _id: new ObjectId(_id) },
16 | { $set: restProject },
17 | );
18 |
19 | return result;
20 |
21 | } catch (error) {
22 | console.log(error);
23 | throw new Error(error.message);
24 | }
25 | finally {
26 | await client.close();
27 | }
28 | }
29 |
30 | export { put };
31 |
--------------------------------------------------------------------------------
/netlify/functions/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "esModuleInterop": true,
4 | "isolatedModules": true
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-clean-architecture-serverless",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build",
8 | "watch": "ng build --watch --configuration development",
9 | "test": "ng test"
10 | },
11 | "private": true,
12 | "dependencies": {
13 | "@angular/animations": "^15.1.0",
14 | "@angular/common": "^15.1.0",
15 | "@angular/compiler": "^15.1.0",
16 | "@angular/core": "^15.1.0",
17 | "@angular/forms": "^15.1.0",
18 | "@angular/platform-browser": "^15.1.0",
19 | "@angular/platform-browser-dynamic": "^15.1.0",
20 | "@angular/router": "^15.1.0",
21 | "@netlify/functions": "^1.4.0",
22 | "emoji-toolkit": "^6.6.0",
23 | "marked": "^4.2.12",
24 | "mongodb": "^4.12.1",
25 | "ngx-markdown": "^15.0.0-next.0",
26 | "prismjs": "^1.29.0",
27 | "rxjs": "~7.8.0",
28 | "tslib": "^2.3.0",
29 | "zone.js": "~0.12.0"
30 | },
31 | "devDependencies": {
32 | "@angular-devkit/build-angular": "^15.1.1",
33 | "@angular/cli": "~15.1.1",
34 | "@angular/compiler-cli": "^15.1.0",
35 | "@tailwindcss/typography": "^0.5.9",
36 | "@types/jasmine": "~4.3.0",
37 | "@types/marked": "^4.0.8",
38 | "autoprefixer": "^10.4.13",
39 | "jasmine-core": "~4.5.0",
40 | "karma": "~6.4.0",
41 | "karma-chrome-launcher": "~3.1.0",
42 | "karma-coverage": "~2.2.0",
43 | "karma-jasmine": "~5.1.0",
44 | "karma-jasmine-html-reporter": "~2.0.0",
45 | "postcss": "^8.4.21",
46 | "tailwindcss": "^3.2.4",
47 | "typescript": "~4.9.4"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
2 |
--------------------------------------------------------------------------------
/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { EditorLayoutComponent } from './presenter/components/layouts/editor-layout/editor-layout.component';
2 | import { PostDetailComponent } from './presenter/pages/post-detail/post-detail.component';
3 | import { NgModule } from '@angular/core';
4 | import { RouterModule, Routes } from '@angular/router';
5 |
6 | import { BlogComponent } from './presenter/pages/blog/blog.component';
7 | import { BookmarksComponent } from './presenter/pages/bookmarks/bookmarks.component';
8 | import { HomeComponent } from './presenter/pages/home/home.component';
9 | import { ProjectsComponent } from './presenter/pages/projects/projects.component';
10 | import { NotFoundComponent } from './presenter/components/shared/not-found/not-found.component';
11 |
12 |
13 | const routes: Routes = [
14 | { path: '', component: HomeComponent },
15 | { path: 'blog', component: BlogComponent },
16 | { path: 'blog/:postId', component: PostDetailComponent },
17 |
18 | { path: 'projects', component: ProjectsComponent },
19 | { path: 'bookmarks', component: BookmarksComponent },
20 | {
21 | path: 'editor',
22 | loadChildren: () => import('./presenter/pages/editor/editor.module').then((c) => c.EditorModule),
23 | component: EditorLayoutComponent
24 | },
25 | { path: '**', component: NotFoundComponent },
26 | ];
27 |
28 | @NgModule({
29 | imports: [RouterModule.forRoot(routes, { scrollPositionRestoration: 'enabled' })],
30 | exports: [RouterModule]
31 | })
32 | export class AppRoutingModule { }
33 |
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | import { PageSeoService } from './presenter/pages/page-seo.service';
4 | import { ThemeSwitcherService } from './presenter/components/shared/theme-switcher/theme-switcher.service';
5 |
6 | @Component({
7 | selector: 'app-root',
8 | templateUrl: './app.component.html',
9 | })
10 | export class AppComponent implements OnInit {
11 |
12 |
13 | public themePref$ = this.themeService.pref$;
14 |
15 | public constructor(
16 | private themeService: ThemeSwitcherService,
17 | private seoService: PageSeoService
18 | ) { }
19 |
20 |
21 | ngOnInit(): void {
22 | this.seoService.setSEO();
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/app/app.module.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { AppModule } from './app.module';
4 |
5 | describe('AppModule', () => {
6 | beforeEach(() => {
7 | TestBed.configureTestingModule({
8 | imports: [AppModule],
9 | });
10 | });
11 |
12 | it('should initialize', () => {
13 | const module = TestBed.inject(AppModule);
14 |
15 | expect(module).toBeTruthy();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { RouterModule } from '@angular/router';
2 | import { NgModule } from '@angular/core';
3 | import { BrowserModule } from '@angular/platform-browser';
4 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
5 | import { FormsModule, ReactiveFormsModule } from '@angular/forms';
6 | import { HttpClientModule } from '@angular/common/http';
7 |
8 | import { MarkdownModule } from 'ngx-markdown';
9 |
10 | import { ToastService } from 'src/app/presenter/components/shared/toast/toast.service';
11 |
12 | import { AppRoutingModule } from './app-routing.module';
13 | import { AppComponent } from './app.component';
14 |
15 | import { DATA_BOOKMARK_IOC, DATA_POST_IOC, DATA_PROJECT_IOC } from './data/data.ioc';
16 | import { PresenterModule } from './presenter/presenter.module';
17 |
18 |
19 | @NgModule({
20 | declarations: [
21 | AppComponent,
22 | ],
23 | imports: [
24 | BrowserModule,
25 | BrowserAnimationsModule,
26 | FormsModule,
27 | ReactiveFormsModule,
28 | HttpClientModule,
29 | RouterModule,
30 | MarkdownModule.forRoot(),
31 | AppRoutingModule,
32 | PresenterModule,
33 | ],
34 | providers: [
35 | ...DATA_BOOKMARK_IOC,
36 | ...DATA_PROJECT_IOC,
37 | ...DATA_POST_IOC,
38 | ToastService
39 | ],
40 | bootstrap: [AppComponent],
41 | exports: []
42 | })
43 | export class AppModule {
44 | }
45 |
--------------------------------------------------------------------------------
/src/app/core/contracts/mapper.contract.ts:
--------------------------------------------------------------------------------
1 | export abstract class Mapper {
2 |
3 | protected abstract toEntity(param: R): E;
4 |
5 | protected abstract toRequest(param: E): R;
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/src/app/core/contracts/usecase.contract.ts:
--------------------------------------------------------------------------------
1 | export interface Usecase {
2 | execute(param: T): R;
3 | }
4 |
--------------------------------------------------------------------------------
/src/app/core/params/no-param.paylod.ts:
--------------------------------------------------------------------------------
1 | export class NoParam {
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/src/app/core/params/param.payload.ts:
--------------------------------------------------------------------------------
1 | export class Param {
2 |
3 | payload: T;
4 |
5 | constructor(payload: T) {
6 | this.payload = payload;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/app/core/types/types.ts:
--------------------------------------------------------------------------------
1 | export interface Result {
2 | acknowledged: boolean;
3 | insertedId?: string;
4 | }
5 |
--------------------------------------------------------------------------------
/src/app/data/data.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 |
3 | @NgModule({
4 | providers: [],
5 | exports: []
6 | })
7 | export class DataModule { }
8 |
--------------------------------------------------------------------------------
/src/app/data/datasources/local/.gitKeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesawo/angular-clean-architecture/4436611555ba841f8c1eee42919972f2fa32ee53/src/app/data/datasources/local/.gitKeep
--------------------------------------------------------------------------------
/src/app/data/datasources/remote/.gitKeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesawo/angular-clean-architecture/4436611555ba841f8c1eee42919972f2fa32ee53/src/app/data/datasources/remote/.gitKeep
--------------------------------------------------------------------------------
/src/app/data/datasources/remote/repo-implementations/bookmark/bookmark.repository.ts:
--------------------------------------------------------------------------------
1 | import { HttpClient } from '@angular/common/http';
2 | import { Injectable } from '@angular/core';
3 |
4 | import { map, Observable } from 'rxjs';
5 |
6 | import { Result } from 'src/app/core/types/types';
7 | import { BookmarkEntity } from 'src/app/domain/entities';
8 | import { IBookmarkRepository } from 'src/app/domain/repositories/ibookmark.repository';
9 |
10 | @Injectable({ providedIn: 'root' })
11 | export class BookmarkRepository extends IBookmarkRepository {
12 |
13 | public baseUrl = window.location.origin + '/.netlify/functions/bookmarks';
14 |
15 | constructor(private http: HttpClient) {
16 | super();
17 | }
18 |
19 | public all(): Observable {
20 | return this.http.get(`${this.baseUrl}`);
21 | }
22 |
23 | public createBookmark(bookmark: BookmarkEntity): Observable {
24 | return this.http.post(`${this.baseUrl}`, bookmark);
25 | }
26 |
27 | public getBookmark(id: string): Observable {
28 | return this.http
29 | .get(`${this.baseUrl}?id=${id}`);
30 | }
31 |
32 | public updateBookmark(id: string, bookmark: BookmarkEntity): Observable {
33 | return this.http.put(`${this.baseUrl}?bookmarkId=${id}`, bookmark);
34 | }
35 |
36 | public removeBookmark(id: string): Observable {
37 | return this.http.delete(`${this.baseUrl}?bookmarkId=${id}`);
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/app/data/datasources/remote/repo-implementations/post/post.repository.ts:
--------------------------------------------------------------------------------
1 | import { HttpClient } from '@angular/common/http';
2 | import { Injectable } from '@angular/core';
3 |
4 | import { Observable } from 'rxjs';
5 |
6 | import { Result } from 'src/app/core/types/types';
7 | import { PostEntity } from 'src/app/domain/entities';
8 | import { IPostRepository } from 'src/app/domain/repositories/ipost.repository';
9 |
10 | @Injectable({ providedIn: 'root' })
11 | export class PostRepository implements IPostRepository {
12 |
13 | public baseUrl = window.location.origin + '/.netlify/functions/posts';
14 |
15 | constructor(private http: HttpClient) { }
16 |
17 | all(): Observable {
18 | return this.http
19 | .get(`${this.baseUrl}`);
20 | }
21 |
22 | createPost(post: PostEntity): Observable {
23 | return this.http.post(`${this.baseUrl}`, post);
24 | }
25 |
26 | getPost(id: string): Observable {
27 | return this.http
28 | .get(`${this.baseUrl}?id=${id}`);
29 | }
30 |
31 | updatePost(id: string, post: PostEntity): Observable {
32 | return this.http.put(`${this.baseUrl}?postId=${id}`, post);
33 | }
34 |
35 | removePost(id: string): Observable {
36 | return this.http.delete(`${this.baseUrl}?postId=${id}`);
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/app/data/datasources/remote/repo-implementations/project/project.repository.ts:
--------------------------------------------------------------------------------
1 | import { HttpClient } from '@angular/common/http';
2 | import { Injectable } from '@angular/core';
3 | import { map, Observable, of } from 'rxjs';
4 |
5 | import { Result } from 'src/app/core/types/types';
6 | import { ProjectEntity } from 'src/app/domain/entities';
7 | import { IProjectRepository } from 'src/app/domain/repositories/iproject.repository';
8 |
9 | @Injectable({ providedIn: 'root' })
10 | export class ProjectRepository implements IProjectRepository {
11 |
12 | public baseUrl = window.location.origin + '/.netlify/functions/projects';
13 |
14 | constructor(private http: HttpClient) { }
15 |
16 | all(): Observable {
17 | return this.http
18 | .get(`${this.baseUrl}`);
19 | }
20 |
21 | createProject(project: ProjectEntity): Observable {
22 | return this.http.post(`${this.baseUrl}`, project);
23 | }
24 |
25 | getProject(id: string): Observable {
26 | return this.http
27 | .get(`${this.baseUrl}?id=${id}`);
28 | }
29 |
30 | updateProject(id: string, project: ProjectEntity): Observable {
31 | return this.http.put(`${this.baseUrl}?projectId=${id}`, project);
32 | }
33 |
34 | removeProject(id: string): Observable {
35 | return this.http.delete(`${this.baseUrl}?projectId=${id}`);
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/app/data/interactors/contracts/ibookmark.interactor.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 | import { Result } from './../../../core/types/types';
4 | import { BookmarkRequest } from 'src/app/data/requests/bookmark.request';
5 |
6 |
7 |
8 | @Injectable({ providedIn: 'root' })
9 | export abstract class IBookmarkInteractor {
10 |
11 | abstract save(bookmark: BookmarkRequest): Observable;
12 |
13 | abstract getMany(): Observable;
14 |
15 | abstract getOne(slug: string): Observable
16 |
17 | abstract create(bookmark: BookmarkRequest): Observable;
18 |
19 | abstract update(bookmark: BookmarkRequest): Observable;
20 |
21 | abstract delete(slug: string): Observable;
22 | }
23 |
--------------------------------------------------------------------------------
/src/app/data/interactors/contracts/ipost.interactor.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 |
4 | import { Result } from './../../../core/types/types';
5 | import { PostInteractor } from '../implementations/post/post.interactor';
6 | import { PostRequest } from '../../requests/posts.request';
7 |
8 |
9 | @Injectable({ providedIn: 'root' })
10 | export abstract class IPostInteractor {
11 |
12 | abstract getMany(): Observable;
13 |
14 | abstract getOne(slug: string): Observable
15 |
16 | abstract create(post: PostRequest): Observable;
17 |
18 | abstract update(post: PostRequest): Observable;
19 |
20 | abstract delete(slug: string): Observable;
21 |
22 | abstract savePost(formValues: PostRequest): Observable;
23 | }
24 |
--------------------------------------------------------------------------------
/src/app/data/interactors/contracts/iproject.interactor.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 |
4 | import { Result } from '../../../core/types/types';
5 | import { ProjectRequest } from '../../requests/project.request';
6 |
7 |
8 |
9 | @Injectable({ providedIn: 'root', })
10 | export abstract class IProjectInteractor {
11 |
12 | abstract save(project: ProjectRequest): Observable;
13 |
14 | abstract getMany(): Observable;
15 |
16 | abstract getOne(slug: string): Observable
17 |
18 | abstract create(project: ProjectRequest): Observable;
19 |
20 | abstract update(project: ProjectRequest): Observable;
21 |
22 | abstract delete(slug: string): Observable;
23 | }
24 |
--------------------------------------------------------------------------------
/src/app/data/interactors/implementations/bookmark/bookmark.interactor.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 |
4 | import { RemoveBookmarkUsecase } from '../../../../domain/usecases/bookmark-usecases/remove-usecase/remove-bookmark.usecase';
5 | import { UpdateBookmarkUsecase } from '../../../../domain/usecases/bookmark-usecases/update-usecase/update-bookmark.usecase';
6 | import { GetOneBookmarkUsecase } from '../../../../domain/usecases/bookmark-usecases/get-one-usecase/get-one-bookmark.usecase';
7 | import { GetManyBookmarksUsecase } from '../../../../domain/usecases/bookmark-usecases/get-many-usecase/get-many-bookmarks.usecase';
8 | import { Result } from '../../../../core/types/types';
9 | import { Param } from 'src/app/core/params/param.payload';
10 | import { BookmarkRequest } from '../../../requests/bookmark.request';
11 | import { CreateBookmarkUsecase } from '../../../../domain/usecases/bookmark-usecases/create-usecase/create-bookmark.usecase';
12 | import { IBookmarkInteractor } from '../../contracts/ibookmark.interactor';
13 | import { NoParam } from 'src/app/core/params/no-param.paylod';
14 |
15 |
16 | @Injectable({ providedIn: 'root' })
17 | export class BookmarkInteractor extends IBookmarkInteractor {
18 |
19 | constructor(
20 | private createBookmarkUsecase: CreateBookmarkUsecase,
21 | private getManyBookmarkUsecase: GetManyBookmarksUsecase,
22 | private getOneBookmarkUsecase: GetOneBookmarkUsecase,
23 | private updateBookmarkUsecase: UpdateBookmarkUsecase,
24 | private removeBookmarkUsecase: RemoveBookmarkUsecase
25 | ) {
26 | super();
27 | }
28 |
29 | public save(bookmark: BookmarkRequest): Observable {
30 | if (bookmark._id) return this.update(bookmark);
31 |
32 | return this.create(bookmark);
33 | }
34 |
35 | public create(bookmark: BookmarkRequest): Observable {
36 | return this.createBookmarkUsecase.execute(new Param(bookmark));
37 | }
38 |
39 | public getMany(): Observable {
40 | return this.getManyBookmarkUsecase.execute(new NoParam());
41 | }
42 |
43 | public getOne(slug: string): Observable {
44 | return this.getOneBookmarkUsecase.execute(new Param(slug));
45 | }
46 |
47 | public update(bookmark: BookmarkRequest): Observable {
48 | return this.updateBookmarkUsecase.execute(new Param(bookmark));
49 | }
50 |
51 | public delete(slug: string): Observable {
52 | return this.removeBookmarkUsecase.execute(new Param(slug));
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/app/data/interactors/implementations/post/post.interactor.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Observable, of } from 'rxjs';
3 |
4 | import { Result } from '../../../../core/types/types';
5 | import { IPostInteractor } from '../../contracts/ipost.interactor';
6 | import { PostRequest } from '../../../requests/posts.request';
7 | import { Param } from 'src/app/core/params/param.payload';
8 | import { NoParam } from 'src/app/core/params/no-param.paylod';
9 | import { CreatePostUsecase } from '../../../../domain/usecases/posts-usecases/create-usecase/create-post.usecase';
10 | import { UpdatePostUsecase } from '../../../../domain/usecases/posts-usecases/update-usecase/update-post.usecase';
11 | import { RemovePostUsecase } from '../../../../domain/usecases/posts-usecases/remove-usecase/remove-post.usecase';
12 | import { GetManyPostUsecase } from '../../../../domain/usecases/posts-usecases/get-many-usecase/get-many-posts.usecase';
13 | import { GetOnePostUsecase } from '../../../../domain/usecases/posts-usecases/get-one-usecase/get-one-post.usecase';
14 |
15 |
16 | @Injectable({ providedIn: 'root' })
17 | export class PostInteractor extends IPostInteractor {
18 |
19 | constructor(
20 | private getOnePostUsecase: GetOnePostUsecase,
21 | private getManyPostUsecase: GetManyPostUsecase,
22 | private removePostUsecase: RemovePostUsecase,
23 | private updatePostUsecase: UpdatePostUsecase,
24 | private createPostUsecase: CreatePostUsecase
25 | ) {
26 | super();
27 | }
28 |
29 | public savePost(post: PostRequest): Observable {
30 | if (post._id) return this.update(post);
31 |
32 | return this.create(post);
33 | }
34 |
35 | public create(post: PostRequest): Observable {
36 | return this.createPostUsecase.execute(new Param(post));
37 | }
38 |
39 | public getMany(): Observable {
40 | const result = this.getManyPostUsecase.execute(new NoParam());
41 | return result;
42 | }
43 |
44 | public getOne(slug: string): Observable {
45 | return this.getOnePostUsecase.execute(new Param(slug));
46 | }
47 |
48 | public update(post: PostRequest): Observable {
49 | return this.updatePostUsecase.execute(new Param({ ...post, id: post._id }));
50 | }
51 |
52 | public delete(slug: string): Observable {
53 | return this.removePostUsecase.execute(new Param(slug));
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/src/app/data/interactors/implementations/project/project.interactor.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Observable, of } from 'rxjs';
3 |
4 | import { Result } from '../../../../core/types/types';
5 | import { Param } from 'src/app/core/params/param.payload';
6 | import { NoParam } from 'src/app/core/params/no-param.paylod';
7 | import { ProjectRequest } from '../../../requests/project.request';
8 | import { IProjectInteractor } from '../../contracts/iproject.interactor';
9 | import { GetOneProjectUsecase } from 'src/app/domain/usecases/projects-usecases/get-one-usecase/get-one-project.usecase';
10 | import { GetManyProjectUsecase } from 'src/app/domain/usecases/projects-usecases/get-many-usecase/get-many-project.usecase';
11 | import { RemoveProjectUsecase } from 'src/app/domain/usecases/projects-usecases/remove-usecase/remove-project.usecase';
12 | import { CreateProjectUsecase } from 'src/app/domain/usecases/projects-usecases/create-usecase/create-project.usecase';
13 | import { UpdateProjectUsecase } from '../../../../domain/usecases/projects-usecases/update-usecase/update-project.usecase';
14 |
15 |
16 | @Injectable({ providedIn: 'root' })
17 | export class ProjectInteractor extends IProjectInteractor {
18 |
19 | constructor(
20 | private getOneProjectUsecase: GetOneProjectUsecase,
21 | private getManyProjectUsecase: GetManyProjectUsecase,
22 | private removeProjectUsecase: RemoveProjectUsecase,
23 | private updateProjectUsecase: UpdateProjectUsecase,
24 | private createProjectUsecase: CreateProjectUsecase
25 | ) {
26 | super();
27 | }
28 |
29 | public save(project: ProjectRequest): Observable {
30 | if (project._id) return this.update(project);
31 |
32 | return this.create(project);
33 | }
34 |
35 | public create(project: ProjectRequest): Observable {
36 | return this.createProjectUsecase.execute(new Param(project));
37 | }
38 |
39 | public getMany(): Observable {
40 | return this.getManyProjectUsecase.execute(new NoParam());
41 | }
42 |
43 | public getOne(slug: string): Observable {
44 | return this.getOneProjectUsecase.execute(new Param(slug));
45 | }
46 |
47 | public update(project: ProjectRequest): Observable {
48 | return this.updateProjectUsecase.execute(new Param({ ...project, id: project._id }));
49 | }
50 |
51 | public delete(slug: string): Observable {
52 | return this.removeProjectUsecase.execute(new Param(slug));
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/app/data/requests/bookmark.request.ts:
--------------------------------------------------------------------------------
1 | import { BookmarkEntity } from './../../domain/entities/bookmark.entity';
2 |
3 |
4 | export class BookmarkRequest extends BookmarkEntity {
5 | _id?: string;
6 | }
7 |
--------------------------------------------------------------------------------
/src/app/data/requests/posts.request.ts:
--------------------------------------------------------------------------------
1 | import { PostEntity } from 'src/app/domain/entities';
2 |
3 | export class PostRequest extends PostEntity {
4 | _id?: string;
5 | }
6 |
--------------------------------------------------------------------------------
/src/app/data/requests/project.request.ts:
--------------------------------------------------------------------------------
1 | import { ProjectEntity } from './../../domain/entities/project.entity';
2 |
3 | export class ProjectRequest extends ProjectEntity {
4 | _id?: string;
5 | }
6 |
--------------------------------------------------------------------------------
/src/app/domain/domain.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 |
3 | @NgModule()
4 | export class DomainModule { }
5 |
--------------------------------------------------------------------------------
/src/app/domain/entities/bookmark.entity.ts:
--------------------------------------------------------------------------------
1 | export class BookmarkEntity {
2 |
3 | id?: string;
4 | short!: string;
5 | url!: string;
6 | date?: string;
7 | tags?: string[];
8 |
9 | constructor(id?: string) {
10 | this.id = id;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/app/domain/entities/index.ts:
--------------------------------------------------------------------------------
1 | export { BookmarkEntity } from "../entities/bookmark.entity";
2 | export { PostEntity } from "../entities/post.entity";
3 | export { ProjectEntity } from "../entities/project.entity";
4 |
--------------------------------------------------------------------------------
/src/app/domain/entities/post.entity.ts:
--------------------------------------------------------------------------------
1 | export class PostEntity {
2 | id?: string;
3 | tags?: string[];
4 | content!: string;
5 | date!: string;
6 | title!: string;
7 | created_at?: Date;
8 | author?: string;
9 | excerpt?: string;
10 | read?: number;
11 | views?: number;
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/app/domain/entities/project.entity.ts:
--------------------------------------------------------------------------------
1 | export type ProjectAction = {
2 | title: string;
3 | link: string;
4 | }
5 |
6 | export class ProjectEntity {
7 | id?: string;
8 | title!: string;
9 | description!: string;
10 | features?: string[];
11 | modules?: string[];
12 | industries?: string[];
13 | tools?: string[];
14 | imageUrl?: string;
15 | action?: ProjectAction;
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/domain/repositories/ibookmark.repository.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | import { Observable } from "rxjs";
4 |
5 | import { Result } from 'src/app/core/types/types';
6 |
7 | import { BookmarkEntity } from "../entities";
8 |
9 | @Injectable({ providedIn: 'root' })
10 | export abstract class IBookmarkRepository {
11 |
12 | abstract all(): Observable;
13 |
14 | abstract createBookmark(bookmark: BookmarkEntity): Observable;
15 |
16 | abstract getBookmark(id: string): Observable;
17 |
18 | abstract updateBookmark(id: string, bookmark: BookmarkEntity): Observable;
19 |
20 | abstract removeBookmark(id: string): Observable;
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/app/domain/repositories/ipost.repository.ts:
--------------------------------------------------------------------------------
1 | import { Result } from 'src/app/core/types/types';
2 | import { Injectable } from '@angular/core';
3 | import { Observable } from 'rxjs';
4 | import { PostEntity } from './../entities/';
5 |
6 | @Injectable({ providedIn: 'root' })
7 | export abstract class IPostRepository {
8 |
9 | abstract all(): Observable;
10 |
11 | abstract createPost(post: PostEntity): Observable;
12 |
13 | abstract getPost(id: string): Observable;
14 |
15 | abstract updatePost(id: string, post: PostEntity): Observable;
16 |
17 | abstract removePost(id: string): Observable;
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/domain/repositories/iproject.repository.ts:
--------------------------------------------------------------------------------
1 | import { Result } from 'src/app/core/types/types';
2 | import { Injectable } from '@angular/core';
3 | import { Observable } from 'rxjs';
4 | import { ProjectEntity } from '../entities';
5 |
6 | @Injectable({ providedIn: 'root' })
7 | export abstract class IProjectRepository {
8 |
9 | abstract all(): Observable;
10 |
11 | abstract createProject(project: ProjectEntity): Observable;
12 |
13 | abstract getProject(id: string): Observable;
14 |
15 | abstract updateProject(id: string, project: ProjectEntity): Observable;
16 |
17 | abstract removeProject(id: string): Observable;
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/bookmark-usecases/create-usecase/create-bookmark.usecase.spec.ts:
--------------------------------------------------------------------------------
1 | import { Observable, of } from 'rxjs';
2 | import { MOCK_BOOKMARKS } from './../../../../data/datasources/remote/repo-implementations/bookmark/bookmark.repository.spec';
3 | import { BookmarkEntity } from './../../../entities/bookmark.entity';
4 | import { Param } from "src/app/core/params/param.payload";
5 | import { IBookmarkRepository } from "src/app/domain/repositories/ibookmark.repository";
6 | import { CreateBookmarkUsecase } from "./create-bookmark.usecase";
7 |
8 |
9 | describe('CreateBookmarkUsecase', () => {
10 | let usecase: CreateBookmarkUsecase;
11 | let ibookmarkRepository: jasmine.SpyObj;
12 | let bookmark: BookmarkEntity;
13 |
14 | beforeEach(() => {
15 | ibookmarkRepository = jasmine.createSpyObj(IBookmarkRepository, ['createBookmark']);
16 | usecase = new CreateBookmarkUsecase(ibookmarkRepository);
17 | bookmark = MOCK_BOOKMARKS[0];
18 | });
19 |
20 | describe('constructor', () => {
21 | it('should be created', () => {
22 | expect(usecase).toBeTruthy();
23 | })
24 | it('should be created with dependency', () => {
25 | expect(usecase['ibookmarkRepository']).toBeTruthy();
26 | })
27 | })
28 |
29 | describe('execute', () => {
30 | it('should accept Param as input', () => {
31 | let usecase: jasmine.SpyObj = jasmine.createSpyObj(CreateBookmarkUsecase, ['execute']);
32 |
33 | usecase.execute(new Param(bookmark));
34 | const args = usecase.execute.calls.argsFor(0);
35 |
36 | expect(args[0]).toBeInstanceOf(Param);
37 | expect(args[0].payload).toBeDefined;
38 | expect(args[0].payload).toEqual(jasmine.objectContaining(bookmark));
39 | })
40 |
41 | it('should return an Observable ', () => {
42 | spyOn(usecase, 'execute').and.returnValue(of())
43 |
44 | const result = usecase.execute(new Param(bookmark));
45 |
46 | expect(result).toBeInstanceOf(Observable);
47 |
48 | });
49 |
50 | it('should call ibookmarkRepository.createBookmark with param', () => {
51 | usecase.execute(new Param(bookmark));
52 |
53 | expect(usecase['ibookmarkRepository'].createBookmark).toHaveBeenCalled();
54 | expect(usecase['ibookmarkRepository'].createBookmark).toHaveBeenCalledWith(bookmark);
55 | });
56 | })
57 | })
58 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/bookmark-usecases/create-usecase/create-bookmark.usecase.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 | import { BookmarkEntity } from '../../../entities/bookmark.entity';
4 | import { Usecase } from '../../../../core/contracts/usecase.contract';
5 | import { Param } from 'src/app/core/params/param.payload';
6 | import { IBookmarkRepository } from '../../../repositories/ibookmark.repository';
7 | import { Result } from 'src/app/core/types/types';
8 |
9 |
10 | @Injectable({ providedIn: 'root' })
11 | export class CreateBookmarkUsecase implements Usecase , Observable> {
12 |
13 | constructor(private ibookmarkRepository: IBookmarkRepository) { }
14 |
15 | execute(param: Param): Observable {
16 | return this.ibookmarkRepository.createBookmark(param.payload);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/bookmark-usecases/get-many-usecase/get-many-bookmarks.uscecase.spec.ts:
--------------------------------------------------------------------------------
1 | import { Observable, of } from "rxjs";
2 | import { NoParam } from "src/app/core/params/no-param.paylod";
3 | import { Param } from "src/app/core/params/param.payload";
4 | import { MOCK_BOOKMARKS } from "src/app/data/datasources/remote/repo-implementations/bookmark/bookmark.repository.spec";
5 | import { BookmarkEntity } from "src/app/domain/entities";
6 | import { IBookmarkRepository } from "src/app/domain/repositories/ibookmark.repository";
7 | import { GetManyBookmarksUsecase } from "./get-many-bookmarks.usecase";
8 |
9 |
10 | describe('GetManyBookmarksUsecase', () => {
11 | let usecase: GetManyBookmarksUsecase;
12 | let ibookmarkRepository: jasmine.SpyObj;
13 | let bookmark: BookmarkEntity;
14 |
15 |
16 | beforeEach(() => {
17 | ibookmarkRepository = jasmine.createSpyObj(IBookmarkRepository, ['all']);
18 | usecase = new GetManyBookmarksUsecase(ibookmarkRepository);
19 | bookmark = MOCK_BOOKMARKS[0];
20 | });
21 |
22 | describe('constructor', () => {
23 | it('should be created', () => {
24 | expect(usecase).toBeTruthy();
25 | })
26 | it('should be created with dependency', () => {
27 | expect(usecase['iBookmarkRepository']).toBeTruthy();
28 | })
29 | })
30 |
31 | describe('execute', () => {
32 | it('should accept NoParam as input', () => {
33 | let usecase: jasmine.SpyObj = jasmine.createSpyObj(GetManyBookmarksUsecase, ['execute']);
34 |
35 | usecase.execute(new NoParam());
36 | const args = usecase.execute.calls.argsFor(0);
37 |
38 | expect(args[0]).toBeInstanceOf(NoParam);
39 | expect(args[0]).toBeDefined;
40 |
41 | })
42 | it('should return an Observable ', () => {
43 | spyOn(usecase, 'execute').and.returnValue(of())
44 |
45 | const result = usecase.execute(new NoParam());
46 |
47 | expect(result).toBeInstanceOf(Observable);
48 | });
49 | it('should call ibookmarkRepository.all', () => {
50 | usecase.execute(new NoParam());
51 |
52 | expect(usecase['iBookmarkRepository'].all).toHaveBeenCalled();
53 | expect(usecase['iBookmarkRepository'].all).toHaveBeenCalledWith();
54 |
55 | });
56 |
57 | })
58 | })
59 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/bookmark-usecases/get-many-usecase/get-many-bookmarks.usecase.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 | import { NoParam } from '../../../../core/params/no-param.paylod';
4 | import { Usecase } from '../../../../core/contracts/usecase.contract';
5 | import { IBookmarkRepository } from '../../../repositories/ibookmark.repository';
6 | import { BookmarkEntity } from '../../../entities';
7 |
8 | @Injectable({ providedIn: 'root' })
9 | export class GetManyBookmarksUsecase implements Usecase> {
10 |
11 | constructor(private iBookmarkRepository: IBookmarkRepository) { }
12 |
13 | execute(payload: NoParam): Observable {
14 | return this.iBookmarkRepository.all();
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/bookmark-usecases/get-one-usecase/get-one-bookmark.usecase.spec.ts:
--------------------------------------------------------------------------------
1 | import { MOCK_BOOKMARKS } from "src/app/data/datasources/remote/repo-implementations/bookmark/bookmark.repository.spec";
2 | import { BookmarkEntity } from "src/app/domain/entities";
3 | import { IBookmarkRepository } from "src/app/domain/repositories/ibookmark.repository";
4 | import { GetOneBookmarkUsecase } from "./get-one-bookmark.usecase";
5 | import { Param } from 'src/app/core/params/param.payload';
6 | import { Observable, of } from 'rxjs';
7 |
8 | describe('GetOneBookmarkUsecase', () => {
9 |
10 | let usecase: GetOneBookmarkUsecase
11 | let ibookmarkRepository: jasmine.SpyObj;
12 | let bookmark: BookmarkEntity;
13 |
14 | beforeEach(() => {
15 | ibookmarkRepository = jasmine.createSpyObj(IBookmarkRepository, ['getBookmark']);
16 | usecase = new GetOneBookmarkUsecase(ibookmarkRepository);
17 | bookmark = MOCK_BOOKMARKS[0];
18 | });
19 |
20 |
21 | describe('constructor', () => {
22 | it('should be created', () => {
23 | expect(usecase).toBeTruthy();
24 | });
25 | it('should accept IBookmarkRepository as dependency', () => {
26 | expect(usecase['ibookmarkRepository']).toBeTruthy();
27 | });
28 |
29 | })
30 |
31 | describe('execute', () => {
32 | it('should accept Param as input', () => {
33 | let usecase: jasmine.SpyObj = jasmine.createSpyObj(GetOneBookmarkUsecase, ['execute']);
34 |
35 | usecase.execute(new Param(bookmark.id!));
36 | const args = usecase.execute.calls.argsFor(0);
37 |
38 | expect(args[0]).toBeInstanceOf(Param);
39 | expect(args[0].payload).toBeDefined;
40 | expect(args[0].payload).toEqual(jasmine.stringContaining(bookmark.id!));
41 |
42 | });
43 | it('should return an Observable', () => {
44 | spyOn(usecase, 'execute').and.returnValue(of())
45 |
46 | const result = usecase.execute(new Param(bookmark.id!));
47 |
48 | expect(result).toBeInstanceOf(Observable);
49 |
50 | });
51 | it('should call ibookmarkRepository.getBookmark with Param', () => {
52 | usecase.execute(new Param(bookmark.id!));
53 |
54 | expect(usecase['ibookmarkRepository'].getBookmark).toHaveBeenCalled();
55 | expect(usecase['ibookmarkRepository'].getBookmark).toHaveBeenCalledWith(bookmark.id!);
56 |
57 | })
58 | })
59 | })
60 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/bookmark-usecases/get-one-usecase/get-one-bookmark.usecase.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 | import { Param } from '../../../../core/params/param.payload';
4 | import { BookmarkEntity } from '../../../entities/bookmark.entity';
5 | import { Usecase } from '../../../../core/contracts/usecase.contract';
6 | import { IBookmarkRepository } from '../../../repositories/ibookmark.repository';
7 |
8 | @Injectable({ providedIn: 'root' })
9 | export class GetOneBookmarkUsecase implements Usecase , Observable> {
10 |
11 | constructor(private ibookmarkRepository: IBookmarkRepository) { }
12 |
13 | execute(param: Param): Observable {
14 | return this.ibookmarkRepository.getBookmark(param.payload);
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/bookmark-usecases/remove-usecase/remove-bookmark.usecase.spec.ts:
--------------------------------------------------------------------------------
1 | import { Observable, of } from "rxjs";
2 | import { Param } from "src/app/core/params/param.payload";
3 | import { MOCK_BOOKMARKS } from "src/app/data/datasources/remote/repo-implementations/bookmark/bookmark.repository.spec";
4 | import { BookmarkEntity } from "src/app/domain/entities";
5 | import { IBookmarkRepository } from "src/app/domain/repositories/ibookmark.repository";
6 | import { RemoveBookmarkUsecase } from "./remove-bookmark.usecase";
7 |
8 | describe('RemoveBookmarkUsecase', () => {
9 |
10 | let usecase: RemoveBookmarkUsecase;
11 | let ibookmarkRepository: jasmine.SpyObj;
12 | let bookmark: BookmarkEntity;
13 |
14 | beforeEach(() => {
15 | ibookmarkRepository = jasmine.createSpyObj(IBookmarkRepository, ['removeBookmark']);
16 | usecase = new RemoveBookmarkUsecase(ibookmarkRepository);
17 | bookmark = MOCK_BOOKMARKS[0];
18 | });
19 |
20 |
21 | describe('constructor', () => {
22 | it('should be created', () => {
23 | expect(usecase).toBeTruthy();
24 | });
25 | it('should accept IBookmarkRepository as dependency', () => {
26 | expect(usecase['ibookmarkRepository']).toBeTruthy();
27 |
28 | });
29 | })
30 |
31 | describe('execute', () => {
32 | it('should accept Param as input', () => {
33 | let usecase: jasmine.SpyObj = jasmine.createSpyObj(RemoveBookmarkUsecase, ['execute']);
34 |
35 | usecase.execute(new Param(bookmark.id!));
36 | const args = usecase.execute.calls.argsFor(0);
37 |
38 | expect(args[0]).toBeInstanceOf(Param);
39 | expect(args[0].payload).toBeDefined;
40 | expect(args[0].payload).toEqual(jasmine.stringContaining(bookmark.id!));
41 |
42 | });
43 | it('should return an Observable', () => {
44 | spyOn(usecase, 'execute').and.returnValue(of())
45 |
46 | const result = usecase.execute(new Param(bookmark.id!));
47 |
48 | expect(result).toBeInstanceOf(Observable);
49 | });
50 | it('should call ibookmarkRepository.removeBookmark', () => {
51 | usecase.execute(new Param(bookmark.id!));
52 |
53 | expect(usecase['ibookmarkRepository'].removeBookmark).toHaveBeenCalled();
54 |
55 | })
56 | it('should call ibookmarkRepository.removeBookmark with id and bookmark as input', () => {
57 | usecase.execute(new Param(bookmark.id!));
58 |
59 | expect(usecase['ibookmarkRepository'].removeBookmark).toHaveBeenCalledWith(bookmark.id!,);
60 |
61 | })
62 | })
63 | })
64 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/bookmark-usecases/remove-usecase/remove-bookmark.usecase.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 | import { Param } from 'src/app/core/params/param.payload';
4 | import { Result } from 'src/app/core/types/types';
5 | import { IBookmarkRepository } from '../../../repositories/ibookmark.repository';
6 | import { Usecase } from '../../../../core/contracts/usecase.contract';
7 |
8 | @Injectable({ providedIn: 'root' })
9 | export class RemoveBookmarkUsecase implements Usecase , Observable> {
10 |
11 | constructor(private ibookmarkRepository: IBookmarkRepository) { }
12 |
13 | execute(param: Param): Observable {
14 | return this.ibookmarkRepository.removeBookmark(param.payload);
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/bookmark-usecases/update-usecase/update-bookmark.usecase.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from 'rxjs';
2 | import { Injectable } from '@angular/core';
3 |
4 | import { BookmarkEntity } from '../../../entities';
5 | import { Result } from '../../../../core/types/types';
6 | import { Param } from '../../../../core/params/param.payload';
7 | import { Usecase } from '../../../../core/contracts/usecase.contract';
8 | import { IBookmarkRepository } from '../../../repositories/ibookmark.repository';
9 |
10 |
11 | @Injectable({ providedIn: 'root' })
12 | export class UpdateBookmarkUsecase implements Usecase , Observable>{
13 |
14 | constructor(private ibookmarkRepository: IBookmarkRepository) { }
15 |
16 | execute(param: Param): Observable {
17 | const { id, ...bookmark } = param.payload;
18 | return this.ibookmarkRepository.updateBookmark(id!, bookmark);
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/posts-usecases/create-usecase/create-post.usecase.spec.ts:
--------------------------------------------------------------------------------
1 | import { Observable, of } from 'rxjs';
2 |
3 | import { Param } from 'src/app/core/params/param.payload';
4 |
5 | import { PostEntity } from '../../../entities';
6 | import { IPostRepository } from '../../../repositories/ipost.repository';
7 | import { MOCK_POSTS } from './../../../../data/datasources/remote/repo-implementations/post/post.repository.spec';
8 |
9 | import { CreatePostUsecase } from './create-post.usecase';
10 |
11 |
12 | describe('CreatePostUsecase', () => {
13 | let usecase: CreatePostUsecase;
14 | let ipostRepository: jasmine.SpyObj;
15 | let post: PostEntity;
16 |
17 | beforeEach(() => {
18 | ipostRepository = jasmine.createSpyObj(IPostRepository, ['createPost']);
19 | usecase = new CreatePostUsecase(ipostRepository);
20 | post = MOCK_POSTS[0];
21 | });
22 |
23 | describe('constructor', () => {
24 | it('should be created', () => {
25 | expect(usecase).toBeTruthy();
26 | })
27 | it('should be created with dependency', () => {
28 | expect(usecase['ipostRepository']).toBeTruthy();
29 | })
30 | })
31 |
32 | describe('execute', () => {
33 | it('should accept Param as input', () => {
34 | let usecase: jasmine.SpyObj = jasmine.createSpyObj(CreatePostUsecase, ['execute']);
35 |
36 | usecase.execute(new Param(post));
37 | const args = usecase.execute.calls.argsFor(0);
38 |
39 | expect(args[0]).toBeInstanceOf(Param);
40 | expect(args[0].payload).toBeDefined;
41 | expect(args[0].payload).toEqual(jasmine.objectContaining(post));
42 | })
43 |
44 | it('should return an Observable ', () => {
45 | spyOn(usecase, 'execute').and.returnValue(of())
46 |
47 | const result = usecase.execute(new Param(post));
48 |
49 | expect(result).toBeInstanceOf(Observable);
50 |
51 | });
52 |
53 | it('should call ipostRepository.createPost with param', () => {
54 | usecase.execute(new Param(post));
55 |
56 | expect(usecase['ipostRepository'].createPost).toHaveBeenCalled();
57 | expect(usecase['ipostRepository'].createPost).toHaveBeenCalledWith(post);
58 | });
59 | })
60 | })
61 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/posts-usecases/create-usecase/create-post.usecase.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | import { Observable } from 'rxjs';
4 |
5 | import { Result } from 'src/app/core/types/types';
6 | import { Param } from 'src/app/core/params/param.payload';
7 |
8 | import { PostEntity } from '../../../entities';
9 | import { IPostRepository } from '../../../repositories/ipost.repository';
10 | import { Usecase } from '../../../../core/contracts/usecase.contract';
11 |
12 |
13 | @Injectable({ providedIn: 'root' })
14 | export class CreatePostUsecase implements Usecase , Observable> {
15 |
16 | constructor(private ipostRepository: IPostRepository) { }
17 |
18 | execute(param: Param): Observable {
19 | return this.ipostRepository.createPost(param.payload);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/posts-usecases/get-many-usecase/get-many-posts.usecase.spec.ts:
--------------------------------------------------------------------------------
1 | import { Observable, of } from 'rxjs';
2 |
3 | import { Param } from 'src/app/core/params/param.payload';
4 |
5 | import { PostEntity } from '../../../entities';
6 | import { IPostRepository } from '../../../repositories/ipost.repository';
7 | import { NoParam } from './../../../../core/params/no-param.paylod';
8 | import { MOCK_POSTS } from '../../../../data/datasources/remote/repo-implementations/post/post.repository.spec';
9 |
10 | import { GetManyPostUsecase } from './get-many-posts.usecase';
11 |
12 | describe('GetManyPostUsecase', () => {
13 | let usecase: GetManyPostUsecase;
14 | let iPostRepository: jasmine.SpyObj;
15 | let post: PostEntity;
16 |
17 | beforeEach(() => {
18 | iPostRepository = jasmine.createSpyObj(IPostRepository, ['all']);
19 | usecase = new GetManyPostUsecase(iPostRepository);
20 | post = MOCK_POSTS[0];
21 | });
22 |
23 | describe('constructor', () => {
24 | it('should be created', () => {
25 | expect(usecase).toBeTruthy();
26 | })
27 | it('should be created with dependency', () => {
28 | expect(usecase['iPostRepository']).toBeTruthy();
29 | })
30 | })
31 |
32 | describe('execute', () => {
33 | it('should accept NoParam as input', () => {
34 | let usecase: jasmine.SpyObj = jasmine.createSpyObj(GetManyPostUsecase, ['execute']);
35 |
36 | usecase.execute(new NoParam());
37 | const args = usecase.execute.calls.argsFor(0);
38 |
39 | expect(args[0]).toBeInstanceOf(NoParam);
40 | expect(args[0]).toBeDefined;
41 | expect(args[0]).toEqual(jasmine.objectContaining(new NoParam()));
42 | })
43 |
44 | it('should return an Observable ', () => {
45 | spyOn(usecase, 'execute').and.returnValue(of())
46 |
47 | const result = usecase.execute(new Param(post));
48 |
49 | expect(result).toBeInstanceOf(Observable);
50 |
51 | });
52 |
53 | it('should call iPostRepository.all with no param', () => {
54 | usecase.execute(new NoParam());
55 |
56 | expect(usecase['iPostRepository'].all).toHaveBeenCalled();
57 | expect(usecase['iPostRepository'].all).toHaveBeenCalledWith();
58 | });
59 | })
60 | })
61 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/posts-usecases/get-many-usecase/get-many-posts.usecase.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 | import { NoParam } from '../../../../core/params/no-param.paylod';
4 | import { Usecase } from '../../../../core/contracts/usecase.contract';
5 | import { PostEntity } from '../../../entities';
6 | import { IPostRepository } from '../../../repositories/ipost.repository';
7 |
8 | @Injectable({ providedIn: 'root' })
9 | export class GetManyPostUsecase implements Usecase> {
10 |
11 | constructor(private iPostRepository: IPostRepository) { }
12 |
13 | execute(payload: NoParam): Observable {
14 | return this.iPostRepository.all();
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/posts-usecases/get-one-usecase/get-one-post.usecase.spec.ts:
--------------------------------------------------------------------------------
1 | import { Observable, of } from 'rxjs';
2 |
3 | import { Param } from 'src/app/core/params/param.payload';
4 |
5 | import { PostEntity } from '../../../entities';
6 | import { IPostRepository } from '../../../repositories/ipost.repository';
7 | import { MOCK_POSTS } from '../../../../data/datasources/remote/repo-implementations/post/post.repository.spec';
8 |
9 | import { GetOnePostUsecase } from './get-one-post.usecase';
10 |
11 | describe('GetOnePostUsecase', () => {
12 | let usecase: GetOnePostUsecase;
13 | let iPostRepository: jasmine.SpyObj;
14 | let post: PostEntity;
15 |
16 | beforeEach(() => {
17 | iPostRepository = jasmine.createSpyObj(IPostRepository, ['getPost']);
18 | usecase = new GetOnePostUsecase(iPostRepository);
19 | post = MOCK_POSTS[0];
20 | });
21 |
22 | describe('constructor', () => {
23 | it('should be created', () => {
24 | expect(usecase).toBeTruthy();
25 | })
26 | it('should be created with dependency', () => {
27 | expect(usecase['iPostRepository']).toBeTruthy();
28 | })
29 | })
30 |
31 | describe('execute', () => {
32 | it('should accept a Param as input', () => {
33 | let usecase: jasmine.SpyObj = jasmine.createSpyObj(GetOnePostUsecase, ['execute']);
34 |
35 | usecase.execute(new Param(post.id!));
36 | const args = usecase.execute.calls.argsFor(0);
37 |
38 | expect(args[0]).toBeInstanceOf(Param);
39 | expect(args[0]).toBeDefined;
40 | expect(args[0]).toEqual(jasmine.objectContaining(new Param(post.id!)));
41 | })
42 |
43 | it('should return an Observable ', () => {
44 | spyOn(usecase, 'execute').and.returnValue(of())
45 |
46 | const result = usecase.execute(new Param(post.id!));
47 |
48 | expect(result).toBeInstanceOf(Observable);
49 | });
50 |
51 | it('should call iPostRepository.getPost with param', () => {
52 | usecase.execute(new Param(post.id!));
53 |
54 | expect(usecase['iPostRepository'].getPost).toHaveBeenCalled();
55 | expect(usecase['iPostRepository'].getPost).toHaveBeenCalledWith(post.id!);
56 | });
57 | })
58 | })
59 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/posts-usecases/get-one-usecase/get-one-post.usecase.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 |
4 | import { Param } from '../../../../core/params/param.payload';
5 | import { Usecase } from '../../../../core/contracts/usecase.contract';
6 | import { PostEntity } from '../../../entities';
7 | import { IPostRepository } from '../../../repositories/ipost.repository';
8 |
9 | @Injectable({ providedIn: 'root' })
10 | export class GetOnePostUsecase implements Usecase , Observable> {
11 |
12 | constructor(private iPostRepository: IPostRepository) { }
13 |
14 | execute(param: Param): Observable {
15 | return this.iPostRepository.getPost(param.payload);
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/posts-usecases/remove-usecase/remove-post.usecase.spec.ts:
--------------------------------------------------------------------------------
1 | import { Observable, of } from 'rxjs';
2 |
3 | import { Param } from 'src/app/core/params/param.payload';
4 |
5 | import { PostEntity } from '../../../entities';
6 | import { IPostRepository } from '../../../repositories/ipost.repository';
7 | import { MOCK_POSTS } from '../../../../data/datasources/remote/repo-implementations/post/post.repository.spec';
8 |
9 | import { RemovePostUsecase } from './remove-post.usecase';
10 |
11 | describe('GetOnePostUsecase', () => {
12 | let usecase: RemovePostUsecase;
13 | let iPostRepository: jasmine.SpyObj;
14 | let post: PostEntity;
15 |
16 | beforeEach(() => {
17 | iPostRepository = jasmine.createSpyObj(IPostRepository, ['removePost']);
18 | usecase = new RemovePostUsecase(iPostRepository);
19 | post = MOCK_POSTS[0];
20 | });
21 |
22 | describe('constructor', () => {
23 | it('should be created', () => {
24 | expect(usecase).toBeTruthy();
25 | })
26 | it('should be created with dependency', () => {
27 | expect(usecase['iPostRepository']).toBeTruthy();
28 | })
29 | })
30 |
31 | describe('execute', () => {
32 | it('should accept a Param as input', () => {
33 | let usecase: jasmine.SpyObj = jasmine.createSpyObj(RemovePostUsecase, ['execute']);
34 |
35 | usecase.execute(new Param(post.id!));
36 | const args = usecase.execute.calls.argsFor(0);
37 |
38 | expect(args[0]).toBeInstanceOf(Param);
39 | expect(args[0]).toBeDefined;
40 | expect(args[0]).toEqual(jasmine.objectContaining(new Param(post.id!)));
41 | })
42 |
43 | it('should return an Observable ', () => {
44 | spyOn(usecase, 'execute').and.returnValue(of())
45 |
46 | const result = usecase.execute(new Param(post.id!));
47 |
48 | expect(result).toBeInstanceOf(Observable);
49 | });
50 |
51 | it('should call iPostRepository.getPost with param', () => {
52 | usecase.execute(new Param(post.id!));
53 |
54 | expect(usecase['iPostRepository'].removePost).toHaveBeenCalled();
55 | expect(usecase['iPostRepository'].removePost).toHaveBeenCalledWith(post.id!);
56 | });
57 | })
58 | })
59 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/posts-usecases/remove-usecase/remove-post.usecase.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | import { Observable } from 'rxjs';
4 |
5 | import { Param } from 'src/app/core/params/param.payload';
6 | import { Result } from 'src/app/core/types/types';
7 |
8 | import { IPostRepository } from '../../../repositories/ipost.repository';
9 | import { Usecase } from '../../../../core/contracts/usecase.contract';
10 |
11 | @Injectable({ providedIn: 'root' })
12 | export class RemovePostUsecase implements Usecase , Observable> {
13 |
14 | constructor(private iPostRepository: IPostRepository) { }
15 |
16 | execute(param: Param): Observable {
17 | return this.iPostRepository.removePost(param.payload);
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/posts-usecases/update-usecase/update-post.usecase.spec.ts:
--------------------------------------------------------------------------------
1 | import { Observable, of } from 'rxjs';
2 |
3 | import { Param } from 'src/app/core/params/param.payload';
4 |
5 | import { PostEntity } from '../../../entities';
6 | import { IPostRepository } from '../../../repositories/ipost.repository';
7 | import { MOCK_POSTS } from '../../../../data/datasources/remote/repo-implementations/post/post.repository.spec';
8 |
9 | import { UpdatePostUsecase } from './update-post.usecase';
10 |
11 |
12 | describe('UpdatePostUsecase', () => {
13 | let usecase: UpdatePostUsecase;
14 | let iPostRepository: jasmine.SpyObj;
15 | let post: PostEntity;
16 |
17 | beforeEach(() => {
18 | iPostRepository = jasmine.createSpyObj(IPostRepository, ['updatePost']);
19 | usecase = new UpdatePostUsecase(iPostRepository);
20 | post = MOCK_POSTS[0];
21 | });
22 |
23 | describe('constructor', () => {
24 | it('should be created', () => {
25 | expect(usecase).toBeTruthy();
26 | })
27 | it('should be created with dependency', () => {
28 | expect(usecase['iPostRepository']).toBeTruthy();
29 | })
30 | })
31 |
32 | describe('execute', () => {
33 | it('should accept a Param as input', () => {
34 | let usecase: jasmine.SpyObj = jasmine.createSpyObj(UpdatePostUsecase, ['execute']);
35 |
36 | usecase.execute(new Param(post));
37 | const args = usecase.execute.calls.argsFor(0);
38 |
39 | expect(args[0]).toBeInstanceOf(Param);
40 | expect(args[0]).toBeDefined;
41 | expect(args[0]).toEqual(jasmine.objectContaining(new Param(post)));
42 | })
43 |
44 | it('should return an Observable ', () => {
45 | spyOn(usecase, 'execute').and.returnValue(of())
46 |
47 | const result = usecase.execute(new Param(post));
48 |
49 | expect(result).toBeInstanceOf(Observable);
50 | });
51 |
52 | it('should call iPostRepository.updatePost with param', () => {
53 | const { id, ...rest } = post;
54 |
55 | usecase.execute(new Param(post));
56 |
57 | expect(usecase['iPostRepository'].updatePost).toHaveBeenCalled();
58 | expect(usecase['iPostRepository'].updatePost).toHaveBeenCalledWith(id!, rest);
59 | });
60 | })
61 | })
62 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/posts-usecases/update-usecase/update-post.usecase.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | import { Observable } from 'rxjs';
4 |
5 | import { PostEntity } from '../../../entities';
6 | import { IPostRepository } from '../../../repositories/ipost.repository';
7 | import { Usecase } from '../../../../core/contracts/usecase.contract';
8 | import { Param } from '../../../../core/params/param.payload';
9 | import { Result } from '../../../../core/types/types';
10 |
11 |
12 | @Injectable({ providedIn: 'root' })
13 | export class UpdatePostUsecase implements Usecase , Observable>{
14 |
15 | constructor(private iPostRepository: IPostRepository) { }
16 |
17 | execute(param: Param): Observable {
18 | const { id, ...Post } = param.payload;
19 | return this.iPostRepository.updatePost(id!, Post);
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/projects-usecases/create-usecase/create-project.usecase.spec.ts:
--------------------------------------------------------------------------------
1 | import { Observable, of } from "rxjs";
2 |
3 | import { Param } from "src/app/core/params/param.payload";
4 | import { ProjectEntity } from "src/app/domain/entities";
5 | import { IProjectRepository } from "src/app/domain/repositories/iproject.repository";
6 |
7 | import { MOCK_PROJECT } from './../../../../data/datasources/remote/repo-implementations/project/project.repository.spec';
8 |
9 | import { CreateProjectUsecase } from "./create-project.usecase";
10 |
11 | describe('CreateProjectUsecase', () => {
12 | let usecase: CreateProjectUsecase;
13 | let iProjectRepository: jasmine.SpyObj;
14 | let project: ProjectEntity;
15 |
16 | beforeEach(() => {
17 | iProjectRepository = jasmine.createSpyObj(IProjectRepository, ['createProject']);
18 | usecase = new CreateProjectUsecase(iProjectRepository);
19 | project = MOCK_PROJECT[0];
20 | });
21 |
22 | describe('constructor', () => {
23 | it('should be created', () => {
24 | expect(usecase).toBeTruthy();
25 | })
26 | it('should be created with dependency', () => {
27 | expect(usecase['iProjectRepository']).toBeTruthy();
28 | })
29 | })
30 |
31 | describe('execute', () => {
32 | it('should accept Param as input', () => {
33 | let usecase: jasmine.SpyObj = jasmine.createSpyObj(CreateProjectUsecase, ['execute']);
34 |
35 | usecase.execute(new Param(project));
36 | const args = usecase.execute.calls.argsFor(0);
37 |
38 | expect(args[0]).toBeInstanceOf(Param);
39 | expect(args[0].payload).toBeDefined;
40 | expect(args[0].payload).toEqual(jasmine.objectContaining(project));
41 | })
42 |
43 | it('should return an Observable ', () => {
44 | spyOn(usecase, 'execute').and.returnValue(of())
45 |
46 | const result = usecase.execute(new Param(project));
47 |
48 | expect(result).toBeInstanceOf(Observable);
49 |
50 | });
51 |
52 | it('should call iProjectRepository.createProject with param', () => {
53 | usecase.execute(new Param(project));
54 |
55 | expect(usecase['iProjectRepository'].createProject).toHaveBeenCalled();
56 | expect(usecase['iProjectRepository'].createProject).toHaveBeenCalledWith(project);
57 | });
58 | })
59 | });
60 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/projects-usecases/create-usecase/create-project.usecase.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 |
4 | import { ProjectEntity } from '../../../entities';
5 | import { Result } from 'src/app/core/types/types';
6 | import { Param } from 'src/app/core/params/param.payload';
7 | import { Usecase } from '../../../../core/contracts/usecase.contract';
8 | import { IProjectRepository } from '../../../repositories/iproject.repository';
9 |
10 |
11 | @Injectable({ providedIn: 'root' })
12 | export class CreateProjectUsecase implements Usecase , Observable> {
13 |
14 | constructor(private iProjectRepository: IProjectRepository) { }
15 |
16 | execute(param: Param): Observable {
17 | return this.iProjectRepository.createProject(param.payload);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/projects-usecases/get-many-usecase/get-many-project.usecase.spec.ts:
--------------------------------------------------------------------------------
1 | import { Observable, of } from "rxjs";
2 |
3 | import { NoParam } from "src/app/core/params/no-param.paylod";
4 | import { MOCK_PROJECT } from "src/app/data/datasources/remote/repo-implementations/project/project.repository.spec";
5 | import { ProjectEntity } from "src/app/domain/entities";
6 | import { IProjectRepository } from "src/app/domain/repositories/iproject.repository";
7 |
8 | import { GetManyProjectUsecase } from "./get-many-project.usecase";
9 |
10 | describe('GetManyProjectUsecase', () => {
11 | let usecase: GetManyProjectUsecase;
12 | let iProjectRepository: jasmine.SpyObj;
13 | let project: ProjectEntity;
14 |
15 | beforeEach(() => {
16 | iProjectRepository = jasmine.createSpyObj(IProjectRepository, ['all']);
17 | usecase = new GetManyProjectUsecase(iProjectRepository);
18 | project = MOCK_PROJECT[0];
19 | });
20 |
21 | describe('constructor', () => {
22 | it('should be created', () => {
23 | expect(usecase).toBeTruthy();
24 | })
25 | it('should be created with dependency', () => {
26 | expect(usecase['iProjectRepository']).toBeTruthy();
27 | })
28 | })
29 |
30 | describe('execute', () => {
31 | it('should accept NoParam as input', () => {
32 | let usecase: jasmine.SpyObj = jasmine.createSpyObj(GetManyProjectUsecase, ['execute']);
33 |
34 | usecase.execute(new NoParam());
35 | const args = usecase.execute.calls.argsFor(0);
36 |
37 | expect(args[0]).toBeInstanceOf(NoParam);
38 | expect(args[0]).toBeDefined;
39 |
40 | })
41 |
42 | it('should return an Observable ', () => {
43 | spyOn(usecase, 'execute').and.returnValue(of())
44 |
45 | const result = usecase.execute(new NoParam());
46 |
47 | expect(result).toBeInstanceOf(Observable);
48 |
49 | });
50 |
51 | it('should call iProjectRepository.all with no param', () => {
52 | usecase.execute(new NoParam());
53 |
54 | expect(usecase['iProjectRepository'].all).toHaveBeenCalled();
55 | expect(usecase['iProjectRepository'].all).toHaveBeenCalledWith();
56 | });
57 | })
58 | });
59 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/projects-usecases/get-many-usecase/get-many-project.usecase.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 | import { NoParam } from '../../../../core/params/no-param.paylod';
4 | import { Usecase } from '../../../../core/contracts/usecase.contract';
5 | import { ProjectEntity } from '../../../entities';
6 | import { IProjectRepository } from '../../../repositories/iproject.repository';
7 |
8 | @Injectable({ providedIn: 'root' })
9 | export class GetManyProjectUsecase implements Usecase> {
10 |
11 | constructor(private iProjectRepository: IProjectRepository) { }
12 |
13 | execute(payload: NoParam): Observable {
14 | return this.iProjectRepository.all();
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/projects-usecases/get-one-usecase/get-one-project.usecase.spec.ts:
--------------------------------------------------------------------------------
1 | import { Observable, of } from "rxjs";
2 |
3 | import { Param } from "src/app/core/params/param.payload";
4 | import { MOCK_PROJECT } from "src/app/data/datasources/remote/repo-implementations/project/project.repository.spec";
5 | import { ProjectEntity } from "src/app/domain/entities";
6 | import { IProjectRepository } from "src/app/domain/repositories/iproject.repository";
7 |
8 | import { GetOneProjectUsecase } from "./get-one-project.usecase";
9 |
10 | describe('GetOneProjectUsecase', () => {
11 | let usecase: GetOneProjectUsecase;
12 | let iProjectRepository: jasmine.SpyObj;
13 | let project: ProjectEntity;
14 |
15 | beforeEach(() => {
16 | iProjectRepository = jasmine.createSpyObj(IProjectRepository, ['getProject']);
17 | usecase = new GetOneProjectUsecase(iProjectRepository);
18 | project = MOCK_PROJECT[0];
19 | });
20 |
21 | describe('constructor', () => {
22 | it('should be created', () => {
23 | expect(usecase).toBeTruthy();
24 | })
25 | it('should be created with dependency', () => {
26 | expect(usecase['iProjectRepository']).toBeTruthy();
27 | })
28 | })
29 |
30 | describe('execute', () => {
31 | it('should accept Param as input', () => {
32 | let usecase: jasmine.SpyObj = jasmine.createSpyObj(GetOneProjectUsecase, ['execute']);
33 |
34 | usecase.execute(new Param(project.id!));
35 | const args = usecase.execute.calls.argsFor(0);
36 |
37 | expect(args[0]).toBeInstanceOf(Param);
38 | expect(args[0]).toBeDefined;
39 | expect(args[0].payload).toEqual(jasmine.stringContaining(project.id!));
40 | })
41 |
42 | it('should return an Observable ', () => {
43 | spyOn(usecase, 'execute').and.returnValue(of())
44 |
45 | const result = usecase.execute(new Param(project.id!));
46 |
47 | expect(result).toBeInstanceOf(Observable);
48 | });
49 |
50 | it('should call iProjectRepository.getProject with param', () => {
51 | usecase.execute(new Param(project.id!));
52 |
53 | expect(usecase['iProjectRepository'].getProject).toHaveBeenCalled();
54 | expect(usecase['iProjectRepository'].getProject).toHaveBeenCalledWith(project.id!);
55 | });
56 | })
57 | })
58 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/projects-usecases/get-one-usecase/get-one-project.usecase.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 |
4 | import { Param } from '../../../../core/params/param.payload';
5 | import { Usecase } from '../../../../core/contracts/usecase.contract';
6 | import { ProjectEntity } from '../../../entities';
7 | import { IProjectRepository } from '../../../repositories/iproject.repository';
8 |
9 | @Injectable({ providedIn: 'root' })
10 | export class GetOneProjectUsecase implements Usecase , Observable> {
11 |
12 | constructor(private iProjectRepository: IProjectRepository) { }
13 |
14 | execute(param: Param): Observable {
15 | return this.iProjectRepository.getProject(param.payload);
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/projects-usecases/remove-usecase/remove-project.usecase.spec.ts:
--------------------------------------------------------------------------------
1 | import { Observable, of } from "rxjs";
2 | import { Param } from "src/app/core/params/param.payload";
3 | import { MOCK_PROJECT } from "src/app/data/datasources/remote/repo-implementations/project/project.repository.spec";
4 | import { ProjectEntity } from "src/app/domain/entities";
5 | import { IProjectRepository } from "src/app/domain/repositories/iproject.repository";
6 | import { RemoveProjectUsecase } from "./remove-project.usecase";
7 |
8 | describe('RemoveProjectUsecase', () => {
9 | let usecase: RemoveProjectUsecase;
10 | let iProjectRepository: jasmine.SpyObj;
11 | let project: ProjectEntity;
12 |
13 | beforeEach(() => {
14 | iProjectRepository = jasmine.createSpyObj(IProjectRepository, ['removeProject']);
15 | usecase = new RemoveProjectUsecase(iProjectRepository);
16 | project = MOCK_PROJECT[0];
17 | });
18 |
19 | describe('constructor', () => {
20 | it('should be created', () => {
21 | expect(usecase).toBeTruthy();
22 | })
23 | it('should be created with dependency', () => {
24 | expect(usecase['iProjectRepository']).toBeTruthy();
25 | })
26 | })
27 |
28 | describe('execute', () => {
29 | it('should accept Param as input', () => {
30 | let usecase: jasmine.SpyObj = jasmine.createSpyObj(RemoveProjectUsecase, ['execute']);
31 |
32 | usecase.execute(new Param(project.id!));
33 | const args = usecase.execute.calls.argsFor(0);
34 |
35 | expect(args[0]).toBeInstanceOf(Param);
36 | expect(args[0]).toBeDefined;
37 | expect(args[0].payload).toEqual(jasmine.stringContaining(project.id!));
38 | })
39 |
40 | it('should return an Observable ', () => {
41 | spyOn(usecase, 'execute').and.returnValue(of())
42 |
43 | const result = usecase.execute(new Param(project.id!));
44 |
45 | expect(result).toBeInstanceOf(Observable);
46 | });
47 |
48 | it('should call iProjectRepository.removeProject with param', () => {
49 | usecase.execute(new Param(project.id!));
50 |
51 | expect(usecase['iProjectRepository'].removeProject).toHaveBeenCalled();
52 | expect(usecase['iProjectRepository'].removeProject).toHaveBeenCalledWith(project.id!);
53 | });
54 | })
55 | })
56 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/projects-usecases/remove-usecase/remove-project.usecase.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Observable } from 'rxjs';
3 | import { Param } from 'src/app/core/params/param.payload';
4 | import { Result } from 'src/app/core/types/types';
5 | import { Usecase } from '../../../../core/contracts/usecase.contract';
6 | import { IProjectRepository } from '../../../repositories/iproject.repository';
7 |
8 | @Injectable({ providedIn: 'root' })
9 | export class RemoveProjectUsecase implements Usecase , Observable> {
10 |
11 | constructor(private iProjectRepository: IProjectRepository) { }
12 |
13 | execute(param: Param): Observable {
14 | return this.iProjectRepository.removeProject(param.payload);
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/projects-usecases/update-usecase/update-project.usecase.spec.ts:
--------------------------------------------------------------------------------
1 | import { Observable, of } from "rxjs";
2 | import { Param } from "src/app/core/params/param.payload";
3 | import { MOCK_PROJECT } from "src/app/data/datasources/remote/repo-implementations/project/project.repository.spec";
4 | import { ProjectEntity } from "src/app/domain/entities";
5 | import { IProjectRepository } from "src/app/domain/repositories/iproject.repository";
6 | import { UpdateProjectUsecase } from "./update-project.usecase";
7 |
8 | describe('UpdateProjectUsecase', () => {
9 | let usecase: UpdateProjectUsecase;
10 | let iProjectRepository: jasmine.SpyObj;
11 | let project: ProjectEntity;
12 |
13 | beforeEach(() => {
14 | iProjectRepository = jasmine.createSpyObj(IProjectRepository, ['updateProject']);
15 | usecase = new UpdateProjectUsecase(iProjectRepository);
16 | project = MOCK_PROJECT[0];
17 | });
18 |
19 | describe('constructor', () => {
20 | it('should be created', () => {
21 | expect(usecase).toBeTruthy();
22 | })
23 | it('should be created with dependency', () => {
24 | expect(usecase['iProjectRepository']).toBeTruthy();
25 | })
26 | })
27 |
28 | describe('execute', () => {
29 | it('should accept Param as input', () => {
30 | let usecase: jasmine.SpyObj = jasmine.createSpyObj(UpdateProjectUsecase, ['execute']);
31 |
32 | usecase.execute(new Param(project));
33 | const args = usecase.execute.calls.argsFor(0);
34 |
35 | expect(args[0]).toBeInstanceOf(Param);
36 | expect(args[0]).toBeDefined;
37 | expect(args[0].payload).toEqual(jasmine.objectContaining(project));
38 | })
39 |
40 | it('should return an Observable ', () => {
41 | spyOn(usecase, 'execute').and.returnValue(of())
42 |
43 | const result = usecase.execute(new Param(project));
44 |
45 | expect(result).toBeInstanceOf(Observable);
46 | });
47 |
48 | it('should call iProjectRepository.updateProject with param', () => {
49 | const { id, ...rest } = project;
50 | usecase.execute(new Param(project));
51 |
52 | expect(usecase['iProjectRepository'].updateProject).toHaveBeenCalled();
53 | expect(usecase['iProjectRepository'].updateProject).toHaveBeenCalledWith(id!, rest);
54 | });
55 | })
56 | })
57 |
--------------------------------------------------------------------------------
/src/app/domain/usecases/projects-usecases/update-usecase/update-project.usecase.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from 'rxjs';
2 | import { Injectable } from '@angular/core';
3 |
4 | import { ProjectEntity } from '../../../entities';
5 | import { Result } from '../../../../core/types/types';
6 | import { Param } from '../../../../core/params/param.payload';
7 | import { Usecase } from '../../../../core/contracts/usecase.contract';
8 | import { IProjectRepository } from '../../../repositories/iproject.repository';
9 |
10 |
11 | @Injectable({ providedIn: 'root' })
12 | export class UpdateProjectUsecase implements Usecase , Observable>{
13 |
14 | constructor(private iProjectRepository: IProjectRepository) { }
15 |
16 | execute(param: Param): Observable {
17 | const { id, ...project } = param.payload;
18 | return this.iProjectRepository.updateProject(id!, project);
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/app/presenter/components/components.module.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { ComponentModule } from './components.module';
4 | describe('ComponentModule', () => {
5 | beforeEach(() => {
6 | TestBed.configureTestingModule({
7 | imports: [ComponentModule]
8 | })
9 | })
10 |
11 | it('should initialize', () => {
12 | const module = TestBed.inject(ComponentModule);
13 |
14 | expect(module).toBeTruthy();
15 | })
16 | })
17 |
--------------------------------------------------------------------------------
/src/app/presenter/components/layouts/editor-header/editor-header.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/presenter/components/layouts/editor-header/editor-header.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 | import { By } from '@angular/platform-browser';
3 | import { RouterModule } from '@angular/router';
4 |
5 | import { LinkComponent } from './../../shared/link/link.component';
6 |
7 | import { EditorHeaderComponent } from './editor-header.component';
8 | describe('EditorHeaderComponent', () => {
9 | let fixture: ComponentFixture;
10 | let component: EditorHeaderComponent;
11 |
12 | beforeEach(async () => {
13 | await TestBed.configureTestingModule({
14 | declarations: [EditorHeaderComponent, LinkComponent],
15 | providers: [],
16 | imports: [
17 | RouterModule.forRoot([])
18 | ]
19 | }).compileComponents();
20 |
21 | fixture = TestBed.createComponent(EditorHeaderComponent);
22 | component = fixture.componentInstance;
23 | fixture.detectChanges();
24 | });
25 |
26 | it('should create', () => {
27 | expect(component).toBeTruthy();
28 | })
29 |
30 | it('should have 3 LinkComponents', () => {
31 | const { debugElement } = fixture;
32 |
33 | const linkComponents = debugElement.queryAll(By.directive(LinkComponent));
34 |
35 | expect(linkComponents.length).toBe(3);
36 | })
37 | })
38 |
--------------------------------------------------------------------------------
/src/app/presenter/components/layouts/editor-header/editor-header.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-editor-header',
5 | templateUrl: './editor-header.component.html',
6 | styles: []
7 | })
8 | export class EditorHeaderComponent {
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/presenter/components/layouts/editor-layout/editor-layout.component.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/app/presenter/components/layouts/editor-layout/editor-layout.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { By } from '@angular/platform-browser';
2 | import { RouterModule } from '@angular/router';
3 | import { ComponentFixture, TestBed } from '@angular/core/testing';
4 |
5 | import { EditorHeaderComponent } from './../editor-header/editor-header.component';
6 | import { LinkComponent } from './../../shared/link/link.component';
7 |
8 | import { EditorLayoutComponent } from './editor-layout.component';
9 |
10 | describe('EditorLayoutComponent', () => {
11 | let component: EditorLayoutComponent;
12 | let fixture: ComponentFixture;
13 |
14 | beforeEach(async () => {
15 | await TestBed.configureTestingModule({
16 | declarations: [EditorLayoutComponent, EditorHeaderComponent, LinkComponent],
17 | imports: [RouterModule.forRoot([])]
18 | })
19 | .compileComponents();
20 |
21 | fixture = TestBed.createComponent(EditorLayoutComponent);
22 | component = fixture.componentInstance;
23 | fixture.detectChanges();
24 | });
25 |
26 | it('should create', () => {
27 | expect(component).toBeTruthy();
28 | });
29 |
30 | it('should create headerComponent', () => {
31 |
32 | const { debugElement } = fixture;
33 | const header = debugElement.query(By.directive(EditorHeaderComponent)).nativeElement;
34 |
35 | expect(header).toBeTruthy();
36 | })
37 | });
38 |
--------------------------------------------------------------------------------
/src/app/presenter/components/layouts/editor-layout/editor-layout.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-editor-layout',
5 | templateUrl: './editor-layout.component.html',
6 | styles: [
7 | ]
8 | })
9 | export class EditorLayoutComponent {
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/app/presenter/components/layouts/footer/footer.component.html:
--------------------------------------------------------------------------------
1 |
34 |
--------------------------------------------------------------------------------
/src/app/presenter/components/layouts/footer/footer.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { FooterComponent } from './footer.component';
4 |
5 | describe('FooterComponent', () => {
6 | let component: FooterComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [FooterComponent]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(FooterComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/src/app/presenter/components/layouts/footer/footer.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-footer',
5 | templateUrl: './footer.component.html',
6 | styles: [
7 | ]
8 | })
9 | export class FooterComponent {
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/app/presenter/components/layouts/header/header.component.html:
--------------------------------------------------------------------------------
1 |
43 |
--------------------------------------------------------------------------------
/src/app/presenter/components/layouts/header/header.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { By } from '@angular/platform-browser';
2 | import { RouterModule } from '@angular/router';
3 | import { ComponentFixture, TestBed } from '@angular/core/testing';
4 |
5 | import { ThemeSwitcherComponent } from './../../shared/theme-switcher/theme-switcher.component';
6 | import { DarkThemeSwitchComponent } from './../../shared/theme-switcher/dark-theme-switch/dark-theme-switch.component';
7 |
8 | import { HeaderComponent } from './header.component';
9 |
10 | describe('HeaderComponent', () => {
11 | let component: HeaderComponent;
12 | let fixture: ComponentFixture;
13 |
14 | beforeEach(async () => {
15 | await TestBed.configureTestingModule({
16 | declarations: [HeaderComponent, ThemeSwitcherComponent, DarkThemeSwitchComponent],
17 | providers: [],
18 | imports: [RouterModule.forRoot([])]
19 | })
20 | .compileComponents();
21 |
22 | fixture = TestBed.createComponent(HeaderComponent);
23 | component = fixture.componentInstance;
24 | fixture.detectChanges();
25 | });
26 |
27 | it('should create', () => {
28 | expect(component).toBeTruthy();
29 | });
30 |
31 | it('should load theme switcher component', () => {
32 | const { debugElement } = fixture;
33 |
34 | const switcherElement = debugElement.query(By.directive(ThemeSwitcherComponent));
35 |
36 | expect(switcherElement).toBeTruthy();
37 | })
38 | });
39 |
--------------------------------------------------------------------------------
/src/app/presenter/components/layouts/header/header.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-header',
5 | templateUrl: './header.component.html',
6 | styles: [
7 | ]
8 | })
9 | export class HeaderComponent {
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/button/button.component.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
7 |
8 |
10 |
11 |
12 |
13 | {{props.text ?? 'New'}}
14 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/button/button.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { ButtonComponent } from './button.component';
4 |
5 | describe('ButtonComponent', () => {
6 | let component: ButtonComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [ButtonComponent]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(ButtonComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 |
24 | describe('props', () => {
25 | let props: {
26 | text?: string,
27 | type?: string,
28 | loading?: boolean,
29 | }
30 | beforeEach(() => {
31 | props = {
32 | text: 'Fake Button',
33 | type: 'button',
34 | loading: false,
35 | };
36 | })
37 |
38 | it('should accept ButtonProps as input', () => {
39 |
40 | component.props = props;
41 |
42 | expect(component.props).toBeTruthy();
43 |
44 | expect(component.props).toEqual(props);
45 |
46 | })
47 | })
48 |
49 | describe('action', () => {
50 |
51 | let action: () => void;
52 |
53 | beforeEach(() => {
54 | action = () => { }
55 | component.action = action;
56 | })
57 |
58 | it('should accept a ButtonAction as input', () => {
59 | expect(component.action).toEqual(action);
60 | })
61 | })
62 | });
63 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/button/button.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 |
3 | export type ButtonProps = {
4 | text?: string,
5 | type?: string,
6 | loading?: boolean,
7 | }
8 |
9 |
10 | @Component({
11 | selector: 'app-button',
12 | templateUrl: './button.component.html',
13 | styles: [
14 | ]
15 | })
16 | export class ButtonComponent {
17 |
18 | @Input()
19 | props: ButtonProps = { type: 'button', loading: false };
20 |
21 | @Input()
22 | action: () => void = () => { }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/form-field/form-field.component.html:
--------------------------------------------------------------------------------
1 |
2 | {{props?.controlName}}:
3 |
4 |
5 |
10 |
11 |
12 |
13 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/form-field/form-field.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { By } from '@angular/platform-browser';
2 | import { FormGroup, ReactiveFormsModule, FormControl } from '@angular/forms';
3 | import { ComponentFixture, TestBed } from '@angular/core/testing';
4 |
5 | import { FormFieldComponent } from './form-field.component';
6 |
7 | describe('FormFieldComponent', () => {
8 | let component: FormFieldComponent;
9 | let fixture: ComponentFixture;
10 |
11 | beforeEach(async () => {
12 | await TestBed.configureTestingModule({
13 | declarations: [FormFieldComponent],
14 | imports: [ReactiveFormsModule]
15 | }).compileComponents();
16 |
17 | fixture = TestBed.createComponent(FormFieldComponent);
18 | component = fixture.componentInstance;
19 |
20 | component.props = {
21 | formGroup: new FormGroup({
22 | FakeInput: new FormControl(),
23 | }),
24 | controlName: "FakeInput",
25 | isValid: (arg: string) => { },
26 | };
27 | fixture.detectChanges();
28 | });
29 |
30 | it('should create', () => {
31 | expect(component).toBeTruthy();
32 | });
33 |
34 | describe('Input props', () => {
35 |
36 | it('should receive props as Input', () => {
37 | const props = component.props;
38 |
39 | expect(props).toBeDefined();
40 | expect(props?.controlName).toEqual('FakeInput');
41 | });
42 |
43 | it('should correctly render the passed @Input value', () => {
44 | const { debugElement } = fixture;
45 |
46 | const label = debugElement.nativeElement.querySelector('label');
47 |
48 | expect(label.innerText).toContain(component.props?.controlName);
49 | });
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/form-field/form-field.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 | import { FormGroup } from '@angular/forms';
3 |
4 | export type FormFieldProps = {
5 | controlName: string,
6 | isValid: (arg: string) => void,
7 | formGroup: FormGroup,
8 | type?: string
9 | }
10 |
11 | @Component({
12 | selector: 'app-form-field',
13 | templateUrl: './form-field.component.html',
14 | styles: [
15 | ]
16 | })
17 | export class FormFieldComponent {
18 |
19 | @Input()
20 | props?: FormFieldProps;
21 | }
22 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/link/link.component.html:
--------------------------------------------------------------------------------
1 |
2 |
5 | {{props?.title}}
6 |
7 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/link/link.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { RouterModule } from '@angular/router';
2 | import { ComponentFixture, TestBed } from '@angular/core/testing';
3 |
4 | import { LinkComponent } from './link.component';
5 |
6 | describe('LinkComponent', () => {
7 | let component: LinkComponent;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(async () => {
11 | await TestBed.configureTestingModule({
12 | declarations: [LinkComponent],
13 | imports: [RouterModule.forRoot([])],
14 | }).compileComponents();
15 |
16 | fixture = TestBed.createComponent(LinkComponent);
17 | component = fixture.componentInstance;
18 | component.props = { link: 'fakelink.cm', title: 'FakeTitle' };
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 |
26 | describe('@Input Props', () => {
27 | it('should receive props as Input', () => {
28 | expect(component.props).toBeDefined();
29 | expect(component.props?.title).toContain('Fake');
30 | });
31 |
32 | it('should render the Input props value correctly', () => {
33 | const { debugElement } = fixture;
34 |
35 | const anchor = debugElement.nativeElement.querySelector('a');
36 |
37 | expect(anchor.innerText).toEqual(component.props?.title);
38 | expect(anchor.href).toContain(component.props?.link);
39 | });
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/link/link.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-link',
5 | templateUrl: './link.component.html',
6 | styles: [
7 | ]
8 | })
9 | export class LinkComponent {
10 |
11 | @Input()
12 | props?: { link: string, title: string };
13 | }
14 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/modal/modal.component.html:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 |
9 | {{title}}
10 |
11 |
14 |
16 |
19 |
20 | Close modal
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/modal/modal.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { ModalService } from 'src/app/presenter/components/shared/modal/modal.service';
4 |
5 | import { ModalComponent } from './modal.component';
6 |
7 | describe('ModalComponent', () => {
8 | let component: ModalComponent;
9 | let fixture: ComponentFixture>;
10 | let modalService: jasmine.SpyObj>;
11 |
12 | let serviceProps = {
13 | title: 'FakeModalTitle',
14 | };
15 |
16 | beforeEach(() => {
17 | modalService = jasmine.createSpyObj(ModalService, [
18 | 'close',
19 | 'open',
20 | 'resolveComponent',
21 | 'appendComponentToHtmlBody',
22 | ]);
23 |
24 | modalService['title'] = serviceProps.title;
25 |
26 | TestBed.configureTestingModule({
27 | declarations: [ModalComponent],
28 | providers: [{ provide: ModalService, useValue: modalService }],
29 | }).compileComponents();
30 |
31 | fixture = TestBed.createComponent(ModalComponent);
32 | component = fixture.componentInstance;
33 |
34 | fixture.detectChanges();
35 | });
36 |
37 | it('should create', () => {
38 | expect(component).toBeTruthy();
39 | });
40 |
41 | describe('constructor', () => {
42 | it('should set title property', () => {
43 | expect(component.title).toEqual(serviceProps.title);
44 | });
45 | });
46 |
47 | describe('close', () => {
48 | it('should set display property to false', () => {
49 | component.close();
50 |
51 | expect(component.display).toBeFalse();
52 | });
53 |
54 | it('should call service.close method', async () => {
55 | component.close();
56 |
57 | expect(modalService.close).toHaveBeenCalled();
58 | });
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/modal/modal.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { ModalService } from './modal.service';
4 |
5 | @Component({
6 | selector: 'app-modal',
7 | templateUrl: './modal.component.html',
8 | styles: [
9 | `
10 | section {
11 | visibility: hidden;
12 | opacity: 0;
13 | transition: opacity 250ms ease-in;
14 | }
15 | section.open {
16 | visibility: inherit;
17 | opacity: 1;
18 | }
19 | `,
20 | ],
21 | })
22 | export class ModalComponent {
23 | public display = true;
24 | public title = '';
25 |
26 | public constructor(private service: ModalService) {
27 | this.title = service.title!;
28 | }
29 |
30 | public close = () => {
31 | this.display = false;
32 | this.service.close();
33 | };
34 | }
35 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/modal/modal.service.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ApplicationRef,
3 | ComponentFactoryResolver,
4 | ComponentRef,
5 | EmbeddedViewRef,
6 | Injectable,
7 | Injector,
8 | Type,
9 | } from '@angular/core';
10 |
11 | export type ComponentInput = {
12 | inputTitle: string;
13 | inputValue: U;
14 | };
15 |
16 | export type ComponentParam = {
17 | component: Type;
18 | modalTitle?: string;
19 | };
20 |
21 | @Injectable({
22 | providedIn: 'root',
23 | })
24 | export class ModalService {
25 | public title?: string;
26 | private componentRef?: ComponentRef;
27 |
28 | constructor(
29 | private componentFactoryResolver: ComponentFactoryResolver,
30 | private appRef: ApplicationRef,
31 | private injector: Injector
32 | ) {}
33 |
34 | public async close(): Promise {
35 | if (!this.componentRef) return;
36 |
37 | window.setTimeout(() => {
38 | if (this.componentRef) {
39 | this.appRef.detachView(this.componentRef.hostView);
40 | this.componentRef.destroy();
41 | this.componentRef = undefined;
42 | }
43 | }, 1000);
44 | }
45 |
46 | public async open(
47 | param: ComponentParam,
48 | input?: ComponentInput
49 | ): Promise {
50 | if (this.componentRef) return;
51 |
52 | this.title = param.modalTitle;
53 | this.resolveComponent(param.component, input);
54 | this.appendComponentToHtmlBody();
55 | }
56 |
57 | private resolveComponent(
58 | component: Type,
59 | input?: ComponentInput
60 | ): void {
61 | // use componentFactoryResolver to create component
62 | this.componentRef = this.componentFactoryResolver
63 | .resolveComponentFactory(component)
64 | .create(this.injector);
65 | this.appRef.attachView(this.componentRef.hostView);
66 |
67 | // set a default input value in the component before modal opens
68 | if (input)
69 | this.componentRef.setInput(input.inputTitle, input?.inputValue);
70 | }
71 |
72 | private appendComponentToHtmlBody(): void {
73 | const domElement = (this.componentRef?.hostView as EmbeddedViewRef)
74 | .rootNodes[0] as HTMLElement;
75 | document.body.appendChild(domElement);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/not-found/not-found.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
Sorry, we couldn't find this page.
8 |
But don't worry, you can find plenty of other things on our homepage.
9 |
10 |
Back to homepage
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/not-found/not-found.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { NotFoundComponent } from './not-found.component';
4 |
5 | describe('NotFoundComponent', () => {
6 | let component: NotFoundComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [NotFoundComponent],
12 | }).compileComponents();
13 |
14 | fixture = TestBed.createComponent(NotFoundComponent);
15 | component = fixture.componentInstance;
16 | fixture.detectChanges();
17 | });
18 |
19 | it('should create', () => {
20 | expect(component).toBeTruthy();
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/not-found/not-found.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-not-found',
5 | templateUrl: './not-found.component.html',
6 | })
7 | export class NotFoundComponent {}
8 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/skeleton-loader/blog-skeleton/blog-skeleton.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
19 |
24 |
25 |
Loading...
26 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/skeleton-loader/blog-skeleton/blog-skeleton.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { BlogSkeletonComponent } from './blog-skeleton.component';
4 |
5 | describe('BlogSkeletonComponent', () => {
6 | let component: BlogSkeletonComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [ BlogSkeletonComponent ]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(BlogSkeletonComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/skeleton-loader/blog-skeleton/blog-skeleton.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-blog-skeleton',
5 | templateUrl: './blog-skeleton.component.html',
6 | styles: [
7 | ]
8 | })
9 | export class BlogSkeletonComponent {
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/skeleton-loader/bookmarks-skeleton/bookmarks-skeleton.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
14 |
15 |
Loading...
16 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/skeleton-loader/bookmarks-skeleton/bookmarks-skeleton.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { BookmarksSkeletonComponent } from './bookmarks-skeleton.component';
4 |
5 | describe('BookmarksSkeletonComponent', () => {
6 | let component: BookmarksSkeletonComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [ BookmarksSkeletonComponent ]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(BookmarksSkeletonComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/skeleton-loader/bookmarks-skeleton/bookmarks-skeleton.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-bookmarks-skeleton',
5 | templateUrl: './bookmarks-skeleton.component.html',
6 | styles: [
7 | ]
8 | })
9 | export class BookmarksSkeletonComponent {
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/skeleton-loader/projects-skeleton/projects-skeleton.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
14 |
15 |
Loading...
16 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/skeleton-loader/projects-skeleton/projects-skeleton.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { ProjectsSkeletonComponent } from './projects-skeleton.component';
4 |
5 | describe('ProjectsSkeletonComponent', () => {
6 | let component: ProjectsSkeletonComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [ ProjectsSkeletonComponent ]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(ProjectsSkeletonComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/skeleton-loader/projects-skeleton/projects-skeleton.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-projects-skeleton',
5 | templateUrl: './projects-skeleton.component.html',
6 | styles: [
7 | ]
8 | })
9 | export class ProjectsSkeletonComponent {
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/skeleton-loader/skeleton-loader.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
10 |
11 |
14 |
15 |
19 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/skeleton-loader/skeleton-loader.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { SkeletonLoaderComponent } from './skeleton-loader.component';
4 |
5 | describe('SkeletonLoaderComponent', () => {
6 | let component: SkeletonLoaderComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [ SkeletonLoaderComponent ]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(SkeletonLoaderComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/skeleton-loader/skeleton-loader.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 |
3 | export enum PageEnum {
4 | blog,
5 | project,
6 | bookmark,
7 | }
8 |
9 | @Component({
10 | selector: 'app-skeleton-loader',
11 | templateUrl: './skeleton-loader.component.html',
12 | })
13 | export class SkeletonLoaderComponent {
14 |
15 | @Input()
16 | public pageEnum!: PageEnum;
17 |
18 | public blog = PageEnum.blog;
19 | public project = PageEnum.project;
20 | public bookmark = PageEnum.bookmark;
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/table/table.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{col.title}}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
15 |
16 |
17 |
19 | {{item[alias] }}
20 |
21 |
22 |
23 |
24 |
25 |
27 | {{title }}
28 |
29 |
30 |
31 |
32 |
33 |
34 | {{date}}
35 |
36 |
37 |
38 |
39 |
40 |
41 | Edit
43 | Remove
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | No data yet
55 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/table/table.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { TableComponent } from './table.component';
4 |
5 | describe('TableComponent', () => {
6 | let component: TableComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [ TableComponent ]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(TableComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/table/table.component.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from 'rxjs';
2 | import { Component, Input, OnInit } from '@angular/core';
3 |
4 | export type TableData = {
5 | _id: string,
6 | title: string,
7 | date?: string,
8 | }
9 |
10 | type TableAction = {
11 | onRemove: (id: string, data?: T[]) => void;
12 | onEdit: (id: string, data?: T) => void;
13 | };
14 |
15 | export type Table = {
16 |
17 | cols?: { title: string }[],
18 | data$?: Observable,
19 | action?: TableAction,
20 | alias?: string
21 | }
22 |
23 |
24 | @Component({
25 | selector: 'app-table',
26 | templateUrl: './table.component.html',
27 | styles: [
28 | ]
29 | })
30 | export class TableComponent {
31 |
32 | @Input()
33 | payload?: Table;
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/tag/tag.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { TagComponent } from './tag.component';
4 |
5 | describe('TagComponent', () => {
6 | let component: TagComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [ TagComponent ]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(TagComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/tag/tag.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-tag',
5 | template: `
6 |
9 | {{text}}
10 |
11 | `
12 | })
13 | export class TagComponent {
14 | @Input()
15 | public text: string = '';
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/theme-switcher/dark-theme-switch/dark-theme-switch.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-dark-switch',
5 | template: `
6 |
8 |
9 | `
10 | })
11 | export class DarkThemeSwitchComponent {
12 | }
13 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/theme-switcher/light-theme-switch/light-theme-switch.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-light-switch',
5 | template: `
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | `
21 | })
22 | export class LightThemeSwitchComponent {
23 | }
24 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/theme-switcher/theme-switcher.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Theme
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/theme-switcher/theme-switcher.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { ThemeSwitcherComponent } from './theme-switcher.component';
4 |
5 | import { DarkThemeSwitchComponent } from './dark-theme-switch/dark-theme-switch.component';
6 | import { LightThemeSwitchComponent } from './light-theme-switch/light-theme-switch.component';
7 |
8 | describe('ThemeSwitcherComponent', () => {
9 | let component: ThemeSwitcherComponent;
10 | let fixture: ComponentFixture;
11 |
12 | beforeEach(async () => {
13 | await TestBed.configureTestingModule({
14 | declarations: [
15 | ThemeSwitcherComponent,
16 | DarkThemeSwitchComponent,
17 | LightThemeSwitchComponent,
18 | ],
19 | }).compileComponents();
20 |
21 | fixture = TestBed.createComponent(ThemeSwitcherComponent);
22 | component = fixture.componentInstance;
23 | fixture.detectChanges();
24 | });
25 |
26 | it('should create', () => {
27 | expect(component).toBeTruthy();
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/theme-switcher/theme-switcher.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | import { ThemeSwitcherService } from './theme-switcher.service';
4 |
5 | @Component({
6 | selector: 'app-theme-switcher',
7 | templateUrl: './theme-switcher.component.html',
8 | styles: []
9 | })
10 | export class ThemeSwitcherComponent implements OnInit {
11 |
12 | public isDarkTheme?: boolean;
13 |
14 | public constructor(private themeService: ThemeSwitcherService) { }
15 |
16 | ngOnInit(): void {
17 | this.themeService
18 | .pref$.subscribe(res => this.isDarkTheme = res === 'dark');
19 | }
20 |
21 | public toggleTheme = () => {
22 | this.isDarkTheme = !this.isDarkTheme;
23 | this.themeService.updateThemePref(this.isDarkTheme ? 'dark' : 'light');
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/theme-switcher/theme-switcher.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from "@angular/core";
2 |
3 | import { BehaviorSubject, Observable } from "rxjs";
4 |
5 |
6 | @Injectable({
7 | providedIn: 'root'
8 | })
9 | export class ThemeSwitcherService {
10 | public pref$: Observable;
11 | private pref: BehaviorSubject;
12 | private prefKey = 'THEME_PREF';
13 |
14 | constructor() {
15 | this.pref = new BehaviorSubject(this.getThemePref());
16 | this.pref$ = this.pref.asObservable();
17 | }
18 |
19 | public updateThemePref(value: 'dark' | 'light'): void {
20 | if (value) {
21 | localStorage.setItem(this.prefKey, value);
22 | this.pref.next(value);
23 | }
24 | }
25 |
26 | private getThemePref(): string {
27 | const localPref = localStorage.getItem(this.prefKey);
28 | return localPref || this.pref?.value;
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/toast/toast.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
8 |
9 |
10 | {{toast.title}}
11 |
12 |
15 |
16 |
17 | {{toast.message}}
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/toast/toast.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { ToastComponent } from './toast.component';
4 |
5 | describe('ToastComponent', () => {
6 | let component: ToastComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [ ToastComponent ]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(ToastComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/toast/toast.component.ts:
--------------------------------------------------------------------------------
1 | import { animate, style, transition, trigger } from '@angular/animations';
2 | import { Component, OnInit } from '@angular/core';
3 | import { Observable } from 'rxjs';
4 | import { Toast, ToastService, ToastType } from './toast.service';
5 |
6 | @Component({
7 | selector: 'app-toast',
8 | templateUrl: './toast.component.html',
9 | animations: [
10 | trigger(
11 | 'inOutAnimation',
12 | [
13 | transition(
14 | ':enter',
15 | [
16 | style({ left: 0, opacity: 0 }),
17 | animate('1s ease-out',
18 | style({ left: 20, opacity: 1 }))
19 | ]
20 | ),
21 | transition(
22 | ':leave',
23 | [
24 | style({ left: 20, opacity: 1 }),
25 | animate('1s ease-in',
26 | style({ left: 0, opacity: 0 }))
27 | ]
28 | )
29 | ]
30 | )
31 | ]
32 | })
33 | export class ToastComponent implements OnInit {
34 |
35 | public toast$?: Observable
36 |
37 | public constructor(private toastService: ToastService) { }
38 |
39 | ngOnInit(): void {
40 | this.toast$ = this.toastService.listen$;
41 | }
42 |
43 | public color(toastType: ToastType): string {
44 | switch (toastType) {
45 | case ToastType.info:
46 | return `bg-blue-500`;
47 | case ToastType.warning:
48 | return `bg-yellow-500`;
49 | case ToastType.success:
50 | return `bg-green-500`;
51 | case ToastType.error:
52 | return `bg-red-500`;
53 | default:
54 | return `bg-green-500`;
55 | }
56 | }
57 |
58 | public close() {
59 | this.toastService.close();
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/src/app/presenter/components/shared/toast/toast.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from "@angular/core";
2 | import { BehaviorSubject, Observable } from "rxjs";
3 |
4 | export enum ToastType {
5 | warning,
6 | success,
7 | info,
8 | error
9 | }
10 |
11 | export type Toast = {
12 | isOpen: boolean,
13 | type?: ToastType,
14 | title?: string,
15 | message?: string,
16 | }
17 |
18 | @Injectable({
19 | providedIn: 'root'
20 | })
21 | export class ToastService {
22 |
23 | public listen$: Observable;
24 | private listen: BehaviorSubject
25 | private options = { autoClose: true, location: 'top-right' };
26 |
27 | constructor() {
28 | this.listen = new BehaviorSubject({ type: ToastType.info, isOpen: false });
29 | this.listen$ = this.listen.asObservable();
30 | }
31 |
32 | public show(args: { title: string, message?: string, type?: ToastType }): void {
33 | this.listen.next({
34 | isOpen: true,
35 | title: args.title,
36 | type: args.type ?? ToastType.info,
37 | message: args.message ?? ''
38 | });
39 | setTimeout(() => this.close(), 5000)
40 | }
41 |
42 | public close(): void {
43 | this.listen.next({ isOpen: false })
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/blog/blog.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Blog
4 |
5 | Welcome to my blog section.
6 |
7 |
8 |
9 |
10 |
11 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/blog/blog.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { IPostInteractor } from 'src/app/data/interactors/contracts/ipost.interactor';
4 |
5 | import { PageSeoService } from '../page-seo.service';
6 | import { SkeletonLoaderComponent } from './../../components/shared/skeleton-loader/skeleton-loader.component';
7 | import { BlogSkeletonComponent } from './../../components/shared/skeleton-loader/blog-skeleton/blog-skeleton.component';
8 |
9 | import { BlogComponent } from './blog.component';
10 |
11 | describe('BlogComponent', () => {
12 | let component: BlogComponent;
13 | let fixture: ComponentFixture;
14 | let interactor: jasmine.SpyObj;
15 | let seoService: jasmine.SpyObj;
16 |
17 | beforeEach(async () => {
18 | interactor = jasmine.createSpyObj(IPostInteractor, ['getMany']);
19 | seoService = jasmine.createSpyObj(PageSeoService, ['setSEO']);
20 |
21 | await TestBed.configureTestingModule({
22 | declarations: [
23 | BlogComponent,
24 | SkeletonLoaderComponent,
25 | BlogSkeletonComponent,
26 | ],
27 | providers: [
28 | { provide: IPostInteractor, useValue: interactor },
29 | { provide: PageSeoService, useValue: seoService },
30 | ],
31 | }).compileComponents();
32 |
33 | fixture = TestBed.createComponent(BlogComponent);
34 | component = fixture.componentInstance;
35 | fixture.detectChanges();
36 | });
37 |
38 | it('should create', () => {
39 | expect(component).toBeTruthy();
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/blog/blog.component.ts:
--------------------------------------------------------------------------------
1 | import { PageSeoService } from './../page-seo.service';
2 | import { Component, OnInit } from '@angular/core';
3 | import { Observable } from 'rxjs';
4 | import { PageEnum } from '../../components/shared/skeleton-loader/skeleton-loader.component';
5 | import { PostRequest } from 'src/app/data/requests/posts.request';
6 | import { IPostInteractor } from 'src/app/data/interactors/contracts/ipost.interactor';
7 |
8 | @Component({
9 | selector: 'app-blog',
10 | templateUrl: './blog.component.html',
11 | styles: []
12 | })
13 | export class BlogComponent implements OnInit {
14 |
15 | public blog = PageEnum.blog;
16 | public posts$?: Observable;
17 |
18 | public constructor(
19 | private interactor: IPostInteractor,
20 | private seoService: PageSeoService
21 | ) { }
22 |
23 | ngOnInit(): void {
24 | this.seoService.setSEO({ pageTitle: 'Blog Posts' });
25 | this.posts$ = this.interactor.getMany();
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/blog/post/post.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 |
9 |
10 |
12 |
13 |
{{post?.author}} • {{post?.date | date}}
14 |
15 |
16 |
17 |
18 |
23 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/blog/post/post.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { By } from '@angular/platform-browser';
2 | import { RouterModule } from '@angular/router';
3 | import { ComponentFixture, TestBed } from '@angular/core/testing';
4 |
5 | import { MOCK_POSTS } from 'src/app/data/datasources/remote/repo-implementations/post/post.repository.spec';
6 |
7 | import { TagComponent } from './../../../components/shared/tag/tag.component';
8 |
9 | import { PostComponent } from './post.component';
10 |
11 | describe('PostComponent', () => {
12 | let component: PostComponent;
13 | let fixture: ComponentFixture;
14 |
15 | beforeEach(async () => {
16 | await TestBed.configureTestingModule({
17 | declarations: [PostComponent, TagComponent],
18 | imports: [RouterModule.forRoot([])]
19 | })
20 | .compileComponents();
21 |
22 | fixture = TestBed.createComponent(PostComponent);
23 | component = fixture.componentInstance;
24 | component.post = MOCK_POSTS[0];
25 | fixture.detectChanges();
26 | });
27 |
28 | it('should create', () => {
29 | expect(component).toBeTruthy();
30 | });
31 |
32 |
33 | it('should set post input', () => {
34 | expect(component.post).toBeTruthy();
35 | });
36 |
37 | it('should add tag to page', () => {
38 |
39 | const { debugElement } = fixture;
40 |
41 | const tag = debugElement.query(By.directive(TagComponent));
42 |
43 | expect(tag).toBeTruthy();
44 | })
45 |
46 | });
47 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/blog/post/post.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 |
3 | import { PostRequest } from './../../../../data/requests/posts.request';
4 |
5 | @Component({
6 | selector: 'app-post',
7 | templateUrl: './post.component.html',
8 | })
9 | export class PostComponent {
10 |
11 | @Input()
12 | post?: PostRequest;
13 |
14 | ngOnInit(): void {
15 | if (this.post && this.post.tags && typeof this.post.tags === 'string') {
16 | const strTag: string = this.post.tags;
17 | this.post.tags = strTag.split(',');
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/bookmarks/bookmark/bookmark.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
11 |
12 |
15 |
16 |
17 | Saved on {{bookmark?.date | date}}
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/bookmarks/bookmark/bookmark.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { BookmarkComponent } from './bookmark.component';
4 |
5 | describe('BookmarkComponent', () => {
6 | let component: BookmarkComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [BookmarkComponent],
12 | }).compileComponents();
13 |
14 | fixture = TestBed.createComponent(BookmarkComponent);
15 | component = fixture.componentInstance;
16 | fixture.detectChanges();
17 | });
18 |
19 | it('should create', () => {
20 | expect(component).toBeTruthy();
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/bookmarks/bookmark/bookmark.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 | import { BookmarkRequest } from './../../../../data/requests/bookmark.request';
3 |
4 | @Component({
5 | selector: 'app-bookmark',
6 | templateUrl: './bookmark.component.html',
7 | })
8 | export class BookmarkComponent {
9 |
10 | @Input()
11 | public bookmark?: BookmarkRequest;
12 | }
13 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/bookmarks/bookmarks.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Bookmarks
4 |
5 |
6 | My favorite place for keeping some useful, interesting, and fun web hyperlinks.
7 |
8 |
9 |
10 |
11 | Popular Tags
12 |
13 |
14 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
32 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/bookmarks/bookmarks.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { of } from 'rxjs';
4 |
5 | import { BookmarkInteractor } from 'src/app/data/interactors/implementations/bookmark/bookmark.interactor';
6 |
7 | import { PageSeoService } from '../page-seo.service';
8 | import { SkeletonLoaderComponent } from './../../components/shared/skeleton-loader/skeleton-loader.component';
9 | import { TagComponent } from './../../components/shared/tag/tag.component';
10 | import { BookmarksSkeletonComponent } from './../../components/shared/skeleton-loader/bookmarks-skeleton/bookmarks-skeleton.component';
11 |
12 | import { BookmarksComponent } from './bookmarks.component';
13 |
14 | describe('BookmarksComponent', () => {
15 | let component: BookmarksComponent;
16 | let fixture: ComponentFixture;
17 | let seo: jasmine.SpyObj;
18 | let bookmarkInteractor: jasmine.SpyObj;
19 |
20 | beforeEach(async () => {
21 | bookmarkInteractor = jasmine.createSpyObj(BookmarkInteractor, [
22 | 'getMany',
23 | ]);
24 | bookmarkInteractor.getMany = jasmine.createSpy().and.returnValue(of());
25 |
26 | seo = jasmine.createSpyObj(PageSeoService, ['setSEO']);
27 |
28 | await TestBed.configureTestingModule({
29 | declarations: [
30 | BookmarksComponent,
31 | TagComponent,
32 | SkeletonLoaderComponent,
33 | BookmarksSkeletonComponent,
34 | ],
35 | providers: [
36 | { provide: PageSeoService, useValue: seo },
37 | { provide: BookmarkInteractor, useValue: bookmarkInteractor },
38 | ],
39 | }).compileComponents();
40 |
41 | fixture = TestBed.createComponent(BookmarksComponent);
42 | component = fixture.componentInstance;
43 | fixture.detectChanges();
44 | });
45 |
46 | it('should create', () => {
47 | expect(component).toBeTruthy();
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/bookmarks/bookmarks.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | import { Observable } from 'rxjs';
4 |
5 | import { BookmarkInteractor } from 'src/app/data/interactors/implementations/bookmark/bookmark.interactor';
6 | import { BookmarkRequest } from 'src/app/data/requests/bookmark.request';
7 |
8 | import { PageSeoService } from './../page-seo.service';
9 | import { PageEnum } from '../../components/shared/skeleton-loader/skeleton-loader.component';
10 |
11 | @Component({
12 | selector: 'app-bookmarks',
13 | templateUrl: './bookmarks.component.html',
14 | styles: []
15 | })
16 | export class BookmarksComponent implements OnInit {
17 |
18 | public bookmarks$?: Observable;
19 | public bookmarkPage = PageEnum.bookmark;
20 |
21 | constructor(
22 | private seo: PageSeoService,
23 | private bookmarkInteractor: BookmarkInteractor
24 | ) { }
25 |
26 | ngOnInit(): void {
27 | this.seo.setSEO({ pageTitle: 'My Bookmarks & Useful Links' });
28 | this.bookmarks$ = this.bookmarkInteractor.getMany();
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/editor/components/bookmark-form/bookmark-form.component.html:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/editor/components/bookmark-form/bookmark-form.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 | import { FormGroup, ReactiveFormsModule, FormControl } from '@angular/forms';
3 |
4 | import { MOCK_BOOKMARKS } from 'src/app/data/datasources/remote/repo-implementations/bookmark/bookmark.repository.spec';
5 | import { ComponentModule } from 'src/app/presenter/components/components.module';
6 | import { ButtonComponent } from 'src/app/presenter/components/shared/button/button.component';
7 |
8 | import { FormFieldComponent } from './../../../../components/shared/form-field/form-field.component';
9 | import { ModalComponent } from './../../../../components/shared/modal/modal.component';
10 |
11 | import {
12 | BookmarFormProps,
13 | BookmarkFormComponent,
14 | } from './bookmark-form.component';
15 |
16 | describe('BookmarkFormComponent', () => {
17 | let component: BookmarkFormComponent;
18 | let fixture: ComponentFixture;
19 |
20 | let props: BookmarFormProps = {
21 | form: new FormGroup({
22 | url: new FormControl(),
23 | short: new FormControl(),
24 | tags: new FormControl(),
25 | date: new FormControl(),
26 | }),
27 | action: () => {},
28 | data: MOCK_BOOKMARKS[0],
29 | isLoading: false,
30 | };
31 |
32 | beforeEach(async () => {
33 | await TestBed.configureTestingModule({
34 | declarations: [
35 | BookmarkFormComponent,
36 | ModalComponent,
37 | FormFieldComponent,
38 | ButtonComponent,
39 | ],
40 | imports: [ReactiveFormsModule, ComponentModule],
41 | }).compileComponents();
42 |
43 | fixture = TestBed.createComponent(BookmarkFormComponent);
44 | component = fixture.componentInstance;
45 | component.defaultValue = props;
46 | fixture.detectChanges();
47 | });
48 |
49 | it('should create', () => {
50 | expect(component).toBeTruthy();
51 | });
52 | });
53 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/editor/components/bookmark-form/bookmark-form.component.ts:
--------------------------------------------------------------------------------
1 | import { BookmarkRequest } from 'src/app/data/requests/bookmark.request';
2 | import { Component, Input } from '@angular/core';
3 | import { FormGroup } from '@angular/forms';
4 | import { isInvalidControl } from '../../editor.functions';
5 |
6 | export type BookmarFormProps = {
7 | form: FormGroup,
8 | action: () => void,
9 | data: BookmarkRequest,
10 | isLoading: boolean,
11 | }
12 |
13 | @Component({
14 | selector: 'app-bookmark-form',
15 | templateUrl: './bookmark-form.component.html',
16 | styles: [
17 | ]
18 | })
19 | export class BookmarkFormComponent {
20 | @Input()
21 | public defaultValue?: BookmarFormProps;
22 |
23 | public constructor() { }
24 |
25 | public isControlInValid = (controlName: string): boolean => {
26 | return isInvalidControl(controlName, this.defaultValue?.form!);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/editor/components/post-form/post-form.component.html:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/editor/components/post-form/post-form.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { FormGroup, ReactiveFormsModule, FormControl } from '@angular/forms';
2 | import { ComponentFixture, TestBed } from '@angular/core/testing';
3 |
4 | import { MOCK_POSTS } from 'src/app/data/datasources/remote/repo-implementations/post/post.repository.spec';
5 | import { FormFieldComponent } from 'src/app/presenter/components/shared/form-field/form-field.component';
6 | import { ComponentModule } from 'src/app/presenter/components/components.module';
7 |
8 | import { ButtonComponent } from './../../../../components/shared/button/button.component';
9 | import { ModalComponent } from './../../../../components/shared/modal/modal.component';
10 |
11 | import { PostFormComponent, PostFormProps } from './post-form.component';
12 |
13 | describe('PostFormComponent', () => {
14 | let component: PostFormComponent;
15 | let fixture: ComponentFixture;
16 |
17 | let props: PostFormProps = {
18 | form: new FormGroup({
19 | title: new FormControl(''),
20 | excerpt: new FormControl(''),
21 | author: new FormControl(''),
22 | tags: new FormControl(''),
23 | date: new FormControl(''),
24 | content: new FormControl(''),
25 | }),
26 | action: () => {},
27 | data: MOCK_POSTS[0],
28 | isLoading: false,
29 | };
30 |
31 | beforeEach(async () => {
32 | await TestBed.configureTestingModule({
33 | declarations: [
34 | PostFormComponent,
35 | ModalComponent,
36 | FormFieldComponent,
37 | ButtonComponent,
38 | ],
39 | imports: [ReactiveFormsModule, ComponentModule],
40 | }).compileComponents();
41 |
42 | fixture = TestBed.createComponent(PostFormComponent);
43 | component = fixture.componentInstance;
44 | component.defaultValue = props;
45 | fixture.detectChanges();
46 | });
47 |
48 | it('should create', () => {
49 | expect(component).toBeTruthy();
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/editor/components/post-form/post-form.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 | import { FormGroup } from '@angular/forms';
3 |
4 | import { isInvalidControl } from '../../editor.functions';
5 | import { PostRequest } from './../../../../../data/requests/posts.request';
6 |
7 | export type PostFormProps = {
8 | form: FormGroup;
9 | action: () => void;
10 | data: PostRequest;
11 | isLoading: boolean;
12 | };
13 |
14 | @Component({
15 | selector: 'app-post-form',
16 | templateUrl: './post-form.component.html',
17 | styles: [],
18 | })
19 | export class PostFormComponent {
20 | @Input()
21 | public defaultValue?: PostFormProps;
22 |
23 | public constructor() {}
24 |
25 | public isControlInValid = (controlName: string): boolean => {
26 | return isInvalidControl(controlName, this.defaultValue?.form!);
27 | };
28 | }
29 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/editor/components/project-form/project-form.component.html:
--------------------------------------------------------------------------------
1 |
2 |
29 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/editor/components/project-form/project-form.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 | import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
3 |
4 | import { MOCK_PROJECT } from 'src/app/data/datasources/remote/repo-implementations/project/project.repository.spec';
5 | import { ComponentModule } from 'src/app/presenter/components/components.module';
6 | import { ButtonComponent } from 'src/app/presenter/components/shared/button/button.component';
7 | import { FormFieldComponent } from 'src/app/presenter/components/shared/form-field/form-field.component';
8 |
9 | import { ModalComponent } from './../../../../components/shared/modal/modal.component';
10 |
11 | import {
12 | ProjectFormComponent,
13 | ProjectFormProps,
14 | } from './project-form.component';
15 |
16 | describe('ProjectFormComponent', () => {
17 | let component: ProjectFormComponent;
18 | let fixture: ComponentFixture;
19 | let props: ProjectFormProps = {
20 | form: new FormGroup({
21 | _id: new FormControl(''),
22 | title: new FormControl(''),
23 | description: new FormControl(''),
24 | features: new FormControl(''),
25 | modules: new FormControl(''),
26 | industries: new FormControl(''),
27 | tools: new FormControl(''),
28 | imageUrl: new FormControl(''),
29 | actionTitle: new FormControl(''),
30 | actionLink: new FormControl(''),
31 | }),
32 | action: () => {},
33 | data: MOCK_PROJECT[0],
34 | isLoading: false,
35 | };
36 |
37 | beforeEach(async () => {
38 | await TestBed.configureTestingModule({
39 | declarations: [
40 | ProjectFormComponent,
41 | ModalComponent,
42 | FormFieldComponent,
43 | ButtonComponent,
44 | ],
45 | imports: [ReactiveFormsModule, ComponentModule],
46 | }).compileComponents();
47 |
48 | fixture = TestBed.createComponent(ProjectFormComponent);
49 | component = fixture.componentInstance;
50 | component.defaultValue = props;
51 | fixture.detectChanges();
52 | });
53 |
54 | it('should create', () => {
55 | expect(component).toBeTruthy();
56 | });
57 | });
58 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/editor/components/project-form/project-form.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 | import { FormGroup } from '@angular/forms';
3 |
4 | import { ProjectRequest } from 'src/app/data/requests/project.request';
5 | import { isInvalidControl } from '../../editor.functions';
6 |
7 | export type ProjectFormProps = {
8 | form: FormGroup,
9 | action: () => void,
10 | data: ProjectRequest,
11 | isLoading: boolean,
12 | }
13 |
14 | @Component({
15 | selector: 'app-project-form',
16 | templateUrl: './project-form.component.html',
17 | styles: [
18 | ]
19 | })
20 | export class ProjectFormComponent {
21 |
22 | @Input()
23 | public defaultValue?: ProjectFormProps;
24 |
25 | public constructor() { }
26 |
27 | public isControlInValid = (controlName: string): boolean => {
28 | return isInvalidControl(controlName, this.defaultValue?.form!);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/editor/editor-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NotFoundComponent } from '../../components/shared/not-found/not-found.component';
2 | import { EditorBookmarksComponent } from './pages/editor-bookmarks/editor-bookmarks.component';
3 | import { EditorProjectsComponent } from './pages/editor-projects/editor-projects.component';
4 | import { EditorPostsComponent } from './pages/editor-posts/editor-posts.component';
5 | import { EditorHomeComponent } from './pages/editor-home/editor-home.component';
6 | import { NgModule } from "@angular/core";
7 | import { RouterModule, Routes } from "@angular/router";
8 |
9 | const routes: Routes = [
10 | { path: '', component: EditorHomeComponent },
11 | { path: 'posts', component: EditorPostsComponent },
12 | { path: 'projects', component: EditorProjectsComponent },
13 | { path: 'bookmarks', component: EditorBookmarksComponent },
14 | { path: '**', component: NotFoundComponent },
15 | ];
16 |
17 | @NgModule({
18 | imports: [RouterModule.forChild(routes)],
19 | exports: [RouterModule]
20 | })
21 | export class EditorRoutingModule { }
22 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/editor/editor.component.html:
--------------------------------------------------------------------------------
1 | editor works!
2 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/editor/editor.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { RouterModule } from '@angular/router';
2 | import { ComponentFixture, TestBed } from '@angular/core/testing';
3 |
4 | import { EditorComponent } from './editor.component';
5 |
6 | describe('EditorComponent', () => {
7 | let component: EditorComponent;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(async () => {
11 | await TestBed.configureTestingModule({
12 | declarations: [EditorComponent],
13 | imports: [RouterModule.forRoot([])],
14 | }).compileComponents();
15 |
16 | fixture = TestBed.createComponent(EditorComponent);
17 | component = fixture.componentInstance;
18 | fixture.detectChanges();
19 | });
20 |
21 | it('should create', () => {
22 | expect(component).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/editor/editor.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | import { PageSeoService } from '../page-seo.service';
4 |
5 | @Component({
6 | selector: 'app-editor',
7 | templateUrl: './editor.component.html',
8 | styles: [
9 | ]
10 | })
11 | export class EditorComponent implements OnInit {
12 |
13 | constructor(
14 | private seoService: PageSeoService,
15 | ) { }
16 |
17 | ngOnInit(): void {
18 | this.seoService.setSEO();
19 | }
20 |
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/editor/editor.module.ts:
--------------------------------------------------------------------------------
1 | import { RouterModule } from '@angular/router';
2 | import { NgModule } from '@angular/core';
3 | import { CommonModule } from '@angular/common';
4 | import { FormsModule, ReactiveFormsModule } from '@angular/forms';
5 |
6 | import { EditorRoutingModule } from './editor-routing.module';
7 | import { EditorComponent } from './editor.component';
8 | import { EditorHomeComponent } from './pages/editor-home/editor-home.component';
9 | import { EditorPostsComponent } from './pages/editor-posts/editor-posts.component';
10 | import { EditorProjectsComponent } from './pages/editor-projects/editor-projects.component';
11 | import { EditorBookmarksComponent } from './pages/editor-bookmarks/editor-bookmarks.component';
12 | import { ComponentModule } from "../../components/components.module";
13 | import { PostFormComponent } from './components/post-form/post-form.component';
14 | import { ProjectFormComponent } from './components/project-form/project-form.component';
15 | import { BookmarkFormComponent } from './components/bookmark-form/bookmark-form.component';
16 |
17 | @NgModule({
18 | declarations: [
19 | EditorComponent,
20 | EditorHomeComponent,
21 | EditorPostsComponent,
22 | EditorProjectsComponent,
23 | EditorBookmarksComponent,
24 | PostFormComponent,
25 | ProjectFormComponent,
26 | BookmarkFormComponent
27 | ],
28 | providers: [],
29 | exports: [
30 | EditorComponent
31 | ],
32 | imports: [
33 | RouterModule,
34 | ComponentModule,
35 | CommonModule,
36 | FormsModule,
37 | ReactiveFormsModule,
38 | EditorRoutingModule,
39 | ]
40 | })
41 | export class EditorModule { }
42 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/editor/pages/editor-bookmarks/editor-bookmarks.component.html:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/editor/pages/editor-bookmarks/editor-bookmarks.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { Observable, of } from 'rxjs';
4 |
5 | import { BookmarkInteractor } from 'src/app/data/interactors/implementations/bookmark/bookmark.interactor';
6 | import { ModalService } from 'src/app/presenter/components/shared/modal/modal.service';
7 | import { ToastService } from 'src/app/presenter/components/shared/toast/toast.service';
8 |
9 | import { ButtonComponent } from './../../../../components/shared/button/button.component';
10 | import { TableComponent } from './../../../../components/shared/table/table.component';
11 |
12 | import { EditorBookmarksComponent } from './editor-bookmarks.component';
13 |
14 | describe('EditorBookmarksComponent', () => {
15 | let component: EditorBookmarksComponent;
16 | let fixture: ComponentFixture;
17 |
18 | let bookmarkInteractor: jasmine.SpyObj;
19 | let modalService: jasmine.SpyObj>;
20 | let toastService: jasmine.SpyObj;
21 |
22 | beforeEach(async () => {
23 | bookmarkInteractor = jasmine.createSpyObj(BookmarkInteractor, [
24 | 'getMany',
25 | ]);
26 |
27 | bookmarkInteractor.getMany = jasmine.createSpy().and.returnValue(of());
28 |
29 | modalService = jasmine.createSpyObj(ModalService, ['open', 'close']);
30 | toastService = jasmine.createSpyObj(ToastService, ['show']);
31 | await TestBed.configureTestingModule({
32 | declarations: [
33 | EditorBookmarksComponent,
34 | ButtonComponent,
35 | TableComponent,
36 | ],
37 | providers: [
38 | { provide: BookmarkInteractor, useValue: bookmarkInteractor },
39 | { provide: ModalService, useValue: modalService },
40 | { provide: ToastService, useValue: toastService },
41 | ],
42 | }).compileComponents();
43 |
44 | fixture = TestBed.createComponent(EditorBookmarksComponent);
45 | component = fixture.componentInstance;
46 | fixture.detectChanges();
47 | });
48 |
49 | it('should create', () => {
50 | expect(component).toBeTruthy();
51 | });
52 | });
53 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/editor/pages/editor-home/editor-home.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Welcome to the editor panel
4 |
5 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/editor/pages/editor-home/editor-home.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { EditorHomeComponent } from './editor-home.component';
4 |
5 | describe('EditorHomeComponent', () => {
6 | let component: EditorHomeComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [ EditorHomeComponent ]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(EditorHomeComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/editor/pages/editor-home/editor-home.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-editor-home',
5 | templateUrl: './editor-home.component.html',
6 | styles: [
7 | ]
8 | })
9 | export class EditorHomeComponent {
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/editor/pages/editor-posts/editor-posts.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/editor/pages/editor-posts/editor-posts.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { ButtonComponent } from 'src/app/presenter/components/shared/button/button.component';
4 | import { TableComponent } from 'src/app/presenter/components/shared/table/table.component';
5 | import { ModalService } from 'src/app/presenter/components/shared/modal/modal.service';
6 | import { ToastService } from 'src/app/presenter/components/shared/toast/toast.service';
7 |
8 | import { of } from 'rxjs';
9 |
10 | import { PostInteractor } from './../../../../../data/interactors/implementations/post/post.interactor';
11 |
12 | import { EditorPostsComponent } from './editor-posts.component';
13 |
14 | describe('EditorPostsComponent', () => {
15 | let component: EditorPostsComponent;
16 | let fixture: ComponentFixture;
17 | let postInteractor: jasmine.SpyObj;
18 | let modalService: jasmine.SpyObj>;
19 | let toastService: jasmine.SpyObj;
20 |
21 | beforeEach(async () => {
22 | postInteractor = jasmine.createSpyObj(PostInteractor, ['getMany']);
23 | postInteractor.getMany = jasmine.createSpy().and.returnValue(of());
24 |
25 | await TestBed.configureTestingModule({
26 | declarations: [
27 | EditorPostsComponent,
28 | ButtonComponent,
29 | TableComponent,
30 | ],
31 | providers: [
32 | { provide: PostInteractor, useValue: postInteractor },
33 | { provide: ModalService, useValue: modalService },
34 | { provide: ToastService, useValue: toastService },
35 | ],
36 | }).compileComponents();
37 |
38 | fixture = TestBed.createComponent(EditorPostsComponent);
39 | component = fixture.componentInstance;
40 | fixture.detectChanges();
41 | });
42 |
43 | it('should create', () => {
44 | expect(component).toBeTruthy();
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/editor/pages/editor-projects/editor-projects.component.html:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/editor/pages/editor-projects/editor-projects.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { of } from 'rxjs';
4 |
5 | import { ModalService } from 'src/app/presenter/components/shared/modal/modal.service';
6 | import { ToastService } from 'src/app/presenter/components/shared/toast/toast.service';
7 |
8 | import { ButtonComponent } from './../../../../components/shared/button/button.component';
9 | import { TableComponent } from './../../../../components/shared/table/table.component';
10 | import { ProjectInteractor } from './../../../../../data/interactors/implementations/project/project.interactor';
11 |
12 | import { EditorProjectsComponent } from './editor-projects.component';
13 |
14 | describe('EditorProjectsComponent', () => {
15 | let component: EditorProjectsComponent;
16 | let fixture: ComponentFixture;
17 | let projectInteractor: jasmine.SpyObj;
18 | let modalService: jasmine.SpyObj>;
19 | let toastService: jasmine.SpyObj;
20 |
21 | beforeEach(async () => {
22 | projectInteractor = jasmine.createSpyObj(ProjectInteractor, [
23 | 'getMany',
24 | ]);
25 | projectInteractor.getMany = jasmine.createSpy().and.returnValue(of());
26 |
27 | await TestBed.configureTestingModule({
28 | declarations: [
29 | EditorProjectsComponent,
30 | ButtonComponent,
31 | TableComponent,
32 | ],
33 | providers: [
34 | { provide: ProjectInteractor, useValue: projectInteractor },
35 | { provide: ModalService, useValue: modalService },
36 | { provide: ToastService, useValue: toastService },
37 | ],
38 | }).compileComponents();
39 |
40 | fixture = TestBed.createComponent(EditorProjectsComponent);
41 | component = fixture.componentInstance;
42 | fixture.detectChanges();
43 | });
44 |
45 | it('should create', () => {
46 | expect(component).toBeTruthy();
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/home/home.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { RouterModule } from '@angular/router';
2 | import { ComponentFixture, TestBed } from '@angular/core/testing';
3 |
4 | import { HomeComponent } from './home.component';
5 |
6 | describe('HomeComponent', () => {
7 | let component: HomeComponent;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(async () => {
11 | await TestBed.configureTestingModule({
12 | declarations: [HomeComponent],
13 | imports: [RouterModule.forRoot([])],
14 | }).compileComponents();
15 |
16 | fixture = TestBed.createComponent(HomeComponent);
17 | component = fixture.componentInstance;
18 | fixture.detectChanges();
19 | });
20 |
21 | it('should create', () => {
22 | expect(component).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/home/home.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-home',
5 | templateUrl: './home.component.html',
6 | })
7 | export class HomeComponent {
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/page-seo.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Meta, Title } from '@angular/platform-browser';
3 |
4 | export interface SeoData {
5 | pageTitle: string;
6 | pageDescription?: string;
7 | pageKeywords?: string;
8 | pageUrl?: string;
9 | author?: string;
10 | pageImageUrl?: string;
11 | }
12 |
13 | @Injectable({
14 | providedIn: 'root'
15 | })
16 | export class PageSeoService {
17 |
18 | constructor(
19 | private meta: Meta,
20 | private title: Title
21 | ) { }
22 |
23 |
24 | public setSEO(data?: SeoData): void {
25 | const defaultImage = `${window.location.origin}/assets/img/blog-thumbnail.jpg`;
26 |
27 | const author = `${data?.author ?? 'James Aworo'}`;
28 | const imagePath = `${data?.pageImageUrl ?? defaultImage}`;
29 | const pageTitle = `${data?.pageTitle ?? `Welcome to my Personal | Projects | Blog Website`}`;
30 | const description = `${data?.pageDescription ??
31 | `
32 | Hi, my name is ${author} welcome to my personal space on the web.
33 | I am a fullstack software engineer.
34 | I'm passionate about building functional and performant products,
35 | I like to learn new stuff and I get excited when work on big problems with amazing people.`
36 | } `;
37 | const keywords = `${data?.pageKeywords ?? 'programming, fullstack engineering, java, typescript, blogging'}`;
38 | const pageUrl = `${data?.pageUrl ?? 'https://jamesaworo.com/'}`;
39 |
40 | this.title.setTitle(`${author} | ${pageTitle}`);
41 | this.meta.updateTag({ name: 'author', content: author })
42 | this.meta.updateTag({ name: 'description', content: description })
43 | this.meta.updateTag({ name: 'keywords', content: keywords })
44 |
45 | this.meta.updateTag({ name: 'og:title', content: pageTitle })
46 | this.meta.updateTag({ name: 'og:description', content: description })
47 | this.meta.updateTag({ name: 'og:image', content: imagePath })
48 | this.meta.updateTag({ name: 'og:type', content: 'article' })
49 | this.meta.updateTag({ name: 'og:url', content: pageUrl })
50 | this.meta.updateTag({ name: 'og:site_name', content: author })
51 | this.meta.updateTag({ name: 'article:author', content: author })
52 |
53 | this.meta.updateTag({ name: 'twitter:title', content: pageTitle })
54 | this.meta.updateTag({ name: 'twitter:description', content: description })
55 | this.meta.updateTag({ name: 'twitter:image', content: imagePath })
56 |
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/pages.module.ts:
--------------------------------------------------------------------------------
1 | import { MarkdownModule } from 'ngx-markdown';
2 | import { RouterModule } from '@angular/router';
3 | import { PostDetailComponent } from './post-detail/post-detail.component';
4 | import { HomeComponent } from './home/home.component';
5 | import { EditorModule } from './editor/editor.module';
6 | import { BookmarksComponent } from './bookmarks/bookmarks.component';
7 | import { BlogComponent } from './blog/blog.component';
8 | import { NgModule } from '@angular/core';
9 | import { ProjectsComponent } from './projects/projects.component';
10 | import { ComponentModule } from "../components/components.module";
11 | import { BookmarkComponent } from './bookmarks/bookmark/bookmark.component';
12 | import { CommonModule } from '@angular/common';
13 | import { PostComponent } from './blog/post/post.component';
14 | import { ProjectComponent } from './projects/project/project.component';
15 |
16 |
17 | @NgModule({
18 | declarations: [
19 | BlogComponent,
20 | BookmarksComponent,
21 | HomeComponent,
22 | PostComponent,
23 | ProjectsComponent,
24 | BookmarkComponent,
25 | PostDetailComponent,
26 | ProjectComponent,
27 | ],
28 | exports: [
29 | CommonModule,
30 | ComponentModule,
31 | PostComponent,
32 | BlogComponent,
33 | ProjectComponent
34 | ],
35 | imports: [
36 | RouterModule,
37 | CommonModule,
38 | ComponentModule,
39 | EditorModule,
40 | MarkdownModule.forChild()
41 | ]
42 | })
43 | export class PagesModule { }
44 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/post-detail/post-detail.component.html:
--------------------------------------------------------------------------------
1 |
2 | {{setSeo(post)}}
3 |
4 |
5 |
6 | {{post?.title}}
7 |
8 |
9 |
10 | {{ post?.date | date}}
11 |
12 |
13 |
14 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/post-detail/post-detail.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | describe('PostComponent', () => {
4 |
5 | describe('ngOnInit', () => {
6 | it('should get route param from Router', () => { });
7 | it('should call Router.navigateByUrl if route param is NOT present');
8 | it('should call interactor.getOne with route param as input if present')
9 | });
10 |
11 | describe('setSeo', () => {
12 | it('should call seoService.setSEO method', () => { });
13 | it('should an object to setSEO method of seoService', () => { });
14 | })
15 | });
16 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/post-detail/post-detail.component.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from 'rxjs';
2 | import { PageSeoService } from './../page-seo.service';
3 | import { IPostInteractor } from 'src/app/data/interactors/contracts/ipost.interactor';
4 | import { PostRequest } from './../../../data/requests/posts.request';
5 | import { Component, OnInit } from '@angular/core';
6 | import { ActivatedRoute, Router } from '@angular/router';
7 |
8 | @Component({
9 | selector: 'app-post-detail',
10 | templateUrl: './post-detail.component.html',
11 | })
12 | export class PostDetailComponent implements OnInit {
13 |
14 | public post$?: Observable
15 | public content?: any;
16 |
17 | public constructor(
18 | private route: ActivatedRoute,
19 | private interactor: IPostInteractor,
20 | private seoService: PageSeoService,
21 | private router: Router
22 | ) { }
23 |
24 | ngOnInit(): void {
25 | const routeParam = this.route.snapshot.paramMap;
26 | const postId = routeParam.get('postId')!;
27 |
28 | if (!postId) {
29 | this.router.navigateByUrl('blog');
30 | return;
31 | }
32 |
33 | this.post$ = this.interactor.getOne(postId);
34 | }
35 |
36 | public setSeo(post?: PostRequest): void {
37 | this.seoService.setSEO({
38 | pageTitle: `${post?.title}`,
39 | pageDescription: `${post?.excerpt}`,
40 | pageKeywords: `${post?.tags?.toString()}`
41 | });
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/projects/project/project.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { ProjectComponent } from './project.component';
4 |
5 | describe('ProjectComponent', () => {
6 | let component: ProjectComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [ ProjectComponent ]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(ProjectComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/projects/project/project.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 | import { ProjectRequest } from 'src/app/data/requests/project.request';
3 |
4 | @Component({
5 | selector: 'app-project',
6 | templateUrl: './project.component.html',
7 |
8 | })
9 | export class ProjectComponent {
10 | @Input()
11 | public project?: ProjectRequest;
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/projects/projects.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Projects
4 |
5 | I enjoy creating beautiful and reliable applications for multiple platforms.
6 | My goal is to build scalable products and performant experiences constantly.
7 |
8 |
9 |
10 | I derive joy in working on projects that solve specific business requirements or help people solve
11 | everyday problems.
12 |
13 |
14 |
15 | My projects range from web, desktop, and mobile apps.
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
28 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/projects/projects.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { of } from 'rxjs';
4 |
5 | import { IProjectInteractor } from 'src/app/data/interactors/contracts/iproject.interactor';
6 | import { ProjectInteractor } from 'src/app/data/interactors/implementations/project/project.interactor';
7 |
8 | import { PageSeoService } from './../page-seo.service';
9 | import { SkeletonLoaderComponent } from './../../components/shared/skeleton-loader/skeleton-loader.component';
10 | import { ProjectsSkeletonComponent } from './../../components/shared/skeleton-loader/projects-skeleton/projects-skeleton.component';
11 |
12 | import { ProjectsComponent } from './projects.component';
13 |
14 | describe('ProjectsComponent', () => {
15 | let component: ProjectsComponent;
16 | let fixture: ComponentFixture;
17 | let seoService: jasmine.SpyObj;
18 | let interactor: jasmine.SpyObj;
19 |
20 | beforeEach(async () => {
21 | seoService = jasmine.createSpyObj(PageSeoService, ['setSEO']);
22 | interactor = jasmine.createSpyObj(IProjectInteractor, ['getMany']);
23 | interactor.getMany = jasmine.createSpy().and.returnValue(of());
24 |
25 | await TestBed.configureTestingModule({
26 | providers: [{ provide: IProjectInteractor, useValue: interactor }],
27 | declarations: [
28 | ProjectsComponent,
29 | SkeletonLoaderComponent,
30 | ProjectsSkeletonComponent,
31 | ],
32 | }).compileComponents();
33 |
34 | fixture = TestBed.createComponent(ProjectsComponent);
35 | component = fixture.componentInstance;
36 | fixture.detectChanges();
37 | });
38 |
39 | it('should create', () => {
40 | expect(component).toBeTruthy();
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/src/app/presenter/pages/projects/projects.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { Observable } from 'rxjs';
4 |
5 | import { ProjectRequest } from 'src/app/data/requests/project.request';
6 | import { IProjectInteractor } from 'src/app/data/interactors/contracts/iproject.interactor';
7 |
8 | import { PageSeoService } from './../page-seo.service';
9 | import { PageEnum } from '../../components/shared/skeleton-loader/skeleton-loader.component';
10 |
11 | @Component({
12 | selector: 'app-projects',
13 | templateUrl: './projects.component.html',
14 | })
15 | export class ProjectsComponent {
16 | public projects$?: Observable;
17 | public page = PageEnum.project;
18 |
19 | constructor(
20 | private seoService: PageSeoService,
21 | private interactor: IProjectInteractor
22 | ) {}
23 |
24 | ngOnInit(): void {
25 | this.seoService.setSEO({ pageTitle: 'My Projects / Portfolio' });
26 | this.projects$ = this.interactor.getMany();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/app/presenter/presenter.module.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { PresenterModule } from './presenter.module';
4 |
5 | describe('PresenterModule', () => {
6 | beforeEach(() => {
7 | TestBed.configureTestingModule({
8 | imports: [PresenterModule]
9 | })
10 | })
11 |
12 | it('should initialize', () => {
13 | const module = TestBed.inject(PresenterModule);
14 |
15 | expect(module).toBeTruthy();
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/src/app/presenter/presenter.module.ts:
--------------------------------------------------------------------------------
1 | import { DataModule } from './../data/data.module';
2 | import { PagesModule } from './pages/pages.module';
3 | import { ComponentModule } from './components/components.module';
4 | import { NgModule } from '@angular/core';
5 | import { CommonModule } from '@angular/common';
6 |
7 | @NgModule({
8 | declarations: [],
9 | exports: [
10 | CommonModule,
11 | ComponentModule,
12 | PagesModule,
13 | ],
14 | imports: [
15 | CommonModule,
16 | DataModule,
17 | ComponentModule,
18 | PagesModule
19 | ],
20 | })
21 | export class PresenterModule { }
22 |
--------------------------------------------------------------------------------
/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesawo/angular-clean-architecture/4436611555ba841f8c1eee42919972f2fa32ee53/src/assets/.gitkeep
--------------------------------------------------------------------------------
/src/assets/img/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesawo/angular-clean-architecture/4436611555ba841f8c1eee42919972f2fa32ee53/src/assets/img/android-chrome-192x192.png
--------------------------------------------------------------------------------
/src/assets/img/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesawo/angular-clean-architecture/4436611555ba841f8c1eee42919972f2fa32ee53/src/assets/img/android-chrome-512x512.png
--------------------------------------------------------------------------------
/src/assets/img/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesawo/angular-clean-architecture/4436611555ba841f8c1eee42919972f2fa32ee53/src/assets/img/apple-touch-icon.png
--------------------------------------------------------------------------------
/src/assets/img/blog-thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesawo/angular-clean-architecture/4436611555ba841f8c1eee42919972f2fa32ee53/src/assets/img/blog-thumbnail.jpg
--------------------------------------------------------------------------------
/src/assets/img/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesawo/angular-clean-architecture/4436611555ba841f8c1eee42919972f2fa32ee53/src/assets/img/favicon-16x16.png
--------------------------------------------------------------------------------
/src/assets/img/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesawo/angular-clean-architecture/4436611555ba841f8c1eee42919972f2fa32ee53/src/assets/img/favicon-32x32.png
--------------------------------------------------------------------------------
/src/assets/img/jamie.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesawo/angular-clean-architecture/4436611555ba841f8c1eee42919972f2fa32ee53/src/assets/img/jamie.png
--------------------------------------------------------------------------------
/src/assets/img/site.webmanifest:
--------------------------------------------------------------------------------
1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesawo/angular-clean-architecture/4436611555ba841f8c1eee42919972f2fa32ee53/src/favicon.ico
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Aworo James | Personal | Blog Website
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
2 |
3 | import { AppModule } from './app/app.module';
4 |
5 |
6 | platformBrowserDynamic().bootstrapModule(AppModule)
7 | .catch(err => console.error(err));
8 |
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 | @tailwind base;
3 | @tailwind components;
4 | @tailwind utilities;
5 |
6 | body {
7 | margin: 0;
8 | padding: 0;
9 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
10 | }
11 |
12 | body.dark .prose h1,
13 | body.dark .prose h2,
14 | body.dark .prose h3,
15 | body.dark .prose h4,
16 | body.dark .prose blockquote>p,
17 | body.dark .prose p>strong,
18 | body.dark .prose p>a,
19 | body.dark .prose {
20 | color: white;
21 | }
22 |
23 | body.dark li>strong {
24 | color: white;
25 | font-weight: bold;
26 | }
27 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | darkMode: 'class',
4 | content: [
5 | "./src/**/*.{html,ts}"
6 | ],
7 | theme: {
8 | extend: {},
9 | },
10 | plugins: [
11 | require('@tailwindcss/typography'),
12 |
13 | ],
14 | corePlugins: {
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts"
10 | ],
11 | "include": [
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "noImplicitOverride": true,
10 | "noPropertyAccessFromIndexSignature": true,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "sourceMap": true,
14 | "declaration": false,
15 | "downlevelIteration": true,
16 | "experimentalDecorators": true,
17 | "moduleResolution": "node",
18 | "importHelpers": true,
19 | "target": "ES2022",
20 | "module": "ES2022",
21 | "useDefineForClassFields": false,
22 | "lib": [
23 | "ES2022",
24 | "dom"
25 | ]
26 | },
27 | "angularCompilerOptions": {
28 | "enableI18nLegacyMessageIdFormat": false,
29 | "strictInjectionParameters": true,
30 | "strictInputAccessModifiers": true,
31 | "strictTemplates": true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/spec",
6 | "types": [
7 | "jasmine",
8 | ]
9 | },
10 | "include": [
11 | "src/**/*.spec.ts",
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------