diff --git a/src/app.controller.spec.ts b/src/app.controller.spec.ts index 0ed848a..c985f69 100644 --- a/src/app.controller.spec.ts +++ b/src/app.controller.spec.ts @@ -1,5 +1,6 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; +import { I18nContext } from 'nestjs-i18n'; import { AppService } from './app.service'; import { ConfigService } from '@nestjs/config'; @@ -15,9 +16,12 @@ describe('AppController', () => { appController = app.get(AppController); }); - describe('root', () => { - it('should be "Hello World!"', () => { - expect(appController.getHello()?.message).toBe('Hello World!'); + describe('index', () => { + it('should return a context object with translated pageTitle', () => { + const i18n = { t: (key: string) => key } as unknown as I18nContext; + expect(appController.index(i18n)).toEqual({ + pageTitle: 'lang.PAGE_TITLE_HOME', + }); }); }); }); diff --git a/src/app.controller.ts b/src/app.controller.ts index 0862e76..2fe2c0e 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -5,10 +5,14 @@ import { Logger, Post, Render, + Req, + Res, Sse, } from '@nestjs/common'; import { Subject } from 'rxjs'; import { AppService } from './app.service'; +import { I18n, I18nContext } from 'nestjs-i18n'; +import { FastifyReply, FastifyRequest } from 'fastify'; @Controller() export class AppController { @@ -20,10 +24,8 @@ export class AppController { @Get() @Render('index') - getHello(): any { - return { - message: this.appService.getHello(), - }; + index(@I18n() i18n: I18nContext) { + return { pageTitle: i18n.t('lang.PAGE_TITLE_HOME') }; } @Get('chat') @@ -97,4 +99,20 @@ export class AppController { well_known() { return {}; // Just return empty object } + + @Get('sitemap.xml') + sitemap(@Req() req: FastifyRequest, @Res() reply: FastifyReply): void { + const baseUrl = `${req.protocol}://${req.host}`; + reply.header('Content-Type', 'application/xml; charset=utf-8'); + reply.send( + ` + + + ${baseUrl}/ + weekly + 1.0 + +`, + ); + } } diff --git a/src/app.module.ts b/src/app.module.ts index 4c8e9e1..4032326 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -70,8 +70,8 @@ import { ViewContextModule } from './view-context/view-context.module'; PORT: Joi.number().default(3000), APP_NAME: Joi.string().default('Boilerplate'), AUTH_ENABLED: Joi.boolean().default(false), - PWA_ENABLED: Joi.boolean().default(false), DISABLE_REGISTRATION: Joi.boolean().default(false), + PWA_ENABLED: Joi.boolean().default(false), ACCESS_TOKEN_SECRET: Joi.string().default('ChangeMe!'), PUBLIC_VAPID_KEY: Joi.optional().default( 'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U', @@ -208,6 +208,6 @@ export class AppModule implements OnModuleInit { async onModuleInit(): Promise { this.logger.log(`NODE_ENV: ${this.configService.get('NODE_ENV')}`); this.logger.log(`DATA_PATH: ${this.configService.get('DATA_PATH')}`); - await this.orm.getMigrator().up(); + await this.orm.migrator.up(); } } diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index dda1352..af99f07 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -16,7 +16,7 @@ import { EmailService } from '../email/email.service'; import { PasswordReset } from '../dal/entity/passwordReset.entity'; import { randomInt } from 'crypto'; import { ResetPasswordDto } from './dto/resetPassword.dto'; -import * as Handlebars from 'handlebars'; +import Handlebars from 'handlebars'; import { readFileSync } from 'fs'; import { join } from 'path'; diff --git a/src/file/controller/file.controller.ts b/src/file/controller/file.controller.ts index e0c1d1d..ae022b3 100644 --- a/src/file/controller/file.controller.ts +++ b/src/file/controller/file.controller.ts @@ -4,7 +4,6 @@ import { Controller, Get, Header, - Inject, Logger, Param, Post, @@ -25,7 +24,6 @@ export class FileController { private logger = new Logger(FileController.name); constructor( - @Inject(FileService) private readonly fileService: FileService, @InjectRepository(UserEntity) private readonly userRepository: EntityRepository, diff --git a/src/file/s3-file/s3-file.service.spec.ts b/src/file/s3-file/s3-file.service.spec.ts index 5385256..d939b67 100644 --- a/src/file/s3-file/s3-file.service.spec.ts +++ b/src/file/s3-file/s3-file.service.spec.ts @@ -2,7 +2,7 @@ import { EntityManager } from '@mikro-orm/core'; import { getRepositoryToken } from '@mikro-orm/nestjs'; import { ConfigService } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; -import { S3Module, S3ModuleOptions } from 'nestjs-s3'; +import { S3Module } from 'nestjs-s3'; import { File } from '../../dal/entity/file.entity'; import { S3FileService } from './s3-file.service'; @@ -20,7 +20,7 @@ describe('S3FileService', () => { s3ForcePathStyle: true, signatureVersion: 'v4', }, - } as S3ModuleOptions), + }), ], providers: [ S3FileService, diff --git a/src/i18n/en/lang.json b/src/i18n/en/lang.json index 89f1a73..e2cf9e2 100644 --- a/src/i18n/en/lang.json +++ b/src/i18n/en/lang.json @@ -1,5 +1,6 @@ { "APP_DESCRIPTION": "A rapid web application development boilerplate built on NestJS, HTMX, Tailwind, DaisyUI, MikroORM, and more.", + "PAGE_TITLE_HOME": "Lazztech.Boilerplate - rapid web application development boilerplate", "TAGLINE": "Ship faster with a proven full-stack boilerplate", "HERO_DESCRIPTION": "A decade of engineering experience distilled into an opinionated, production-ready starting point for modern web apps. Built on NestJS, HTMX, Tailwind, DaisyUI, MikroORM, and more.", "GET_STARTED": "Get Started", diff --git a/views/layout.hbs b/views/layout.hbs index 9598b73..ff26b47 100644 --- a/views/layout.hbs +++ b/views/layout.hbs @@ -34,7 +34,7 @@ https://nestjs-i18n.com/quick-start --}} https://htmx.org/attributes/hx-swap/#modifiers --}} {{!-- https://htmx.org/quirks/#attribute-inheritance --}} - + {{appName}} {{! Used inconjunction with service worker sw.js to enable PWA (Progressive @@ -58,10 +58,12 @@ https://nestjs-i18n.com/quick-start --}} user experience where the body gets swapped out without a full page reload similar to a SPA. https://htmx.org/attributes/hx-boost/ https://htmx.org/quirks/#some-people-don-t-like-hx-boost - - safe-area-inset-bottom ensures smartphone nav doesnt interfere with UI. - }} - + safe-area-inset-bottom ensures smartphone nav doesnt interfere with UI. }} + {{! Caution: Triple brackets in handlebarsjs disables auto-escaping and may pose a XSS risk with HTMX. However the exception to this rule is when loading hbs rendered content which is pre-santized by the handlebars