manifest
Version:
Self-hosted Manifest LLM router with embedded server, SQLite database, and dashboard
184 lines • 9.11 kB
JavaScript
;
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EmailProviderConfigService = void 0;
const common_1 = require("@nestjs/common");
const config_1 = require("@nestjs/config");
const typeorm_1 = require("typeorm");
const uuid_1 = require("uuid");
const render_1 = require("@react-email/render");
const sql_dialect_1 = require("../../common/utils/sql-dialect");
const crypto_util_1 = require("../../common/utils/crypto.util");
const email_provider_validation_1 = require("./email-provider-validation");
const resolve_provider_1 = require("./email-providers/resolve-provider");
const test_email_1 = require("../emails/test-email");
let EmailProviderConfigService = class EmailProviderConfigService {
ds;
configService;
dialect;
fromEmail;
constructor(ds, configService) {
this.ds = ds;
this.configService = configService;
this.dialect = (0, sql_dialect_1.detectDialect)(ds.options.type);
this.fromEmail = this.configService.get('app.notificationFromEmail', 'noreply@manifest.build');
}
sql(query) {
return (0, sql_dialect_1.portableSql)(query, this.dialect);
}
decryptKey(stored) {
if ((0, crypto_util_1.isEncrypted)(stored)) {
return (0, crypto_util_1.decrypt)(stored, (0, crypto_util_1.getEncryptionSecret)());
}
return stored;
}
async getConfig(userId) {
const rows = await this.ds.query(this.sql(`SELECT provider, domain, key_prefix, is_active, notification_email FROM email_provider_configs WHERE user_id = $1`), [userId]);
if (!rows.length)
return null;
const row = rows[0];
return {
provider: row.provider,
domain: row.domain ?? null,
keyPrefix: row.key_prefix ?? '****',
is_active: !!row.is_active,
notificationEmail: row.notification_email ?? null,
};
}
async upsert(userId, dto) {
const notificationEmail = dto.notificationEmail?.trim().toLowerCase() || null;
const now = new Date().toISOString();
const existing = await this.ds.query(this.sql(`SELECT id, api_key_encrypted FROM email_provider_configs WHERE user_id = $1`), [userId]);
if (existing.length > 0 && !dto.apiKey) {
const storedKey = existing[0].api_key_encrypted;
const plainKey = this.decryptKey(storedKey);
const validation = (0, email_provider_validation_1.validateProviderConfig)(dto.provider, plainKey, dto.domain);
if (!validation.valid) {
throw new common_1.BadRequestException(validation.errors);
}
const { domain, provider } = validation.normalized;
await this.ds.query(this.sql(`UPDATE email_provider_configs SET provider = $1, domain = $2, is_active = $3, updated_at = $4, notification_email = $5 WHERE user_id = $6`), [provider, domain || null, 1, now, notificationEmail, userId]);
return {
provider,
domain: domain || null,
keyPrefix: existing[0].key_prefix ?? plainKey.substring(0, 8),
is_active: true,
notificationEmail,
};
}
if (!dto.apiKey) {
throw new common_1.BadRequestException('API key is required for new configurations');
}
const validation = (0, email_provider_validation_1.validateProviderConfig)(dto.provider, dto.apiKey, dto.domain);
if (!validation.valid) {
throw new common_1.BadRequestException(validation.errors);
}
const { apiKey, domain, provider } = validation.normalized;
const secret = (0, crypto_util_1.getEncryptionSecret)();
const encryptedKey = (0, crypto_util_1.encrypt)(apiKey, secret);
const prefix = apiKey.substring(0, 8);
if (existing.length > 0) {
await this.ds.query(this.sql(`UPDATE email_provider_configs SET provider = $1, api_key_encrypted = $2, key_prefix = $3, domain = $4, is_active = $5, updated_at = $6, notification_email = $7 WHERE user_id = $8`), [provider, encryptedKey, prefix, domain || null, 1, now, notificationEmail, userId]);
}
else {
await this.ds.query(this.sql(`INSERT INTO email_provider_configs (id, user_id, provider, api_key_encrypted, key_prefix, domain, is_active, created_at, updated_at, notification_email) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`), [
(0, uuid_1.v4)(),
userId,
provider,
encryptedKey,
prefix,
domain || null,
1,
now,
now,
notificationEmail,
]);
}
return {
provider,
domain: domain || null,
keyPrefix: prefix,
is_active: true,
notificationEmail,
};
}
async remove(userId) {
await this.ds.query(this.sql(`DELETE FROM email_provider_configs WHERE user_id = $1`), [
userId,
]);
}
async getFullConfig(userId) {
const rows = await this.ds.query(this.sql(`SELECT provider, api_key_encrypted, domain, notification_email FROM email_provider_configs WHERE user_id = $1 AND is_active = $2`), [userId, 1]);
if (!rows.length)
return null;
const row = rows[0];
return {
provider: row.provider,
apiKey: this.decryptKey(row.api_key_encrypted),
domain: row.domain ?? null,
notificationEmail: row.notification_email ?? null,
};
}
async getNotificationEmail(userId) {
const rows = await this.ds.query(this.sql(`SELECT notification_email FROM email_provider_configs WHERE user_id = $1`), [userId]);
return rows[0]?.notification_email ?? null;
}
async setNotificationEmail(userId, email) {
const existing = await this.ds.query(this.sql(`SELECT id FROM email_provider_configs WHERE user_id = $1`), [userId]);
const now = new Date().toISOString();
if (existing.length > 0) {
await this.ds.query(this.sql(`UPDATE email_provider_configs SET notification_email = $1, updated_at = $2 WHERE user_id = $3`), [email.trim().toLowerCase(), now, userId]);
}
}
async testSavedConfig(userId, toEmail) {
const config = await this.getFullConfig(userId);
if (!config) {
return { success: false, error: 'No email provider configured' };
}
return this.testConfig({ provider: config.provider, apiKey: config.apiKey, domain: config.domain ?? undefined }, toEmail);
}
async testConfig(dto, toEmail) {
const validation = (0, email_provider_validation_1.validateProviderConfig)(dto.provider, dto.apiKey, dto.domain);
if (!validation.valid) {
return { success: false, error: validation.errors.join(', ') };
}
const { provider, apiKey, domain } = validation.normalized;
try {
const config = {
provider: provider,
apiKey,
domain: domain || undefined,
};
const emailProvider = (0, resolve_provider_1.createProvider)(config);
const html = await (0, render_1.render)((0, test_email_1.TestEmail)());
const text = await (0, render_1.render)((0, test_email_1.TestEmail)(), { plainText: true });
const from = domain ? `Manifest <noreply@${domain}>` : `Manifest <${this.fromEmail}>`;
const sent = await emailProvider.send({
to: toEmail,
subject: 'Manifest — Test Email',
html,
text,
from,
});
return sent ? { success: true } : { success: false, error: 'Provider returned failure' };
}
catch (err) {
return { success: false, error: String(err) };
}
}
};
exports.EmailProviderConfigService = EmailProviderConfigService;
exports.EmailProviderConfigService = EmailProviderConfigService = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [typeorm_1.DataSource,
config_1.ConfigService])
], EmailProviderConfigService);
//# sourceMappingURL=email-provider-config.service.js.map