@idevconn/create-icore 0.4.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +304 -24
- package/dist/index.cjs +301 -23
- package/dist/index.d.cts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +300 -23
- package/package.json +1 -1
- package/templates/apps/api/.env.example +14 -0
- package/templates/apps/api/src/app/app.module.ts +5 -1
- package/templates/apps/api/src/main.ts +12 -6
- package/templates/apps/microservices/auth/package.json +1 -1
- package/templates/apps/microservices/auth/project.json +2 -1
- package/templates/apps/microservices/auth/src/app/app.module.ts +50 -39
- package/templates/apps/microservices/auth/src/main.ts +6 -23
- package/templates/apps/microservices/jobs/project.json +2 -1
- package/templates/apps/microservices/jobs/src/app/redis-connection.ts +35 -0
- package/templates/apps/microservices/jobs/src/app/workers/cleanup.worker.ts +2 -1
- package/templates/apps/microservices/jobs/src/app/workers/email.worker.ts +2 -1
- package/templates/apps/microservices/jobs/src/app/workers/image-process.worker.ts +2 -1
- package/templates/apps/microservices/notes/project.json +2 -1
- package/templates/apps/microservices/notes/src/app/app.module.ts +52 -38
- package/templates/apps/microservices/notes/src/main.ts +6 -23
- package/templates/apps/microservices/payment/project.json +2 -1
- package/templates/apps/microservices/payment/src/app/app.module.ts +37 -12
- package/templates/apps/microservices/payment/src/main.ts +6 -23
- package/templates/apps/microservices/upload/package.json +1 -1
- package/templates/apps/microservices/upload/project.json +2 -1
- package/templates/apps/microservices/upload/src/app/app.module.ts +50 -42
- package/templates/apps/microservices/upload/src/main.ts +6 -23
- package/templates/apps/templates/client-antd/.env.example +7 -0
- package/templates/apps/templates/client-antd/vite.config.mts +4 -4
- package/templates/apps/templates/client-mui/.env.example +7 -0
- package/templates/apps/templates/client-mui/vite.config.mts +4 -4
- package/templates/apps/templates/client-shadcn/.env.example +6 -1
- package/templates/apps/templates/client-shadcn/vite.config.mts +4 -4
- package/templates/libs/auth-client/src/index.ts +1 -0
- package/templates/libs/auth-client/src/lib/auth-client.module.ts +1 -1
- package/templates/libs/auth-client/src/lib/auth-client.service.ts +1 -1
- package/templates/libs/auth-client/src/lib/auth-client.tokens.ts +4 -0
- package/templates/libs/firebase-admin/README.md +11 -0
- package/templates/libs/firebase-admin/eslint.config.mjs +24 -0
- package/templates/libs/firebase-admin/package.json +12 -0
- package/templates/libs/firebase-admin/project.json +19 -0
- package/templates/libs/firebase-admin/src/index.ts +1 -0
- package/templates/libs/firebase-admin/src/lib/__tests__/firebase-admin.unit.test.ts +105 -0
- package/templates/libs/firebase-admin/src/lib/firebase-admin.ts +70 -0
- package/templates/libs/firebase-admin/tsconfig.json +23 -0
- package/templates/libs/firebase-admin/tsconfig.lib.json +23 -0
- package/templates/libs/firebase-admin/tsconfig.spec.json +22 -0
- package/templates/libs/firebase-admin/vitest.config.mts +21 -0
- package/templates/libs/jobs-client/src/index.ts +1 -0
- package/templates/libs/jobs-client/src/lib/jobs-client.module.ts +1 -1
- package/templates/libs/jobs-client/src/lib/jobs-client.service.ts +15 -3
- package/templates/libs/jobs-client/src/lib/jobs-client.tokens.ts +4 -0
- package/templates/libs/notes-client/src/index.ts +1 -0
- package/templates/libs/notes-client/src/lib/notes-client.module.ts +1 -1
- package/templates/libs/notes-client/src/lib/notes-client.service.ts +1 -1
- package/templates/libs/notes-client/src/lib/notes-client.tokens.ts +4 -0
- package/templates/libs/payment-client/src/index.ts +1 -0
- package/templates/libs/payment-client/src/lib/payment-client.module.ts +1 -1
- package/templates/libs/payment-client/src/lib/payment-client.service.ts +1 -1
- package/templates/libs/payment-client/src/lib/payment-client.tokens.ts +4 -0
- package/templates/libs/shared/src/__tests__/bootstrap.unit.test.ts +92 -0
- package/templates/libs/shared/src/__tests__/transport.unit.test.ts +14 -2
- package/templates/libs/shared/src/bootstrap.ts +79 -0
- package/templates/libs/shared/src/env.ts +88 -0
- package/templates/libs/shared/src/index.ts +2 -0
- package/templates/libs/shared/src/transport.ts +62 -3
- package/templates/libs/upload-client/src/index.ts +1 -0
- package/templates/libs/upload-client/src/lib/upload-client.module.ts +1 -1
- package/templates/libs/upload-client/src/lib/upload-client.service.ts +1 -1
- package/templates/libs/upload-client/src/lib/upload-client.tokens.ts +4 -0
- package/templates/libs/vite-plugins/src/index.d.mts +6 -0
- package/templates/libs/vite-plugins/src/index.mjs +50 -0
- package/templates/package.json +1 -0
- package/templates/tools/create-icore/_template-shell/package.json +1 -0
- package/templates/tsconfig.base.json +2 -1
|
@@ -1,36 +1,41 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
-
import { Module } from '@nestjs/common';
|
|
2
|
+
import { Module, Logger } from '@nestjs/common';
|
|
3
3
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
4
4
|
import { createClient } from '@supabase/supabase-js';
|
|
5
|
-
import * as admin from 'firebase-admin';
|
|
6
5
|
import { SupabaseAuthStrategy } from '@icore/auth-supabase';
|
|
7
6
|
import { FirebaseAuthStrategy, HttpIdentityToolkitClient } from '@icore/auth-firebase';
|
|
8
|
-
import {
|
|
7
|
+
import { getFirebaseAdmin, FIREBASE_ADMIN_REQUIRED_ENV } from '@icore/firebase-admin';
|
|
8
|
+
import { FakeAuthStrategy, missingEnv, formatEnvBanner } from '@icore/shared';
|
|
9
9
|
import type { AuthStrategy } from '@icore/shared';
|
|
10
|
-
import { Logger } from '@nestjs/common';
|
|
11
10
|
import { AuthController } from './auth.controller';
|
|
12
11
|
|
|
12
|
+
const ENV_PATH = 'apps/microservices/auth/.env';
|
|
13
|
+
|
|
14
|
+
// Env vars each provider needs (besides AUTH_PROVIDER itself).
|
|
15
|
+
const REQUIRED_ENV: Record<string, string[]> = {
|
|
16
|
+
supabase: ['SUPABASE_URL', 'SUPABASE_SERVICE_ROLE_KEY'],
|
|
17
|
+
firebase: [...FIREBASE_ADMIN_REQUIRED_ENV, 'FIREBASE_WEB_API_KEY'],
|
|
18
|
+
};
|
|
19
|
+
|
|
13
20
|
function requireEnv(cfg: ConfigService, key: string): string {
|
|
14
|
-
|
|
15
|
-
if (!val) throw new Error(`${key} is not set — check apps/microservices/auth/.env`);
|
|
16
|
-
return val;
|
|
21
|
+
return cfg.getOrThrow<string>(key);
|
|
17
22
|
}
|
|
18
23
|
|
|
19
|
-
function
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
function makeSupabaseAuth(cfg: ConfigService): AuthStrategy {
|
|
25
|
+
const client = createClient(
|
|
26
|
+
requireEnv(cfg, 'SUPABASE_URL'),
|
|
27
|
+
requireEnv(cfg, 'SUPABASE_SERVICE_ROLE_KEY'),
|
|
28
|
+
{ auth: { autoRefreshToken: false, persistSession: false } },
|
|
29
|
+
);
|
|
30
|
+
return new SupabaseAuthStrategy({ client });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function makeFirebaseAuth(cfg: ConfigService): AuthStrategy {
|
|
34
|
+
const app = getFirebaseAdmin(cfg);
|
|
30
35
|
const identityToolkit = new HttpIdentityToolkitClient(requireEnv(cfg, 'FIREBASE_WEB_API_KEY'));
|
|
31
36
|
return new FirebaseAuthStrategy({
|
|
32
37
|
identityToolkit,
|
|
33
|
-
adminAuth:
|
|
38
|
+
adminAuth: app.auth(),
|
|
34
39
|
});
|
|
35
40
|
}
|
|
36
41
|
|
|
@@ -49,28 +54,34 @@ function makeFirebaseStrategy(cfg: ConfigService): AuthStrategy {
|
|
|
49
54
|
{
|
|
50
55
|
provide: 'AuthStrategy',
|
|
51
56
|
useFactory: (cfg: ConfigService): AuthStrategy => {
|
|
57
|
+
const logger = new Logger('AuthStrategy');
|
|
58
|
+
const provider = cfg.get<string>('AUTH_PROVIDER')?.trim();
|
|
59
|
+
const keys = provider ? REQUIRED_ENV[provider] : undefined;
|
|
60
|
+
const missing = keys ? missingEnv((k) => cfg.get<string>(k), keys) : [];
|
|
61
|
+
|
|
62
|
+
// Prod: fail fast — never silently run a fake auth strategy.
|
|
63
|
+
// Dev: warn with a boxed banner + fall back to the in-memory fake.
|
|
64
|
+
const fallback = (reason?: string): AuthStrategy => {
|
|
65
|
+
const banner = formatEnvBanner({
|
|
66
|
+
service: 'auth MS',
|
|
67
|
+
provider,
|
|
68
|
+
missing,
|
|
69
|
+
envPath: ENV_PATH,
|
|
70
|
+
reason,
|
|
71
|
+
});
|
|
72
|
+
if (process.env.NODE_ENV === 'production') throw new Error(banner);
|
|
73
|
+
logger.warn(banner);
|
|
74
|
+
return new FakeAuthStrategy();
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
if (!keys || missing.length > 0) return fallback();
|
|
78
|
+
|
|
52
79
|
try {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
case 'supabase': {
|
|
56
|
-
const client = createClient(
|
|
57
|
-
requireEnv(cfg, 'SUPABASE_URL'),
|
|
58
|
-
requireEnv(cfg, 'SUPABASE_SERVICE_ROLE_KEY'),
|
|
59
|
-
{ auth: { autoRefreshToken: false, persistSession: false } },
|
|
60
|
-
);
|
|
61
|
-
return new SupabaseAuthStrategy({ client });
|
|
62
|
-
}
|
|
63
|
-
case 'firebase':
|
|
64
|
-
return makeFirebaseStrategy(cfg);
|
|
65
|
-
default:
|
|
66
|
-
throw new Error(`Unsupported AUTH_PROVIDER: ${provider}`);
|
|
67
|
-
}
|
|
80
|
+
if (provider === 'supabase') return makeSupabaseAuth(cfg);
|
|
81
|
+
return makeFirebaseAuth(cfg);
|
|
68
82
|
} catch (err) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
`Requests will fail until apps/microservices/auth/.env is set.`,
|
|
72
|
-
);
|
|
73
|
-
return new FakeAuthStrategy();
|
|
83
|
+
// Vars present but invalid (e.g. placeholder URL the SDK rejects).
|
|
84
|
+
return fallback(err instanceof Error ? err.message : String(err));
|
|
74
85
|
}
|
|
75
86
|
},
|
|
76
87
|
inject: [ConfigService],
|
|
@@ -1,28 +1,11 @@
|
|
|
1
1
|
import { Logger } from '@nestjs/common';
|
|
2
2
|
import { NestFactory } from '@nestjs/core';
|
|
3
3
|
import { MicroserviceOptions } from '@nestjs/microservices';
|
|
4
|
-
import { buildTransportMS } from '@icore/shared';
|
|
4
|
+
import { bootstrapMicroservice, buildTransportMS } from '@icore/shared';
|
|
5
5
|
import { AppModule } from './app/app.module';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
await app.listen();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
bootstrap()
|
|
16
|
-
.then(() => {
|
|
17
|
-
const logger = new Logger('Auth-Bootstrap');
|
|
18
|
-
logger.log(
|
|
19
|
-
`Auth MS Bootstrap completed: transport=${process.env.AUTH_TRANSPORT ?? 'tcp'} host=${process.env.AUTH_HOST ?? '127.0.0.1'} port=${process.env.AUTH_PORT ?? '4001'}`,
|
|
20
|
-
);
|
|
21
|
-
})
|
|
22
|
-
.catch((err) => {
|
|
23
|
-
new Logger('Auth-Bootstrap').error(
|
|
24
|
-
'Auth MS bootstrap failed',
|
|
25
|
-
err instanceof Error ? err.stack : err,
|
|
26
|
-
);
|
|
27
|
-
process.exit(1);
|
|
28
|
-
});
|
|
7
|
+
void bootstrapMicroservice(
|
|
8
|
+
'AUTH',
|
|
9
|
+
() => NestFactory.createMicroservice<MicroserviceOptions>(AppModule, buildTransportMS('AUTH')),
|
|
10
|
+
new Logger('Auth-Bootstrap'),
|
|
11
|
+
);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Logger } from '@nestjs/common';
|
|
2
|
+
import IORedis from 'ioredis';
|
|
3
|
+
import { formatEnvBanner } from '@icore/shared';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates an IORedis connection that NEVER crashes the process when Redis is
|
|
7
|
+
* unreachable. Without an 'error' handler, ioredis emits an unhandled 'error'
|
|
8
|
+
* event → Node exits. Here we log one boxed banner and let ioredis keep
|
|
9
|
+
* retrying in the background, so the jobs MS stays up and connects once Redis
|
|
10
|
+
* is available (honours the "never crash on missing infra" rule).
|
|
11
|
+
*/
|
|
12
|
+
export function createJobsRedis(url: string, logger: Logger): IORedis {
|
|
13
|
+
const connection = new IORedis(url, {
|
|
14
|
+
maxRetriesPerRequest: null,
|
|
15
|
+
retryStrategy: (times) => Math.min(times * 200, 5000),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
let warned = false;
|
|
19
|
+
connection.on('error', (err: Error) => {
|
|
20
|
+
if (warned) return;
|
|
21
|
+
warned = true;
|
|
22
|
+
logger.warn(
|
|
23
|
+
formatEnvBanner({
|
|
24
|
+
service: 'jobs MS',
|
|
25
|
+
provider: 'redis',
|
|
26
|
+
missing: [],
|
|
27
|
+
envPath: 'apps/microservices/jobs/.env (JOBS_REDIS_URL)',
|
|
28
|
+
reason: `${err.message} — retrying in the background until Redis is up at ${url}`,
|
|
29
|
+
headline: `⚠ jobs MS — Redis unreachable (workers idle until it's up)`,
|
|
30
|
+
}),
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return connection;
|
|
35
|
+
}
|
|
@@ -2,6 +2,7 @@ import { Injectable, Logger, type OnModuleDestroy, type OnModuleInit } from '@ne
|
|
|
2
2
|
import { ConfigService } from '@nestjs/config';
|
|
3
3
|
import { Worker, type Job } from 'bullmq';
|
|
4
4
|
import IORedis from 'ioredis';
|
|
5
|
+
import { createJobsRedis } from '../redis-connection';
|
|
5
6
|
import type { CleanupJob } from '@icore/shared';
|
|
6
7
|
|
|
7
8
|
@Injectable()
|
|
@@ -15,7 +16,7 @@ export class CleanupWorker implements OnModuleInit, OnModuleDestroy {
|
|
|
15
16
|
onModuleInit(): void {
|
|
16
17
|
const url = this.cfg.get<string>('JOBS_REDIS_URL') ?? 'redis://localhost:6379';
|
|
17
18
|
const concurrency = Number(this.cfg.get<string>('JOBS_WORKER_CONCURRENCY') ?? '5');
|
|
18
|
-
this.connection =
|
|
19
|
+
this.connection = createJobsRedis(url, this.logger);
|
|
19
20
|
this.worker = new Worker<CleanupJob>(
|
|
20
21
|
'cleanup',
|
|
21
22
|
async (job: Job<CleanupJob>) => {
|
|
@@ -2,6 +2,7 @@ import { Injectable, Logger, type OnModuleDestroy, type OnModuleInit } from '@ne
|
|
|
2
2
|
import { ConfigService } from '@nestjs/config';
|
|
3
3
|
import { Worker, type Job } from 'bullmq';
|
|
4
4
|
import IORedis from 'ioredis';
|
|
5
|
+
import { createJobsRedis } from '../redis-connection';
|
|
5
6
|
import type { EmailJob } from '@icore/shared';
|
|
6
7
|
|
|
7
8
|
@Injectable()
|
|
@@ -15,7 +16,7 @@ export class EmailWorker implements OnModuleInit, OnModuleDestroy {
|
|
|
15
16
|
onModuleInit(): void {
|
|
16
17
|
const url = this.cfg.get<string>('JOBS_REDIS_URL') ?? 'redis://localhost:6379';
|
|
17
18
|
const concurrency = Number(this.cfg.get<string>('JOBS_WORKER_CONCURRENCY') ?? '5');
|
|
18
|
-
this.connection =
|
|
19
|
+
this.connection = createJobsRedis(url, this.logger);
|
|
19
20
|
this.worker = new Worker<EmailJob>(
|
|
20
21
|
'email',
|
|
21
22
|
async (job: Job<EmailJob>) => {
|
|
@@ -2,6 +2,7 @@ import { Injectable, Logger, type OnModuleDestroy, type OnModuleInit } from '@ne
|
|
|
2
2
|
import { ConfigService } from '@nestjs/config';
|
|
3
3
|
import { Worker, type Job } from 'bullmq';
|
|
4
4
|
import IORedis from 'ioredis';
|
|
5
|
+
import { createJobsRedis } from '../redis-connection';
|
|
5
6
|
import type { ImageProcessJob } from '@icore/shared';
|
|
6
7
|
|
|
7
8
|
@Injectable()
|
|
@@ -15,7 +16,7 @@ export class ImageProcessWorker implements OnModuleInit, OnModuleDestroy {
|
|
|
15
16
|
onModuleInit(): void {
|
|
16
17
|
const url = this.cfg.get<string>('JOBS_REDIS_URL') ?? 'redis://localhost:6379';
|
|
17
18
|
const concurrency = Number(this.cfg.get<string>('JOBS_WORKER_CONCURRENCY') ?? '5');
|
|
18
|
-
this.connection =
|
|
19
|
+
this.connection = createJobsRedis(url, this.logger);
|
|
19
20
|
this.worker = new Worker<ImageProcessJob>(
|
|
20
21
|
'image-process',
|
|
21
22
|
async (job: Job<ImageProcessJob>) => {
|
|
@@ -1,19 +1,41 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
-
import { Module } from '@nestjs/common';
|
|
2
|
+
import { Module, Logger } from '@nestjs/common';
|
|
3
3
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
4
4
|
import { createClient } from '@supabase/supabase-js';
|
|
5
|
-
import * as admin from 'firebase-admin';
|
|
6
5
|
import { SupabaseDBStrategy } from '@icore/db-supabase';
|
|
7
6
|
import { FirestoreDBStrategy } from '@icore/db-firestore';
|
|
8
|
-
import {
|
|
7
|
+
import { getFirebaseAdmin, FIREBASE_ADMIN_REQUIRED_ENV } from '@icore/firebase-admin';
|
|
8
|
+
import { FakeDBStrategy, missingEnv, formatEnvBanner } from '@icore/shared';
|
|
9
9
|
import type { DBStrategy } from '@icore/shared';
|
|
10
|
-
import { Logger } from '@nestjs/common';
|
|
11
10
|
import { NotesController } from './notes.controller';
|
|
12
11
|
|
|
12
|
+
const ENV_PATH = 'apps/microservices/notes/.env';
|
|
13
|
+
|
|
14
|
+
// DB_PROVIDER accepts supabase | firestore | firebase (latter two are Firestore).
|
|
15
|
+
const REQUIRED_ENV: Record<string, string[]> = {
|
|
16
|
+
supabase: ['SUPABASE_URL', 'SUPABASE_SERVICE_ROLE_KEY'],
|
|
17
|
+
firestore: [...FIREBASE_ADMIN_REQUIRED_ENV],
|
|
18
|
+
firebase: [...FIREBASE_ADMIN_REQUIRED_ENV],
|
|
19
|
+
};
|
|
20
|
+
|
|
13
21
|
function requireEnv(cfg: ConfigService, key: string): string {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
22
|
+
return cfg.getOrThrow<string>(key);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function makeSupabaseDB(cfg: ConfigService): DBStrategy {
|
|
26
|
+
const client = createClient(
|
|
27
|
+
requireEnv(cfg, 'SUPABASE_URL'),
|
|
28
|
+
requireEnv(cfg, 'SUPABASE_SERVICE_ROLE_KEY'),
|
|
29
|
+
{ auth: { autoRefreshToken: false, persistSession: false } },
|
|
30
|
+
);
|
|
31
|
+
return new SupabaseDBStrategy({ client });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function makeFirestoreDB(cfg: ConfigService): DBStrategy {
|
|
35
|
+
const app = getFirebaseAdmin(cfg);
|
|
36
|
+
return new FirestoreDBStrategy({
|
|
37
|
+
db: app.firestore() as unknown as ConstructorParameters<typeof FirestoreDBStrategy>[0]['db'],
|
|
38
|
+
});
|
|
17
39
|
}
|
|
18
40
|
|
|
19
41
|
@Module({
|
|
@@ -31,39 +53,31 @@ function requireEnv(cfg: ConfigService, key: string): string {
|
|
|
31
53
|
{
|
|
32
54
|
provide: 'DBStrategy',
|
|
33
55
|
useFactory: (cfg: ConfigService): DBStrategy => {
|
|
56
|
+
const logger = new Logger('DBStrategy');
|
|
57
|
+
const provider = cfg.get<string>('DB_PROVIDER')?.trim();
|
|
58
|
+
const keys = provider ? REQUIRED_ENV[provider] : undefined;
|
|
59
|
+
const missing = keys ? missingEnv((k) => cfg.get<string>(k), keys) : [];
|
|
60
|
+
|
|
61
|
+
const fallback = (reason?: string): DBStrategy => {
|
|
62
|
+
const banner = formatEnvBanner({
|
|
63
|
+
service: 'notes MS',
|
|
64
|
+
provider,
|
|
65
|
+
missing,
|
|
66
|
+
envPath: ENV_PATH,
|
|
67
|
+
reason,
|
|
68
|
+
});
|
|
69
|
+
if (process.env.NODE_ENV === 'production') throw new Error(banner);
|
|
70
|
+
logger.warn(banner);
|
|
71
|
+
return new FakeDBStrategy();
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
if (!keys || missing.length > 0) return fallback();
|
|
75
|
+
|
|
34
76
|
try {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const client = createClient(
|
|
38
|
-
requireEnv(cfg, 'SUPABASE_URL'),
|
|
39
|
-
requireEnv(cfg, 'SUPABASE_SERVICE_ROLE_KEY'),
|
|
40
|
-
{ auth: { autoRefreshToken: false, persistSession: false } },
|
|
41
|
-
);
|
|
42
|
-
return new SupabaseDBStrategy({ client });
|
|
43
|
-
}
|
|
44
|
-
if (provider === 'firestore' || provider === 'firebase') {
|
|
45
|
-
if (admin.apps.length === 0) {
|
|
46
|
-
admin.initializeApp({
|
|
47
|
-
credential: admin.credential.cert({
|
|
48
|
-
projectId: requireEnv(cfg, 'FB_ADMIN_PROJECT_ID'),
|
|
49
|
-
clientEmail: requireEnv(cfg, 'FB_ADMIN_CLIENT_EMAIL'),
|
|
50
|
-
privateKey: requireEnv(cfg, 'FB_ADMIN_PRIVATE_KEY').replace(/\\n/g, '\n'),
|
|
51
|
-
}),
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
return new FirestoreDBStrategy({
|
|
55
|
-
db: admin.firestore() as unknown as ConstructorParameters<
|
|
56
|
-
typeof FirestoreDBStrategy
|
|
57
|
-
>[0]['db'],
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
throw new Error(`Unsupported DB_PROVIDER: ${provider}`);
|
|
77
|
+
if (provider === 'supabase') return makeSupabaseDB(cfg);
|
|
78
|
+
return makeFirestoreDB(cfg);
|
|
61
79
|
} catch (err) {
|
|
62
|
-
|
|
63
|
-
`Not configured: ${err instanceof Error ? err.message : String(err)}. ` +
|
|
64
|
-
`Requests will fail until apps/microservices/notes/.env is set.`,
|
|
65
|
-
);
|
|
66
|
-
return new FakeDBStrategy();
|
|
80
|
+
return fallback(err instanceof Error ? err.message : String(err));
|
|
67
81
|
}
|
|
68
82
|
},
|
|
69
83
|
inject: [ConfigService],
|
|
@@ -1,28 +1,11 @@
|
|
|
1
1
|
import { Logger } from '@nestjs/common';
|
|
2
2
|
import { NestFactory } from '@nestjs/core';
|
|
3
3
|
import { MicroserviceOptions } from '@nestjs/microservices';
|
|
4
|
-
import { buildTransportMS } from '@icore/shared';
|
|
4
|
+
import { bootstrapMicroservice, buildTransportMS } from '@icore/shared';
|
|
5
5
|
import { AppModule } from './app/app.module';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
await app.listen();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
bootstrap()
|
|
16
|
-
.then(() => {
|
|
17
|
-
const logger = new Logger('Notes-Bootstrap');
|
|
18
|
-
logger.log(
|
|
19
|
-
`Notes MS Bootstrap completed: transport=${process.env.NOTES_TRANSPORT ?? 'tcp'} host=${process.env.NOTES_HOST ?? '127.0.0.1'} port=${process.env.NOTES_PORT ?? '4004'}`,
|
|
20
|
-
);
|
|
21
|
-
})
|
|
22
|
-
.catch((err) => {
|
|
23
|
-
new Logger('Notes-Bootstrap').error(
|
|
24
|
-
'Notes MS bootstrap failed',
|
|
25
|
-
err instanceof Error ? err.stack : err,
|
|
26
|
-
);
|
|
27
|
-
process.exit(1);
|
|
28
|
-
});
|
|
7
|
+
void bootstrapMicroservice(
|
|
8
|
+
'NOTES',
|
|
9
|
+
() => NestFactory.createMicroservice<MicroserviceOptions>(AppModule, buildTransportMS('NOTES')),
|
|
10
|
+
new Logger('Notes-Bootstrap'),
|
|
11
|
+
);
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
-
import { Module } from '@nestjs/common';
|
|
2
|
+
import { Module, Logger } from '@nestjs/common';
|
|
3
3
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
4
4
|
import { PaymentRegistry, PaypalStrategy, createPayment } from '@idevconn/payment';
|
|
5
|
+
import { missingEnv, formatEnvBanner } from '@icore/shared';
|
|
5
6
|
import { PaymentController } from './payment.controller';
|
|
6
7
|
|
|
8
|
+
const ENV_PATH = 'apps/microservices/payment/.env';
|
|
9
|
+
|
|
10
|
+
const REQUIRED_ENV: Record<string, string[]> = {
|
|
11
|
+
paypal: ['PAYPAL_CLIENT_ID', 'PAYPAL_CLIENT_SECRET'],
|
|
12
|
+
};
|
|
13
|
+
|
|
7
14
|
@Module({
|
|
8
15
|
imports: [
|
|
9
16
|
ConfigModule.forRoot({
|
|
@@ -19,19 +26,37 @@ import { PaymentController } from './payment.controller';
|
|
|
19
26
|
{
|
|
20
27
|
provide: PaymentRegistry,
|
|
21
28
|
useFactory: (cfg: ConfigService) => {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
const logger = new Logger('PaymentRegistry');
|
|
30
|
+
const provider = (cfg.get<string>('PAYMENT_PROVIDER') ?? 'paypal').trim();
|
|
31
|
+
const keys = REQUIRED_ENV[provider];
|
|
32
|
+
if (!keys) throw new Error(`Unsupported PAYMENT_PROVIDER: ${provider}`);
|
|
33
|
+
|
|
34
|
+
const missing = missingEnv((k) => cfg.get<string>(k), keys);
|
|
35
|
+
if (missing.length > 0) {
|
|
36
|
+
const banner = formatEnvBanner({
|
|
37
|
+
service: 'payment MS',
|
|
38
|
+
provider,
|
|
39
|
+
missing,
|
|
40
|
+
envPath: ENV_PATH,
|
|
41
|
+
headline: `⚠ payment MS — ${provider} credentials missing (payments will fail)`,
|
|
32
42
|
});
|
|
43
|
+
// Prod: fail fast. Dev: warn + register an EMPTY strategy map so the
|
|
44
|
+
// MS boots (PaypalStrategy's constructor throws on blank creds, so we
|
|
45
|
+
// must not instantiate it) — payment endpoints fail until creds are set.
|
|
46
|
+
if (process.env.NODE_ENV === 'production') throw new Error(banner);
|
|
47
|
+
logger.warn(banner);
|
|
48
|
+
return createPayment({ strategies: {} });
|
|
33
49
|
}
|
|
34
|
-
|
|
50
|
+
|
|
51
|
+
return createPayment({
|
|
52
|
+
strategies: {
|
|
53
|
+
paypal: new PaypalStrategy({
|
|
54
|
+
clientId: cfg.getOrThrow<string>('PAYPAL_CLIENT_ID'),
|
|
55
|
+
secret: cfg.getOrThrow<string>('PAYPAL_CLIENT_SECRET'),
|
|
56
|
+
environment: cfg.get<'sandbox' | 'live'>('PAYPAL_ENVIRONMENT') ?? 'sandbox',
|
|
57
|
+
}),
|
|
58
|
+
},
|
|
59
|
+
});
|
|
35
60
|
},
|
|
36
61
|
inject: [ConfigService],
|
|
37
62
|
},
|
|
@@ -1,28 +1,11 @@
|
|
|
1
1
|
import { Logger } from '@nestjs/common';
|
|
2
2
|
import { NestFactory } from '@nestjs/core';
|
|
3
3
|
import { MicroserviceOptions } from '@nestjs/microservices';
|
|
4
|
-
import { buildTransportMS } from '@icore/shared';
|
|
4
|
+
import { bootstrapMicroservice, buildTransportMS } from '@icore/shared';
|
|
5
5
|
import { AppModule } from './app/app.module';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
await app.listen();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
bootstrap()
|
|
16
|
-
.then(() => {
|
|
17
|
-
const logger = new Logger('Payment-Bootstrap');
|
|
18
|
-
logger.log(
|
|
19
|
-
`Payment MS Bootstrap completed: transport=${process.env.PAYMENT_TRANSPORT ?? 'tcp'} host=${process.env.PAYMENT_HOST ?? '127.0.0.1'} port=${process.env.PAYMENT_PORT ?? '4003'}`,
|
|
20
|
-
);
|
|
21
|
-
})
|
|
22
|
-
.catch((err) => {
|
|
23
|
-
new Logger('Payment-Bootstrap').error(
|
|
24
|
-
'Payment MS bootstrap failed',
|
|
25
|
-
err instanceof Error ? err.stack : err,
|
|
26
|
-
);
|
|
27
|
-
process.exit(1);
|
|
28
|
-
});
|
|
7
|
+
void bootstrapMicroservice(
|
|
8
|
+
'PAYMENT',
|
|
9
|
+
() => NestFactory.createMicroservice<MicroserviceOptions>(AppModule, buildTransportMS('PAYMENT')),
|
|
10
|
+
new Logger('Payment-Bootstrap'),
|
|
11
|
+
);
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
"version": "0.0.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"dependencies": {
|
|
6
|
+
"@icore/firebase-admin": "*",
|
|
6
7
|
"@icore/shared": "*",
|
|
7
8
|
"@icore/storage-cloudinary": "*",
|
|
8
9
|
"@icore/storage-firebase": "*",
|
|
9
10
|
"@icore/storage-supabase": "*",
|
|
10
11
|
"cloudinary": "^2.0.0",
|
|
11
|
-
"firebase-admin": "^13.0.0",
|
|
12
12
|
"@nestjs/common": "^11.1.24",
|
|
13
13
|
"@nestjs/config": "^4.0.4",
|
|
14
14
|
"@nestjs/core": "^11.1.24",
|