diff --git a/.eslintrc.js b/.eslintrc.js index 7616bda..9fa5f84 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,6 +6,7 @@ module.exports = { '.eslintrc.js', 'scripts', 'build', + 'dist', 'coverage.webpack.js', 'karma.conf.js', 'tailwind.config.js', @@ -112,7 +113,7 @@ module.exports = { }, }, { - files: ['server/**/*.ts'], + files: ['{server,test}/**/*.ts'], parserOptions: { project: 'server/tsconfig.json', tsconfigRootDir: __dirname, diff --git a/.github/workflows/pr-e2e-test.yml b/.github/workflows/pr-e2e-test.yml index f6941c2..5162b7e 100644 --- a/.github/workflows/pr-e2e-test.yml +++ b/.github/workflows/pr-e2e-test.yml @@ -41,20 +41,7 @@ jobs: run: | wget https://fastdl.mongodb.org/tools/db/mongodb-database-tools-ubuntu2204-x86_64-100.6.1.deb sudo apt install ./mongodb-database-tools-*-100.6.1.deb - - run: mongoimport test/heroes.json # TODO find a way to run this inside the mongo service container, then the `Setup mongodb-tools` would not be needed neither - - - name: Create es index - run: curl -X PUT localhost:9200/heroes - - name: Update index to have 0 replicas # TODO es create 1 repl set per node by default, index status is yellow. - run: | - curl -X PUT http://localhost:9200/heroes/_settings -H "Content-Type: application/json" -d '{ - "index" : { - "number_of_replicas" : 0 - } - }' - - name: Inject data into es - run: | - curl -X PUT http://localhost:9200/heroes/_create/1 -H "Content-Type: application/json" -d @test/heroes.json + - run: mongoimport test/heroes.json --jsonArray # TODO find a way to run this inside the mongo service container, then the `Setup mongodb-tools` would not be needed neither - name: Install dependencies run: npm i diff --git a/client/angular.json b/client/angular.json index 7825a74..fcafd52 100644 --- a/client/angular.json +++ b/client/angular.json @@ -30,9 +30,6 @@ "simple-web-site/src/public" ], "styles": [ - { - "input": "simple-web-site/src/theme.scss" - }, "simple-web-site/src/styles.scss" ], "scripts": [] @@ -86,9 +83,6 @@ "simple-web-site/src/public" ], "styles": [ - { - "input": "simple-web-site/src/theme.scss" - }, "simple-web-site/src/styles.scss" ], "scripts": [] @@ -125,9 +119,6 @@ "simple-web-site/src/public" ], "styles": [ - { - "input": "simple-web-site/src/theme.scss" - }, "simple-web-site/src/styles.scss" ], "scripts": [], diff --git a/client/simple-web-site/src/app/hero/hero.component.html b/client/simple-web-site/src/app/hero/hero.component.html index f94f7b0..613f888 100644 --- a/client/simple-web-site/src/app/hero/hero.component.html +++ b/client/simple-web-site/src/app/hero/hero.component.html @@ -1,24 +1,37 @@ {{ hero().homeTown }} + >{{ hero().homeTown }} {{ hero().squadName }} +

{{ hero().secretBase }}

{{ hero().content }}

- - -

{{ member.name }}

-

{{ member.age }}

-

{{ member.powers }}

-

{{ member.secretIdentity }}

-
-
+
+ + + member name + {{ member.name }} + + + member age + {{ member.age }} + + + member powers + {{ member.powers }} + + + member secretIdentity + {{ member.secretIdentity }} + + +
diff --git a/client/simple-web-site/src/app/hero/hero.component.ts b/client/simple-web-site/src/app/hero/hero.component.ts index e62d7d9..34d137b 100644 --- a/client/simple-web-site/src/app/hero/hero.component.ts +++ b/client/simple-web-site/src/app/hero/hero.component.ts @@ -1,13 +1,7 @@ import { NgForOf } from '@angular/common'; import { ChangeDetectionStrategy, Component, input } from '@angular/core'; -import { - MatCard, - MatCardContent, - MatCardHeader, - MatCardSubtitle, - MatCardTitle, -} from '@angular/material/card'; -import { MatList, MatListItem } from '@angular/material/list'; +import { MatCardModule } from '@angular/material/card'; +import { MatListModule } from '@angular/material/list'; import { RouterLink } from '@angular/router'; import { Hero } from '../model/hero'; @@ -16,15 +10,10 @@ import { Hero } from '../model/hero'; selector: 'app-hero', standalone: true, imports: [ - MatCard, - MatCardContent, - MatCardHeader, - MatCardSubtitle, - MatCardTitle, - MatList, - MatListItem, NgForOf, RouterLink, + MatCardModule, + MatListModule, ], templateUrl: './hero.component.html', styleUrl: './hero.component.scss', diff --git a/client/simple-web-site/src/app/search/search.component.html b/client/simple-web-site/src/app/search/search.component.html index 310275c..435ce89 100644 --- a/client/simple-web-site/src/app/search/search.component.html +++ b/client/simple-web-site/src/app/search/search.component.html @@ -24,19 +24,21 @@
-
+

Search Result From ES:

-
+
- + +
-
+

Search Result From Mongo:

-
+
- + +
diff --git a/client/simple-web-site/src/theme.scss b/client/simple-web-site/src/material-theme.scss similarity index 100% rename from client/simple-web-site/src/theme.scss rename to client/simple-web-site/src/material-theme.scss diff --git a/client/simple-web-site/src/styles.scss b/client/simple-web-site/src/styles.scss index d79ee83..8595cd2 100644 --- a/client/simple-web-site/src/styles.scss +++ b/client/simple-web-site/src/styles.scss @@ -1,14 +1,15 @@ +@import "material-theme"; +@import 'color'; + @tailwind base; @tailwind components; @tailwind utilities; -@import 'color'; - html, body { margin: 0; } .active { - background: $my-blue; + background: $my-blue !important; } diff --git a/tailwind.config.js b/client/tailwind.config.js similarity index 75% rename from tailwind.config.js rename to client/tailwind.config.js index e3995d2..e950098 100644 --- a/tailwind.config.js +++ b/client/tailwind.config.js @@ -1,6 +1,6 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - content: ['client/simple-web-site/src/**/*.{html,ts}'], + content: ['./**/*.{html,ts,js}'], theme: { extend: { screens: { @@ -15,5 +15,5 @@ module.exports = { }, }, plugins: [], - important: true, + important: false, }; diff --git a/package-lock.json b/package-lock.json index 732b9a1..09d9f76 100644 --- a/package-lock.json +++ b/package-lock.json @@ -114,7 +114,7 @@ "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", "standard-version": "^9.5.0", - "tailwindcss": "^3.4.16", + "tailwindcss": "^3.4.17", "three": "^0.165.0", "tslib": "^2.3.0", "typescript": "~5.5.4", diff --git a/package.json b/package.json index c1a209d..99e731a 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "test:playwright": "npx playwright test" }, "lint-staged": { - "*.{js,ts,css,scss,html,md}": [ + "{client,server}*.{js,ts,css,scss,html,md}": [ "eslint --fix" ] }, @@ -121,7 +121,6 @@ "ajv": "^8.12.0", "allure-commandline": "^2.29.0", "allure-playwright": "^3.0.0-beta.5", - "autoprefixer": "^10.4.20", "commitizen": "^4.3.0", "cookie-parser": "^1.4.7", "cors": "^2.8.5", @@ -143,12 +142,13 @@ "lint-staged": "^15.2.10", "mongoose": "^8.9.2", "nyc": "^15.1.0", - "postcss": "^8.4.49", "prettier": "^3.4.2", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", "standard-version": "^9.5.0", - "tailwindcss": "^3.4.16", + "tailwindcss": "^3.4.17", + "postcss": "^8.4.49", + "autoprefixer": "^10.4.20", "three": "^0.165.0", "tslib": "^2.3.0", "typescript": "~5.5.4", diff --git a/server/.env b/server/.env index bcb484a..d000bff 100644 --- a/server/.env +++ b/server/.env @@ -2,3 +2,4 @@ ENV_NAME=development DATABASE_PROTOCOL=mongodb:// DATABASE_HOST=localhost:27017/ DATABASE_NAME=test +PORT=3000 diff --git a/server/.env.production b/server/.env.production index 2d92bea..065edfa 100644 --- a/server/.env.production +++ b/server/.env.production @@ -2,3 +2,4 @@ ENV_NAME=production DATABASE_PROTOCOL=mongodb:// DATABASE_HOST=localhost:27017/ DATABASE_NAME=test +PORT=3001 diff --git a/server/nest-cli.json b/server/nest-cli.json index f57d4b6..6ba8b7d 100644 --- a/server/nest-cli.json +++ b/server/nest-cli.json @@ -9,6 +9,10 @@ "include": "../.env.production", "outDir": "../build/.env" }, + { + "include": "./assets/*", + "outDir": "../build" + }, { "include": "../package.json", "outDir": "../build/package.json" diff --git a/server/src/app.module.ts b/server/src/app.module.ts index 5c0a51a..0964ffc 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -8,6 +8,7 @@ import * as Joi from 'joi'; import { AppController } from 'src/app.controller'; import { AppService } from 'src/app.service'; +import { EsService } from 'src/es/es.service'; import { HeroesModule } from 'src/heroes/heroes.module'; const ENV = process.env.NODE_ENV; @@ -53,6 +54,6 @@ const ENV = process.env.NODE_ENV; HeroesModule, ], controllers: [AppController], - providers: [AppService], + providers: [AppService, EsService], }) export class AppModule {} diff --git a/server/src/assets/heroes.json b/server/src/assets/heroes.json new file mode 100644 index 0000000..0e69f34 --- /dev/null +++ b/server/src/assets/heroes.json @@ -0,0 +1,50 @@ +[ + { + "squadName": "Super hero squad", + "homeTown": "Metro City", + "formed": 2016, + "secretBase": "Super tower", + "active": true, + "members": [ + { + "name": "Molecule Man", + "age": 29, + "secretIdentity": "Dan Jukes", + "powers": [ + "Radiation resistance", + "Turning tiny", + "Radiation blast" + ] + }, + { + "name": "Madame Uppercut", + "age": 39, + "secretIdentity": "Jane Wilson", + "powers": [ + "Million tonne punch", + "Damage resistance", + "Superhuman reflexes" + ] + }, + { + "name": "Eternal Flame", + "age": 1000000, + "secretIdentity": "Unknown", + "powers": [ + "Immortality", + "Heat Immunity", + "Inferno", + "Teleportation", + "Interdimensional travel" + ] + } + ] + }, + { + "squadName": "Lonely Super hero", + "homeTown": "Lonely City", + "formed": 2025, + "secretBase": "Lonely tower", + "active": false + } +] diff --git a/server/src/assets/test.json b/server/src/assets/test.json new file mode 100644 index 0000000..0cfd648 --- /dev/null +++ b/server/src/assets/test.json @@ -0,0 +1,3 @@ +{ + "description": "this file is for testing nestJS build process, which should output this file to build/assets folder" +} diff --git a/server/src/es/es.service.ts b/server/src/es/es.service.ts new file mode 100644 index 0000000..89cb371 --- /dev/null +++ b/server/src/es/es.service.ts @@ -0,0 +1,57 @@ +import { Client } from '@elastic/elasticsearch'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class EsService { + client = new Client({ + node: 'http://localhost:9200', + }); + + constructor() {} + + async deleteHeroIndex(indexName: string) { + try { + const response = await this.client.indices.delete({ + index: indexName, + }); + if (response.acknowledged) { + console.log(`Index ${indexName} deleted successfully.`); + } else { + console.error(`Failed to delete index ${indexName}.`); + } + } catch (error) { + console.error(`Error deleting index ${indexName}:`, error); + } + } + + async createHeroIndex(indexName: string) { + try { + const exists = await this.client.indices.exists({ index: indexName }); + if (!exists) { + await this.client.indices.create({ + index: indexName, + }); + console.log(`Index '${indexName}' created successfully.`); + } else { + console.log(`Index '${indexName}' already exists.`); + } + } catch (err) { + console.error('Error creating index:', err); + } + } + + async injectData(indexName: string, data: any[]) { + const body = data.flatMap((doc) => [{ index: { _index: indexName } }, doc]); + + const response = await this.client.bulk({ + refresh: true, + body, + }); + + if (response.errors) { + console.error('Errors occurred during indexing:', response.errors); + } else { + console.log(`Successfully indexed ${data.length} documents.`); + } + } +} diff --git a/server/src/main.ts b/server/src/main.ts index c999c15..f8b0540 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -1,8 +1,12 @@ +import * as fs from 'fs'; +import * as path from 'path'; + import { NestFactory } from '@nestjs/core'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import * as cookieParser from 'cookie-parser'; import { AppModule } from 'src/app.module'; +import { EsService } from 'src/es/es.service'; import { MyLogger } from 'src/myLogger'; async function bootstrap() { @@ -12,6 +16,13 @@ async function bootstrap() { app.enableCors(); app.use(cookieParser()); + const esService = app.get(EsService); + await esService.deleteHeroIndex('heroes'); + await esService.createHeroIndex('heroes'); + const filePath = path.join(__dirname, 'assets/heroes.json'); + const data = fs.readFileSync(filePath, 'utf-8'); + await esService.injectData('heroes', JSON.parse(data)); + const config = new DocumentBuilder() .setTitle('Simple Web Site OpenAPI') .setDescription('The API description') diff --git a/test/heroes.json b/test/heroes.json index 510983b..520b885 100644 --- a/test/heroes.json +++ b/test/heroes.json @@ -1,50 +1,56 @@ [ { - "squadName": "Super hero squad", - "homeTown": "Metro City", - "formed": 2016, - "secretBase": "Super tower", - "active": true, - "members": [ - { - "name": "Molecule Man", - "age": 29, - "secretIdentity": "Dan Jukes", - "powers": [ - "Radiation resistance", - "Turning tiny", - "Radiation blast" - ] + "_id": { + "$oid": "676f1540126a297f0007405e" }, - { - "name": "Madame Uppercut", - "age": 39, - "secretIdentity": "Jane Wilson", - "powers": [ - "Million tonne punch", - "Damage resistance", - "Superhuman reflexes" - ] - }, - { - "name": "Eternal Flame", - "age": 1000000, - "secretIdentity": "Unknown", - "powers": [ - "Immortality", - "Heat Immunity", - "Inferno", - "Teleportation", - "Interdimensional travel" - ] - } - ] -}, + "squadName": "Super hero squad", + "homeTown": "Metro City", + "formed": 2016, + "secretBase": "Super tower", + "active": true, + "members": [ + { + "name": "Molecule Man", + "age": 29, + "secretIdentity": "Dan Jukes", + "powers": [ + "Radiation resistance", + "Turning tiny", + "Radiation blast" + ] + }, + { + "name": "Madame Uppercut", + "age": 39, + "secretIdentity": "Jane Wilson", + "powers": [ + "Million tonne punch", + "Damage resistance", + "Superhuman reflexes" + ] + }, + { + "name": "Eternal Flame", + "age": 1000000, + "secretIdentity": "Unknown", + "powers": [ + "Immortality", + "Heat Immunity", + "Inferno", + "Teleportation", + "Interdimensional travel" + ] + } + ] + }, { - "squadName": "Lonely Super hero", - "homeTown": "Lonely City", - "formed": 2025, - "secretBase": "Lonely tower", - "active": false -} + "_id": { + "$oid": "676f1540126a297f0007405f" + }, + "squadName": "Lonely Super hero", + "homeTown": "Lonely City", + "formed": 2025, + "secretBase": "Lonely tower", + "active": false + } ] diff --git a/test/src/search-page.spec.ts b/test/src/search-page.spec.ts index 132ea2a..953d85e 100644 --- a/test/src/search-page.spec.ts +++ b/test/src/search-page.spec.ts @@ -1,4 +1,4 @@ -import { expect } from '@playwright/test'; +import {expect} from '@playwright/test'; import test from 'test/fixtures/baseTest'; test.describe('Click search link', async () => { @@ -8,13 +8,19 @@ test.describe('Click search link', async () => { await expect(page).toHaveTitle(`Search`); }); test('es result', async ({page}) => { - await expect(page.getByTestId('esResult')).toContainText( + const appHero = page.getByTestId('esResult').locator('app-hero'); + await expect(appHero.first()).toContainText( 'Radiation resistance,Turning tiny,Radiation blast' ); + await expect(appHero.nth(1)).toContainText('Lonely Super hero' + ); }); test('mongo result', async ({page}) => { - await expect(page.getByTestId('mongoResult')).toContainText( - 'Radiation resistance,Turning tiny,Radiation blast' + const appHero = page.getByTestId('mongoResult').locator('app-hero'); + await expect(appHero.first()).toContainText( + 'Lonely Super hero' + ); + await expect(appHero.nth(1)).toContainText('Radiation resistance,Turning tiny,Radiation blast' ); }); });