Initial commit, based off of the Panblog project.
This commit is contained in:
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/tmp
|
||||||
|
/out-tsc
|
||||||
|
|
||||||
|
/node_modules
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
.vscode/*
|
||||||
|
.idea/*
|
||||||
33
Dockerfile
Normal file
33
Dockerfile
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Builds the required node packages.
|
||||||
|
FROM node:25 AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY src/package*.json ./
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
FROM node:25 AS production
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
COPY src/* ./
|
||||||
|
|
||||||
|
# Sets users and permissions.
|
||||||
|
RUN groupadd --system --gid 1001 sysmailer && \
|
||||||
|
useradd --uid 1001 --gid 1001 -M -N --system sysmailer
|
||||||
|
|
||||||
|
COPY --from=builder --chown=sysmailer:sysmailer /app /app
|
||||||
|
|
||||||
|
# De-escelates permissions for improved security.
|
||||||
|
USER sysmailer
|
||||||
|
|
||||||
|
# Defines the expected outputs.
|
||||||
|
EXPOSE 6868
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
|
CMD node -e "require('http').get('http://localhost:8080', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })"
|
||||||
|
|
||||||
|
CMD ["npm", "start"]
|
||||||
9
README.md
Normal file
9
README.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# NodeMailer
|
||||||
|
|
||||||
|
NodeMailer is a server-side application that provides mailing list services.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
* **User Management**: Keeps track of users and their emails, both internally and over LDAP.
|
||||||
|
* **Mailing Lists**: Maintain an infinite number of mailing lists.
|
||||||
|
* **Reply Chains**: Keep track of all emails that are replies to a single original email.
|
||||||
|
* **Database Free**: All data is stored in the file system and kept in memory, without relying on databases.
|
||||||
40
package.json
Normal file
40
package.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "NodeMailer",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "NodeMailer is a server-side application that provides mailing list services.",
|
||||||
|
"keywords": [
|
||||||
|
"nodemailer",
|
||||||
|
"mail list",
|
||||||
|
"mail",
|
||||||
|
"list",
|
||||||
|
"http",
|
||||||
|
"ldap"
|
||||||
|
],
|
||||||
|
"license": "ISC",
|
||||||
|
"author": "Markil 3",
|
||||||
|
"type": "module",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bcrypt": "^6.0.0",
|
||||||
|
"@types/cors": "^2.8.19",
|
||||||
|
"@types/escape-html": "^1.0.4",
|
||||||
|
"@types/express": "^5.0.6",
|
||||||
|
"@types/mime": "^3.0.4",
|
||||||
|
"@types/multer": "^2.2.0",
|
||||||
|
"@types/node": "^26.4.0",
|
||||||
|
"typescript": "^6.0.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bcrypt": "^6.0.0",
|
||||||
|
"cors": "^2.8.6",
|
||||||
|
"escape-html": "^1.0.3",
|
||||||
|
"express": "^5.2.1",
|
||||||
|
"mime": "^4.1.0",
|
||||||
|
"multer": "^2.3.0",
|
||||||
|
"node-html-parser": "^9.0.2",
|
||||||
|
"xmlbuilder2": "^4.0.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
146
src/config.ts
Normal file
146
src/config.ts
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Markil 3. All rights reserved.
|
||||||
|
*/
|
||||||
|
import fs from 'node:fs/promises';
|
||||||
|
import http from 'http';
|
||||||
|
import {IncomingMessage} from "node:http";
|
||||||
|
|
||||||
|
const CONFIG_PATH = "config_path";
|
||||||
|
const BASE_URL = "base_url";
|
||||||
|
const UPLOAD_PATH = "upload_path";
|
||||||
|
const CACHE_PATH = "cache_path";
|
||||||
|
const INDEX_PATH = "index_path";
|
||||||
|
const TMP_PATH = "tmp_path";
|
||||||
|
const TMP_UPLOAD_PATH = "tmp_upload_path";
|
||||||
|
const MAX_UPLOAD_SIZE = "max_upload_size";
|
||||||
|
const AUTH_KEY = "auth_key";
|
||||||
|
const AUTH_KEY_HASH = "auth_key_hash";
|
||||||
|
|
||||||
|
let config: {[key: string]: any} = {};
|
||||||
|
config[CONFIG_PATH] = "/etc/node_mailer/config";
|
||||||
|
config[BASE_URL] = "";
|
||||||
|
config[UPLOAD_PATH] = "/var/lib/node_mailer/uploads";
|
||||||
|
config[CACHE_PATH] = "/var/cache/node_mailer/output";
|
||||||
|
config[INDEX_PATH] = "/var/cache/node_mailer/index.json";
|
||||||
|
config[TMP_PATH] = "/var/cache/node_mailer/tmp";
|
||||||
|
config[TMP_UPLOAD_PATH] = "/var/cache/node_mailer/upload";
|
||||||
|
config[MAX_UPLOAD_SIZE] = 10485760;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a configuration value, whether read from a file (if
|
||||||
|
* it has a "file:" prefix) or a URL (if it has a "url:" prefix).
|
||||||
|
*
|
||||||
|
* @param value - A raw value from our configuration.
|
||||||
|
* @returns A promise that resolves to the value.
|
||||||
|
*/
|
||||||
|
function resolveValue(value: any): Promise<any|undefined>
|
||||||
|
{
|
||||||
|
let path: string;
|
||||||
|
if (!value)
|
||||||
|
{
|
||||||
|
return value?.trim();
|
||||||
|
}
|
||||||
|
if (value.startsWith("file:"))
|
||||||
|
{
|
||||||
|
path = value.substring(5);
|
||||||
|
return fs.open(path).then((fh) => {try {return fh.readFile({encoding: "utf-8"});} finally {fh.close();}}).then((val) => val?.trim());
|
||||||
|
}
|
||||||
|
else if (value.startsWith("url:"))
|
||||||
|
{
|
||||||
|
path = value.substring(4);
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
http.get(path, (response: IncomingMessage) => {
|
||||||
|
if (!response.statusCode || response.statusCode < 200 || response.statusCode >= 300)
|
||||||
|
{
|
||||||
|
response.resume();
|
||||||
|
reject(new Error(`HTTP ${response.statusCode} from URL ${path}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
response.setEncoding("utf8");
|
||||||
|
let data = '';
|
||||||
|
response.on('data', (chunk) => { data += chunk; });
|
||||||
|
response.on('end', () => { resolve(data.trim()) });
|
||||||
|
response.on('error', reject);
|
||||||
|
}).on('error', reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEnv(parameter: string): any|undefined
|
||||||
|
{
|
||||||
|
return process.env["PANBLOG_" + parameter.toUpperCase()];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads configuration first from the environmental variables, followed by the configuration file.
|
||||||
|
* @returns A promise that resolves to the configuration object.
|
||||||
|
*/
|
||||||
|
async function readConfiguration():Promise<{[key: string]: any}>
|
||||||
|
{
|
||||||
|
let param, value;
|
||||||
|
if (process.env.PANBLOG_CONFIG_PATH)
|
||||||
|
{
|
||||||
|
config[CONFIG_PATH] = await resolveValue(process.env.PANBLOG_CONFIG_PATH);
|
||||||
|
}
|
||||||
|
let fh;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
fh = await fs.open(config[CONFIG_PATH]);
|
||||||
|
let i: number = 0;
|
||||||
|
for await (const line of fh.readLines())
|
||||||
|
{
|
||||||
|
if (line.indexOf("=") == -1)
|
||||||
|
{
|
||||||
|
console.error(`Illegal configuration format, line ${i}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
param = line.substring(0, line.indexOf('=')).trim();
|
||||||
|
value = line.substring(line.indexOf('=') + 1).trim();
|
||||||
|
config[param] = await resolveValue(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
console.error("Could not read configuration file");
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
fh?.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the values from the environmental variables.
|
||||||
|
*/
|
||||||
|
for (const parameter of [BASE_URL, UPLOAD_PATH, CACHE_PATH, INDEX_PATH, TMP_PATH, TMP_UPLOAD_PATH, MAX_UPLOAD_SIZE, AUTH_KEY, AUTH_KEY_HASH])
|
||||||
|
{
|
||||||
|
value = await resolveValue(getEnv(parameter));
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
config[parameter] = await resolveValue(getEnv(parameter));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config[MAX_UPLOAD_SIZE])
|
||||||
|
{
|
||||||
|
config[MAX_UPLOAD_SIZE] = parseInt(config[MAX_UPLOAD_SIZE]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
CONFIG_PATH,
|
||||||
|
BASE_URL,
|
||||||
|
UPLOAD_PATH,
|
||||||
|
CACHE_PATH,
|
||||||
|
INDEX_PATH,
|
||||||
|
TMP_PATH,
|
||||||
|
TMP_UPLOAD_PATH,
|
||||||
|
MAX_UPLOAD_SIZE,
|
||||||
|
AUTH_KEY,
|
||||||
|
AUTH_KEY_HASH,
|
||||||
|
readConfiguration,
|
||||||
|
config
|
||||||
|
}
|
||||||
22
src/index.ts
Normal file
22
src/index.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Markil 3. All rights reserved.
|
||||||
|
*/
|
||||||
|
import http from 'http';
|
||||||
|
import RequestFramework from './requests';
|
||||||
|
import {Server} from "node:http";
|
||||||
|
|
||||||
|
const PORT = 6868;
|
||||||
|
|
||||||
|
function startServer(): Server
|
||||||
|
{
|
||||||
|
let app = new RequestFramework();
|
||||||
|
let server = http.createServer(app.app);
|
||||||
|
server.listen(PORT, () => {
|
||||||
|
console.log(`Starting blog server at http://localhost:${PORT}/`);
|
||||||
|
});
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
startServer
|
||||||
|
}
|
||||||
7
src/mailer_framework.ts
Normal file
7
src/mailer_framework.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Markil 3. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export {
|
||||||
|
|
||||||
|
}
|
||||||
46
src/main.ts
Normal file
46
src/main.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Markil 3. All rights reserved.
|
||||||
|
*/
|
||||||
|
import { mkdir } from 'node:fs/promises';
|
||||||
|
import { UPLOAD_PATH, CACHE_PATH, TMP_PATH, readConfiguration, config } from './config';
|
||||||
|
import { startServer } from './index';
|
||||||
|
import {Server} from "node:http";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sets up the default directories.
|
||||||
|
*/
|
||||||
|
mkdir(config[UPLOAD_PATH], {recursive: true});
|
||||||
|
mkdir(config[CACHE_PATH], {recursive: true});
|
||||||
|
mkdir(config[TMP_PATH], {recursive: true});
|
||||||
|
|
||||||
|
let server: Server;
|
||||||
|
|
||||||
|
const shutdown = (signal: string|undefined) => {
|
||||||
|
console.log(`Received OS ${signal}. Shutting down...`);
|
||||||
|
|
||||||
|
if (server)
|
||||||
|
{
|
||||||
|
// Close the HTTP server
|
||||||
|
server.close(() => {
|
||||||
|
console.log('HTTP server closed');
|
||||||
|
|
||||||
|
// Cleanup tasks (e.g., close database connections)
|
||||||
|
console.log('Cleanup complete. Exiting.');
|
||||||
|
process.exit(0); // Exit after cleanup
|
||||||
|
});
|
||||||
|
server.closeAllConnections();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
process.on('SIGINT', () => shutdown('SIGINT'));
|
||||||
|
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
||||||
|
|
||||||
|
|
||||||
|
readConfiguration().then((config) => {
|
||||||
|
console.info("Read configuration");
|
||||||
|
server = startServer();
|
||||||
|
}).catch((e) => {
|
||||||
|
console.error("Error reading configuration.");
|
||||||
|
console.error(e);
|
||||||
|
shutdown(undefined);
|
||||||
|
});
|
||||||
70
src/requests.ts
Normal file
70
src/requests.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Markil 3. All rights reserved.
|
||||||
|
*/
|
||||||
|
import path from 'node:path';
|
||||||
|
import fs from 'node:fs/promises';
|
||||||
|
import express, {Express, Request, Response, NextFunction} from 'express';
|
||||||
|
import cors from 'cors';
|
||||||
|
import multer from 'multer';
|
||||||
|
import { MulterError } from 'multer';
|
||||||
|
import {BASE_URL, UPLOAD_PATH, CACHE_PATH, TMP_UPLOAD_PATH, MAX_UPLOAD_SIZE, config} from "./config";
|
||||||
|
import {isAuthenticated, getMime} from "./utils";
|
||||||
|
|
||||||
|
class RequestFramework
|
||||||
|
{
|
||||||
|
private static instance: RequestFramework;
|
||||||
|
private multer: multer.Multer;
|
||||||
|
app: Express;
|
||||||
|
|
||||||
|
constructor()
|
||||||
|
{
|
||||||
|
if (RequestFramework.instance)
|
||||||
|
{
|
||||||
|
//return RequestFramework.instance;
|
||||||
|
}
|
||||||
|
RequestFramework.instance = this;
|
||||||
|
|
||||||
|
this.multer = multer({ dest: config[TMP_UPLOAD_PATH], preservePath: true, limits: {fileSize: config[MAX_UPLOAD_SIZE]} } );
|
||||||
|
|
||||||
|
this.app = express();
|
||||||
|
this.app.use(express.urlencoded());
|
||||||
|
this.app.use(cors());
|
||||||
|
|
||||||
|
let router = express.Router();
|
||||||
|
|
||||||
|
router.use((request, response, next) => {
|
||||||
|
isAuthenticated(request).then((valid: boolean) => {
|
||||||
|
if (valid)
|
||||||
|
{
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
response.status(401).send('<!DOCTYPE html><html><body><h1>401 Authorization Error</h1><p>Not authorized</p></body></html>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.all("/", (request, response, next) => {
|
||||||
|
response.send("<!DOCTYPE html><html><body><p>Now witness the power of this fully armed and operational blog station.</p></body></html>");
|
||||||
|
});
|
||||||
|
router.get("/ping", this.ping);
|
||||||
|
router.all("/error", this.error);
|
||||||
|
|
||||||
|
this.app.use(config[BASE_URL], router);
|
||||||
|
}
|
||||||
|
|
||||||
|
ping(request: Request, response: Response, next: NextFunction)
|
||||||
|
{
|
||||||
|
response.send("Pong");
|
||||||
|
}
|
||||||
|
|
||||||
|
error(request: Request, response: Response, next: NextFunction)
|
||||||
|
{
|
||||||
|
let y: number = 5;
|
||||||
|
let x = y / 0;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RequestFramework;
|
||||||
213
src/utils.ts
Normal file
213
src/utils.ts
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Markil 3. All rights reserved.
|
||||||
|
*/
|
||||||
|
import bcrypt from "bcrypt";
|
||||||
|
import {Request} from "express";
|
||||||
|
import {config, AUTH_KEY, AUTH_KEY_HASH} from './config.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks to see if a request is allowed, based on whether it has the appropriate authentication headers.
|
||||||
|
* @param request - The request to verify.
|
||||||
|
* @return A promise that resolves to True if the headers are valid, false otherwise.
|
||||||
|
*/
|
||||||
|
async function isAuthenticated(request: Request): Promise<boolean>
|
||||||
|
{
|
||||||
|
if (config[AUTH_KEY_HASH] || config[AUTH_KEY])
|
||||||
|
{
|
||||||
|
let authHeader = request.get('Authorization'), encodedCredentials, credentials;
|
||||||
|
if (!authHeader)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (authHeader.startsWith("Basic "))
|
||||||
|
{
|
||||||
|
encodedCredentials = authHeader.substring('Basic '.length);
|
||||||
|
if (config[AUTH_KEY_HASH])
|
||||||
|
{
|
||||||
|
return await bcrypt.compare(encodedCredentials, config[AUTH_KEY_HASH]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return encodedCredentials == config[AUTH_KEY];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* We do not have authentication keys configured. We can let anything through.
|
||||||
|
*/
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMime(filePath: string): string
|
||||||
|
{
|
||||||
|
let pathExt;
|
||||||
|
if (filePath.lastIndexOf('.') > -1)
|
||||||
|
{
|
||||||
|
pathExt = filePath.substring(filePath.lastIndexOf('.')).toLowerCase();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pathExt = '';
|
||||||
|
return "text/plain";
|
||||||
|
}
|
||||||
|
if (pathExt == ".html" || pathExt == ".htm")
|
||||||
|
{
|
||||||
|
return "text/html";
|
||||||
|
}
|
||||||
|
if (pathExt == ".json")
|
||||||
|
{
|
||||||
|
return "application/json";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".css")
|
||||||
|
{
|
||||||
|
return "text/css";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".js")
|
||||||
|
{
|
||||||
|
return "text/javascript";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".eot")
|
||||||
|
{
|
||||||
|
return "application/vnd.ms-fontobject";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".otf")
|
||||||
|
{
|
||||||
|
return "font/otf";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".ttf")
|
||||||
|
{
|
||||||
|
return "font/ttf";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".woff")
|
||||||
|
{
|
||||||
|
return "font/woff";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".woff2")
|
||||||
|
{
|
||||||
|
return "font/woff2";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".png")
|
||||||
|
{
|
||||||
|
return "image/png";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".apng")
|
||||||
|
{
|
||||||
|
return "image/apng";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".jpg" || pathExt == ".jpeg")
|
||||||
|
{
|
||||||
|
return "image/jpeg";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".gif")
|
||||||
|
{
|
||||||
|
return "image/gif";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".bmp")
|
||||||
|
{
|
||||||
|
return "image/bmp";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".webp")
|
||||||
|
{
|
||||||
|
return "image/webp";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".avif")
|
||||||
|
{
|
||||||
|
return "image/avif";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".ico")
|
||||||
|
{
|
||||||
|
return "image/vnd.microsoft.icon";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".svg")
|
||||||
|
{
|
||||||
|
return "image/svg+xml";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".aac")
|
||||||
|
{
|
||||||
|
return "audio/aac";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".mp3")
|
||||||
|
{
|
||||||
|
return "audio/mpeg";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".ogg" || pathExt == ".oga" || pathExt == ".opus")
|
||||||
|
{
|
||||||
|
return "audio/ogg";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".wav")
|
||||||
|
{
|
||||||
|
return "audio/wav";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".weba")
|
||||||
|
{
|
||||||
|
return "audio/webm";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".avi")
|
||||||
|
{
|
||||||
|
return "video/x-msvideo";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".mp4")
|
||||||
|
{
|
||||||
|
return "video/mp4";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".mpeg")
|
||||||
|
{
|
||||||
|
return "video/mpeg";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".ogv")
|
||||||
|
{
|
||||||
|
return "video/ogg";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".webm")
|
||||||
|
{
|
||||||
|
return "video/webm";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".md")
|
||||||
|
{
|
||||||
|
return "text/markdown";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".azw")
|
||||||
|
{
|
||||||
|
return "application/vnd.amazon.ebook";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".epub")
|
||||||
|
{
|
||||||
|
return "application/epub+zip";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".doc")
|
||||||
|
{
|
||||||
|
return "application/msword";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".docx")
|
||||||
|
{
|
||||||
|
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".odt")
|
||||||
|
{
|
||||||
|
return "application/vnd.oasis.opendocument.text";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".pdf")
|
||||||
|
{
|
||||||
|
return "application/pdf";
|
||||||
|
}
|
||||||
|
else if (pathExt == ".rtf")
|
||||||
|
{
|
||||||
|
return "application/rtf";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "text/plain";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
isAuthenticated,
|
||||||
|
getMime
|
||||||
|
};
|
||||||
13
tsconfig.json
Normal file
13
tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es2016",
|
||||||
|
"module": "commonjs",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"outDir": "dist",
|
||||||
|
"types": ["node", "express", "cors", "multer", "bcrypt", "escape-html", "mime"]
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user