Skip to content

Commit d45f49b

Browse files
authored
feat(bff): add & use banner slider loaded from the backend (#322)
1 parent a679217 commit d45f49b

25 files changed

+416
-0
lines changed

apps/blog-bff/src/main.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { HTTPException } from 'hono/http-exception';
44

55
import { articles } from '@angular-love/blog-bff/articles/api';
66
import { authors } from '@angular-love/blog-bff/authors/api';
7+
import { banners } from '@angular-love/blog-bff/banners/api';
78
import { newsletter } from '@angular-love/blog-bff/newsletter/api';
89

910
const app = new Hono();
@@ -13,6 +14,7 @@ app.use('*', cors());
1314
app.route('/articles', articles);
1415
app.route('/authors', authors);
1516
app.route('/newsletter', newsletter);
17+
app.route('/banners', banners);
1618

1719
app.onError((err, c) => {
1820
console.error(err);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"extends": ["../../../../.eslintrc.json"],
3+
"ignorePatterns": ["!**/*"],
4+
"overrides": [
5+
{
6+
"files": ["*.ts"],
7+
"extends": [
8+
"plugin:@nx/angular",
9+
"plugin:@angular-eslint/template/process-inline-templates"
10+
],
11+
"rules": {
12+
"@angular-eslint/directive-selector": [
13+
"error",
14+
{
15+
"type": "attribute",
16+
"prefix": "al",
17+
"style": "camelCase"
18+
}
19+
],
20+
"@angular-eslint/component-selector": [
21+
"error",
22+
{
23+
"type": "element",
24+
"prefix": "al",
25+
"style": "kebab-case"
26+
}
27+
]
28+
}
29+
},
30+
{
31+
"files": ["*.html"],
32+
"extends": ["plugin:@nx/angular-template"],
33+
"rules": {}
34+
}
35+
]
36+
}

libs/blog-bff/banners/api/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# api-banners
2+
3+
This library was generated with [Nx](https://nx.dev).
4+
5+
## Running unit tests
6+
7+
Run `nx test api-banners` to execute the unit tests.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/* eslint-disable */
2+
export default {
3+
displayName: 'api-banners',
4+
preset: '../../../../jest.preset.js',
5+
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
6+
coverageDirectory: '../../../../coverage/libs/blog-bff/banners/api',
7+
transform: {
8+
'^.+\\.(ts|mjs|js|html)$': [
9+
'jest-preset-angular',
10+
{
11+
tsconfig: '<rootDir>/tsconfig.spec.json',
12+
stringifyContentPathRegex: '\\.(html|svg)$',
13+
},
14+
],
15+
},
16+
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
17+
snapshotSerializers: [
18+
'jest-preset-angular/build/serializers/no-ng-attributes',
19+
'jest-preset-angular/build/serializers/ng-snapshot',
20+
'jest-preset-angular/build/serializers/html-comment',
21+
],
22+
};
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "api-banners",
3+
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
4+
"sourceRoot": "libs/blog-bff/banners/api/src",
5+
"prefix": "al",
6+
"projectType": "library",
7+
"tags": ["scope:bff", "type:api"],
8+
"targets": {
9+
"test": {
10+
"executor": "@nx/jest:jest",
11+
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
12+
"options": {
13+
"jestConfig": "libs/blog-bff/banners/api/jest.config.ts"
14+
}
15+
},
16+
"lint": {
17+
"executor": "@nx/eslint:lint"
18+
}
19+
}
20+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as banners } from './lib/api';
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Hono } from 'hono';
2+
3+
import {
4+
appCache,
5+
langMw,
6+
} from '@angular-love/blog-bff/shared/util-middleware';
7+
import { wpClientMw } from '@angular-love/util-wp';
8+
9+
import { toBanner } from './mappers';
10+
import { WpBanners } from './wp-banners';
11+
12+
const app = new Hono().use(appCache).use(wpClientMw).use(langMw());
13+
14+
app.get('/', async (c) => {
15+
const client = new WpBanners(c.var.createWPClient());
16+
17+
const banner = (await client.getBanners()).data[0];
18+
const media = await client.getMediaByBannerId(banner.id);
19+
20+
return c.json(toBanner(banner, media.data));
21+
});
22+
23+
export default app;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export interface WPBannerDto {
2+
id: number;
3+
acf: {
4+
display_time: string;
5+
slides: {
6+
slide_image: number /* slideId */;
7+
slide_url: string /* url to navigate to after click */;
8+
}[];
9+
};
10+
}
11+
12+
export interface WPBannerMediaDto {
13+
id: number;
14+
alt_text: string;
15+
guid: {
16+
rendered: string; // URL
17+
};
18+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Slider } from '@angular-love/blog/contracts/banners';
2+
3+
import { WPBannerDto, WPBannerMediaDto } from './dtos';
4+
5+
export const toBanner = (
6+
dto: WPBannerDto,
7+
mediaDto: WPBannerMediaDto[],
8+
): Slider => {
9+
return {
10+
slideDisplayTimeMs: +dto.acf.display_time,
11+
slides: dto.acf.slides.map((slide) => {
12+
const media = mediaDto.find((media) => media.id === slide.slide_image)!;
13+
return {
14+
url: media.guid.rendered,
15+
alt: media.alt_text,
16+
navigateTo: slide.slide_url,
17+
};
18+
}),
19+
};
20+
};
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { WPResponse, WPRestClient } from '@angular-love/util-wp';
2+
3+
import { WPBannerDto, WPBannerMediaDto } from './dtos';
4+
5+
export class WpBanners {
6+
constructor(private readonly _wpClient: WPRestClient) {}
7+
8+
async getBanners(
9+
query?: Record<string, string | number>,
10+
): Promise<WPResponse<WPBannerDto[]>> {
11+
return this._wpClient.get<WPBannerDto[]>('banner', {
12+
...query,
13+
_fields: 'id,acf',
14+
});
15+
}
16+
17+
async getMediaByBannerId(
18+
bannerId: number,
19+
): Promise<WPResponse<WPBannerMediaDto[]>> {
20+
return this._wpClient.get<WPBannerMediaDto[]>('media', {
21+
parent: `${bannerId}`,
22+
_fields: 'id,guid,alt_text',
23+
});
24+
}
25+
}

0 commit comments

Comments
 (0)