Skip to content

Commit 1a627da

Browse files
committed
WIP
1 parent 3cfe27b commit 1a627da

25 files changed

+567
-55
lines changed

demo-saas/admin/src/common/apollo/createApolloClient.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ApolloClient, ApolloLink, HttpLink, InMemoryCache } from "@apollo/client";
2+
import { setContext } from "@apollo/client/link/context";
23
import { createErrorDialogApolloLink } from "@comet/admin";
34
import { includeInvisibleContentContext } from "@comet/cms-admin";
45
import fragmentTypes from "@src/fragmentTypes.json";
@@ -9,7 +10,16 @@ export const createApolloClient = (apiUrl: string) => {
910
credentials: "include",
1011
});
1112

12-
const link = ApolloLink.from([createErrorDialogApolloLink(), includeInvisibleContentContext, httpLink]);
13+
const tenantContext = setContext((_, { headers }) => {
14+
return {
15+
headers: {
16+
...headers,
17+
...{ "x-tenant-id": "2f4e657d-1857-42dd-b53c-92d32990935f" },
18+
},
19+
};
20+
});
21+
22+
const link = ApolloLink.from([createErrorDialogApolloLink(), includeInvisibleContentContext, tenantContext, httpLink]);
1323

1424
const cache = new InMemoryCache({
1525
possibleTypes: fragmentTypes.possibleTypes,
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { defineConfig } from "@comet/admin-generator";
2+
import { type GQLDepartment } from "@src/graphql.generated";
3+
4+
export default defineConfig<GQLDepartment>({
5+
type: "form",
6+
gqlType: "Department",
7+
fragmentName: "DepartmentForm",
8+
9+
fields: [
10+
{
11+
type: "text",
12+
name: "name",
13+
label: "Name",
14+
required: true,
15+
},
16+
],
17+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { defineConfig } from "@comet/admin-generator";
2+
import { type GQLDepartment } from "@src/graphql.generated";
3+
4+
export default defineConfig<GQLDepartment>({
5+
type: "grid",
6+
gqlType: "Department",
7+
fragmentName: "DepartmentGrid",
8+
queryParamsPrefix: "departments",
9+
newEntryText: "Add Department",
10+
delete: false, // It is disabled for the time being. We first need to consider whether we want to delete it and then clear all tables. Or whether there will perhaps only be an archive function.
11+
columns: [
12+
{
13+
type: "text",
14+
name: "name",
15+
headerName: "Name",
16+
},
17+
],
18+
});

demo-saas/admin/src/tenants/departments/generated/DepartmentForm.gql.tsx

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo-saas/admin/src/tenants/departments/generated/DepartmentForm.tsx

Lines changed: 103 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo-saas/admin/src/tenants/departments/generated/DepartmentsGrid.tsx

Lines changed: 93 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo-saas/api/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"console:prod": "node dist/console.js",
1515
"db:migrate": "pnpm console migrate --",
1616
"db:migrate:prod": "pnpm console:prod migrate --",
17-
"dev:nest": "pnpm clean && pnpm intl:compile && pnpm db:migrate && pnpm console createBlockIndexViews && NODE_OPTIONS='--max-old-space-size=1024' dotenv -e .env.secrets -e .env.local -e .env -e .env.site-configs -- nest start --debug --watch --preserveWatchOutput",
17+
"dev:nest": "pnpm clean && pnpm intl:compile && NODE_OPTIONS='--max-old-space-size=1024' dotenv -e .env.secrets -e .env.local -e .env -e .env.site-configs -- nest start --debug --watch --preserveWatchOutput",
1818
"dev:reload": "rimraf src/reload.ts && chokidar \"node_modules/@comet/cms-api/lib/**\" -s -c \"echo '// change' >> src/reload.ts\"",
1919
"fixtures": "pnpm console fixtures --",
2020
"fixtures:prod": "pnpm console:prod fixtures --",

demo-saas/api/schema.gql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ type Manufacturer {
311311
coordinates: Coordinates
312312
id: ID!
313313
name: String!
314+
tenant: Tenant!
314315
updatedAt: DateTime!
315316
}
316317

@@ -551,6 +552,7 @@ type Product {
551552
status: ProductStatus!
552553
tags: [ProductTag!]!
553554
tagsWithStatus: [ProductToTag!]!
555+
tenant: Tenant!
554556
title: String!
555557
type: ProductType!
556558
updatedAt: DateTime!

demo-saas/api/src/app.module.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,17 @@ import {
1818
UserPermissionsModule,
1919
} from "@comet/cms-api";
2020
import { ApolloDriver, ApolloDriverConfig, ValidationError } from "@nestjs/apollo";
21-
import { DynamicModule, Module } from "@nestjs/common";
22-
import { ModuleRef } from "@nestjs/core";
21+
import { DynamicModule, MiddlewareConsumer, Module } from "@nestjs/common";
22+
import { APP_INTERCEPTOR, ModuleRef } from "@nestjs/core";
2323
import { Enhancer, GraphQLModule } from "@nestjs/graphql";
2424
import { AppPermission } from "@src/auth/app-permission.enum";
2525
import { Config } from "@src/config/config";
2626
import { ConfigModule } from "@src/config/config.module";
2727
import { ContentGenerationService } from "@src/content-generation/content-generation.service";
2828
import { DbModule } from "@src/db/db.module";
2929
import { TranslationModule } from "@src/translation/translation.module";
30-
import { Request } from "express";
30+
import { AsyncLocalStorage } from "async_hooks";
31+
import { NextFunction, Request } from "express";
3132

3233
import { AccessControlService } from "./auth/access-control.service";
3334
import { AuthModule, SYSTEM_USER_NAME } from "./auth/auth.module";
@@ -36,14 +37,23 @@ import { DepartmentsModule } from "./department/departments.module";
3637
import { OpenTelemetryModule } from "./open-telemetry/open-telemetry.module";
3738
import { ProductsModule } from "./products/products.module";
3839
import { StatusModule } from "./status/status.module";
40+
import { TenantInterceptor } from "./tenant/tenant.interceptor";
3941
import { TenantsModule } from "./tenant/tenants.module";
4042

4143
@Module({})
4244
export class AppModule {
45+
constructor(private readonly asyncLocalStorage: AsyncLocalStorage<{ tenantId?: string }>) {}
46+
4347
static forRoot(config: Config): DynamicModule {
4448
const authModule = AuthModule.forRoot(config);
4549

4650
return {
51+
providers: [
52+
{
53+
provide: APP_INTERCEPTOR,
54+
useClass: TenantInterceptor,
55+
},
56+
],
4757
module: AppModule,
4858
imports: [
4959
ConfigModule.forRoot(config),
@@ -83,13 +93,18 @@ export class AppModule {
8393
}),
8494
authModule,
8595
UserPermissionsModule.forRootAsync({
86-
useFactory: (userService: UserService, accessControlService: AccessControlService) => ({
96+
useFactory: (
97+
userService: UserService,
98+
accessControlService: AccessControlService,
99+
asyncLocalStorage: AsyncLocalStorage<{ tenantId?: string }>,
100+
) => ({
87101
availableContentScopes: () => accessControlService.getAvailableContentScopes(),
88102
userService,
89103
accessControlService,
90104
systemUsers: [SYSTEM_USER_NAME],
105+
asyncLocalStorage,
91106
}),
92-
inject: [UserService, AccessControlService],
107+
inject: [UserService, AccessControlService, AsyncLocalStorage<{ tenantId?: string }>],
93108
imports: [authModule],
94109
AppPermission,
95110
}),
@@ -151,4 +166,15 @@ export class AppModule {
151166
],
152167
};
153168
}
169+
170+
configure(consumer: MiddlewareConsumer) {
171+
consumer
172+
.apply((req: Request, res: Response, next: NextFunction) => {
173+
const tenantIdHeader = req.headers["x-tenant-id"];
174+
const tenantId = typeof tenantIdHeader === "string" ? tenantIdHeader : undefined;
175+
const store: { tenantId?: string } = tenantId ? { tenantId } : {};
176+
this.asyncLocalStorage.run(store, () => next());
177+
})
178+
.forRoutes("*path");
179+
}
154180
}

0 commit comments

Comments
 (0)