Skip to content

Commit dedaac5

Browse files
committed
Add caching support
1 parent 2125271 commit dedaac5

File tree

16 files changed

+625
-74
lines changed

16 files changed

+625
-74
lines changed

demo/src/payload.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ export default buildConfig({
6666
access: (user: any) => {
6767
return Boolean(user);
6868
},
69+
cache: {
70+
slug: "dashboardAnalytics",
71+
},
6972
navigation: {
7073
afterNavLinks: [
7174
{

src/index.ts

Lines changed: 78 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,50 @@ const dashboardAnalytics =
2424
(incomingConfig: DashboardAnalyticsConfig) =>
2525
(config: PayloadConfig): PayloadConfig => {
2626
const { admin, collections, globals } = config;
27-
const { provider, navigation, dashboard, access } = incomingConfig;
27+
const { provider, navigation, dashboard, access, cache } = incomingConfig;
2828
const endpoints = config.endpoints ?? [];
2929
const apiProvider = getProvider(provider);
3030

31+
const cacheSlug =
32+
typeof cache === "object"
33+
? cache?.slug ?? "analyticsData"
34+
: "analyticsData";
35+
36+
const cacheCollection: CollectionConfig = {
37+
slug: cacheSlug,
38+
admin: {
39+
defaultColumns: ["id", "cacheTimestamp", "cacheKey"],
40+
},
41+
access: {
42+
read: () => true,
43+
update: () => true,
44+
create: () => true,
45+
delete: () => true,
46+
},
47+
fields: [
48+
{
49+
type: "text",
50+
name: "cacheKey",
51+
},
52+
{
53+
type: "text",
54+
name: "cacheTimestamp",
55+
},
56+
{
57+
type: "json",
58+
name: "data",
59+
},
60+
],
61+
};
62+
63+
const routeOptions = {
64+
access: access,
65+
cache: {
66+
...(typeof cache === "object" ? cache : {}),
67+
slug: cacheSlug,
68+
},
69+
};
70+
3171
const processedConfig: PayloadConfig = {
3272
...config,
3373
admin: {
@@ -71,41 +111,45 @@ const dashboardAnalytics =
71111
},
72112
endpoints: [
73113
...endpoints,
74-
getGlobalAggregate(apiProvider, access),
75-
getGlobalChart(apiProvider, access),
76-
getPageChart(apiProvider, access),
77-
getPageAggregate(apiProvider, access),
78-
getLive(apiProvider, access),
79-
getReport(apiProvider, access),
114+
getGlobalAggregate(apiProvider, routeOptions),
115+
getGlobalChart(apiProvider, routeOptions),
116+
getPageChart(apiProvider, routeOptions),
117+
getPageAggregate(apiProvider, routeOptions),
118+
getLive(apiProvider, routeOptions),
119+
getReport(apiProvider, routeOptions),
80120
],
81-
...(collections && {
82-
collections: collections.map((collection) => {
83-
const targetCollection = incomingConfig.collections?.find(
84-
(pluginCollection) => {
85-
if (pluginCollection.slug === collection.slug) return true;
86-
return false;
87-
}
88-
);
89-
90-
if (targetCollection) {
91-
const collectionConfigWithHooks: CollectionConfig = {
92-
...collection,
93-
fields: [
94-
...collection.fields,
95-
...targetCollection.widgets.map((widget, index) => {
96-
const field = PageWidgetMap[widget.type];
97-
98-
return field(widget, index, apiProvider.metricsMap);
99-
}),
100-
],
101-
};
102-
103-
return collectionConfigWithHooks;
104-
}
105121

106-
return collection;
107-
}),
108-
}),
122+
collections: [
123+
...(collections
124+
? collections.map((collection) => {
125+
const targetCollection = incomingConfig.collections?.find(
126+
(pluginCollection) => {
127+
if (pluginCollection.slug === collection.slug) return true;
128+
return false;
129+
}
130+
);
131+
132+
if (targetCollection) {
133+
const collectionConfigWithHooks: CollectionConfig = {
134+
...collection,
135+
fields: [
136+
...collection.fields,
137+
...targetCollection.widgets.map((widget, index) => {
138+
const field = PageWidgetMap[widget.type];
139+
140+
return field(widget, index, apiProvider.metricsMap);
141+
}),
142+
],
143+
};
144+
145+
return collectionConfigWithHooks;
146+
}
147+
148+
return collection;
149+
})
150+
: []),
151+
...(cache ? [cacheCollection] : []),
152+
],
109153
...(globals && {
110154
globals: globals.map((global) => {
111155
const targetGlobal = incomingConfig.globals?.find((pluginGlobal) => {

src/routes/getGlobalAggregate/handler.ts

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import type { Endpoint } from "payload/config";
22
import type { ApiProvider } from "../../providers";
3-
import type { AccessControl } from "../../types";
3+
import type { RouteOptions } from "../../types";
4+
import type { Payload } from "payload";
5+
import { dayInMinutes } from "../../utilities/timings";
6+
import { differenceInMinutes } from "date-fns";
47

5-
const handler = (provider: ApiProvider, access?: AccessControl) => {
8+
const handler = (provider: ApiProvider, options: RouteOptions) => {
69
const handler: Endpoint["handler"] = async (req, res, next) => {
7-
const { payload, user } = req;
10+
const { user } = req;
11+
const payload: Payload = req.payload;
812
const { timeframe, metrics } = req.body;
13+
const { access, cache } = options;
914

1015
if (access) {
1116
const accessControl = access(user);
@@ -26,6 +31,83 @@ const handler = (provider: ApiProvider, access?: AccessControl) => {
2631
}
2732

2833
try {
34+
if (cache) {
35+
const timeNow = new Date();
36+
const cacheKey = `globalAggregate|${metrics.join("-")}|${
37+
timeframe ?? "30d"
38+
}`;
39+
const cacheLifetime =
40+
options.cache?.routes?.pageAggregate ?? dayInMinutes;
41+
42+
const {
43+
docs: [cachedData],
44+
} = await payload.find({
45+
collection: cache.slug,
46+
where: {
47+
and: [
48+
{
49+
cacheKey: {
50+
equals: cacheKey,
51+
},
52+
},
53+
],
54+
},
55+
});
56+
57+
if (!cachedData) {
58+
const data = await provider
59+
.getGlobalAggregateData({
60+
timeframe,
61+
metrics,
62+
})
63+
.catch((error) => payload.logger.error(error));
64+
65+
await payload.create({
66+
collection: cache.slug,
67+
data: {
68+
cacheKey: cacheKey,
69+
cacheTimestamp: timeNow.toISOString(),
70+
data: data,
71+
},
72+
});
73+
74+
res.status(200).send(data);
75+
return next();
76+
}
77+
78+
if (cachedData) {
79+
if (
80+
differenceInMinutes(
81+
Date.parse(cachedData.cacheTimestamp),
82+
timeNow
83+
) > cacheLifetime
84+
) {
85+
const data = await provider
86+
.getGlobalAggregateData({
87+
timeframe,
88+
metrics,
89+
})
90+
.catch((error) => payload.logger.error(error));
91+
92+
await payload.update({
93+
id: cachedData.id,
94+
collection: cache.slug,
95+
data: {
96+
cacheKey: cacheKey,
97+
cacheTimestamp: timeNow.toISOString(),
98+
data: data,
99+
},
100+
});
101+
102+
res.status(200).send(data);
103+
return next();
104+
} else {
105+
res.status(200).send(cachedData.data);
106+
return next();
107+
}
108+
}
109+
}
110+
29111
const data = await provider
30112
.getGlobalAggregateData({
31113
timeframe,

src/routes/getGlobalAggregate/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import type { Endpoint } from "payload/config";
22
import type { ApiProvider } from "../../providers";
3-
import type { AccessControl } from "../../types";
3+
import type { RouteOptions } from "../../types";
44
import handler from "./handler";
55

66
const getGlobalAggregate = (
77
provider: ApiProvider,
8-
access?: AccessControl
8+
options: RouteOptions
99
): Endpoint => {
1010
return {
1111
path: "/analytics/globalAggregate",
1212
method: "post",
13-
handler: handler(provider, access),
13+
handler: handler(provider, options),
1414
};
1515
};
1616

src/routes/getGlobalChart/handler.ts

Lines changed: 91 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import type { Endpoint } from "payload/config";
22
import type { ApiProvider } from "../../providers";
3-
import type { AccessControl } from "../../types";
3+
import type { RouteOptions } from "../../types";
4+
import type { Payload } from "payload";
5+
import { dayInMinutes } from "../../utilities/timings";
6+
import { differenceInMinutes } from "date-fns";
47

5-
const handler = (provider: ApiProvider, access?: AccessControl) => {
8+
const handler = (provider: ApiProvider, options: RouteOptions) => {
69
const handler: Endpoint["handler"] = async (req, res, next) => {
7-
const { payload, user } = req;
10+
const { user } = req;
11+
const payload: Payload = req.payload;
812
const { timeframe, metrics } = req.body;
13+
const { access, cache } = options;
914

1015
if (access) {
1116
const accessControl = access(user);
@@ -26,10 +31,89 @@ const handler = (provider: ApiProvider, access?: AccessControl) => {
2631
}
2732

2833
try {
29-
const data = await provider.getGlobalChartData({
30-
timeframe: timeframe,
31-
metrics: metrics,
32-
});
34+
if (cache) {
35+
const timeNow = new Date();
36+
const cacheKey = `globalChart|${metrics.join("-")}|${
37+
timeframe ?? "30d"
38+
}`;
39+
const cacheLifetime =
40+
options.cache?.routes?.pageAggregate ?? dayInMinutes;
41+
42+
const {
43+
docs: [cachedData],
44+
} = await payload.find({
45+
collection: cache.slug,
46+
where: {
47+
and: [
48+
{
49+
cacheKey: {
50+
equals: cacheKey,
51+
},
52+
},
53+
],
54+
},
55+
});
56+
57+
if (!cachedData) {
58+
const data = await provider
59+
.getGlobalChartData({
60+
timeframe: timeframe,
61+
metrics: metrics,
62+
})
63+
.catch((error) => payload.logger.error(error));
64+
65+
await payload.create({
66+
collection: cache.slug,
67+
data: {
68+
cacheKey: cacheKey,
69+
cacheTimestamp: timeNow.toISOString(),
70+
data: data,
71+
},
72+
});
73+
74+
res.status(200).send(data);
75+
return next();
76+
}
77+
78+
if (cachedData) {
79+
if (
80+
differenceInMinutes(
81+
Date.parse(cachedData.cacheTimestamp),
82+
timeNow
83+
) > cacheLifetime
84+
) {
85+
const data = await provider
86+
.getGlobalChartData({
87+
timeframe: timeframe,
88+
metrics: metrics,
89+
})
90+
.catch((error) => payload.logger.error(error));
91+
92+
await payload.update({
93+
id: cachedData.id,
94+
collection: cache.slug,
95+
data: {
96+
cacheKey: cacheKey,
97+
cacheTimestamp: timeNow.toISOString(),
98+
data: data,
99+
},
100+
});
101+
102+
res.status(200).send(data);
103+
return next();
104+
} else {
105+
res.status(200).send(cachedData.data);
106+
return next();
107+
}
108+
}
109+
}
110+
111+
const data = await provider
112+
.getGlobalChartData({
113+
timeframe: timeframe,
114+
metrics: metrics,
115+
})
116+
.catch((error) => payload.logger.error(error));
33117

34118
res.status(200).send(data);
35119
} catch (error) {

src/routes/getGlobalChart/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import type { Endpoint } from "payload/config";
22
import type { ApiProvider } from "../../providers";
3-
import type { AccessControl } from "../../types";
3+
import type { RouteOptions } from "../../types";
44
import handler from "./handler";
55

66
const getGlobalChart = (
77
provider: ApiProvider,
8-
access?: AccessControl
8+
options: RouteOptions
99
): Endpoint => {
1010
return {
1111
path: "/analytics/globalChart",
1212
method: "post",
13-
handler: handler(provider, access),
13+
handler: handler(provider, options),
1414
};
1515
};
1616

0 commit comments

Comments
 (0)