Page Menu
Home
Sealhub
Search
Configure Global Search
Log In
Files
F1262777
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
17 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Fri, Jan 24, 15:16 (1 d, 4 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
601003
Default Alt Text
(17 KB)
Attached To
Mode
rS Sealious
Attached
Detach File
Event Timeline
Log In to Comment