Page MenuHomeSealhub

No OneTemporary

diff --git a/src/app/app.ts b/src/app/app.ts
index f965da4f..81dcf461 100644
--- a/src/app/app.ts
+++ b/src/app/app.ts
@@ -1,210 +1,201 @@
// @ts-ignore
const locreq = require("locreq")(__dirname);
import assert from "assert";
-import { ActionName } from "../action";
import Mailer from "../email/mailer";
import Emittery from "emittery";
import Datastore from "../datastore/datastore";
import Metadata from "./metadata";
-import { SubjectPathEquiv } from "../data-structures/subject-path";
import { PartialConfig } from "./config";
import Manifest, { ManifestData } from "./manifest";
import BaseCollections from "./collections/base-collections";
import Logger from "./logger";
import ConfigManager from "./config-manager";
import HttpServer from "../http/http";
import Subject from "../subject/subject";
import Context, { SuperContext } from "../context";
import Collection from "../chip-types/collection";
-import {
- EmailFactory,
- RootSubject,
- MetadataFactory,
- i18nFactory,
-} from "../main";
+import { RootSubject, MetadataFactory, i18nFactory } from "../main";
import Users from "./collections/users";
import UserRoles from "./collections/user-roles";
import Sessions from "./collections/sessions";
+import LoggerMailer from "../email/logger-mailer";
const default_config = locreq("default_config.json");
export type AppEvents = "starting" | "started" | "stopping" | "stopped";
/** The heart of your, well app. It all starts with `new App(...)` */
abstract class App extends Emittery {
/** The current status of the app */
status: "stopped" | "running" | "starting" | "stopping";
/** The base collections including users, registration intents, etc */
static BaseCollections = BaseCollections;
/** The manifest assigned to this app. Stores things like the app name, domain, logo*/
abstract manifest: ManifestData;
/** The function that's used to generate translated versions of phrases */
i18n: (phrase_id: string, params?: any) => string;
/** ConfigManager instance. It serves the config based on default
* values and the config object provided to the app constructor */
ConfigManager: ConfigManager;
/** The {@link Logger} instance assigned to this application */
Logger: Logger;
- /** Mailer configured according to the app's config */
- Email: Mailer;
-
/** The server that runs the REST API routing and allows to add custom routes etc */
HTTPServer: HttpServer;
/** The root subject of the app. It's where all subjects are derived from */
RootSubject: Subject;
/** The mongoDB client connected to the database specified in the app config */
Datastore: Datastore;
/** The Metadata manager assigned to this app. Used to store
* certain state information that's not meant to be shown to the
* user
*
* @internal
*/
Metadata: Metadata;
/** The collections defined within the given app. */
abstract collections: {
users: Users;
"user-roles": UserRoles;
sessions: Sessions;
[name: string]: Collection;
};
/** A shorthand-way to create a new SuperContext: `new app.SuperContext()`. */
public SuperContext: new () => SuperContext;
/** A shorthand-way to create a new context: `new app.Context()`. */
public Context: new () => Context;
abstract config: PartialConfig;
+ public mailer: Mailer = new LoggerMailer();
/** The app constructor.
*
* @param custom_config Specify the details, such as database
* address and the port to listen on. This is private information
* and won't be shown to user. See {@link Config}
*
* @param manifest Specify additional information, such as the
* URL, logo or the main color of the app. This is public
* information.
*/
constructor() {
super();
this.ConfigManager = new ConfigManager();
for (let key in default_config) {
this.ConfigManager.setDefault(key, default_config[key]);
}
this.status = "stopped";
this.Logger = new Logger("error");
- this.Email = EmailFactory(this);
this.HTTPServer = new HttpServer(this);
this.RootSubject = new RootSubject(this);
this.Datastore = new Datastore(this);
this.Metadata = new MetadataFactory(this);
const app = this;
/** Shorthand way to create a {@link SuperContext} */
this.SuperContext = class extends SuperContext {
/** This constructor does not take any parameters as the
* {@link App} instance is automatically filled in */
constructor() {
super(app);
}
};
/** Shorthand way to create a {@link Context} */
this.Context = class extends Context {
/** This constructor does not take any parameters as the
* {@link App} instance is automatically filled in */
constructor() {
super(app);
}
};
}
/** Initializes all the collection fields, prepares all the hooks,
* connects to the database and starts the app, serving the REST
* API */
async start() {
this.ConfigManager.setRoot(this.config);
assert(
this.ConfigManager.get("upload_path"),
"'upload_path' not set in config"
);
this.Logger.setLevel(this.ConfigManager.get("logger").level);
this.i18n = i18nFactory(this.manifest.default_language);
new Manifest(this.manifest).validate();
this.status = "starting";
assert(
["dev", "production"].includes(
this.ConfigManager.get("core").environment
),
`"core.environment" config should be either "dev" or "production"`
);
for (const [name, collection] of Object.entries(this.collections)) {
await collection.init(this, name);
}
await this.emit("starting");
await this.Datastore.start();
- await this.Email.init();
+ await this.mailer.init(this);
await this.HTTPServer.start();
await this.emit("started");
this.status = "running";
}
/** Stops the HTTP server, disconnects from the DB */
async stop() {
this.status = "stopping";
await this.emit("stopping");
await this.HTTPServer.stop();
await this.Datastore.stop();
this.status = "stopped";
await this.emit("stopped");
this.clearListeners();
for (const collection of Object.values(this.collections)) {
collection.clearListeners();
}
}
/** Removes all data inside the app. USE WITH CAUTION
* @internal
*/
async removeAllData() {
Object.keys(this.collections).map((collection_name) =>
this.Datastore.remove(collection_name, {}, "just_one" && false)
);
}
/** Allows to listen for basic app status change events */
// @ts-ignore
async on(event_name: AppEvents, callback: () => void) {
return super.on(event_name, callback);
}
/** registers a collection within the app
* @internal
*/
registerCollection(collection: Collection) {
this.collections[collection.name] = collection;
}
}
export default App;
diff --git a/src/app/config-manager.ts b/src/app/config-manager.ts
index 29d314e5..391481a7 100644
--- a/src/app/config-manager.ts
+++ b/src/app/config-manager.ts
@@ -1,50 +1,51 @@
import dotProp from "dot-prop";
import merge from "deepmerge";
import Config from "./config";
+import Mailer from "../email/mailer";
type ConfigObject = { [key: string]: any };
export default class ConfigManager {
DEFAULT_CONFIG: ConfigObject;
CUSTOM_CONFIG: ConfigObject;
isLocked: boolean;
constructor() {
this.DEFAULT_CONFIG = {};
this.CUSTOM_CONFIG = {};
this.isLocked = false;
}
setDefault(key: string, value: any) {
this._setGivenConfig(this.DEFAULT_CONFIG, key, value);
}
getDefaultConfig(key: string) {
return dotProp.get(this.DEFAULT_CONFIG, key);
}
set(key: string, value: any) {
this._setGivenConfig(this.CUSTOM_CONFIG, key, value);
}
_setGivenConfig(config: ConfigObject, key: string, value: any) {
this._warnIfLocked();
dotProp.set(config, key, value);
}
_warnIfLocked() {
if (this.isLocked) {
console.warn(
"Warning: " +
"you shouldn't change config after ConfigManager was locked"
);
}
}
setRoot(params: ConfigObject) {
this._warnIfLocked();
this.CUSTOM_CONFIG = merge(this.CUSTOM_CONFIG, params);
}
get<Key extends keyof Config>(key: Key): Config[Key] {
return dotProp.get(
merge(this.DEFAULT_CONFIG, this.CUSTOM_CONFIG),
key
) as Config[Key];
}
lock() {
this.isLocked = true;
}
}
diff --git a/src/app/config.ts b/src/app/config.ts
index 183623d6..3f59ad76 100644
--- a/src/app/config.ts
+++ b/src/app/config.ts
@@ -1,54 +1,48 @@
import { LoggerLevel } from "./logger";
export type Environment = "dev" | "production";
type Config = {
core: {
environment: Environment;
};
logger: {
level: LoggerLevel;
};
"www-server": {
port: number;
"api-base": string;
"session-cookie-name": string;
"max-payload-bytes": number;
};
datastore_mongo: {
host: string;
port: number;
db_name: string;
password: string;
};
roles: string[];
password_hash: {
iterations: number;
key_length: number;
salt_length: number;
};
image_formats: {};
accout_creation_success_path: false | string;
email: {
from_address: string;
from_name: string;
};
upload_path: string;
- smtp: {
- host: string;
- port: number;
- user: string;
- password: string;
- };
app: {
version: string;
};
};
export default Config;
type RecursivePartial<T> = {
[P in keyof T]?: RecursivePartial<T[P]>;
};
export type PartialConfig = RecursivePartial<Config>;
diff --git a/src/email/email.ts b/src/email/email.ts
deleted file mode 100644
index 5f36be63..00000000
--- a/src/email/email.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import assert from "assert";
-import SmtpMailer from "./smtp-mailer";
-import LoggerMailer from "./logger-mailer";
-import { App } from "../main";
-import Mailer from "./mailer";
-import { Environment } from "../app/config";
-
-const environment_to_mailer: {
- [env in Environment]: new (app: App) => Mailer;
-} = {
- dev: LoggerMailer,
- production: SmtpMailer,
-};
-
-export default (app: App) => {
- let environment: Environment;
- let config;
- let mailer;
- app.ConfigManager.setDefault("email", {
- from_name: "Noreply",
- from_address: "example@example.com",
- });
- app.ConfigManager.setDefault("smtp", SmtpMailer.default_config);
- config = app.ConfigManager.get("email");
-
- assert(typeof config.from_name === "string");
- assert(
- typeof config.from_address === "string",
- "Please set a config value for 'email.from_address'"
- );
-
- environment = app.ConfigManager.get("core").environment;
- mailer = new environment_to_mailer[environment as Environment](app);
-
- return mailer;
-};
diff --git a/src/email/mailer.ts b/src/email/mailer.ts
index 3bdc840e..1e8ef2d5 100644
--- a/src/email/mailer.ts
+++ b/src/email/mailer.ts
@@ -1,20 +1,19 @@
import { App } from "../main";
import { MessageData } from "./message";
export default abstract class Mailer {
app: App;
- constructor(app: App) {
- this.app = app;
- }
abstract verify(): Promise<boolean>;
abstract sendEmail(
message: MessageData & { from_name: string }
): Promise<void>;
async send(message_data: MessageData) {
return this.sendEmail({
...message_data,
from_name: this.app.ConfigManager.get("email").from_name,
});
}
- async init() {}
+ async init(app: App) {
+ this.app = app;
+ }
}
diff --git a/src/email/message.ts b/src/email/message.ts
index faa989be..1caade86 100644
--- a/src/email/message.ts
+++ b/src/email/message.ts
@@ -1,26 +1,26 @@
import assert from "assert";
import App from "../app/app";
export type MessageData = {
to: string;
subject: string;
html: string;
attachments: any[];
text: string;
};
export default class Message {
data: MessageData;
constructor(data: MessageData) {
assert(data.to);
assert(data.subject);
assert(data.html);
assert(
data.attachments === undefined || Array.isArray(data.attachments)
);
this.data = data;
}
async send(app: App) {
- return app.Email.send(this.data);
+ return app.mailer.send(this.data);
}
}
diff --git a/src/email/smtp-mailer.ts b/src/email/smtp-mailer.ts
index 1e7818c5..d0c1d64d 100644
--- a/src/email/smtp-mailer.ts
+++ b/src/email/smtp-mailer.ts
@@ -1,56 +1,66 @@
import assert from "assert";
import nodemailer from "nodemailer";
import { App } from "../main";
import Mailer from "./mailer";
import { MessageData } from "./message";
export default class SmtpMailer extends Mailer {
mail_config: { from_name: string; from_address: string };
transport: nodemailer.Transporter;
- constructor(app: App) {
- super(app);
- const config = app.ConfigManager.get("smtp");
+ constructor(config: {
+ host: string;
+ port: number;
+ user: string;
+ password: string;
+ }) {
+ super();
assert(typeof config.host == "string");
assert(typeof config.port == "number");
assert(typeof config.user == "string");
assert(typeof config.password == "string");
- this.mail_config = app.ConfigManager.get("email");
this.transport = nodemailer.createTransport({
host: config.host,
port: config.port,
auth: {
user: config.user,
pass: config.password,
},
});
}
+
async verify() {
return this.transport.verify();
}
+
+ async init(app: App) {
+ await super.init(app);
+ this.mail_config = app.ConfigManager.get("email");
+ }
+
async sendEmail({
to,
subject,
text,
html,
from_name,
attachments,
}: MessageData & { from_name: string }) {
return this.transport.sendMail({
from: `${from_name || this.mail_config.from_name} <${
this.mail_config.from_address
}>`,
to,
subject: subject.toString(),
text,
html,
attachments,
});
}
static default_config = {
host: null,
port: null,
user: null,
password: null,
};
}
diff --git a/src/main.ts b/src/main.ts
index 29a4ba3c..9e6a6121 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,36 +1,37 @@
export { default as Query } from "./datastore/query";
export { default as QueryTypes } from "./datastore/query-types";
export { default as SpecialFilter } from "./chip-types/special-filter";
export * as SpecialFilters from "./app/base-chips/special_filters/special-filters";
export { PolicyClass } from "./chip-types/policy";
export { default as Policy } from "./chip-types/policy";
export { default as Collection } from "./chip-types/collection";
export * as Policies from "./app/policy-types/policy-types";
export { default as Field } from "./chip-types/field";
export * from "./chip-types/field";
export * as FieldTypes from "./app/base-chips/field-types/field-types";
export { ActionName } from "./action";
export { default as Action } from "./action";
export { default as App } from "./app/app";
export { default as Config } from "./app/config";
export { default as ConfigManager } from "./app/config-manager";
export { default as Logger } from "./app/logger";
export { default as Manifest } from "./app/manifest";
export { default as MetadataFactory } from "./app/metadata";
export { default as Context, SuperContext } from "./context";
export {
default as SubjectPath,
SubjectPathEquiv,
} from "./data-structures/subject-path";
export * as Queries from "./datastore/query";
-export { default as EmailFactory } from "./email/email";
export { default as HttpServer } from "./http/http";
export { default as i18nFactory } from "./i18n/i18n";
export { default as RootSubject } from "./subject/predefined-subjects/root-subject";
export { default as Subject } from "./subject/subject";
export { default as CalculatedField } from "./chip-types/calculated-field";
export * as EmailTemplates from "./email/templates/templates";
export * as Errors from "./response/errors";
export { default as File } from "./data-structures/file";
export { default as ItemList } from "./chip-types/item-list";
+export { default as SMTPMailer } from "./email/smtp-mailer";
+export { default as LoggerMailer } from "./email/logger-mailer";
diff --git a/src/test_utils/test-app.ts b/src/test_utils/test-app.ts
index 761796a1..8f5ac875 100644
--- a/src/test_utils/test-app.ts
+++ b/src/test_utils/test-app.ts
@@ -1,81 +1,81 @@
// @ts-ignore
const locreq = require("locreq")(__dirname);
-import { App } from "../main";
+import { App, SMTPMailer } from "../main";
import { Environment } from "../app/config";
import { LoggerLevel } from "../app/logger";
export const get_test_app = ({
env,
port,
base_url,
}: {
env: Environment;
port: number;
base_url: string;
}) => {
return class TestApp extends App {
clear_database_on_stop: boolean = true;
collections = { ...App.BaseCollections };
config = {
upload_path: "/tmp",
datastore_mongo: {
host: "localhost",
password: "sealious-test",
port: 20722,
},
- smtp: {
- host: "localhost",
- port: 1025,
- user: "any",
- password: "any",
- },
email: {
from_name: "Sealious test app",
from_address: "sealious@example.com",
},
core: { environment: env },
app: { version: "0.0.0-test" },
logger: { level: "none" as LoggerLevel },
"www-server": {
port,
},
password_hash: {
iterations: 1,
},
};
manifest = {
name: "testing app",
logo: locreq.resolve("src/assets/logo.png"),
default_language: "pl",
version: "0.0.0-test",
base_url,
colors: {
primary: "#4d394b",
},
admin_email: "admin@example.com",
};
+ mailer = new SMTPMailer({
+ host: "localhost",
+ port: 1025,
+ user: "any",
+ password: "any",
+ });
async start() {
this.on("stopping", async () => {
if (this.clear_database_on_stop && this.Datastore.db) {
for (const collection_name in this.collections) {
await this.Datastore.remove(
collection_name,
{},
"just_one" && false
);
}
await this.Datastore.remove(
this.Metadata.db_collection_name,
{},
"just_one" && false
);
}
});
await super.start();
}
};
};
export type TestAppType = ReturnType<typeof get_test_app>;

File Metadata

Mime Type
text/x-diff
Expires
Fri, Jan 24, 15:16 (1 d, 9 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
601003
Default Alt Text
(17 KB)

Event Timeline