2024-02-01 16:14:59 +00:00
|
|
|
import { log, Mutex, redis, TTLCache } from "../deps.ts";
|
2022-09-06 19:21:40 +00:00
|
|
|
import config from "./config.ts";
|
|
|
|
|
2022-09-07 13:28:25 +00:00
|
|
|
const KEY_EXPIRY = 3; // seconds
|
|
|
|
|
|
|
|
interface CommentManager {
|
|
|
|
getAndIncrement(key: string): Promise<number>;
|
|
|
|
}
|
|
|
|
|
|
|
|
class LocalCommentManager implements CommentManager {
|
|
|
|
private cache: TTLCache<number>;
|
2022-09-06 19:21:40 +00:00
|
|
|
constructor() {
|
2022-09-07 13:28:25 +00:00
|
|
|
this.cache = new TTLCache(KEY_EXPIRY * 1000);
|
|
|
|
}
|
|
|
|
|
|
|
|
getAndIncrement(key: string): Promise<number> {
|
|
|
|
const value = this.cache.get(key) ?? 0;
|
|
|
|
this.cache.set(key, value + 1);
|
|
|
|
return Promise.resolve(value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class RedisCommentManager implements CommentManager {
|
2022-09-07 19:44:22 +00:00
|
|
|
private connectOptions: redis.RedisConnectOptions;
|
2024-02-01 16:14:59 +00:00
|
|
|
private lock: Mutex;
|
2022-09-07 19:44:22 +00:00
|
|
|
private _redis: Promise<redis.Redis> | undefined;
|
|
|
|
|
|
|
|
constructor(options: redis.RedisConnectOptions) {
|
|
|
|
this.connectOptions = options;
|
2024-02-01 16:14:59 +00:00
|
|
|
this.lock = new Mutex();
|
2022-09-07 19:44:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// manual lazy init, redis.createLazyClient currently doesn't support pipelines :/
|
|
|
|
private redis(): Promise<redis.Redis> {
|
|
|
|
if (!this._redis) {
|
|
|
|
this._redis = redis.connect({
|
|
|
|
...this.connectOptions,
|
|
|
|
maxRetryCount: 3,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return this._redis;
|
2022-09-06 19:21:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async getAndIncrement(key: string): Promise<number> {
|
2022-09-07 22:04:25 +00:00
|
|
|
await this.lock.acquire();
|
|
|
|
try {
|
|
|
|
const redis = await this.redis();
|
|
|
|
key = `reviewcomment:${key}`;
|
2022-09-07 13:28:25 +00:00
|
|
|
|
2022-09-07 22:04:25 +00:00
|
|
|
const pl = redis.pipeline();
|
|
|
|
pl.incr(key);
|
|
|
|
pl.expire(key, KEY_EXPIRY);
|
|
|
|
const results = await pl.flush();
|
2022-09-06 19:21:40 +00:00
|
|
|
|
2022-09-07 22:04:25 +00:00
|
|
|
const newValue = results[0] as number;
|
|
|
|
return newValue - 1; // return old value
|
|
|
|
} finally {
|
|
|
|
this.lock.release();
|
|
|
|
}
|
2022-09-06 19:21:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-07 13:42:22 +00:00
|
|
|
let commentManager: CommentManager;
|
|
|
|
if (config.redisUrl) {
|
|
|
|
const opts = redis.parseURL(config.redisUrl);
|
|
|
|
|
|
|
|
// validate perms now to avoid unnecessarily retrying later on
|
|
|
|
const host = opts.hostname + (opts.port ? `:${opts.port}` : "");
|
|
|
|
const permStatus = await Deno.permissions.request({ name: "net", host: host });
|
|
|
|
if (permStatus.state !== "granted") {
|
|
|
|
throw new Error("Network access to redis url is required");
|
|
|
|
}
|
|
|
|
|
|
|
|
log.debug("using RedisCommentManager");
|
|
|
|
commentManager = new RedisCommentManager(opts);
|
|
|
|
} else {
|
|
|
|
log.debug("using LocalCommentManager, no redis url configured");
|
|
|
|
commentManager = new LocalCommentManager();
|
|
|
|
}
|
|
|
|
|
|
|
|
export { commentManager };
|