From 2449234e28aab435ffe28d567ece8c651d45d2b3 Mon Sep 17 00:00:00 2001 From: INOUE Daisuke Date: Sat, 22 Mar 2025 21:03:25 +0900 Subject: DateTime format, and some translation fix --- src/lib/event.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/lib') diff --git a/src/lib/event.ts b/src/lib/event.ts index 334ddf6..09631b9 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[], event: EventListEvent, ) => { - const month = event.startMoment.format("MMMM YYYY"); + event.startMoment.locale(i18next.language); + const month = event.startMoment.format(i18next.t("year-month-format" )); const matchingBucket = acc.find((bucket) => bucket.title === month); if (!matchingBucket) { acc.push({ -- cgit v1.2.3 From 90357f6a7729e82d5498835d92bf86e8e07d3478 Mon Sep 17 00:00:00 2001 From: INOUE Daisuke Date: Sun, 23 Mar 2025 16:22:10 +0900 Subject: instance description etc. fix translation --- src/lib/config.ts | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) (limited to 'src/lib') diff --git a/src/lib/config.ts b/src/lib/config.ts index e8b774a..406775d 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; @@ -109,44 +110,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,12 +157,13 @@ 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."; + i18next.t("config.defaultinstancedesc"); 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); -- cgit v1.2.3 From 1fd6a7d2249fdf6ba5ca9ced6ea43348ebe0941d Mon Sep 17 00:00:00 2001 From: INOUE Daisuke Date: Wed, 2 Apr 2025 01:49:11 +0900 Subject: rename translation keys as filestructure --- src/lib/event.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/lib') diff --git a/src/lib/event.ts b/src/lib/event.ts index 09631b9..bcb7cd9 100644 --- a/src/lib/event.ts +++ b/src/lib/event.ts @@ -17,7 +17,7 @@ export const bucketEventsByMonth = ( event: EventListEvent, ) => { event.startMoment.locale(i18next.language); - const month = event.startMoment.format(i18next.t("year-month-format" )); + const month = event.startMoment.format(i18next.t("common.year-month-format" )); const matchingBucket = acc.find((bucket) => bucket.title === month); if (!matchingBucket) { acc.push({ -- cgit v1.2.3 From d2c2147ac6a02d647217a303e5810125e0d112c0 Mon Sep 17 00:00:00 2001 From: INOUE Daisuke Date: Sun, 11 May 2025 18:26:06 +0900 Subject: Avoid "Type 'GathioConfig' is missing the following properties from type 'IObject[]':..." --- src/lib/middleware.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/lib') 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(config, override); + res.locals.config = deepMerge(config, override); return next(); } res.locals.config = config; -- cgit v1.2.3 From b2e8547dc9c91bf48bd5743e1bda2c9e507da908 Mon Sep 17 00:00:00 2001 From: INOUE Daisuke Date: Mon, 19 May 2025 00:16:39 +0900 Subject: Fix to parse markdown, default instance description --- src/lib/config.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/lib') diff --git a/src/lib/config.ts b/src/lib/config.ts index 5b74e4a..b08fa31 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -157,8 +157,9 @@ export const instanceRules = (): InstanceRule[] => { export const instanceDescription = (): string => { const config = getConfig(); - const defaultInstanceDescription = - i18next.t("config.defaultinstancedesc"); + const defaultInstanceDescription = markdownToSanitizedHTML( + i18next.t("config.defaultinstancedesc", "Welcome to this Gathio instance!") + ); let instanceDescription = defaultInstanceDescription; let instancedescfile = "./static/instance-description-" + i18next.language + ".md"; try { -- cgit v1.2.3 From bc9e983b16d9ac2d27a4458c0a87f9d11aa80c0e Mon Sep 17 00:00:00 2001 From: Raphael Kabo Date: Tue, 27 May 2025 19:07:27 +0100 Subject: Add Mailgun email service --- src/lib/config.ts | 7 +++++- src/lib/email.ts | 71 ++++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 66 insertions(+), 12 deletions(-) (limited to 'src/lib') diff --git a/src/lib/config.ts b/src/lib/config.ts index 6642eef..3fd6eb7 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -21,7 +21,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 +37,11 @@ export interface GathioConfig { sendgrid?: { api_key: string; }; + mailgun?: { + api_key: string; + api_url: string; + domain: string; + }; static_pages?: StaticPage[]; } 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 { 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 { + }): Promise { 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, }); } } -- cgit v1.2.3