Automated Action 545563e776 Implement comprehensive real-time chat API with NestJS
- Complete NestJS TypeScript implementation with WebSocket support
- Direct messaging (DM) and group chat functionality
- End-to-end encryption with AES encryption and key pairs
- Media file support (images, videos, audio, documents) up to 100MB
- Push notifications with Firebase Cloud Messaging integration
- Mention alerts and real-time typing indicators
- User authentication with JWT and Passport
- SQLite database with TypeORM entities and relationships
- Comprehensive API documentation with Swagger/OpenAPI
- File upload handling with secure access control
- Online/offline status tracking and presence management
- Message editing, deletion, and reply functionality
- Notification management with automatic cleanup
- Health check endpoint for monitoring
- CORS configuration for cross-origin requests
- Environment-based configuration management
- Structured for Flutter SDK integration

Features implemented:
 Real-time messaging with Socket.IO
 User registration and authentication
 Direct messages and group chats
 Media file uploads and management
 End-to-end encryption
 Push notifications
 Mention alerts
 Typing indicators
 Message read receipts
 Online status tracking
 File access control
 Comprehensive API documentation

Ready for Flutter SDK development and production deployment.
2025-06-21 17:13:05 +00:00

455 lines
17 KiB
JavaScript
Executable File

#!/usr/bin/env node
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadEsmModule = exports.main = void 0;
// symbol polyfill must go first
require("symbol-observable");
const node_1 = require("@angular-devkit/core/node");
const schematics_1 = require("@angular-devkit/schematics");
const tools_1 = require("@angular-devkit/schematics/tools");
const ansi_colors_1 = __importDefault(require("ansi-colors"));
const node_fs_1 = require("node:fs");
const path = __importStar(require("node:path"));
const yargs_parser_1 = __importStar(require("yargs-parser"));
/**
* Parse the name of schematic passed in argument, and return a {collection, schematic} named
* tuple. The user can pass in `collection-name:schematic-name`, and this function will either
* return `{collection: 'collection-name', schematic: 'schematic-name'}`, or it will error out
* and show usage.
*
* In the case where a collection name isn't part of the argument, the default is to use the
* schematics package (@angular-devkit/schematics-cli) as the collection.
*
* This logic is entirely up to the tooling.
*
* @param str The argument to parse.
* @return {{collection: string, schematic: (string)}}
*/
function parseSchematicName(str) {
let collection = '@angular-devkit/schematics-cli';
let schematic = str;
if (schematic?.includes(':')) {
const lastIndexOfColon = schematic.lastIndexOf(':');
[collection, schematic] = [
schematic.slice(0, lastIndexOfColon),
schematic.substring(lastIndexOfColon + 1),
];
}
return { collection, schematic };
}
function _listSchematics(workflow, collectionName, logger) {
try {
const collection = workflow.engine.createCollection(collectionName);
logger.info(collection.listSchematicNames().join('\n'));
}
catch (error) {
logger.fatal(error instanceof Error ? error.message : `${error}`);
return 1;
}
return 0;
}
function _createPromptProvider() {
return async (definitions) => {
const questions = definitions.map((definition) => {
const question = {
name: definition.id,
message: definition.message,
default: definition.default,
};
const validator = definition.validator;
if (validator) {
question.validate = (input) => validator(input);
// Filter allows transformation of the value prior to validation
question.filter = async (input) => {
for (const type of definition.propertyTypes) {
let value;
switch (type) {
case 'string':
value = String(input);
break;
case 'integer':
case 'number':
value = Number(input);
break;
default:
value = input;
break;
}
// Can be a string if validation fails
const isValid = (await validator(value)) === true;
if (isValid) {
return value;
}
}
return input;
};
}
switch (definition.type) {
case 'confirmation':
return { ...question, type: 'confirm' };
case 'list':
return {
...question,
type: definition.multiselect ? 'checkbox' : 'list',
choices: definition.items &&
definition.items.map((item) => {
if (typeof item == 'string') {
return item;
}
else {
return {
name: item.label,
value: item.value,
};
}
}),
};
default:
return { ...question, type: definition.type };
}
});
const { default: inquirer } = await loadEsmModule('inquirer');
return inquirer.prompt(questions);
};
}
function findUp(names, from) {
if (!Array.isArray(names)) {
names = [names];
}
const root = path.parse(from).root;
let currentDir = from;
while (currentDir && currentDir !== root) {
for (const name of names) {
const p = path.join(currentDir, name);
if ((0, node_fs_1.existsSync)(p)) {
return p;
}
}
currentDir = path.dirname(currentDir);
}
return null;
}
/**
* return package manager' name by lock file
*/
function getPackageManagerName() {
// order by check priority
const LOCKS = {
'package-lock.json': 'npm',
'yarn.lock': 'yarn',
'pnpm-lock.yaml': 'pnpm',
};
const lockPath = findUp(Object.keys(LOCKS), process.cwd());
if (lockPath) {
return LOCKS[path.basename(lockPath)];
}
return 'npm';
}
// eslint-disable-next-line max-lines-per-function
async function main({ args, stdout = process.stdout, stderr = process.stderr, }) {
const { cliOptions, schematicOptions, _ } = parseArgs(args);
// Create a separate instance to prevent unintended global changes to the color configuration
const colors = ansi_colors_1.default.create();
/** Create the DevKit Logger used through the CLI. */
const logger = (0, node_1.createConsoleLogger)(!!cliOptions.verbose, stdout, stderr, {
info: (s) => s,
debug: (s) => s,
warn: (s) => colors.bold.yellow(s),
error: (s) => colors.bold.red(s),
fatal: (s) => colors.bold.red(s),
});
if (cliOptions.help) {
logger.info(getUsage());
return 0;
}
/** Get the collection an schematic name from the first argument. */
const { collection: collectionName, schematic: schematicName } = parseSchematicName(_.shift() || null);
const isLocalCollection = collectionName.startsWith('.') || collectionName.startsWith('/');
/** Gather the arguments for later use. */
const debugPresent = cliOptions.debug !== null;
const debug = debugPresent ? !!cliOptions.debug : isLocalCollection;
const dryRunPresent = cliOptions['dry-run'] !== null;
const dryRun = dryRunPresent ? !!cliOptions['dry-run'] : debug;
const force = !!cliOptions.force;
const allowPrivate = !!cliOptions['allow-private'];
/** Create the workflow scoped to the working directory that will be executed with this run. */
const workflow = new tools_1.NodeWorkflow(process.cwd(), {
force,
dryRun,
resolvePaths: [process.cwd(), __dirname],
schemaValidation: true,
packageManager: getPackageManagerName(),
});
/** If the user wants to list schematics, we simply show all the schematic names. */
if (cliOptions['list-schematics']) {
return _listSchematics(workflow, collectionName, logger);
}
if (!schematicName) {
logger.info(getUsage());
return 1;
}
if (debug) {
logger.info(`Debug mode enabled${isLocalCollection ? ' by default for local collections' : ''}.`);
}
// Indicate to the user when nothing has been done. This is automatically set to off when there's
// a new DryRunEvent.
let nothingDone = true;
// Logging queue that receives all the messages to show the users. This only get shown when no
// errors happened.
let loggingQueue = [];
let error = false;
/**
* Logs out dry run events.
*
* All events will always be executed here, in order of discovery. That means that an error would
* be shown along other events when it happens. Since errors in workflows will stop the Observable
* from completing successfully, we record any events other than errors, then on completion we
* show them.
*
* This is a simple way to only show errors when an error occur.
*/
workflow.reporter.subscribe((event) => {
nothingDone = false;
// Strip leading slash to prevent confusion.
const eventPath = event.path.startsWith('/') ? event.path.slice(1) : event.path;
switch (event.kind) {
case 'error':
error = true;
const desc = event.description == 'alreadyExist' ? 'already exists' : 'does not exist';
logger.error(`ERROR! ${eventPath} ${desc}.`);
break;
case 'update':
loggingQueue.push(`${colors.cyan('UPDATE')} ${eventPath} (${event.content.length} bytes)`);
break;
case 'create':
loggingQueue.push(`${colors.green('CREATE')} ${eventPath} (${event.content.length} bytes)`);
break;
case 'delete':
loggingQueue.push(`${colors.yellow('DELETE')} ${eventPath}`);
break;
case 'rename':
const eventToPath = event.to.startsWith('/') ? event.to.slice(1) : event.to;
loggingQueue.push(`${colors.blue('RENAME')} ${eventPath} => ${eventToPath}`);
break;
}
});
/**
* Listen to lifecycle events of the workflow to flush the logs between each phases.
*/
workflow.lifeCycle.subscribe((event) => {
if (event.kind == 'workflow-end' || event.kind == 'post-tasks-start') {
if (!error) {
// Flush the log queue and clean the error state.
loggingQueue.forEach((log) => logger.info(log));
}
loggingQueue = [];
error = false;
}
});
// Show usage of deprecated options
workflow.registry.useXDeprecatedProvider((msg) => logger.warn(msg));
// Pass the rest of the arguments as the smart default "argv". Then delete it.
workflow.registry.addSmartDefaultProvider('argv', (schema) => 'index' in schema ? _[Number(schema['index'])] : _);
// Add prompts.
if (cliOptions.interactive && isTTY()) {
workflow.registry.usePromptProvider(_createPromptProvider());
}
/**
* Execute the workflow, which will report the dry run events, run the tasks, and complete
* after all is done.
*
* The Observable returned will properly cancel the workflow if unsubscribed, error out if ANY
* step of the workflow failed (sink or task), with details included, and will only complete
* when everything is done.
*/
try {
await workflow
.execute({
collection: collectionName,
schematic: schematicName,
options: schematicOptions,
allowPrivate: allowPrivate,
debug: debug,
logger: logger,
})
.toPromise();
if (nothingDone) {
logger.info('Nothing to be done.');
}
else if (dryRun) {
logger.info(`Dry run enabled${dryRunPresent ? '' : ' by default in debug mode'}. No files written to disk.`);
}
return 0;
}
catch (err) {
if (err instanceof schematics_1.UnsuccessfulWorkflowExecution) {
// "See above" because we already printed the error.
logger.fatal('The Schematic workflow failed. See above.');
}
else if (debug && err instanceof Error) {
logger.fatal(`An error occured:\n${err.stack}`);
}
else {
logger.fatal(`Error: ${err instanceof Error ? err.message : err}`);
}
return 1;
}
}
exports.main = main;
/**
* Get usage of the CLI tool.
*/
function getUsage() {
return `
schematics [collection-name:]schematic-name [options, ...]
By default, if the collection name is not specified, use the internal collection provided
by the Schematics CLI.
Options:
--debug Debug mode. This is true by default if the collection is a relative
path (in that case, turn off with --debug=false).
--allow-private Allow private schematics to be run from the command line. Default to
false.
--dry-run Do not output anything, but instead just show what actions would be
performed. Default to true if debug is also true.
--force Force overwriting files that would otherwise be an error.
--list-schematics List all schematics from the collection, by name. A collection name
should be suffixed by a colon. Example: '@angular-devkit/schematics-cli:'.
--no-interactive Disables interactive input prompts.
--verbose Show more information.
--help Show this message.
Any additional option is passed to the Schematics depending on its schema.
`;
}
/** Parse the command line. */
const booleanArgs = [
'allow-private',
'debug',
'dry-run',
'force',
'help',
'list-schematics',
'verbose',
'interactive',
];
/** Parse the command line. */
function parseArgs(args) {
const { _, ...options } = (0, yargs_parser_1.default)(args, {
boolean: booleanArgs,
default: {
'interactive': true,
'debug': null,
'dry-run': null,
},
configuration: {
'dot-notation': false,
'boolean-negation': true,
'strip-aliased': true,
'camel-case-expansion': false,
},
});
// Camelize options as yargs will return the object in kebab-case when camel casing is disabled.
const schematicOptions = {};
const cliOptions = {};
const isCliOptions = (key) => booleanArgs.includes(key);
for (const [key, value] of Object.entries(options)) {
if (/[A-Z]/.test(key)) {
throw new Error(`Unknown argument ${key}. Did you mean ${(0, yargs_parser_1.decamelize)(key)}?`);
}
if (isCliOptions(key)) {
cliOptions[key] = value;
}
else {
schematicOptions[(0, yargs_parser_1.camelCase)(key)] = value;
}
}
return {
_: _.map((v) => v.toString()),
schematicOptions,
cliOptions,
};
}
function isTTY() {
const isTruthy = (value) => {
// Returns true if value is a string that is anything but 0 or false.
return value !== undefined && value !== '0' && value.toUpperCase() !== 'FALSE';
};
// If we force TTY, we always return true.
const force = process.env['NG_FORCE_TTY'];
if (force !== undefined) {
return isTruthy(force);
}
return !!process.stdout.isTTY && !isTruthy(process.env['CI']);
}
if (require.main === module) {
const args = process.argv.slice(2);
main({ args })
.then((exitCode) => (process.exitCode = exitCode))
.catch((e) => {
throw e;
});
}
/**
* Lazily compiled dynamic import loader function.
*/
let load;
/**
* This uses a dynamic import to load a module which may be ESM.
* CommonJS code can load ESM code via a dynamic import. Unfortunately, TypeScript
* will currently, unconditionally downlevel dynamic import into a require call.
* require calls cannot load ESM code and will result in a runtime error. To workaround
* this, a Function constructor is used to prevent TypeScript from changing the dynamic import.
* Once TypeScript provides support for keeping the dynamic import this workaround can
* be dropped.
*
* @param modulePath The path of the module to load.
* @returns A Promise that resolves to the dynamically imported module.
*/
function loadEsmModule(modulePath) {
load ??= new Function('modulePath', `return import(modulePath);`);
return load(modulePath);
}
exports.loadEsmModule = loadEsmModule;