diff --git a/TODO/index.ts b/TODO/index.ts deleted file mode 100644 index cfc3ca9..0000000 --- a/TODO/index.ts +++ /dev/null @@ -1,517 +0,0 @@ -#!/usr/bin/env node - -import path from "path"; -import express from "express"; -import { SMTPServer } from "smtp-server"; -import mailparser from "mailparser"; -import fs from "fs-extra"; -import cryptoRandomString from "crypto-random-string"; -import { html, HTML } from "@leafac/html"; -import { css, process as processCSS } from "@leafac/css"; -import javascript from "tagged-template-noop"; -import { sql, Database } from "@leafac/sqlite"; -import databaseMigrate from "@leafac/sqlite-migration"; - -const VERSION = require("../package.json").version; - -export default function killTheNewsletter( - rootDirectory: string -): { webApplication: express.Express; emailApplication: SMTPServer } { - const webApplication = express(); - - webApplication.set("url", "http://localhost:4000"); - webApplication.set("email", "smtp://localhost:2525"); - webApplication.set("administrator", "mailto:kill-the-newsletter@leafac.com"); - - fs.ensureDirSync(rootDirectory); - const database = new Database( - path.join(rootDirectory, "kill-the-newsletter.db") - ); - databaseMigrate(database, [ - sql` - CREATE TABLE "feeds" ( - "id" INTEGER PRIMARY KEY AUTOINCREMENT, - "createdAt" TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, - "reference" TEXT NOT NULL UNIQUE, - "title" TEXT NOT NULL - ); - - CREATE TABLE "entries" ( - "id" INTEGER PRIMARY KEY AUTOINCREMENT, - "createdAt" TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, - "reference" TEXT NOT NULL UNIQUE, - "feed" INTEGER NOT NULL REFERENCES "feeds", - "title" TEXT NOT NULL, - "author" TEXT NOT NULL, - "content" TEXT NOT NULL - ); - `, - sql` - CREATE INDEX "entriesFeed" ON "entries" ("feed"); - `, - ]); - - webApplication.use(express.static(path.join(__dirname, "../public"))); - webApplication.use(express.urlencoded({ extended: true })); - - const logo = fs.readFileSync(path.join(__dirname, "../public/logo.svg")); - - function layout(body: HTML): HTML { - return processCSS(html` - - -
- - - - -Convert email newsletters into Atom feeds
-- $${logo} -
-- Error: Missing newsletter name. - Try again. -
- ` - ) - ); - - const feedReference = newReference(); - const welcomeTitle = `“${req.body.name}” inbox created`; - const welcomeContent = html` -
- Sign up for the newsletter with
- ${feedReference}@${new URL(webApplication.get("email"))
- .hostname}
-
- Subscribe to the Atom feed at
- ${webApplication.get("url")}/feeds/${feedReference}.xml
-
- Don’t share these addresses.
- They contain an identifier that other people could use to send you spam
- and to control your newsletter subscriptions.
-
Enjoy your readings!
- - `; - - database.executeTransaction(() => { - const feedId = database.run( - sql`INSERT INTO "feeds" ("reference", "title") VALUES (${feedReference}, ${req.body.name})` - ).lastInsertRowid; - database.run( - sql` - INSERT INTO "entries" ("reference", "feed", "title", "author", "content") - VALUES ( - ${newReference()}, - ${feedId}, - ${welcomeTitle}, - ${"Kill the Newsletter!"}, - ${welcomeContent} - ) - ` - ); - }); - - res.send( - layout(html` -${welcomeTitle}
- $${welcomeContent} - `) - ); - }); - - function renderFeed(feedReference: string): HTML | undefined { - const feed = database.get<{ - id: number; - updatedAt: string; - title: string; - }>( - sql`SELECT "id", "updatedAt", "title" FROM "feeds" WHERE "reference" = ${feedReference}` - ); - if (feed === undefined) return; - - const entries = database.all<{ - createdAt: string; - reference: string; - title: string; - author: string; - content: string; - }>( - sql` - SELECT "createdAt", "reference", "title", "author", "content" - FROM "entries" - WHERE "feed" = ${feed.id} - ORDER BY "id" DESC - ` - ); - - return html` - -404 Not found
- - `) - ); - }); - - const emailApplication = new SMTPServer({ - disabledCommands: ["AUTH", "STARTTLS"], - async onData(stream, session, callback) { - try { - const email = await mailparser.simpleParser(stream); - const from = email.from?.text ?? ""; - const subject = email.subject ?? ""; - const body = - typeof email.html === "string" ? email.html : email.textAsHtml ?? ""; - database.executeTransaction(() => { - for (const address of new Set( - session.envelope.rcptTo.map( - (smtpServerAddress) => smtpServerAddress.address - ) - )) { - const addressParts = address.split("@"); - if (addressParts.length !== 2) continue; - const [feedReference, hostname] = addressParts; - if (hostname !== new URL(webApplication.get("email")).hostname) - continue; - const feed = database.get<{ id: number }>( - sql`SELECT "id" FROM "feeds" WHERE "reference" = ${feedReference}` - ); - if (feed === undefined) continue; - database.run( - sql` - INSERT INTO "entries" ("reference", "feed", "title", "author", "content") - VALUES ( - ${newReference()}, - ${feed.id}, - ${subject}, - ${from}, - ${body} - ) - ` - ); - database.run( - sql`UPDATE "feeds" SET "updatedAt" = CURRENT_TIMESTAMP WHERE "id" = ${feed.id}` - ); - while (renderFeed(feedReference)!.length > 500_000) - database.run( - sql`DELETE FROM "entries" WHERE "feed" = ${feed.id} ORDER BY "id" ASC LIMIT 1` - ); - } - }); - callback(); - } catch (error) { - console.error( - `Failed to receive message: ‘${JSON.stringify(session, null, 2)}’` - ); - console.error(error); - stream.resume(); - callback(new Error("Failed to receive message. Please try again.")); - } - }, - }); - - function newReference(): string { - return cryptoRandomString({ - length: 16, - characters: "abcdefghijklmnopqrstuvwxyz0123456789", - }); - } - - return { webApplication, emailApplication }; -} - -if (require.main === module) { - console.log(`Kill the Newsletter!/${VERSION}`); - if (process.argv[2] === undefined) { - const { webApplication, emailApplication } = killTheNewsletter( - path.join(process.cwd(), "data") - ); - webApplication.listen(new URL(webApplication.get("url")).port, () => { - console.log(`Web server started at ${webApplication.get("url")}`); - }); - emailApplication.listen(new URL(webApplication.get("email")).port, () => { - console.log(`Email server started at ${webApplication.get("email")}`); - }); - } else { - const configurationFile = path.resolve(process.argv[2]); - require(configurationFile)(require); - console.log(`Configuration loaded from ‘${configurationFile}’.`); - } -} diff --git a/server/index.mts b/server/index.mts index 242e4d8..bc27eef 100644 --- a/server/index.mts +++ b/server/index.mts @@ -291,27 +291,523 @@ await commander.program application.configuration.hstsPreload ??= false; application.configuration.caddy ??= caddyfile``; - // application.server.locals.middleware = {} as any; - // application.server.locals.helpers = {} as any; + // #!/usr/bin/env node - await logging(application); - await database(application); - await healthChecks(application); - await base(application); - // await liveUpdates(application); - // await authentication(application); - // await layouts(application); - // await about(application); - // await administration(application); - // await user(application); - // await course(application); - // await conversation(application); - // await message(application); - // await content(application); - // await email(application); - // await demonstration(application); - // await error(application); - // await helpers(application); + // import path from "path"; + // import express from "express"; + // import { SMTPServer } from "smtp-server"; + // import mailparser from "mailparser"; + // import fs from "fs-extra"; + // import cryptoRandomString from "crypto-random-string"; + // import { html, HTML } from "@leafac/html"; + // import { css, process as processCSS } from "@leafac/css"; + // import javascript from "tagged-template-noop"; + // import { sql, Database } from "@leafac/sqlite"; + // import databaseMigrate from "@leafac/sqlite-migration"; + + // const VERSION = require("../package.json").version; + + // export default function killTheNewsletter( + // rootDirectory: string + // ): { webApplication: express.Express; emailApplication: SMTPServer } { + // const webApplication = express(); + + // webApplication.set("url", "http://localhost:4000"); + // webApplication.set("email", "smtp://localhost:2525"); + // webApplication.set("administrator", "mailto:kill-the-newsletter@leafac.com"); + + // fs.ensureDirSync(rootDirectory); + // const database = new Database( + // path.join(rootDirectory, "kill-the-newsletter.db") + // ); + // databaseMigrate(database, [ + // sql` + // CREATE TABLE "feeds" ( + // "id" INTEGER PRIMARY KEY AUTOINCREMENT, + // "createdAt" TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + // "updatedAt" TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + // "reference" TEXT NOT NULL UNIQUE, + // "title" TEXT NOT NULL + // ); + + // CREATE TABLE "entries" ( + // "id" INTEGER PRIMARY KEY AUTOINCREMENT, + // "createdAt" TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + // "reference" TEXT NOT NULL UNIQUE, + // "feed" INTEGER NOT NULL REFERENCES "feeds", + // "title" TEXT NOT NULL, + // "author" TEXT NOT NULL, + // "content" TEXT NOT NULL + // ); + // `, + // sql` + // CREATE INDEX "entriesFeed" ON "entries" ("feed"); + // `, + // ]); + + // webApplication.use(express.static(path.join(__dirname, "../public"))); + // webApplication.use(express.urlencoded({ extended: true })); + + // const logo = fs.readFileSync(path.join(__dirname, "../public/logo.svg")); + + // function layout(body: HTML): HTML { + // return processCSS(html` + // + // + // + // + // + // + // + //Convert email newsletters into Atom feeds
+ //+ // $${logo} + //
+ //+ // Error: Missing newsletter name. + // Try again. + //
+ // ` + // ) + // ); + + // const feedReference = newReference(); + // const welcomeTitle = `“${req.body.name}” inbox created`; + // const welcomeContent = html` + //
+ // Sign up for the newsletter with
+ // ${feedReference}@${new URL(webApplication.get("email"))
+ // .hostname}
+ //
+ // Subscribe to the Atom feed at
+ // ${webApplication.get("url")}/feeds/${feedReference}.xml
+ //
+ // Don’t share these addresses.
+ // They contain an identifier that other people could use to send you spam
+ // and to control your newsletter subscriptions.
+ //
Enjoy your readings!
+ //+ // Create another inbox + //
+ // `; + + // database.executeTransaction(() => { + // const feedId = database.run( + // sql`INSERT INTO "feeds" ("reference", "title") VALUES (${feedReference}, ${req.body.name})` + // ).lastInsertRowid; + // database.run( + // sql` + // INSERT INTO "entries" ("reference", "feed", "title", "author", "content") + // VALUES ( + // ${newReference()}, + // ${feedId}, + // ${welcomeTitle}, + // ${"Kill the Newsletter!"}, + // ${welcomeContent} + // ) + // ` + // ); + // }); + + // res.send( + // layout(html` + //${welcomeTitle}
+ // $${welcomeContent} + // `) + // ); + // }); + + // function renderFeed(feedReference: string): HTML | undefined { + // const feed = database.get<{ + // id: number; + // updatedAt: string; + // title: string; + // }>( + // sql`SELECT "id", "updatedAt", "title" FROM "feeds" WHERE "reference" = ${feedReference}` + // ); + // if (feed === undefined) return; + + // const entries = database.all<{ + // createdAt: string; + // reference: string; + // title: string; + // author: string; + // content: string; + // }>( + // sql` + // SELECT "createdAt", "reference", "title", "author", "content" + // FROM "entries" + // WHERE "feed" = ${feed.id} + // ORDER BY "id" DESC + // ` + // ); + + // return html` + // + //404 Not found
+ //+ // Create a new inbox + //
+ // `) + // ); + // }); + + // const emailApplication = new SMTPServer({ + // disabledCommands: ["AUTH", "STARTTLS"], + // async onData(stream, session, callback) { + // try { + // const email = await mailparser.simpleParser(stream); + // const from = email.from?.text ?? ""; + // const subject = email.subject ?? ""; + // const body = + // typeof email.html === "string" ? email.html : email.textAsHtml ?? ""; + // database.executeTransaction(() => { + // for (const address of new Set( + // session.envelope.rcptTo.map( + // (smtpServerAddress) => smtpServerAddress.address + // ) + // )) { + // const addressParts = address.split("@"); + // if (addressParts.length !== 2) continue; + // const [feedReference, hostname] = addressParts; + // if (hostname !== new URL(webApplication.get("email")).hostname) + // continue; + // const feed = database.get<{ id: number }>( + // sql`SELECT "id" FROM "feeds" WHERE "reference" = ${feedReference}` + // ); + // if (feed === undefined) continue; + // database.run( + // sql` + // INSERT INTO "entries" ("reference", "feed", "title", "author", "content") + // VALUES ( + // ${newReference()}, + // ${feed.id}, + // ${subject}, + // ${from}, + // ${body} + // ) + // ` + // ); + // database.run( + // sql`UPDATE "feeds" SET "updatedAt" = CURRENT_TIMESTAMP WHERE "id" = ${feed.id}` + // ); + // while (renderFeed(feedReference)!.length > 500_000) + // database.run( + // sql`DELETE FROM "entries" WHERE "feed" = ${feed.id} ORDER BY "id" ASC LIMIT 1` + // ); + // } + // }); + // callback(); + // } catch (error) { + // console.error( + // `Failed to receive message: ‘${JSON.stringify(session, null, 2)}’` + // ); + // console.error(error); + // stream.resume(); + // callback(new Error("Failed to receive message. Please try again.")); + // } + // }, + // }); + + // function newReference(): string { + // return cryptoRandomString({ + // length: 16, + // characters: "abcdefghijklmnopqrstuvwxyz0123456789", + // }); + // } + + // return { webApplication, emailApplication }; + // } + + // if (require.main === module) { + // console.log(`Kill the Newsletter!/${VERSION}`); + // if (process.argv[2] === undefined) { + // const { webApplication, emailApplication } = killTheNewsletter( + // path.join(process.cwd(), "data") + // ); + // webApplication.listen(new URL(webApplication.get("url")).port, () => { + // console.log(`Web server started at ${webApplication.get("url")}`); + // }); + // emailApplication.listen(new URL(webApplication.get("email")).port, () => { + // console.log(`Email server started at ${webApplication.get("email")}`); + // }); + // } else { + // const configurationFile = path.resolve(process.argv[2]); + // require(configurationFile)(require); + // console.log(`Configuration loaded from ‘${configurationFile}’.`); + // } + // } switch (application.process.type) { case "main": {