mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c3bcc75e88 | |||
| 92383486dc | |||
| 37a58ead2e |
@@ -208,6 +208,7 @@
|
||||
"react-waypoint": "^10.3.0",
|
||||
"react-window": "^1.8.10",
|
||||
"reakit": "^1.3.11",
|
||||
"redlock": "^5.0.0-beta.2",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"refractor": "^3.6.0",
|
||||
"request-filtering-agent": "^1.1.2",
|
||||
|
||||
@@ -7,6 +7,7 @@ import HTMLHelper from "@server/models/helpers/HTMLHelper";
|
||||
import NotificationSettingsHelper from "@server/models/helpers/NotificationSettingsHelper";
|
||||
import SubscriptionHelper from "@server/models/helpers/SubscriptionHelper";
|
||||
import { can } from "@server/policies";
|
||||
import { CacheHelper } from "@server/utils/CacheHelper";
|
||||
import BaseEmail, { EmailMessageCategory, EmailProps } from "./BaseEmail";
|
||||
import Body from "./components/Body";
|
||||
import Button from "./components/Button";
|
||||
@@ -68,21 +69,28 @@ export default class DocumentPublishedOrUpdatedEmail extends BaseEmail<
|
||||
|
||||
let body;
|
||||
if (revisionId && team?.getPreference(TeamPreference.PreviewsInEmails)) {
|
||||
// generate the diff html for the email
|
||||
const revision = await Revision.findByPk(revisionId);
|
||||
body = await CacheHelper.getDataOrSet<string>(
|
||||
`diff:${revisionId}`,
|
||||
async () => {
|
||||
// generate the diff html for the email
|
||||
const revision = await Revision.findByPk(revisionId);
|
||||
|
||||
if (revision) {
|
||||
const before = await revision.before();
|
||||
const content = await DocumentHelper.toEmailDiff(before, revision, {
|
||||
includeTitle: false,
|
||||
centered: false,
|
||||
signedUrls: 4 * Day.seconds,
|
||||
baseUrl: props.teamUrl,
|
||||
});
|
||||
if (revision) {
|
||||
const before = await revision.before();
|
||||
const content = await DocumentHelper.toEmailDiff(before, revision, {
|
||||
includeTitle: false,
|
||||
centered: false,
|
||||
signedUrls: 4 * Day.seconds,
|
||||
baseUrl: props.teamUrl,
|
||||
});
|
||||
|
||||
// inline all css so that it works in as many email providers as possible.
|
||||
body = content ? await HTMLHelper.inlineCSS(content) : undefined;
|
||||
}
|
||||
// inline all css so that it works in as many email providers as possible.
|
||||
return content ? await HTMLHelper.inlineCSS(content) : undefined;
|
||||
}
|
||||
return;
|
||||
},
|
||||
30
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Day } from "@shared/utils/time";
|
||||
import Logger from "@server/logging/Logger";
|
||||
import Redis from "@server/storage/redis";
|
||||
import { MutexLock } from "./MutexLock";
|
||||
|
||||
/**
|
||||
* A Helper class for server-side cache management
|
||||
@@ -9,6 +10,54 @@ export class CacheHelper {
|
||||
// Default expiry time for cache data in seconds
|
||||
private static defaultDataExpiry = Day.seconds;
|
||||
|
||||
/**
|
||||
* Given a key this method will attempt to get the data from cache store first
|
||||
* If data is not found, it will call the callback to get the data and save it in cache
|
||||
* using a distributed lock to prevent multiple writes.
|
||||
*
|
||||
* @param key Cache key
|
||||
* @param callback Callback to get the data if not found in cache
|
||||
* @param expiry Cache data expiry in seconds
|
||||
*/
|
||||
public static async getDataOrSet<T>(
|
||||
key: string,
|
||||
callback: () => Promise<T | undefined>,
|
||||
expiry?: number
|
||||
): Promise<T | undefined> {
|
||||
let cache = await this.getData<T>(key);
|
||||
|
||||
if (cache) {
|
||||
return cache;
|
||||
}
|
||||
|
||||
// Nothing in the cache, acquire a lock to prevent multiple writes
|
||||
let lock;
|
||||
const lockKey = `lock:${key}`;
|
||||
try {
|
||||
try {
|
||||
lock = await MutexLock.lock.acquire(
|
||||
[lockKey],
|
||||
MutexLock.defaultLockTimeout
|
||||
);
|
||||
} catch (err) {
|
||||
Logger.error(`Could not acquire lock for ${key}`, err);
|
||||
}
|
||||
cache = await this.getData<T>(key);
|
||||
if (cache) {
|
||||
return cache;
|
||||
}
|
||||
|
||||
// Get the data from the callback and save it in cache
|
||||
const value = await callback();
|
||||
if (value) {
|
||||
await this.setData<T>(key, value, expiry);
|
||||
}
|
||||
return value;
|
||||
} finally {
|
||||
await lock?.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a key, gets the data from cache store
|
||||
*
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import Redlock from "redlock";
|
||||
import Redis from "@server/storage/redis";
|
||||
|
||||
export class MutexLock {
|
||||
// Default expiry time for qcuiring lock in milliseconds
|
||||
public static defaultLockTimeout = 5000;
|
||||
|
||||
/**
|
||||
* Returns the redlock instance
|
||||
*/
|
||||
public static get lock(): Redlock {
|
||||
this.redlock ??= new Redlock([Redis.defaultClient], {
|
||||
retryJitter: 10,
|
||||
});
|
||||
|
||||
return this.redlock;
|
||||
}
|
||||
|
||||
private static redlock: Redlock;
|
||||
}
|
||||
@@ -4075,7 +4075,7 @@
|
||||
"@smithy/util-stream" "^3.3.1"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@smithy/types@^3.3.0", "@smithy/types@^3.7.0", "@smithy/types@^3.7.1":
|
||||
"@smithy/types@^3.7.0", "@smithy/types@^3.7.1":
|
||||
version "3.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@smithy/types/-/types-3.7.1.tgz#4af54c4e28351e9101996785a33f2fdbf93debe7"
|
||||
integrity sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==
|
||||
@@ -11906,7 +11906,7 @@ node-abort-controller@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-1.2.1.tgz#1eddb57eb8fea734198b11b28857596dc6165708"
|
||||
integrity "sha1-Ht21frj+pzQZixGyiFdZbcYWVwg= sha512-79PYeJuj6S9+yOHirR0JBLFOgjB6sQCir10uN6xRx25iD+ZD4ULqgRn3MwWBRaQGB0vEgReJzWwJo42T1R6YbQ=="
|
||||
|
||||
node-abort-controller@^3.1.1:
|
||||
node-abort-controller@^3.0.1, node-abort-controller@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548"
|
||||
integrity "sha1-qUN36WSpo3rDl22EjLXHZYM7hUg= sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ=="
|
||||
@@ -13479,6 +13479,13 @@ redis@^3.0.0:
|
||||
redis-errors "^1.2.0"
|
||||
redis-parser "^3.0.0"
|
||||
|
||||
redlock@^5.0.0-beta.2:
|
||||
version "5.0.0-beta.2"
|
||||
resolved "https://registry.yarnpkg.com/redlock/-/redlock-5.0.0-beta.2.tgz#a629c07e07d001c0fdd9f2efa614144c4416fe44"
|
||||
integrity sha512-2RDWXg5jgRptDrB1w9O/JgSZC0j7y4SlaXnor93H/UJm/QyDiFgBKNtrh0TI6oCXqYSaSoXxFh6Sd3VtYfhRXw==
|
||||
dependencies:
|
||||
node-abort-controller "^3.0.1"
|
||||
|
||||
redux@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.0.tgz#46f10d6e29b6666df758780437651eeb2b969f13"
|
||||
|
||||
Reference in New Issue
Block a user