diff --git a/.github/workflows/ci.yaml b/.github/workflows/build-test.yaml similarity index 77% rename from .github/workflows/ci.yaml rename to .github/workflows/build-test.yaml index 882c2c9..d84282c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/build-test.yaml @@ -22,10 +22,7 @@ jobs: - name: Install dependencies run: pnpm install - name: Set up config files - run: | - cp src/config/api-example.js src/config/api.js - cp src/config/database-example.js src/config/database.js - cp src/config/domain-example.js src/config/domain.js + run: cp config/config.example.toml config/config.toml - name: Build run: pnpm build continue-on-error: true diff --git a/.gitignore b/.gitignore index 763f664..683b41a 100755 --- a/.gitignore +++ b/.gitignore @@ -2,10 +2,8 @@ # gathio custom +config/config.toml dist -src/config/api.js -src/config/database.js -src/config/domain.js public/events/* !public/events/.gitkeep diff --git a/config/config.example.toml b/config/config.example.toml new file mode 100644 index 0000000..1ffbeb7 --- /dev/null +++ b/config/config.example.toml @@ -0,0 +1,33 @@ +[general] +# Your domain goes here. If there is a port it should be 'domain.com:port', but +# otherwise just 'domain.com'. +domain = "localhost:3000" +port = "3000" +email = "contact@example.com" +site_name = "gathio" +is_federated = true +# If left blank, this defaults to +# https://yourdomain.com/images/gathio-email-logo.gif. Set a full URL here to +# change it to your own logo (or just change the file itself). +email_logo_url = "" +# Show a Ko-Fi box to donate money to Raphael (Gathio's creator) on the front +# page. +show_kofi = false +# Which mail service to use to send emails to hosts and attendees. Options are +# 'nodemailer' or 'sendgrid'. Configure settings for this mail +# service below. +mail_service = "nodemailer" + +[database] +# Set up for a locally running MongoDB connection. Change this to +# 'mongodb://mongo:27017/gathio' for a Dockerised connection. +mongodb_url = "mongodb://localhost:27017/gathio" + +[nodemailer] +smtp_server = "" +smtp_port = "" +smtp_username = "" +smtp_password = "" + +[sendgrid] +api_key = "" diff --git a/package.json b/package.json index db85892..90047c3 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "1.3.0", "description": "", "main": "index.js", + "type": "module", "scripts": { "build": "tsc", "start": "node dist/start.js", @@ -19,7 +20,6 @@ "@sendgrid/mail": "^6.5.5", "body-parser": "^1.20.2", "cors": "^2.8.5", - "dotenv": "^6.2.0", "express": "^4.18.2", "express-fileupload": "^1.4.0", "express-handlebars": "^6.0.7", @@ -38,7 +38,8 @@ "nodemailer": "^6.9.2", "randomstring": "^1.2.3", "request": "^2.88.2", - "sanitize-html": "^2.10.0" + "sanitize-html": "^2.10.0", + "toml": "^3.0.0" }, "devDependencies": { "eslint": "^8.40.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 33b638a..e310e2f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,9 +10,6 @@ dependencies: cors: specifier: ^2.8.5 version: 2.8.5 - dotenv: - specifier: ^6.2.0 - version: 6.2.0 express: specifier: ^4.18.2 version: 4.18.2 @@ -70,6 +67,9 @@ dependencies: sanitize-html: specifier: ^2.10.0 version: 2.10.0 + toml: + specifier: ^3.0.0 + version: 3.0.0 devDependencies: eslint: @@ -1108,11 +1108,6 @@ packages: domhandler: 5.0.3 dev: false - /dotenv@6.2.0: - resolution: {integrity: sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==} - engines: {node: '>=6'} - dev: false - /ecc-jsbn@0.1.2: resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} dependencies: @@ -2739,6 +2734,10 @@ packages: ieee754: 1.2.1 dev: false + /toml@3.0.0: + resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} + dev: false + /touch@3.1.0: resolution: {integrity: sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==} hasBin: true diff --git a/src/activitypub.js b/src/activitypub.js index c8fc682..555f44d 100644 --- a/src/activitypub.js +++ b/src/activitypub.js @@ -1,24 +1,24 @@ -const domain = require("./config/domain.js").domain; -const contactEmail = require("./config/domain.js").email; -const siteName = require("./config/domain.js").sitename; -const isFederated = require("./config/domain.js").isFederated; -const request = require("request"); -const addToLog = require("./helpers.js").addToLog; -const crypto = require("crypto"); +import request from "request"; +import { addToLog } from "./helpers.js"; +import crypto from "crypto"; +import { customAlphabet } from "nanoid"; +import moment from "moment-timezone"; +import sanitizeHtml from "sanitize-html"; +import { getConfig } from "./lib/config.js"; +const config = getConfig(); +const domain = config.general.domain; +const siteName = config.general.site_name; +const isFederated = config.general.is_federated; +import Event from "./models/Event.js"; + // This alphabet (used to generate all event, group, etc. IDs) is missing '-' // because ActivityPub doesn't like it in IDs -const { customAlphabet } = require("nanoid"); const nanoid = customAlphabet( "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_", 21 ); -var moment = require("moment-timezone"); -const mongoose = require("mongoose"); -const Event = mongoose.model("Event"); -const EventGroup = mongoose.model("EventGroup"); -var sanitizeHtml = require("sanitize-html"); -function createActivityPubActor( +export function createActivityPubActor( eventID, domain, pubkey, @@ -70,7 +70,7 @@ function createActivityPubActor( return JSON.stringify(actor); } -function createActivityPubEvent( +export function createActivityPubEvent( name, startUTC, endUTC, @@ -92,7 +92,7 @@ function createActivityPubEvent( return JSON.stringify(eventObject); } -function createFeaturedPost( +export function createFeaturedPost( eventID, name, startUTC, @@ -113,7 +113,7 @@ function createFeaturedPost( return featured; } -function updateActivityPubEvent( +export function updateActivityPubEvent( oldEvent, name, startUTC, @@ -137,7 +137,7 @@ function updateActivityPubEvent( return JSON.stringify(eventObject); } -function updateActivityPubActor( +export function updateActivityPubActor( actor, description, name, @@ -168,7 +168,7 @@ function updateActivityPubActor( return JSON.stringify(actor); } -function signAndSend(message, eventID, targetDomain, inbox, callback) { +export function signAndSend(message, eventID, targetDomain, inbox, callback) { if (!isFederated) return; let inboxFragment = inbox.replace("https://" + targetDomain, ""); // get the private key @@ -264,7 +264,7 @@ function signAndSend(message, eventID, targetDomain, inbox, callback) { // this function sends something to the timeline of every follower in the followers array // it's also an unlisted public message, meaning non-followers can see the message if they look at // the profile but it doesn't spam federated timelines -function broadcastCreateMessage(apObject, followers, eventID) { +export function broadcastCreateMessage(apObject, followers, eventID) { if (!isFederated) return; let guidCreate = crypto.randomBytes(16).toString("hex"); Event.findOne( @@ -325,7 +325,7 @@ function broadcastCreateMessage(apObject, followers, eventID) { } // sends an Announce for the apObject -function broadcastAnnounceMessage(apObject, followers, eventID) { +export function broadcastAnnounceMessage(apObject, followers, eventID) { if (!isFederated) return; let guidUpdate = crypto.randomBytes(16).toString("hex"); Event.findOne( @@ -386,7 +386,7 @@ function broadcastAnnounceMessage(apObject, followers, eventID) { } // sends an Update for the apObject -function broadcastUpdateMessage(apObject, followers, eventID) { +export function broadcastUpdateMessage(apObject, followers, eventID) { if (!isFederated) return; let guidUpdate = crypto.randomBytes(16).toString("hex"); // iterate over followers @@ -440,7 +440,7 @@ function broadcastUpdateMessage(apObject, followers, eventID) { ); } -function broadcastDeleteMessage(apObject, followers, eventID, callback) { +export function broadcastDeleteMessage(apObject, followers, eventID, callback) { callback = callback || function () {}; if (!isFederated) { callback([]); @@ -521,7 +521,7 @@ function broadcastDeleteMessage(apObject, followers, eventID, callback) { } // this sends a message "to:" an individual fediverse user -function sendDirectMessage(apObject, actorId, eventID, callback) { +export function sendDirectMessage(apObject, actorId, eventID, callback) { if (!isFederated) return; callback = callback || function () {}; const guidCreate = crypto.randomBytes(16).toString("hex"); @@ -567,7 +567,7 @@ function sendDirectMessage(apObject, actorId, eventID, callback) { ); } -function sendAcceptMessage(thebody, eventID, targetDomain, callback) { +export function sendAcceptMessage(thebody, eventID, targetDomain, callback) { if (!isFederated) return; callback = callback || function () {}; const guid = crypto.randomBytes(16).toString("hex"); @@ -1185,7 +1185,7 @@ function _handleCreateNoteComment(req, res) { } // end public message } -function processInbox(req, res) { +export function processInbox(req, res) { if (!isFederated) return res.sendStatus(404); try { // if a Follow activity hits the inbox @@ -1251,7 +1251,7 @@ function processInbox(req, res) { } } -function createWebfinger(eventID, domain) { +export function createWebfinger(eventID, domain) { return { subject: `acct:${eventID}@${domain}`, @@ -1264,20 +1264,3 @@ function createWebfinger(eventID, domain) { ], }; } - -module.exports = { - processInbox, - sendAcceptMessage, - sendDirectMessage, - broadcastAnnounceMessage, - broadcastUpdateMessage, - broadcastDeleteMessage, - broadcastCreateMessage, - signAndSend, - createActivityPubActor, - updateActivityPubActor, - createActivityPubEvent, - updateActivityPubEvent, - createFeaturedPost, - createWebfinger, -}; diff --git a/src/app.js b/src/app.js index 4a2137e..9feb239 100755 --- a/src/app.js +++ b/src/app.js @@ -1,10 +1,7 @@ -const express = require("express"); -const path = require("path"); -const session = require("express-session"); -const cors = require("cors"); -const routes = require("./routes"); -const hbs = require("express-handlebars"); -const bodyParser = require("body-parser"); +import express from "express"; +import routes from "./routes.js"; +import hbs from "express-handlebars"; +import bodyParser from "body-parser"; const app = express(); @@ -50,4 +47,4 @@ app.use(bodyParser.json({ type: "application/activity+json" })); // support json app.use(bodyParser.urlencoded({ extended: true })); app.use("/", routes); -module.exports = app; +export default app; diff --git a/src/config/api-example.js b/src/config/api-example.js deleted file mode 100644 index 493b9d6..0000000 --- a/src/config/api-example.js +++ /dev/null @@ -1,8 +0,0 @@ -// Which of these fields are used depends on the 'mailService' config entry in config/domain.js -module.exports = { - sendgrid: "", // If using SendGrid, the Sendgrid API key goes here - smtpServer: "", // If using Nodemailer, your SMTP server hostname goes here - smtpPort: "", // If using Nodemailer, your SMTP server port goes here - smtpUsername: "", // If using Nodemailer, your SMTP server username goes here - smtpPassword: "", // If using Nodemailer, your SMTP password goes here -}; diff --git a/src/config/database-docker.js b/src/config/database-docker.js deleted file mode 100644 index 96c987d..0000000 --- a/src/config/database-docker.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - url: "mongodb://mongo:27017/gathio", // For dockerised MongoDB connection -}; diff --git a/src/config/database-example.js b/src/config/database-example.js deleted file mode 100644 index ca7bdcc..0000000 --- a/src/config/database-example.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - url: "mongodb://localhost:27017/gathio", // For local MongoDB connection -}; diff --git a/src/config/domain-example.js b/src/config/domain-example.js deleted file mode 100644 index abac094..0000000 --- a/src/config/domain-example.js +++ /dev/null @@ -1,13 +0,0 @@ -module.exports = { - // Your domain goes here. If there is a port it should be 'domain:port', but otherwise just 'domain' - domain: "localhost:3000", - port: "3000", - email: "contact@example.com", - mailService: "nodemailer", // Which mail service to use to send emails to attendees. Options are 'nodemailer' or 'sendgrid'. Configure settings for the mail service in config/api.js.z - sitename: "gathio", - isFederated: true, - // If left blank, this defaults to https://yourdomain.com/images/gathio-email-logo.gif. Set a full URL here to change it to your own logo (or just change the file itself) - logo_url: "", - // Show a Ko-Fi box to donate money to Raphael Kabo (Gathio's creator) on the front page - showKofi: false, -}; diff --git a/src/config/gathio.service b/src/config/gathio.service deleted file mode 100644 index 447d44f..0000000 --- a/src/config/gathio.service +++ /dev/null @@ -1,13 +0,0 @@ -[Unit] -Description=GathIO -After=network.target - -[Service] -Type=simple -User=gathio -WorkingDirectory=/srv/gathio -ExecStart=/usr/bin/npm start -Restart=on-failure - -[Install] -WantedBy=multi-user.target diff --git a/src/helpers.js b/src/helpers.js index 9b7559f..305187f 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1,14 +1,14 @@ -const domain = require("./config/domain.js").domain; -const siteName = require("./config/domain.js").sitename; - -const mongoose = require("mongoose"); -const Log = mongoose.model("Log"); -var moment = require("moment-timezone"); -const icalGenerator = require("ical-generator"); +import moment from "moment-timezone"; +import icalGenerator from "ical-generator"; +import Log from "./models/Log.js"; +import { getConfig } from "./lib/config.js"; +const config = getConfig(); +const domain = config.general.domain; +const siteName = config.general.site_name; // LOGGING -function addToLog(process, status, message) { +export function addToLog(process, status, message) { let logEntry = new Log({ status: status, process: process, @@ -20,7 +20,7 @@ function addToLog(process, status, message) { }); } -function exportIcal(events, calendarName) { +export function exportIcal(events, calendarName) { // Create a new icalGenerator... generator const cal = icalGenerator({ name: calendarName || siteName, @@ -52,8 +52,3 @@ function exportIcal(events, calendarName) { const string = cal.toString(); return string; } - -module.exports = { - addToLog, - exportIcal, -}; diff --git a/src/lib/config.ts b/src/lib/config.ts new file mode 100644 index 0000000..d235145 --- /dev/null +++ b/src/lib/config.ts @@ -0,0 +1,56 @@ +import fs from "fs"; +import toml from "toml"; + +interface GathioConfig { + general: { + domain: string; + port: string; + email: string; + site_name: string; + is_federated: boolean; + email_logo_url: string; + show_kofi: boolean; + mail_service: "nodemailer" | "sendgrid"; + }; + database: { + mongodb_url: string; + }; + nodemailer?: { + smtp_server: string; + smtp_port: string; + smtp_username: string; + smtp_password: string; + }; + sendgrid?: { + api_key: string; + }; +} + +export const publicConfig = () => { + const config = getConfig(); + return { + domain: config.general.domain, + siteName: config.general.site_name, + isFederated: config.general.is_federated, + emailLogoUrl: config.general.email_logo_url, + showKofi: config.general.show_kofi, + }; +}; + +// Attempt to load our global config. Will stop the app if the config file +// cannot be read (there's no point trying to continue!) +export const getConfig = (): GathioConfig => { + try { + const config = toml.parse( + fs.readFileSync("./config/config.toml", "utf-8") + ) as GathioConfig; + return config; + } catch { + console.error( + "\x1b[31mConfiguration file not found! Have you renamed './config/config-example.toml' to './config/config.toml'?" + ); + process.exit(1); + } +}; + +export default getConfig; diff --git a/src/models/Event.js b/src/models/Event.js index 234084a..63a03ae 100755 --- a/src/models/Event.js +++ b/src/models/Event.js @@ -1,4 +1,4 @@ -const mongoose = require("mongoose"); +import mongoose from "mongoose"; const Attendees = new mongoose.Schema({ name: { @@ -256,4 +256,4 @@ const EventSchema = new mongoose.Schema({ activityPubMessages: [ActivityPubMessages], }); -module.exports = mongoose.model("Event", EventSchema); +export default mongoose.model("Event", EventSchema); diff --git a/src/models/EventGroup.js b/src/models/EventGroup.js index 1a4ac64..f19e374 100755 --- a/src/models/EventGroup.js +++ b/src/models/EventGroup.js @@ -1,4 +1,4 @@ -const mongoose = require("mongoose"); +import mongoose from "mongoose"; const Subscriber = new mongoose.Schema({ email: { @@ -54,4 +54,4 @@ const EventGroupSchema = new mongoose.Schema({ subscribers: [Subscriber], }); -module.exports = mongoose.model("EventGroup", EventGroupSchema); +export default mongoose.model("EventGroup", EventGroupSchema); diff --git a/src/models/Log.js b/src/models/Log.js index b048a45..f165900 100755 --- a/src/models/Log.js +++ b/src/models/Log.js @@ -1,4 +1,4 @@ -const mongoose = require("mongoose"); +import mongoose from "mongoose"; const LogSchema = new mongoose.Schema({ status: { @@ -23,4 +23,4 @@ const LogSchema = new mongoose.Schema({ }, }); -module.exports = mongoose.model("Log", LogSchema); +export default mongoose.model("Log", LogSchema); diff --git a/src/routes.js b/src/routes.js index 8c7f630..55436c9 100755 --- a/src/routes.js +++ b/src/routes.js @@ -1,49 +1,56 @@ -const fs = require("fs"); - -const express = require("express"); - -const mongoose = require("mongoose"); +import fs from "fs"; +import express from "express"; +import { customAlphabet } from "nanoid"; +import randomstring from "randomstring"; +import { getConfig } from "./lib/config.js"; +import { addToLog, exportIcal } from "./helpers.js"; +import moment from "moment-timezone"; +import { marked } from "marked"; +import generateRSAKeypair from "generate-rsa-keypair"; +import crypto from "crypto"; +import request from "request"; +import niceware from "niceware"; +import ical from "ical"; +import sgMail from "@sendgrid/mail"; +import nodemailer from "nodemailer"; +import fileUpload from "express-fileupload"; +import Jimp from "jimp"; +import schedule from "node-schedule"; +import { + createActivityPubActor, + createActivityPubEvent, + createFeaturedPost, + createWebfinger, + updateActivityPubActor, + updateActivityPubEvent, + broadcastCreateMessage, + broadcastUpdateMessage, + broadcastDeleteMessage, + sendDirectMessage, + processInbox, +} from "./activitypub.js"; +import Event from "./models/Event.js"; +import EventGroup from "./models/EventGroup.js"; +import path from "path"; + +const config = getConfig(); +const domain = config.general.domain; +const contactEmail = config.general.email; +const siteName = config.general.site_name; +const mailService = config.general.mail_service; +const siteLogo = config.general.email_logo_url; +const isFederated = config.general.is_federated || true; +const showKofi = config.general.show_kofi; // This alphabet (used to generate all event, group, etc. IDs) is missing '-' // because ActivityPub doesn't like it in IDs -const { customAlphabet } = require("nanoid"); const nanoid = customAlphabet( "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_", 21 ); -const randomstring = require("randomstring"); - -const { body, validationResult } = require("express-validator"); - const router = express.Router(); -const Event = mongoose.model("Event"); -const EventGroup = mongoose.model("EventGroup"); -const addToLog = require("./helpers.js").addToLog; - -var moment = require("moment-timezone"); - -const marked = require("marked"); - -const generateRSAKeypair = require("generate-rsa-keypair"); -const crypto = require("crypto"); -const request = require("request"); -const niceware = require("niceware"); - -const domain = require("./config/domain.js").domain; -const contactEmail = require("./config/domain.js").email; -const mailService = require("./config/domain.js").mailService; -const siteName = require("./config/domain.js").sitename; -const siteLogo = require("./config/domain.js").logo_url; -let isFederated = require("./config/domain.js").isFederated; -let showKofi = require("./config/domain.js").showKofi; -// if the federation config isn't set, things are federated by default -if (isFederated === undefined) { - isFederated = true; -} -const ap = require("./activitypub.js"); - // Extra marked renderer (used to render plaintext event description for page metadata) // Adapted from https://dustinpfister.github.io/2017/11/19/nodejs-marked/ // ? to ? helper @@ -85,31 +92,23 @@ function render_plain() { return render; } -const ical = require("ical"); -const { exportIcal } = require("./helpers.js"); - -const sgMail = require("@sendgrid/mail"); -const nodemailer = require("nodemailer"); - -const apiCredentials = require("./config/api.js"); - let sendEmails = false; let nodemailerTransporter; -if (mailService) { - switch (mailService) { +if (config.general.mail_service) { + switch (config.general.mail_service) { case "sendgrid": - sgMail.setApiKey(apiCredentials.sendgrid); + sgMail.setApiKey(config.sendgrid?.api_key); console.log("Sendgrid is ready to send emails."); sendEmails = true; break; case "nodemailer": nodemailerTransporter = nodemailer.createTransport({ - host: apiCredentials.smtpServer, - port: apiCredentials.smtpPort, + host: config.nodemailer?.smtp_server, + port: config.nodemailer?.smtp_port, secure: false, // true for 465, false for other ports auth: { - user: apiCredentials.smtpUsername, // generated ethereal user - pass: apiCredentials.smtpPassword, // generated ethereal password + user: config.nodemailer?.smtp_username, + pass: config.nodemailer?.smtp_password, }, }); nodemailerTransporter.verify((error, success) => { @@ -128,12 +127,9 @@ if (mailService) { } } -const fileUpload = require("express-fileupload"); -var Jimp = require("jimp"); router.use(fileUpload()); // SCHEDULED DELETION -const schedule = require("node-schedule"); schedule.scheduleJob("59 23 * * *", function (fireDate) { const too_old = moment.tz("Etc/UTC").subtract(7, "days").toDate(); console.log( @@ -166,24 +162,27 @@ schedule.scheduleJob("59 23 * * *", function (fireDate) { }; if (event.image) { - fs.unlink(global.appRoot + "/public/events/" + event.image, (err) => { - if (err) { + fs.unlink( + path.join(process.cwd(), "/public/events/" + event.image), + (err) => { + if (err) { + addToLog( + "deleteOldEvents", + "error", + "Attempt to delete event image for old event " + + event.id + + " failed with error: " + + err + ); + } + // Image removed addToLog( "deleteOldEvents", "error", - "Attempt to delete event image for old event " + - event.id + - " failed with error: " + - err + "Image deleted for old event " + event.id ); } - // Image removed - addToLog( - "deleteOldEvents", - "error", - "Image deleted for old event " + event.id - ); - }); + ); } // Check if event has ActivityPub fields if (event.activityPubActor && event.activityPubEvent) { @@ -192,12 +191,12 @@ schedule.scheduleJob("59 23 * * *", function (fireDate) { const jsonUpdateObject = JSON.parse(event.activityPubActor); const jsonEventObject = JSON.parse(event.activityPubEvent); // first broadcast AP messages, THEN delete from DB - ap.broadcastDeleteMessage( + broadcastDeleteMessage( jsonUpdateObject, event.followers, event.id, function (statuses) { - ap.broadcastDeleteMessage( + broadcastDeleteMessage( jsonEventObject, event.followers, event.id, @@ -274,7 +273,7 @@ router.get("/:eventID/featured", (req, res) => { "@context": "https://www.w3.org/ns/activitystreams", id: `https://${domain}/${eventID}/featured`, type: "OrderedCollection", - orderedItems: [ap.createFeaturedPost(eventID)], + orderedItems: [createFeaturedPost(eventID)], }; if ( req.headers.accept && @@ -367,11 +366,11 @@ router.get("/.well-known/webfinger", (req, res) => { ) { res .header("Content-Type", "application/activity+json") - .send(ap.createWebfinger(eventID, domain)); + .send(createWebfinger(eventID, domain)); } else { res .header("Content-Type", "application/json") - .send(ap.createWebfinger(eventID, domain)); + .send(createWebfinger(eventID, domain)); } } }) @@ -938,7 +937,7 @@ router.post("/newevent", async (req, res) => { usersCanComment: req.body.interactionCheckbox ? true : false, maxAttendees: req.body.maxAttendees, firstLoad: true, - activityPubActor: ap.createActivityPubActor( + activityPubActor: createActivityPubActor( eventID, domain, pair.public, @@ -950,7 +949,7 @@ router.post("/newevent", async (req, res) => { endUTC, req.body.timezone ), - activityPubEvent: ap.createActivityPubEvent( + activityPubEvent: createActivityPubEvent( req.body.eventName, startUTC, endUTC, @@ -962,7 +961,7 @@ router.post("/newevent", async (req, res) => { { id: `https://${domain}/${eventID}/m/featuredPost`, content: JSON.stringify( - ap.createFeaturedPost( + createFeaturedPost( eventID, req.body.eventName, startUTC, @@ -1376,7 +1375,7 @@ router.post("/editevent/:eventID/:editToken", (req, res) => { : null, eventGroup: isPartOfEventGroup ? eventGroup._id : null, activityPubActor: event.activityPubActor - ? ap.updateActivityPubActor( + ? updateActivityPubActor( JSON.parse(event.activityPubActor), req.body.eventDescription, req.body.eventName, @@ -1388,7 +1387,7 @@ router.post("/editevent/:eventID/:editToken", (req, res) => { ) : null, activityPubEvent: event.activityPubEvent - ? ap.updateActivityPubEvent( + ? updateActivityPubEvent( JSON.parse(event.activityPubEvent), req.body.eventName, req.body.startUTC, @@ -1463,17 +1462,17 @@ router.post("/editevent/:eventID/:editToken", (req, res) => { cc: "https://www.w3.org/ns/activitystreams#Public", content: `${diffText} See here: https://${domain}/${req.params.eventID}`, }; - ap.broadcastCreateMessage(jsonObject, event.followers, eventID); + broadcastCreateMessage(jsonObject, event.followers, eventID); // also broadcast an Update profile message to all followers so that at least Mastodon servers will update the local profile information const jsonUpdateObject = JSON.parse(event.activityPubActor); - ap.broadcastUpdateMessage( + broadcastUpdateMessage( jsonUpdateObject, event.followers, eventID ); // also broadcast an Update/Event for any calendar apps that are consuming our Events const jsonEventObject = JSON.parse(event.activityPubEvent); - ap.broadcastUpdateMessage( + broadcastUpdateMessage( jsonEventObject, event.followers, eventID @@ -1495,7 +1494,7 @@ router.post("/editevent/:eventID/:editToken", (req, res) => { ], }; // send direct message to user - ap.sendDirectMessage(jsonObject, attendee.id, eventID); + sendDirectMessage(jsonObject, attendee.id, eventID); } } }); @@ -1709,31 +1708,10 @@ router.post("/deleteimage/:eventID/:editToken", (req, res) => { "This event doesn't have a linked image. What are you even doing" ); } - fs.unlink(global.appRoot + "/public/events/" + eventImage, (err) => { - if (err) { - res.status(500).send(err); - addToLog( - "deleteEventImage", - "error", - "Attempt to delete event image for event " + - req.params.eventID + - " failed with error: " + - err - ); - } - // Image removed - addToLog( - "deleteEventImage", - "success", - "Image for event " + req.params.eventID + " deleted" - ); - event.image = ""; - event - .save() - .then((response) => { - res.status(200).send("Success"); - }) - .catch((err) => { + fs.unlink( + path.join(process.cwd(), "/public/events/" + eventImage), + (err) => { + if (err) { res.status(500).send(err); addToLog( "deleteEventImage", @@ -1743,8 +1721,32 @@ router.post("/deleteimage/:eventID/:editToken", (req, res) => { " failed with error: " + err ); - }); - }); + } + // Image removed + addToLog( + "deleteEventImage", + "success", + "Image for event " + req.params.eventID + " deleted" + ); + event.image = ""; + event + .save() + .then((response) => { + res.status(200).send("Success"); + }) + .catch((err) => { + res.status(500).send(err); + addToLog( + "deleteEventImage", + "error", + "Attempt to delete event image for event " + + req.params.eventID + + " failed with error: " + + err + ); + }); + } + ); } }); }); @@ -1768,7 +1770,7 @@ router.post("/deleteevent/:eventID/:editToken", (req, res) => { const guidUpdateObject = crypto.randomBytes(16).toString("hex"); const jsonUpdateObject = JSON.parse(event.activityPubActor); // first broadcast AP messages, THEN delete from DB - ap.broadcastDeleteMessage( + broadcastDeleteMessage( jsonUpdateObject, event.followers, req.params.eventID, @@ -1790,7 +1792,7 @@ router.post("/deleteevent/:eventID/:editToken", (req, res) => { // Delete image if (eventImage) { fs.unlink( - global.appRoot + "/public/events/" + eventImage, + path.join(process.cwd(), "/public/events/" + eventImage), (err) => { if (err) { res.send(err); @@ -1943,7 +1945,7 @@ router.post("/deleteeventgroup/:eventGroupID/:editToken", (req, res) => { // Delete image if (eventGroupImage) { fs.unlink( - global.appRoot + "/public/events/" + eventGroupImage, + path.join(process.cwd(), "/public/events/" + eventGroupImage), (err) => { if (err) { res.send(err); @@ -2569,7 +2571,7 @@ router.post("/post/comment/:eventID", (req, res) => { cc: "https://www.w3.org/ns/activitystreams#Public", content: `

${req.body.commentAuthor} commented: ${req.body.commentContent}.

See the full conversation here.

`, }; - ap.broadcastCreateMessage( + broadcastCreateMessage( jsonObject, event.followers, req.params.eventID @@ -2681,7 +2683,7 @@ router.post("/post/reply/:eventID/:commentID", (req, res) => { cc: "https://www.w3.org/ns/activitystreams#Public", content: `

${req.body.replyAuthor} commented: ${req.body.replyContent}

See the full conversation here.

`, }; - ap.broadcastCreateMessage( + broadcastCreateMessage( jsonObject, event.followers, req.params.eventID @@ -2877,7 +2879,7 @@ router.post("/activitypub/inbox", (req, res) => { const result = verifier.verify(publicKeyBuf, signatureBuf); if (result) { // actually process the ActivityPub message now that it's been verified - ap.processInbox(req, res); + processInbox(req, res); } else { return res.status(401).send("Signature could not be verified."); } @@ -2896,4 +2898,4 @@ router.use(function (req, res, next) { addToLog("startup", "success", "Started up successfully"); -module.exports = router; +export default router; diff --git a/src/start.js b/src/start.js index a6ecfbf..ca17862 100755 --- a/src/start.js +++ b/src/start.js @@ -1,13 +1,10 @@ -require("dotenv").config(); +import mongoose from "mongoose"; +import { getConfig } from "./lib/config.js"; +import app from "./app.js"; -const path = require("path"); +const config = getConfig(); -const mongoose = require("mongoose"); - -const databaseCredentials = require("./config/database.js"); -const port = require("./config/domain.js").port; - -mongoose.connect(databaseCredentials.url, { +mongoose.connect(config.database.mongodb_url, { useNewUrlParser: true, useUnifiedTopology: true, }); @@ -21,15 +18,7 @@ mongoose.connection console.log("Connection error: ${err.message}"); }); -require("./models/Event"); -require("./models/Log"); -require("./models/EventGroup"); - -const app = require("./app.js"); - -global.appRoot = path.resolve(__dirname); - -const server = app.listen(port, () => { +const server = app.listen(config.general.port, () => { console.log( `Welcome to gathio! The app is now running on http://localhost:${ server.address().port