summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorRaphael <mail@raphaelkabo.com>2025-05-28 18:58:46 +0100
committerGitHub <noreply@github.com>2025-05-28 18:58:46 +0100
commit3d84891118f8a81af3ddb978af9b3f8b02fd5d65 (patch)
tree0a8d344e331a0551b73435bbbb3919107737f69f /src/lib
parent6f0b7a44b995b6b66baf42a9369182fc05a90b34 (diff)
parent4664b6968fdcaca54268d60f400da02364213f05 (diff)
Merge branch 'main' into main
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/config.ts34
-rw-r--r--src/lib/email.ts71
-rw-r--r--src/lib/event.ts4
-rw-r--r--src/lib/middleware.ts2
4 files changed, 85 insertions, 26 deletions
diff --git a/src/lib/config.ts b/src/lib/config.ts
index 6642eef..35fc42c 100644
--- a/src/lib/config.ts
+++ b/src/lib/config.ts
@@ -3,6 +3,7 @@ import toml from "toml";
import { exitWithError } from "./process.js";
import { Response } from "express";
import { markdownToSanitizedHTML } from "../util/markdown.js";
+import i18next from "i18next";
interface StaticPage {
title: string;
@@ -21,7 +22,7 @@ export interface GathioConfig {
email_logo_url: string;
show_kofi: boolean;
show_public_event_list: boolean;
- mail_service: "nodemailer" | "sendgrid" | "none";
+ mail_service: "nodemailer" | "sendgrid" | "mailgun" | "none";
creator_email_addresses: string[];
};
database: {
@@ -37,6 +38,11 @@ export interface GathioConfig {
sendgrid?: {
api_key: string;
};
+ mailgun?: {
+ api_key: string;
+ api_url: string;
+ domain: string;
+ };
static_pages?: StaticPage[];
}
@@ -110,44 +116,44 @@ export const instanceRules = (): InstanceRule[] => {
rules.push(
config.general.show_public_event_list
? {
- text: "Public events and groups are displayed on the homepage",
+ text: i18next.t("config.instancerule.showpubliceventlist-true"),
icon: "fas fa-eye",
}
: {
- text: "Events and groups can only be accessed by direct link",
+ text: i18next.t("config.instancerule.showpubliceventlist-false"),
icon: "fas fa-eye-slash",
},
);
rules.push(
config.general.creator_email_addresses?.length
? {
- text: "Only specific people can create events and groups",
+ text: i18next.t("config.instancerule.creatoremail-true"),
icon: "fas fa-user-check",
}
: {
- text: "Anyone can create events and groups",
+ text: i18next.t("config.instancerule.creatoremail-false"),
icon: "fas fa-users",
},
);
rules.push(
config.general.delete_after_days > 0
? {
- text: `Events are automatically deleted ${config.general.delete_after_days} days after they end`,
+ text: i18next.t("config.instancerule.deleteafterdays-true", { days: config.general.delete_after_days } ),
icon: "far fa-calendar-times",
}
: {
- text: "Events are permanent, and are never automatically deleted",
+ text: i18next.t("config.instancerule.deleteafterdays-false"),
icon: "far fa-calendar-check",
},
);
rules.push(
config.general.is_federated
? {
- text: "This instance federates with other instances using ActivityPub",
+ text: i18next.t("config.instancerule.isfederated-true"),
icon: "fas fa-globe",
}
: {
- text: "This instance does not federate with other instances",
+ text: i18next.t("config.instancerule.isfederated-false"),
icon: "fas fa-globe",
},
);
@@ -156,13 +162,15 @@ export const instanceRules = (): InstanceRule[] => {
export const instanceDescription = (): string => {
const config = getConfig();
- const defaultInstanceDescription =
- "**{{ siteName }}** is running on Gathio — a simple, federated, privacy-first event hosting platform.";
+ const defaultInstanceDescription = markdownToSanitizedHTML(
+ i18next.t("config.defaultinstancedesc", "Welcome to this Gathio instance!")
+ );
let instanceDescription = defaultInstanceDescription;
+ let instancedescfile = "./static/instance-description-" + i18next.language + ".md";
try {
- if (fs.existsSync("./static/instance-description.md")) {
+ if (fs.existsSync(instancedescfile)) {
const fileBody = fs.readFileSync(
- "./static/instance-description.md",
+ instancedescfile,
"utf-8",
);
instanceDescription = markdownToSanitizedHTML(fileBody);
diff --git a/src/lib/email.ts b/src/lib/email.ts
index 7c7c2dd..fc585a1 100644
--- a/src/lib/email.ts
+++ b/src/lib/email.ts
@@ -5,6 +5,8 @@ import nodemailer, { Transporter } from "nodemailer";
import { GathioConfig, getConfig } from "./config.js";
import SMTPTransport from "nodemailer/lib/smtp-transport/index.js";
import { exitWithError } from "./process.js";
+import Mailgun from "mailgun.js";
+import { IMailgunClient } from "node_modules/mailgun.js/Types/Interfaces/index.js";
const config = getConfig();
@@ -24,7 +26,8 @@ type EmailTemplateName =
export class EmailService {
nodemailerTransporter: Transporter | undefined = undefined;
sgMail: typeof sgMail | undefined = undefined;
- hbs: ExpressHandlebars
+ mailgunClient: IMailgunClient | undefined = undefined;
+ hbs: ExpressHandlebars;
public constructor(config: GathioConfig, hbs: ExpressHandlebars) {
this.hbs = hbs;
@@ -40,6 +43,26 @@ export class EmailService {
console.log("Sendgrid is ready to send emails.");
break;
}
+ case "mailgun": {
+ if (
+ !config.mailgun?.api_key ||
+ !config.mailgun?.api_url ||
+ !config.mailgun?.domain
+ ) {
+ return exitWithError(
+ "Mailgun is configured as the email service, but not all required fields are provided. Please provide all required fields in the config file.",
+ );
+ }
+ const mailgun = new Mailgun(FormData);
+ this.mailgunClient = mailgun.client({
+ username: "api",
+ key: config.mailgun.api_key,
+ url: config.mailgun.api_url,
+ });
+ // TODO: Can we verify the Mailgun connection?
+ console.log("Mailgun is ready to send emails.");
+ break;
+ }
case "nodemailer": {
if (config.nodemailer?.smtp_url) {
this.nodemailerTransporter = nodemailer.createTransport(
@@ -73,13 +96,13 @@ export class EmailService {
nodemailer.createTransport(nodemailerConfig);
}
}
-
}
}
public async verify(): Promise<boolean> {
if (this.nodemailerTransporter) {
- const nodemailerVerified = await this.nodemailerTransporter.verify();
+ const nodemailerVerified =
+ await this.nodemailerTransporter.verify();
if (nodemailerVerified) {
console.log("Nodemailer is ready to send emails.");
return true;
@@ -118,9 +141,36 @@ export class EmailService {
return true;
} catch (e: unknown | sgHelpers.classes.ResponseError) {
if (e instanceof sgHelpers.classes.ResponseError) {
- console.error('sendgrid error', e.response.body);
+ console.error("sendgrid error", e.response.body);
+ } else {
+ console.error("sendgrid error", e);
+ }
+ return false;
+ }
+ } else if (this.mailgunClient) {
+ try {
+ if (!config.mailgun?.domain) {
+ return exitWithError(
+ "Mailgun is configured as the email service, but no domain is provided. Please provide a domain in the config file.",
+ );
+ }
+ await this.mailgunClient.messages.create(
+ config.mailgun.domain,
+ {
+ from: config.general.email,
+ to,
+ bcc,
+ subject: `${config.general.site_name}: ${subject}`,
+ text,
+ html,
+ },
+ );
+ return true;
+ } catch (e: any) {
+ if (e.response) {
+ console.error(e.response.body);
} else {
- console.error('sendgrid error', e);
+ console.error(e);
}
return false;
}
@@ -150,15 +200,14 @@ export class EmailService {
bcc = "",
subject,
templateName,
- templateData = {}
+ templateData = {},
}: {
to: string | string[];
bcc?: string | string[] | undefined;
subject: string;
templateName: EmailTemplateName;
templateData?: object;
- },
- ): Promise<boolean> {
+ }): Promise<boolean> {
const [html, text] = await Promise.all([
this.hbs.renderView(
`./views/emails/${templateName}/${templateName}Html.handlebars`,
@@ -172,7 +221,7 @@ export class EmailService {
cache: true,
layout: "email.handlebars",
...templateData,
- }
+ },
),
this.hbs.renderView(
`./views/emails/${templateName}/${templateName}Text.handlebars`,
@@ -186,7 +235,7 @@ export class EmailService {
cache: true,
layout: "email.handlebars",
...templateData,
- }
+ },
),
]);
@@ -195,7 +244,7 @@ export class EmailService {
bcc,
subject: `${config.general.site_name}: ${subject}`,
text,
- html
+ html,
});
}
}
diff --git a/src/lib/event.ts b/src/lib/event.ts
index 334ddf6..bcb7cd9 100644
--- a/src/lib/event.ts
+++ b/src/lib/event.ts
@@ -1,3 +1,4 @@
+import i18next from "i18next";
import { IEventGroup } from "../models/EventGroup.js";
export interface EventListEvent {
@@ -15,7 +16,8 @@ export const bucketEventsByMonth = (
acc: Record<string, any>[],
event: EventListEvent,
) => {
- const month = event.startMoment.format("MMMM YYYY");
+ event.startMoment.locale(i18next.language);
+ const month = event.startMoment.format(i18next.t("common.year-month-format" ));
const matchingBucket = acc.find((bucket) => bucket.title === month);
if (!matchingBucket) {
acc.push({
diff --git a/src/lib/middleware.ts b/src/lib/middleware.ts
index 69fbe4e..be05c3f 100644
--- a/src/lib/middleware.ts
+++ b/src/lib/middleware.ts
@@ -62,7 +62,7 @@ export const getConfigMiddleware = (
if (process.env.CYPRESS === "true" && req.cookies?.cypressConfigOverride) {
console.log("Overriding config with Cypress config");
const override = JSON.parse(req.cookies.cypressConfigOverride);
- res.locals.config = deepMerge<GathioConfig>(config, override);
+ res.locals.config = deepMerge(config, override);
return next();
}
res.locals.config = config;