From 1e41168adf10269be2824d069653c045d318aecd Mon Sep 17 00:00:00 2001
From: Bruno
Date: Wed, 23 Oct 2024 15:35:02 +0200
Subject: [PATCH 01/11] feat: display api doc on prod env
---
packages/core/manifest/src/app.module.ts | 3 +--
packages/core/manifest/src/main.ts | 14 ++++++--------
2 files changed, 7 insertions(+), 10 deletions(-)
diff --git a/packages/core/manifest/src/app.module.ts b/packages/core/manifest/src/app.module.ts
index 5c46f8b5..c12e0fef 100644
--- a/packages/core/manifest/src/app.module.ts
+++ b/packages/core/manifest/src/app.module.ts
@@ -66,9 +66,8 @@ export class AppModule {
private async init() {
const isSeed: boolean = process.argv[1].includes('seed')
const isTest: boolean = process.env.NODE_ENV === 'test'
- const isProduction: boolean = process.env.NODE_ENV === 'production'
- if (!isSeed && !isTest && !isProduction) {
+ if (!isSeed && !isTest) {
this.loggerService.initMessage()
}
}
diff --git a/packages/core/manifest/src/main.ts b/packages/core/manifest/src/main.ts
index 02f1d37d..0def0a9c 100644
--- a/packages/core/manifest/src/main.ts
+++ b/packages/core/manifest/src/main.ts
@@ -51,14 +51,13 @@ async function bootstrap() {
}
})
- if (!isProduction) {
- const openApiService: OpenApiService = app.get(OpenApiService)
+ const openApiService: OpenApiService = app.get(OpenApiService)
- SwaggerModule.setup('api', app, openApiService.generateOpenApiObject(), {
- customfavIcon: 'assets/images/open-api/favicon.ico',
- customSiteTitle: 'Manifest API Doc',
+ SwaggerModule.setup('api', app, openApiService.generateOpenApiObject(), {
+ customfavIcon: 'assets/images/open-api/favicon.ico',
+ customSiteTitle: 'Manifest API Doc',
- customCss: `
+ customCss: `
.swagger-ui html {
box-sizing: border-box;
@@ -1791,8 +1790,7 @@ background: #ce107c;
fill: #535356;
}
`
- })
- }
+ })
await app.listen(configService.get('PORT') || DEFAULT_PORT)
}
From 5fd6fec209f6e7dc7f334a80bc28a34e0b076b81 Mon Sep 17 00:00:00 2001
From: Bruno
Date: Thu, 31 Oct 2024 15:16:30 +0100
Subject: [PATCH 02/11] feat(addManifest): adds README and gitignore folders
---
README.md | 2 +-
packages/add-manifest/README.md | 4 +-
packages/add-manifest/assets/README.md | 58 +++++++++++++++++++++
packages/add-manifest/src/commands/index.ts | 41 +++++++++++----
packages/core/manifest/package.json | 2 +-
5 files changed, 94 insertions(+), 13 deletions(-)
create mode 100644 packages/add-manifest/assets/README.md
diff --git a/README.md b/README.md
index c831ee95..fec5ff0c 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
-A backend so simple that it fits in a YAML file
+A backend so simple that it fits into 1 YAML file
diff --git a/packages/add-manifest/README.md b/packages/add-manifest/README.md
index e6067011..31290f2d 100644
--- a/packages/add-manifest/README.md
+++ b/packages/add-manifest/README.md
@@ -14,9 +14,11 @@ npm install
# Run from a test folder to prevent messing with project files.
mkdir test-folder
cd test-folder
-../bin/dev.js create
+../bin/dev.js
```
+However due to the monorepo workspace structure, the launch script will fail as the path to the node modules folder is different than when served.
+
## Publish
```bash
diff --git a/packages/add-manifest/assets/README.md b/packages/add-manifest/assets/README.md
new file mode 100644
index 00000000..0cd97613
--- /dev/null
+++ b/packages/add-manifest/assets/README.md
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+A backend so simple that it fits into 1 YAML file
+
+
+
+
+
+
+
+
+
+
+## Description
+
+This project was made with [Manifest](https://github.com/mnfst/manifest).
+
+## Installation
+
+```bash
+$ npm install
+```
+
+## Running the app
+
+To run the app in the development mode:
+
+```bash
+npm run manifest
+```
+
+- Open [http://localhost:1111](http://localhost:1111) to open your admin UI it in your browser
+- Open [http://localhost:1111/api](http://localhost:111/api) to view your REST API documentation
+
+The page will reload when you make changes.
+
+## Seed dummy data
+
+Seeds some dummy data for your entities:
+
+```bash
+npm run manifest:seed
+```
+
+## Community & Resources
+
+- [Docs](https://manifest.build/docs) - Get started with Manifest
+- [Discord](https://discord.gg/FepAked3W7) - Come chat with the community
+- [Github](https://github.com/mnfst/manifest/issues) - Report bugs and share ideas to improve the product.
diff --git a/packages/add-manifest/src/commands/index.ts b/packages/add-manifest/src/commands/index.ts
index c9eba975..96764de4 100644
--- a/packages/add-manifest/src/commands/index.ts
+++ b/packages/add-manifest/src/commands/index.ts
@@ -28,11 +28,12 @@ export class MyCommand extends Command {
* 5. Update the .vscode/settings.json file with the recommended settings.
* 6. Update the .gitignore file with the recommended settings.
* 7. Update the .env file with the environment variables.
- * 8. Install the new packages.
- * 9. Serve the new app.
- * 10. Wait for the server to start.
- * 11. Seed the database.
- * 12. Open the browser.
+ * 8. If no README.md file exists, create one.
+ * 9. Install the new packages.
+ * 10. Serve the new app.
+ * 11. Wait for the server to start.
+ * 12. Seed the database.
+ * 13. Open the browser.
*/
async run(): Promise {
const folderName = 'manifest'
@@ -42,6 +43,7 @@ export class MyCommand extends Command {
const spinner = ora('Add Manifest to your project...').start()
+ // * 1. Create a folder with the name `manifest`.
// Construct the folder path. This example creates the folder in the current working directory.
const folderPath = path.join(process.cwd(), folderName)
@@ -56,6 +58,7 @@ export class MyCommand extends Command {
// Create the folder
fs.mkdirSync(folderPath)
+ // * 2. Create a file inside the folder with the name `manifest.yml`.
// Path where the new file should be created
const newFilePath = path.join(folderPath, initialFileName)
@@ -155,7 +158,7 @@ export class MyCommand extends Command {
})
)
- // Update the .gitignore file with the recommended settings.
+ // * 7. Update the .env file with the environment variables.
const gitignorePath = path.join(process.cwd(), '.gitignore')
let gitignoreContent = ''
@@ -163,14 +166,32 @@ export class MyCommand extends Command {
gitignoreContent = fs.readFileSync(gitignorePath, 'utf8')
}
- if (!gitignoreContent.includes('node_modules')) {
- gitignoreContent += '\nnode_modules'
- gitignoreContent += '\n.env'
- }
+ const newGitignoreLines: string[] = [
+ 'node_modules',
+ '.env',
+ 'public',
+ 'manifest/backend.db'
+ ]
+ newGitignoreLines.forEach((line) => {
+ if (!gitignoreContent.includes(line)) {
+ gitignoreContent += `\n${line}`
+ }
+ })
fs.writeFileSync(gitignorePath, gitignoreContent)
spinner.succeed()
+
+ // * 8. Add a README.md file if it doesn't exist.
+ const readmeFilePath = path.join(process.cwd(), 'README.md')
+ if (!fs.existsSync(readmeFilePath)) {
+ fs.writeFileSync(
+ readmeFilePath,
+ fs.readFileSync(path.join(assetFolderPath, 'README.md'), 'utf8')
+ )
+ }
+
+ // * 9. Install the new packages.
spinner.start('Install dependencies...')
// Install deps.
diff --git a/packages/core/manifest/package.json b/packages/core/manifest/package.json
index dd27cb55..d2fb44df 100644
--- a/packages/core/manifest/package.json
+++ b/packages/core/manifest/package.json
@@ -16,7 +16,7 @@
"manifest",
"backend",
"backend-as-a-service",
- "bass",
+ "baas",
"api",
"rest",
"fullstack",
From e7ecff863505f8ad79ef8bfec8dff70d5370e5c5 Mon Sep 17 00:00:00 2001
From: Bruno
Date: Mon, 4 Nov 2024 11:01:56 +0100
Subject: [PATCH 03/11] feat(onboarding): signup first admin endpoint (if db
empty)
---
.../core/manifest/src/auth/auth.controller.ts | 11 ++++
.../core/manifest/src/auth/auth.service.ts | 6 +-
.../dtos/signup-authenticable-entity.dto.ts | 4 +-
.../src/auth/guards/is-db-empty.guard.ts | 43 +++++++++++++
.../src/auth/tests/is-db-empty.guard.spec.ts | 62 +++++++++++++++++++
5 files changed, 123 insertions(+), 3 deletions(-)
create mode 100644 packages/core/manifest/src/auth/guards/is-db-empty.guard.ts
create mode 100644 packages/core/manifest/src/auth/tests/is-db-empty.guard.spec.ts
diff --git a/packages/core/manifest/src/auth/auth.controller.ts b/packages/core/manifest/src/auth/auth.controller.ts
index 1d51e5b2..ee98c7ae 100644
--- a/packages/core/manifest/src/auth/auth.controller.ts
+++ b/packages/core/manifest/src/auth/auth.controller.ts
@@ -14,6 +14,7 @@ import { AuthService } from './auth.service'
import { SignupAuthenticableEntityDto } from './dtos/signup-authenticable-entity.dto'
import { Rule } from './decorators/rule.decorator'
import { AuthorizationGuard } from './guards/authorization.guard'
+import { IsDbEmptyGuard } from './guards/is-db-empty.guard'
@Controller('auth')
@UseGuards(AuthorizationGuard)
@@ -30,6 +31,16 @@ export class AuthController {
return this.authService.createToken(entity, signupUserDto)
}
+ @Post(':admins/signup')
+ @UseGuards(IsDbEmptyGuard)
+ public async signupAdmin(
+ @Body() signupUserDto: SignupAuthenticableEntityDto
+ ): Promise<{
+ token: string
+ }> {
+ return this.authService.signup('admins', signupUserDto, true)
+ }
+
@Post(':entity/signup')
@Rule('signup')
public async signup(
diff --git a/packages/core/manifest/src/auth/auth.service.ts b/packages/core/manifest/src/auth/auth.service.ts
index f3cd53cc..528600dd 100644
--- a/packages/core/manifest/src/auth/auth.service.ts
+++ b/packages/core/manifest/src/auth/auth.service.ts
@@ -78,15 +78,17 @@ export class AuthService {
* @param entitySlug The slug of the AuthenticableEntity where the user is going to be created
* @param email The email of the user
* @param password The password of the user
+ * @param byPassAdminCheck If true, the method will not check if the entity is an admin
*
* @returns A JWT token of the created user
*
*/
async signup(
entitySlug: string,
- signupUserDto: SignupAuthenticableEntityDto
+ signupUserDto: SignupAuthenticableEntityDto,
+ byPassAdminCheck = false
): Promise<{ token: string }> {
- if (entitySlug === ADMIN_ENTITY_MANIFEST.slug) {
+ if (entitySlug === ADMIN_ENTITY_MANIFEST.slug && !byPassAdminCheck) {
throw new HttpException(
'Admins cannot be created with this method.',
HttpStatus.BAD_REQUEST
diff --git a/packages/core/manifest/src/auth/dtos/signup-authenticable-entity.dto.ts b/packages/core/manifest/src/auth/dtos/signup-authenticable-entity.dto.ts
index 9bba208f..a7aa2130 100644
--- a/packages/core/manifest/src/auth/dtos/signup-authenticable-entity.dto.ts
+++ b/packages/core/manifest/src/auth/dtos/signup-authenticable-entity.dto.ts
@@ -1,9 +1,11 @@
-import { IsEmail, IsNotEmpty } from 'class-validator'
+import { IsEmail, IsNotEmpty, IsString } from 'class-validator'
export class SignupAuthenticableEntityDto {
@IsEmail()
+ @IsNotEmpty()
public email: string
+ @IsString()
@IsNotEmpty()
public password: string
}
diff --git a/packages/core/manifest/src/auth/guards/is-db-empty.guard.ts b/packages/core/manifest/src/auth/guards/is-db-empty.guard.ts
new file mode 100644
index 00000000..39c3cfe3
--- /dev/null
+++ b/packages/core/manifest/src/auth/guards/is-db-empty.guard.ts
@@ -0,0 +1,43 @@
+import { CanActivate, Injectable } from '@nestjs/common'
+import { ManifestService } from '../../manifest/services/manifest.service'
+import { AppManifest, EntityManifest } from '@repo/types'
+import { EntityService } from '../../entity/services/entity.service'
+import { ADMIN_ENTITY_MANIFEST } from '../../constants'
+
+@Injectable()
+export class IsDbEmptyGuard implements CanActivate {
+ constructor(
+ private readonly manifestService: ManifestService,
+ private readonly entityService: EntityService
+ ) {}
+
+ /**
+ * Check if the database is empty (no items in any entity, even admin).
+ *
+ * @returns True if the database is empty, false otherwise.
+ * */
+ async canActivate(): Promise {
+ const appManifest: AppManifest = this.manifestService.getAppManifest()
+
+ const entities = [
+ ...Object.values(appManifest.entities),
+ ADMIN_ENTITY_MANIFEST
+ ]
+ let totalItems = 0
+
+ await Promise.all(
+ Object.values(entities).map(async (entityManifest: EntityManifest) => {
+ return this.entityService
+ .getEntityRepository({
+ entitySlug: entityManifest.slug
+ })
+ .createQueryBuilder('entity')
+ .getCount()
+ })
+ ).then((counts: number[]) => {
+ totalItems = counts.reduce((acc, count) => acc + count, 0)
+ })
+
+ return totalItems === 0
+ }
+}
diff --git a/packages/core/manifest/src/auth/tests/is-db-empty.guard.spec.ts b/packages/core/manifest/src/auth/tests/is-db-empty.guard.spec.ts
new file mode 100644
index 00000000..f6671415
--- /dev/null
+++ b/packages/core/manifest/src/auth/tests/is-db-empty.guard.spec.ts
@@ -0,0 +1,62 @@
+import { Test } from '@nestjs/testing'
+import { IsDbEmptyGuard } from '../guards/is-db-empty.guard'
+import { ManifestService } from '../../manifest/services/manifest.service'
+import { EntityService } from '../../entity/services/entity.service'
+
+describe('IsDbEmptyGuard', () => {
+ let manifestService: ManifestService
+ let entityService: EntityService
+
+ beforeEach(async () => {
+ const module = await Test.createTestingModule({
+ providers: [
+ IsDbEmptyGuard,
+ {
+ provide: ManifestService,
+ useValue: {
+ getAppManifest: jest.fn().mockReturnValue({
+ entities: {}
+ })
+ }
+ },
+ {
+ provide: EntityService,
+ useValue: {
+ getEntityRepository: jest.fn().mockReturnValue({
+ createQueryBuilder: jest.fn().mockReturnValue({
+ getCount: jest.fn().mockReturnValue(0)
+ })
+ })
+ }
+ }
+ ]
+ }).compile()
+
+ manifestService = module.get(ManifestService)
+ entityService = module.get(EntityService)
+ })
+
+ it('should be defined', () => {
+ expect(new IsDbEmptyGuard(manifestService, entityService)).toBeDefined()
+ })
+
+ it('should return true if the database is empty', async () => {
+ const isDbEmptyGuard = new IsDbEmptyGuard(manifestService, entityService)
+ const res = await isDbEmptyGuard.canActivate()
+
+ expect(res).toBe(true)
+ })
+
+ it('should return false if the database is not empty', async () => {
+ jest.spyOn(entityService, 'getEntityRepository').mockReturnValue({
+ createQueryBuilder: jest.fn().mockReturnValue({
+ getCount: jest.fn().mockReturnValue(1)
+ })
+ } as any)
+
+ const isDbEmptyGuard = new IsDbEmptyGuard(manifestService, entityService)
+ const res = await isDbEmptyGuard.canActivate()
+
+ expect(res).toBe(false)
+ })
+})
From 9f05a6cce4b3b1a5d3ecf9036d1803d9888bd483 Mon Sep 17 00:00:00 2001
From: Bruno
Date: Mon, 4 Nov 2024 11:45:04 +0100
Subject: [PATCH 04/11] feat(onboarding): isDbEmpty endpoint
Refactor: Extracted isDbEmpty guard logic in new database service
---
.../core/manifest/src/auth/auth.module.ts | 3 +-
.../src/auth/guards/is-db-empty.guard.ts | 33 +---------
.../src/auth/tests/is-db-empty.guard.spec.ts | 39 ++++--------
.../crud/controllers/database.controller.ts | 16 +++++
.../core/manifest/src/crud/crud.module.ts | 7 ++-
.../src/crud/services/database.service.ts | 43 +++++++++++++
.../crud/tests/database.controller.spec.ts | 18 ++++++
.../src/crud/tests/database.service.spec.ts | 62 +++++++++++++++++++
8 files changed, 160 insertions(+), 61 deletions(-)
create mode 100644 packages/core/manifest/src/crud/controllers/database.controller.ts
create mode 100644 packages/core/manifest/src/crud/services/database.service.ts
create mode 100644 packages/core/manifest/src/crud/tests/database.controller.spec.ts
create mode 100644 packages/core/manifest/src/crud/tests/database.service.spec.ts
diff --git a/packages/core/manifest/src/auth/auth.module.ts b/packages/core/manifest/src/auth/auth.module.ts
index 6afc6c91..1df89597 100644
--- a/packages/core/manifest/src/auth/auth.module.ts
+++ b/packages/core/manifest/src/auth/auth.module.ts
@@ -4,11 +4,12 @@ import { EntityModule } from '../entity/entity.module'
import { AuthController } from './auth.controller'
import { AuthService } from './auth.service'
import { ManifestModule } from '../manifest/manifest.module'
+import { DatabaseService } from '../crud/services/database.service'
@Module({
imports: [EntityModule, forwardRef(() => ManifestModule)],
controllers: [AuthController],
- providers: [AuthService],
+ providers: [AuthService, DatabaseService],
exports: [AuthService]
})
export class AuthModule {}
diff --git a/packages/core/manifest/src/auth/guards/is-db-empty.guard.ts b/packages/core/manifest/src/auth/guards/is-db-empty.guard.ts
index 39c3cfe3..80fc483f 100644
--- a/packages/core/manifest/src/auth/guards/is-db-empty.guard.ts
+++ b/packages/core/manifest/src/auth/guards/is-db-empty.guard.ts
@@ -1,15 +1,9 @@
import { CanActivate, Injectable } from '@nestjs/common'
-import { ManifestService } from '../../manifest/services/manifest.service'
-import { AppManifest, EntityManifest } from '@repo/types'
-import { EntityService } from '../../entity/services/entity.service'
-import { ADMIN_ENTITY_MANIFEST } from '../../constants'
+import { DatabaseService } from '../../crud/services/database.service'
@Injectable()
export class IsDbEmptyGuard implements CanActivate {
- constructor(
- private readonly manifestService: ManifestService,
- private readonly entityService: EntityService
- ) {}
+ constructor(private readonly databaseService: DatabaseService) {}
/**
* Check if the database is empty (no items in any entity, even admin).
@@ -17,27 +11,6 @@ export class IsDbEmptyGuard implements CanActivate {
* @returns True if the database is empty, false otherwise.
* */
async canActivate(): Promise {
- const appManifest: AppManifest = this.manifestService.getAppManifest()
-
- const entities = [
- ...Object.values(appManifest.entities),
- ADMIN_ENTITY_MANIFEST
- ]
- let totalItems = 0
-
- await Promise.all(
- Object.values(entities).map(async (entityManifest: EntityManifest) => {
- return this.entityService
- .getEntityRepository({
- entitySlug: entityManifest.slug
- })
- .createQueryBuilder('entity')
- .getCount()
- })
- ).then((counts: number[]) => {
- totalItems = counts.reduce((acc, count) => acc + count, 0)
- })
-
- return totalItems === 0
+ return this.databaseService.isDbEmpty()
}
}
diff --git a/packages/core/manifest/src/auth/tests/is-db-empty.guard.spec.ts b/packages/core/manifest/src/auth/tests/is-db-empty.guard.spec.ts
index f6671415..341a9cb0 100644
--- a/packages/core/manifest/src/auth/tests/is-db-empty.guard.spec.ts
+++ b/packages/core/manifest/src/auth/tests/is-db-empty.guard.spec.ts
@@ -1,60 +1,43 @@
import { Test } from '@nestjs/testing'
import { IsDbEmptyGuard } from '../guards/is-db-empty.guard'
-import { ManifestService } from '../../manifest/services/manifest.service'
-import { EntityService } from '../../entity/services/entity.service'
+import { DatabaseService } from '../../crud/services/database.service'
describe('IsDbEmptyGuard', () => {
- let manifestService: ManifestService
- let entityService: EntityService
+ let databaseService: DatabaseService
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
IsDbEmptyGuard,
{
- provide: ManifestService,
+ provide: DatabaseService,
useValue: {
- getAppManifest: jest.fn().mockReturnValue({
- entities: {}
- })
- }
- },
- {
- provide: EntityService,
- useValue: {
- getEntityRepository: jest.fn().mockReturnValue({
- createQueryBuilder: jest.fn().mockReturnValue({
- getCount: jest.fn().mockReturnValue(0)
- })
- })
+ isDbEmpty: jest.fn().mockReturnValue(Promise.resolve(true))
}
}
]
}).compile()
- manifestService = module.get(ManifestService)
- entityService = module.get(EntityService)
+ databaseService = module.get(DatabaseService)
})
it('should be defined', () => {
- expect(new IsDbEmptyGuard(manifestService, entityService)).toBeDefined()
+ expect(new IsDbEmptyGuard(databaseService)).toBeDefined()
})
it('should return true if the database is empty', async () => {
- const isDbEmptyGuard = new IsDbEmptyGuard(manifestService, entityService)
+ const isDbEmptyGuard = new IsDbEmptyGuard(databaseService)
const res = await isDbEmptyGuard.canActivate()
expect(res).toBe(true)
})
it('should return false if the database is not empty', async () => {
- jest.spyOn(entityService, 'getEntityRepository').mockReturnValue({
- createQueryBuilder: jest.fn().mockReturnValue({
- getCount: jest.fn().mockReturnValue(1)
- })
- } as any)
+ jest
+ .spyOn(databaseService, 'isDbEmpty')
+ .mockReturnValue(Promise.resolve(false))
- const isDbEmptyGuard = new IsDbEmptyGuard(manifestService, entityService)
+ const isDbEmptyGuard = new IsDbEmptyGuard(databaseService)
const res = await isDbEmptyGuard.canActivate()
expect(res).toBe(false)
diff --git a/packages/core/manifest/src/crud/controllers/database.controller.ts b/packages/core/manifest/src/crud/controllers/database.controller.ts
new file mode 100644
index 00000000..8eaae478
--- /dev/null
+++ b/packages/core/manifest/src/crud/controllers/database.controller.ts
@@ -0,0 +1,16 @@
+import { Controller, Get } from '@nestjs/common'
+import { DatabaseService } from '../services/database.service'
+
+@Controller('db')
+export class DatabaseController {
+ constructor(private readonly databaseService: DatabaseService) {}
+
+ @Get('is-db-empty')
+ public async isDbEmpty(): Promise<{
+ empty: boolean
+ }> {
+ const empty = await this.databaseService.isDbEmpty()
+
+ return { empty }
+ }
+}
diff --git a/packages/core/manifest/src/crud/crud.module.ts b/packages/core/manifest/src/crud/crud.module.ts
index 6d1a06d1..7e8daee7 100644
--- a/packages/core/manifest/src/crud/crud.module.ts
+++ b/packages/core/manifest/src/crud/crud.module.ts
@@ -8,10 +8,13 @@ import { CrudService } from './services/crud.service'
import { PaginationService } from './services/pagination.service'
import { ValidationModule } from '../validation/validation.module'
import { AuthService } from '../auth/auth.service'
+import { DatabaseService } from './services/database.service'
+import { DatabaseController } from './controllers/database.controller'
@Module({
imports: [EntityModule, ManifestModule, ValidationModule],
- controllers: [CrudController],
- providers: [CrudService, PaginationService, AuthService]
+ controllers: [CrudController, DatabaseController],
+ providers: [CrudService, PaginationService, AuthService, DatabaseService],
+ exports: [DatabaseService]
})
export class CrudModule {}
diff --git a/packages/core/manifest/src/crud/services/database.service.ts b/packages/core/manifest/src/crud/services/database.service.ts
new file mode 100644
index 00000000..639c1a70
--- /dev/null
+++ b/packages/core/manifest/src/crud/services/database.service.ts
@@ -0,0 +1,43 @@
+import { Injectable } from '@nestjs/common'
+import { ManifestService } from '../../manifest/services/manifest.service'
+import { EntityService } from '../../entity/services/entity.service'
+import { AppManifest, EntityManifest } from '../../../../types/src'
+import { ADMIN_ENTITY_MANIFEST } from '../../constants'
+
+@Injectable()
+export class DatabaseService {
+ constructor(
+ private manifestService: ManifestService,
+ private entityService: EntityService
+ ) {}
+
+ /**
+ * Check if the database is empty (no items in any entity, even admin).
+ *
+ * @returns true if the database is empty, false otherwise.
+ * */
+ async isDbEmpty(): Promise {
+ const appManifest: AppManifest = this.manifestService.getAppManifest()
+
+ const entities = [
+ ...Object.values(appManifest.entities),
+ ADMIN_ENTITY_MANIFEST
+ ]
+ let totalItems = 0
+
+ await Promise.all(
+ Object.values(entities).map(async (entityManifest: EntityManifest) => {
+ return this.entityService
+ .getEntityRepository({
+ entitySlug: entityManifest.slug
+ })
+ .createQueryBuilder('entity')
+ .getCount()
+ })
+ ).then((counts: number[]) => {
+ totalItems = counts.reduce((acc, count) => acc + count, 0)
+ })
+
+ return totalItems === 0
+ }
+}
diff --git a/packages/core/manifest/src/crud/tests/database.controller.spec.ts b/packages/core/manifest/src/crud/tests/database.controller.spec.ts
new file mode 100644
index 00000000..55a602ab
--- /dev/null
+++ b/packages/core/manifest/src/crud/tests/database.controller.spec.ts
@@ -0,0 +1,18 @@
+import { Test, TestingModule } from '@nestjs/testing'
+import { DatabaseController } from '../controllers/database.controller'
+
+describe('DatabaseController', () => {
+ let controller: DatabaseController
+
+ beforeEach(async () => {
+ const module: TestingModule = await Test.createTestingModule({
+ controllers: [DatabaseController]
+ }).compile()
+
+ controller = module.get(DatabaseController)
+ })
+
+ it('should be defined', () => {
+ expect(controller).toBeDefined()
+ })
+})
diff --git a/packages/core/manifest/src/crud/tests/database.service.spec.ts b/packages/core/manifest/src/crud/tests/database.service.spec.ts
new file mode 100644
index 00000000..6c1a7c5b
--- /dev/null
+++ b/packages/core/manifest/src/crud/tests/database.service.spec.ts
@@ -0,0 +1,62 @@
+import { Test } from '@nestjs/testing'
+import { DatabaseService } from '../services/database.service'
+import { ManifestService } from '../../manifest/services/manifest.service'
+import { EntityService } from '../../entity/services/entity.service'
+
+describe('DatabaseService', () => {
+ let manifestService: ManifestService
+ let entityService: EntityService
+ let service: DatabaseService
+
+ beforeEach(async () => {
+ const module = await Test.createTestingModule({
+ providers: [
+ DatabaseService,
+ {
+ provide: ManifestService,
+ useValue: {
+ getAppManifest: jest.fn().mockReturnValue({
+ entities: {}
+ })
+ }
+ },
+ {
+ provide: EntityService,
+ useValue: {
+ getEntityRepository: jest.fn().mockReturnValue({
+ createQueryBuilder: jest.fn().mockReturnValue({
+ getCount: jest.fn().mockReturnValue(0)
+ })
+ })
+ }
+ }
+ ]
+ }).compile()
+
+ manifestService = module.get(ManifestService)
+ entityService = module.get(EntityService)
+ service = module.get(DatabaseService)
+ })
+
+ it('should be defined', () => {
+ expect(service).toBeDefined()
+ })
+
+ it('should return true if the database is empty', async () => {
+ const res = await service.isDbEmpty()
+
+ expect(res).toBe(true)
+ })
+
+ it('should return false if the database is not empty', async () => {
+ jest.spyOn(entityService, 'getEntityRepository').mockReturnValue({
+ createQueryBuilder: jest.fn().mockReturnValue({
+ getCount: jest.fn().mockReturnValue(1)
+ })
+ } as any)
+
+ const res = await service.isDbEmpty()
+
+ expect(res).toBe(false)
+ })
+})
From 05723aa9e76509c185da9ee337a26c9acbc9dfd5 Mon Sep 17 00:00:00 2001
From: Bruno
Date: Mon, 4 Nov 2024 15:06:00 +0100
Subject: [PATCH 05/11] feat(admin): register first admin page
---
packages/core/admin/src/app/app.component.ts | 9 ++-
.../app/modules/auth/auth-routing.module.ts | 7 ++
.../admin/src/app/modules/auth/auth.module.ts | 3 +-
.../src/app/modules/auth/auth.service.ts | 44 ++++++++++++
.../modules/auth/guards/is-db-empty.guard.ts | 23 ++++++
.../auth/utlis/confirm-password-validator.ts | 22 ++++++
.../auth/views/login/login.component.ts | 10 ++-
.../register-first-admin.component.html | 71 +++++++++++++++++++
.../register-first-admin.component.scss | 0
.../register-first-admin.component.ts | 63 ++++++++++++++++
.../email-input/email-input.component.ts | 2 +-
.../password-input.component.ts | 1 +
.../crud/tests/database.controller.spec.ts | 11 ++-
13 files changed, 259 insertions(+), 7 deletions(-)
create mode 100644 packages/core/admin/src/app/modules/auth/guards/is-db-empty.guard.ts
create mode 100644 packages/core/admin/src/app/modules/auth/utlis/confirm-password-validator.ts
create mode 100644 packages/core/admin/src/app/modules/auth/views/register-first-admin/register-first-admin.component.html
create mode 100644 packages/core/admin/src/app/modules/auth/views/register-first-admin/register-first-admin.component.scss
create mode 100644 packages/core/admin/src/app/modules/auth/views/register-first-admin/register-first-admin.component.ts
diff --git a/packages/core/admin/src/app/app.component.ts b/packages/core/admin/src/app/app.component.ts
index e3f3c932..be2098ca 100644
--- a/packages/core/admin/src/app/app.component.ts
+++ b/packages/core/admin/src/app/app.component.ts
@@ -11,13 +11,18 @@ export class AppComponent implements OnInit {
currentUser: Admin
isLogin = true
- constructor(private authService: AuthService, private router: Router) {}
+ constructor(
+ private authService: AuthService,
+ private router: Router
+ ) {}
ngOnInit() {
this.router.events.subscribe((routeChanged) => {
if (routeChanged instanceof NavigationEnd) {
window.scrollTo(0, 0)
- this.isLogin = routeChanged.url.includes('/auth/login')
+ this.isLogin =
+ routeChanged.url.includes('/auth/login') ||
+ routeChanged.url.includes('/auth/welcome')
if (this.isLogin) {
this.currentUser = null
diff --git a/packages/core/admin/src/app/modules/auth/auth-routing.module.ts b/packages/core/admin/src/app/modules/auth/auth-routing.module.ts
index 81cfd67b..85a4ce0e 100644
--- a/packages/core/admin/src/app/modules/auth/auth-routing.module.ts
+++ b/packages/core/admin/src/app/modules/auth/auth-routing.module.ts
@@ -4,6 +4,8 @@ import { RouterModule, Routes } from '@angular/router'
import { NotLoggedInGuard } from './guards/not-logged-in.guard'
import { LoginComponent } from './views/login/login.component'
import { LogoutComponent } from './views/logout/logout.component'
+import { RegisterFirstAdminComponent } from './views/register-first-admin/register-first-admin.component'
+import { IsDbEmptyGuard } from './guards/is-db-empty.guard'
export const authRoutes: Routes = [
{
@@ -14,6 +16,11 @@ export const authRoutes: Routes = [
{
path: 'logout',
component: LogoutComponent
+ },
+ {
+ path: 'welcome',
+ component: RegisterFirstAdminComponent,
+ canActivate: [IsDbEmptyGuard]
}
]
diff --git a/packages/core/admin/src/app/modules/auth/auth.module.ts b/packages/core/admin/src/app/modules/auth/auth.module.ts
index 8d6c1ccc..8ca69a11 100644
--- a/packages/core/admin/src/app/modules/auth/auth.module.ts
+++ b/packages/core/admin/src/app/modules/auth/auth.module.ts
@@ -5,9 +5,10 @@ import { SharedModule } from '../shared/shared.module'
import { AuthRoutingModule } from './auth-routing.module'
import { LoginComponent } from './views/login/login.component'
import { LogoutComponent } from './views/logout/logout.component'
+import { RegisterFirstAdminComponent } from './views/register-first-admin/register-first-admin.component'
@NgModule({
- declarations: [LoginComponent, LogoutComponent],
+ declarations: [LoginComponent, LogoutComponent, RegisterFirstAdminComponent],
imports: [CommonModule, AuthRoutingModule, SharedModule]
})
export class AuthModule {}
diff --git a/packages/core/admin/src/app/modules/auth/auth.service.ts b/packages/core/admin/src/app/modules/auth/auth.service.ts
index e74bc049..a06dfb5b 100644
--- a/packages/core/admin/src/app/modules/auth/auth.service.ts
+++ b/packages/core/admin/src/app/modules/auth/auth.service.ts
@@ -41,6 +41,37 @@ export class AuthService {
})
}
+ /**
+ * Signs up a new admin and logs them in.
+ *
+ * @param {Object} credentials - The credentials of the new admin
+ * @param {string} credentials.email - The email of the new admin
+ * @param {string} credentials.password - The password of the new admin
+ *
+ * @returns {Promise} The token of the new admin
+ */
+ async signup(credentials: {
+ email: string
+ password: string
+ }): Promise {
+ return (
+ firstValueFrom(
+ this.http.post(
+ `${environment.apiBaseUrl}/auth/admins/signup`,
+ credentials
+ )
+ ) as Promise<{
+ token: string
+ }>
+ ).then((res: { token: string }) => {
+ const token = res?.token
+ if (token) {
+ localStorage.setItem(TOKEN_KEY, token)
+ }
+ return token
+ })
+ }
+
logout(): void {
delete this.currentUserPromise
localStorage.removeItem(TOKEN_KEY)
@@ -76,4 +107,17 @@ export class AuthService {
) as Promise<{ exists: boolean }>
).then((res) => res.exists)
}
+
+ /**
+ * Returns true if the database is empty (no items, even admins), false otherwise.
+ *
+ * @returns {Promise} true if the database is empty, false otherwise
+ */
+ async isDbEmpty(): Promise {
+ return (
+ firstValueFrom(
+ this.http.get(`${environment.apiBaseUrl}/db/is-db-empty`)
+ ) as Promise<{ empty: boolean }>
+ ).then((res) => res.empty)
+ }
}
diff --git a/packages/core/admin/src/app/modules/auth/guards/is-db-empty.guard.ts b/packages/core/admin/src/app/modules/auth/guards/is-db-empty.guard.ts
new file mode 100644
index 00000000..4635f38d
--- /dev/null
+++ b/packages/core/admin/src/app/modules/auth/guards/is-db-empty.guard.ts
@@ -0,0 +1,23 @@
+import { Injectable } from '@angular/core'
+import { Router } from '@angular/router'
+import { AuthService } from '../auth.service'
+
+@Injectable({
+ providedIn: 'root'
+})
+export class IsDbEmptyGuard {
+ constructor(
+ private authService: AuthService,
+ private router: Router
+ ) {}
+ async canActivate(): Promise {
+ const isDbEmpty = await this.authService.isDbEmpty()
+
+ if (isDbEmpty) {
+ return true
+ }
+
+ this.router.navigate(['/auth/login'])
+ return false
+ }
+}
diff --git a/packages/core/admin/src/app/modules/auth/utlis/confirm-password-validator.ts b/packages/core/admin/src/app/modules/auth/utlis/confirm-password-validator.ts
new file mode 100644
index 00000000..765747f5
--- /dev/null
+++ b/packages/core/admin/src/app/modules/auth/utlis/confirm-password-validator.ts
@@ -0,0 +1,22 @@
+import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms'
+
+export function confirmPasswordValidator(
+ passwordControlName: string
+): ValidatorFn {
+ return (control: AbstractControl): ValidationErrors | null => {
+ const formGroup = control.parent
+ if (!formGroup) return null
+
+ const passwordControl = formGroup.get(passwordControlName)
+ if (!passwordControl) return null
+
+ const password = passwordControl.value
+ const confirmPassword = control.value
+
+ if (!confirmPassword || password !== confirmPassword) {
+ return { confirmPasswordMismatch: true }
+ }
+
+ return null
+ }
+}
diff --git a/packages/core/admin/src/app/modules/auth/views/login/login.component.ts b/packages/core/admin/src/app/modules/auth/views/login/login.component.ts
index 2a411167..f91d5ca0 100644
--- a/packages/core/admin/src/app/modules/auth/views/login/login.component.ts
+++ b/packages/core/admin/src/app/modules/auth/views/login/login.component.ts
@@ -31,6 +31,7 @@ export class LoginComponent implements OnInit {
ngOnInit(): void {
this.activatedRoute.queryParams.subscribe(async (queryParams: Params) => {
+ // Set suggested email and password from query params or default admin credentials.
if (queryParams['email'] && queryParams['password']) {
this.suggestedEmail = queryParams['email']
this.suggestedPassword = queryParams['password']
@@ -40,15 +41,20 @@ export class LoginComponent implements OnInit {
this.suggestedPassword = DEFAULT_ADMIN_CREDENTIALS.password
}
}
-
this.form = new FormGroup({
email: new FormControl(this.suggestedEmail || '', [
- Validators.required
+ Validators.required,
+ Validators.email
]),
password: new FormControl(this.suggestedPassword || '', [
Validators.required
])
})
+
+ // Redirect to register first admin if the database is empty.
+ if (await this.authService.isDbEmpty()) {
+ this.router.navigate(['/auth/welcome'])
+ }
})
}
diff --git a/packages/core/admin/src/app/modules/auth/views/register-first-admin/register-first-admin.component.html b/packages/core/admin/src/app/modules/auth/views/register-first-admin/register-first-admin.component.html
new file mode 100644
index 00000000..16098386
--- /dev/null
+++ b/packages/core/admin/src/app/modules/auth/views/register-first-admin/register-first-admin.component.html
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
Welcome
+
+
+ Welcome to your admin panel. Create your first admin account to
+ continue. You can always change those values later.
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/core/admin/src/app/modules/auth/views/register-first-admin/register-first-admin.component.scss b/packages/core/admin/src/app/modules/auth/views/register-first-admin/register-first-admin.component.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/packages/core/admin/src/app/modules/auth/views/register-first-admin/register-first-admin.component.ts b/packages/core/admin/src/app/modules/auth/views/register-first-admin/register-first-admin.component.ts
new file mode 100644
index 00000000..2755f27e
--- /dev/null
+++ b/packages/core/admin/src/app/modules/auth/views/register-first-admin/register-first-admin.component.ts
@@ -0,0 +1,63 @@
+import { Component, OnInit } from '@angular/core'
+import { FormControl, FormGroup, Validators } from '@angular/forms'
+import { PropType } from '@repo/types'
+import { confirmPasswordValidator } from '../../utlis/confirm-password-validator'
+import { AuthService } from '../../auth.service'
+import { Router } from '@angular/router'
+import { FlashMessageService } from '../../../shared/services/flash-message.service'
+
+@Component({
+ selector: 'app-register-first-admin',
+ templateUrl: './register-first-admin.component.html',
+ styleUrl: './register-first-admin.component.scss'
+})
+export class RegisterFirstAdminComponent implements OnInit {
+ form: FormGroup
+ PropType = PropType
+
+ constructor(
+ private authService: AuthService,
+ private router: Router,
+ private flashMessageService: FlashMessageService
+ ) {}
+
+ ngOnInit(): void {
+ this.form = new FormGroup({
+ email: new FormControl('', [Validators.required, Validators.email]),
+ password: new FormControl('', [Validators.required]),
+ confirmPassword: new FormControl('', [
+ Validators.required,
+ confirmPasswordValidator('password')
+ ])
+ })
+ }
+
+ /**
+ * Patch value to the form
+ *
+ * @param controlName
+ * @param value
+ *
+ * @returns void
+ */
+ patchValue(controlName: string, value: string) {
+ this.form.get(controlName)?.patchValue(value)
+ }
+
+ /**
+ * Submit the form
+ */
+ async submit(): Promise {
+ const token: string = await this.authService.signup(this.form.value)
+
+ if (!token) {
+ return this.flashMessageService.error('Error: Failed to register')
+ }
+
+ this.flashMessageService.success(
+ 'Welcome! You have successfully registered as an admin.'
+ )
+
+ this.router.navigate(['/'])
+ }
+}
diff --git a/packages/core/admin/src/app/modules/shared/inputs/email-input/email-input.component.ts b/packages/core/admin/src/app/modules/shared/inputs/email-input/email-input.component.ts
index c613dfed..a1350192 100644
--- a/packages/core/admin/src/app/modules/shared/inputs/email-input/email-input.component.ts
+++ b/packages/core/admin/src/app/modules/shared/inputs/email-input/email-input.component.ts
@@ -20,7 +20,7 @@ import { PropertyManifest } from '@repo/types'
class="input"
[ngClass]="{ 'is-danger': isError }"
type="email"
- placeholder="Email"
+ placeholder="Email..."
autocomplete="email"
(change)="onChange($event)"
#input
diff --git a/packages/core/admin/src/app/modules/shared/inputs/password-input/password-input.component.ts b/packages/core/admin/src/app/modules/shared/inputs/password-input/password-input.component.ts
index 7f9c2f42..95e6ae68 100644
--- a/packages/core/admin/src/app/modules/shared/inputs/password-input/password-input.component.ts
+++ b/packages/core/admin/src/app/modules/shared/inputs/password-input/password-input.component.ts
@@ -18,6 +18,7 @@ import { PropertyManifest } from '@repo/types'
{
let controller: DatabaseController
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
- controllers: [DatabaseController]
+ controllers: [DatabaseController],
+ providers: [
+ {
+ provide: DatabaseService,
+ useValue: {
+ isDbEmpty: jest.fn().mockReturnValue(Promise.resolve(true))
+ }
+ }
+ ]
}).compile()
controller = module.get(DatabaseController)
From 9183c15dc840be388f62c2bf410503e42fb85688 Mon Sep 17 00:00:00 2001
From: Bruno
Date: Mon, 4 Nov 2024 15:20:13 +0100
Subject: [PATCH 06/11] workflow: add "add-manifest" package in PR template
---
.github/pull_request_template.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 15147794..de3d2ae9 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -9,6 +9,7 @@
Check the NPM packages that require a new publication or release:
- [ ] [manifest](https://www.npmjs.com/package/manifest)
+- [] [add-manifest](https://www.npmjs.com/package/add-manifest)
- [ ] [@mnfst/sdk](https://www.npmjs.com/package/@mnfst/sdk)
## Check list before submitting
From 6fe42752ec067aebf095e78018c1ce2048c91ca0 Mon Sep 17 00:00:00 2001
From: Bruno
Date: Mon, 4 Nov 2024 15:35:19 +0100
Subject: [PATCH 07/11] fix: cannot signup with authenticable entities
---
packages/core/manifest/src/auth/auth.controller.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/core/manifest/src/auth/auth.controller.ts b/packages/core/manifest/src/auth/auth.controller.ts
index ee98c7ae..e22e3b17 100644
--- a/packages/core/manifest/src/auth/auth.controller.ts
+++ b/packages/core/manifest/src/auth/auth.controller.ts
@@ -31,7 +31,7 @@ export class AuthController {
return this.authService.createToken(entity, signupUserDto)
}
- @Post(':admins/signup')
+ @Post('admins/signup')
@UseGuards(IsDbEmptyGuard)
public async signupAdmin(
@Body() signupUserDto: SignupAuthenticableEntityDto
From abec404fe1f24f5718101722197dc539ed00a22f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Conejo?=
Date: Mon, 4 Nov 2024 16:18:25 +0100
Subject: [PATCH 08/11] fix: fields were nested into the first field
---
.../auth/views/login/login.component.html | 40 +++++++++----------
1 file changed, 20 insertions(+), 20 deletions(-)
diff --git a/packages/core/admin/src/app/modules/auth/views/login/login.component.html b/packages/core/admin/src/app/modules/auth/views/login/login.component.html
index fa175774..7ac5945a 100644
--- a/packages/core/admin/src/app/modules/auth/views/login/login.component.html
+++ b/packages/core/admin/src/app/modules/auth/views/login/login.component.html
@@ -25,32 +25,32 @@ Sign in
(valueChanged)="patchValue('email', $event)"
>
-
+
-
-
- Login
-
+
+
+ Login
+
-
+
From 78ecb37d02e6b5c27f7abbf0e8360545de89c4f7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Conejo?=
Date: Mon, 4 Nov 2024 16:19:26 +0100
Subject: [PATCH 09/11] style: set register style and update box-shadow global
style
---
.../core/admin/src/app/app.component.html | 5 +-
.../register-first-admin.component.html | 98 ++++++++++---------
.../register-first-admin.component.scss | 61 ++++++++++++
.../admin/src/styles/variables/_cards.scss | 8 +-
.../admin/src/styles/variables/_generic.scss | 6 +-
5 files changed, 125 insertions(+), 53 deletions(-)
diff --git a/packages/core/admin/src/app/app.component.html b/packages/core/admin/src/app/app.component.html
index d2e65e82..3577b25c 100644
--- a/packages/core/admin/src/app/app.component.html
+++ b/packages/core/admin/src/app/app.component.html
@@ -1,11 +1,10 @@
-
-
+
-
+
diff --git a/packages/core/admin/src/app/modules/auth/views/register-first-admin/register-first-admin.component.html b/packages/core/admin/src/app/modules/auth/views/register-first-admin/register-first-admin.component.html
index 16098386..5ab7bf88 100644
--- a/packages/core/admin/src/app/modules/auth/views/register-first-admin/register-first-admin.component.html
+++ b/packages/core/admin/src/app/modules/auth/views/register-first-admin/register-first-admin.component.html
@@ -2,69 +2,73 @@
-
-
+
+
+
+
+
+
Welcome
-
Welcome
+
+ Welcome to your admin panel. Create your first admin account
+ to continue. You can always change those values later.
+
-
- Welcome to your admin panel. Create your first admin account to
- continue. You can always change those values later.
-
+
-
diff --git a/packages/core/admin/src/app/modules/auth/views/register-first-admin/register-first-admin.component.scss b/packages/core/admin/src/app/modules/auth/views/register-first-admin/register-first-admin.component.scss
index e69de29b..88db893c 100644
--- a/packages/core/admin/src/app/modules/auth/views/register-first-admin/register-first-admin.component.scss
+++ b/packages/core/admin/src/app/modules/auth/views/register-first-admin/register-first-admin.component.scss
@@ -0,0 +1,61 @@
+@import '../../../../../styles/variables/all';
+@import 'bulma/sass/utilities/mixins';
+
+.container {
+ max-width: 100%;
+ width: 100%;
+}
+
+.hero.is-fullheight {
+ min-height: calc(100vh - 64px);
+
+ @include touch {
+ min-height: calc(100vh - 64px);
+ }
+}
+
+.notification {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: auto;
+ border-radius: 0;
+ bottom: unset;
+}
+
+.col-welcome {
+ position: fixed;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: $white;
+ left: 0;
+ top: 0;
+ width: 100vw;
+ height: 100vh;
+
+ > div {
+ margin: auto;
+
+ @include widescreen {
+ max-width: 482px;
+ min-width: 382px;
+ }
+
+ @include desktop {
+ max-width: 424px;
+ min-width: 360px;
+ }
+
+ @include tablet {
+ max-width: 424px;
+ min-width: 320px;
+ }
+
+ @include mobile {
+ min-width: 50%;
+ max-width: 380px;
+ }
+ }
+}
diff --git a/packages/core/admin/src/styles/variables/_cards.scss b/packages/core/admin/src/styles/variables/_cards.scss
index acbae357..2f30a0f3 100644
--- a/packages/core/admin/src/styles/variables/_cards.scss
+++ b/packages/core/admin/src/styles/variables/_cards.scss
@@ -1,11 +1,15 @@
$card-color: $dark !default;
-$card-radius: 6px !default;
+$card-radius: 12px !default;
$border-color: $white-ter !default;
$card-header-color: inherit !default;
$card-header-padding: 2rem !default;
$card-content-padding: 2rem !default;
$card-header-shadow: 0 1px 0 $grey-lighter !default;
-$card-shadow: 0 10px 20px rgba($dark, 0.07) !default;
+$card-shadow:
+ rgba($dark, 0) 0px 0px 0px 0px,
+ rgba($dark, 0) 0px 0px 0px 0px,
+ rgba($dark, 0.1) 0px 1px 3px 0px,
+ rgba($dark, 0.1) 0px 1px 2px -1px !default;
$card-footer-padding: 2rem !default;
$card-footer-border-top: 1px solid $grey-lighter !default;
diff --git a/packages/core/admin/src/styles/variables/_generic.scss b/packages/core/admin/src/styles/variables/_generic.scss
index cd9ea33b..6f50a290 100644
--- a/packages/core/admin/src/styles/variables/_generic.scss
+++ b/packages/core/admin/src/styles/variables/_generic.scss
@@ -1,5 +1,9 @@
// generic
-$shadow: 0 10px 20px 0 rgba($dark, 0.07) !default;
+$shadow:
+ rgba($dark, 0) 0px 0px 0px 0px,
+ rgba($dark, 0) 0px 0px 0px 0px,
+ rgba($dark, 0.1) 0px 1px 3px 0px,
+ rgba($dark, 0.1) 0px 1px 2px -1px !default;
$border-color: $white-ter !default;
$radius-large: 6px !default;
$link-hover: $primary !default;
From 70df39bca6fd89b1174c806507ef1363292eff09 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Conejo?=
Date: Mon, 4 Nov 2024 16:27:38 +0100
Subject: [PATCH 10/11] style: update margins on home
---
.../admin/src/app/pages/home/home.component.html | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/packages/core/admin/src/app/pages/home/home.component.html b/packages/core/admin/src/app/pages/home/home.component.html
index 826eccfd..974ebd4e 100644
--- a/packages/core/admin/src/app/pages/home/home.component.html
+++ b/packages/core/admin/src/app/pages/home/home.component.html
@@ -37,7 +37,7 @@ {{ appManifest.name }}
@@ -48,7 +48,7 @@
{{ appManifest.name }}
@@ -80,7 +80,7 @@ {{ appManifest.name }}
@@ -90,7 +90,7 @@
{{ appManifest.name }}
@@ -116,7 +116,7 @@ {{ appManifest.name }}
@@ -126,7 +126,7 @@
{{ appManifest.name }}
@@ -156,7 +156,7 @@ {{ appManifest.name }}
@@ -167,7 +167,7 @@
{{ appManifest.name }}
From 3ecdabdf1d5fb8f8dc35dfaf3a6a48de3793789c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Conejo?=
Date: Mon, 4 Nov 2024 16:38:43 +0100
Subject: [PATCH 11/11] style: update doc icon on the homepage
---
.../admin/src/assets/images/ext-link-01.svg | 81 +++++++++++++++----
1 file changed, 66 insertions(+), 15 deletions(-)
diff --git a/packages/core/admin/src/assets/images/ext-link-01.svg b/packages/core/admin/src/assets/images/ext-link-01.svg
index b90b59ef..b8ed3b6f 100644
--- a/packages/core/admin/src/assets/images/ext-link-01.svg
+++ b/packages/core/admin/src/assets/images/ext-link-01.svg
@@ -1,17 +1,68 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+