feat(subscriptions): added polar as payment provider for subscriptions
* feature(dashboard): add polar / subscription * wip(payments): manage subscription * wip(payments): add free product, faq and some other improvements * fix(root): change node to bundler in tsconfig * wip(payments): display current subscription * feat(dashboard): schedule project for deletion * wip(payments): support custom products/subscriptions * wip(payments): fix polar scripts * wip(payments): add json package to dockerfiles
This commit is contained in:
committed by
GitHub
parent
86bf9dd064
commit
168ebc3430
@@ -1,5 +1,28 @@
|
||||
import { getRedisCache } from './redis';
|
||||
|
||||
export async function getCache<T>(
|
||||
key: string,
|
||||
expireInSec: number,
|
||||
fn: () => Promise<T>,
|
||||
): Promise<T> {
|
||||
const hit = await getRedisCache().get(key);
|
||||
if (hit) {
|
||||
return JSON.parse(hit, (_, value) => {
|
||||
if (
|
||||
typeof value === 'string' &&
|
||||
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*Z$/.test(value)
|
||||
) {
|
||||
return new Date(value);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
const data = await fn();
|
||||
await getRedisCache().setex(key, expireInSec, JSON.stringify(data));
|
||||
return data;
|
||||
}
|
||||
|
||||
export function cacheable<T extends (...args: any) => any>(
|
||||
fn: T,
|
||||
expireInSec: number,
|
||||
@@ -37,7 +60,15 @@ export function cacheable<T extends (...args: any) => any>(
|
||||
const cached = await getRedisCache().get(key);
|
||||
if (cached) {
|
||||
try {
|
||||
return JSON.parse(cached);
|
||||
return JSON.parse(cached, (_, value) => {
|
||||
if (
|
||||
typeof value === 'string' &&
|
||||
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*Z$/.test(value)
|
||||
) {
|
||||
return new Date(value);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Failed to parse cache', e);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './redis';
|
||||
export * from './cachable';
|
||||
export * from './run-every';
|
||||
export * from './publisher';
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@openpanel/json": "workspace:*",
|
||||
"ioredis": "^5.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@openpanel/db": "workspace:*",
|
||||
"@openpanel/tsconfig": "workspace:*",
|
||||
"@types/node": "20.14.8",
|
||||
"prisma": "^5.1.1",
|
||||
|
||||
86
packages/redis/publisher.ts
Normal file
86
packages/redis/publisher.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { type Redis, getRedisPub, getRedisSub } from './redis';
|
||||
|
||||
import type { IServiceEvent, Notification } from '@openpanel/db';
|
||||
import { getSuperJson, setSuperJson } from '@openpanel/json';
|
||||
|
||||
export type IPublishChannels = {
|
||||
organization: {
|
||||
subscription_updated: {
|
||||
organizationId: string;
|
||||
};
|
||||
};
|
||||
events: {
|
||||
received: IServiceEvent;
|
||||
saved: IServiceEvent;
|
||||
};
|
||||
notification: {
|
||||
created: Notification;
|
||||
};
|
||||
};
|
||||
|
||||
export function getSubscribeChannel<Channel extends keyof IPublishChannels>(
|
||||
channel: Channel,
|
||||
type: keyof IPublishChannels[Channel],
|
||||
) {
|
||||
return `${channel}:${String(type)}`;
|
||||
}
|
||||
|
||||
export function publishEvent<Channel extends keyof IPublishChannels>(
|
||||
channel: Channel,
|
||||
type: keyof IPublishChannels[Channel],
|
||||
event: IPublishChannels[Channel][typeof type],
|
||||
multi?: ReturnType<Redis['multi']>,
|
||||
) {
|
||||
const redis = multi ?? getRedisPub();
|
||||
return redis.publish(getSubscribeChannel(channel, type), setSuperJson(event));
|
||||
}
|
||||
|
||||
export function parsePublishedEvent<Channel extends keyof IPublishChannels>(
|
||||
_channel: Channel,
|
||||
_type: keyof IPublishChannels[Channel],
|
||||
message: string,
|
||||
): IPublishChannels[Channel][typeof _type] {
|
||||
return getSuperJson<IPublishChannels[Channel][typeof _type]>(message)!;
|
||||
}
|
||||
|
||||
export function subscribeToPublishedEvent<
|
||||
Channel extends keyof IPublishChannels,
|
||||
>(
|
||||
channel: Channel,
|
||||
type: keyof IPublishChannels[Channel],
|
||||
callback: (event: IPublishChannels[Channel][typeof type]) => void,
|
||||
) {
|
||||
const subscribeChannel = getSubscribeChannel(channel, type);
|
||||
getRedisSub().subscribe(subscribeChannel);
|
||||
|
||||
const message = (messageChannel: string, message: string) => {
|
||||
if (subscribeChannel === messageChannel) {
|
||||
const event = parsePublishedEvent(channel, type, message);
|
||||
if (event) {
|
||||
callback(event);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
getRedisSub().on('message', message);
|
||||
|
||||
return () => {
|
||||
getRedisSub().unsubscribe(subscribeChannel);
|
||||
getRedisSub().off('message', message);
|
||||
};
|
||||
}
|
||||
|
||||
export function psubscribeToPublishedEvent(
|
||||
pattern: string,
|
||||
callback: (key: string) => void,
|
||||
) {
|
||||
getRedisSub().psubscribe(pattern);
|
||||
const pmessage = (_: unknown, pattern: string, key: string) => callback(key);
|
||||
|
||||
getRedisSub().on('pmessage', pmessage);
|
||||
|
||||
return () => {
|
||||
getRedisSub().punsubscribe(pattern);
|
||||
getRedisSub().off('pmessage', pmessage);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user