Skip to content

Commit e8542fc

Browse files
committed
add caching
1 parent aee48f5 commit e8542fc

File tree

8 files changed

+148
-17
lines changed

8 files changed

+148
-17
lines changed

server/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515
"license": "GPL-3.0",
1616
"devDependencies": {
1717
"@types/body-parser": "^1.19.5",
18+
"@types/compression": "^1.7.5",
1819
"@types/express": "^4.17.21",
1920
"@types/morgan": "^1.9.9",
2021
"@types/node": "^20.11.16",
2122
"@types/pg": "^8.11.6",
23+
"@types/swagger-ui-express": "^4.1.8",
2224
"@typescript-eslint/eslint-plugin": "^6.21.0",
2325
"@typescript-eslint/parser": "^6.21.0",
2426
"eslint": "^8.56.0",
@@ -28,13 +30,15 @@
2830
"dependencies": {
2931
"axios": "^1.7.5",
3032
"body-parser": "^1.20.2",
33+
"compression": "^1.8.0",
3134
"crypto": "^1.0.1",
3235
"dotenv": "^16.4.5",
3336
"express": "^4.18.2",
3437
"express-rate-limit": "^7.4.0",
3538
"express-validator": "^7.2.0",
3639
"moment": "^2.30.1",
3740
"morgan": "^1.10.0",
38-
"pg": "^8.12.0"
41+
"pg": "^8.12.0",
42+
"swagger-ui-express": "^5.0.1"
3943
}
4044
}

server/src/Components/TimedMap.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// copied from https://github.com/FireMario211/dashend-min/blob/main/src/utils.ts except sorta improved !!!
2+
// the amount of time it took for me to come up with this is insane
3+
export interface TimedEntry<T> {
4+
value: T;
5+
expiration: number;
6+
created: number;
7+
}
8+
9+
export class TimedMap<K, V> {
10+
private map: Map<K, TimedEntry<V>> = new Map();
11+
private max: number;
12+
private cleanInterval: NodeJS.Timeout | null = null;
13+
constructor(interval: number, max = Infinity) {
14+
this.max = max;
15+
if (interval && interval > 0) {
16+
this.cleanInterval = setInterval(() => this.cleanup(), interval);
17+
}
18+
}
19+
set(key: K, value: V, ttl: number): void {
20+
if (this.map.size >= this.max) {
21+
this.removeOldest();
22+
}
23+
const created = Date.now();
24+
const expiration = created + ttl;
25+
this.map.set(key, { value, expiration, created });
26+
}
27+
get(key: K): V | null {
28+
const entry = this.map.get(key);
29+
if (entry) {
30+
if (Date.now() < entry.expiration) {
31+
return entry.value;
32+
} else {
33+
this.map.delete(key);
34+
}
35+
}
36+
return null;
37+
}
38+
delete(key: K): boolean {
39+
return this.map.delete(key);
40+
}
41+
find(fn: (value: TimedEntry<V>, key: K) => boolean): [K, TimedEntry<V>] | null {
42+
for (const [key, value] of this.map.entries()) {
43+
if (fn(value, key)) {
44+
return [key, value];
45+
}
46+
}
47+
return null;
48+
}
49+
cleanup(): void {
50+
const now = Date.now();
51+
for (const [key, value] of this.map.entries()) {
52+
if (now >= value.expiration) {
53+
this.delete(key);
54+
}
55+
}
56+
}
57+
removeOldest(): boolean {
58+
if (this.map.size == 0) return false;
59+
let oldestKey: K | null = null;
60+
let oldestCreated: number = Infinity;
61+
for (const [key, value] of this.map.entries()) {
62+
if (value.created < oldestCreated) {
63+
oldestCreated = value.created;
64+
oldestKey = key;
65+
}
66+
}
67+
if (oldestKey != null) {
68+
return this.delete(oldestKey);
69+
}
70+
return false;
71+
}
72+
}

server/src/cache.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Request, Response, NextFunction } from 'express'
2+
import { TimedMap } from './Components/TimedMap';
3+
4+
type ShouldCacheRoute = (req: Request) => boolean;
5+
6+
class CacheManager {
7+
private static cache = new TimedMap<string, any>(60000, 1000);
8+
static get<T>(key: string): T | null {
9+
return this.cache.get(key);
10+
}
11+
static set<T>(key: string, data: T, ttl: number): void {
12+
this.cache.set(key, data, ttl * 1000);
13+
}
14+
static delete(key: string): boolean {
15+
return this.cache.delete(key);
16+
}
17+
}
18+
19+
export const cacheMiddleware = (ttl: number, cacheRoute: ShouldCacheRoute = () => true) => {
20+
return (req: Request, res: Response, next: NextFunction) => {
21+
if (!cacheRoute(req)) return next();
22+
const key = `${req.method}:${req.originalUrl}`;
23+
const data = CacheManager.get(key);
24+
if (data) return res.json(data);
25+
const origData = res.json;
26+
res.json = function(body: any) {
27+
if (res.statusCode >= 200 && res.statusCode < 300) {
28+
CacheManager.set(key, body, ttl);
29+
}
30+
return origData.call(this, body);
31+
};
32+
next();
33+
}
34+
}

server/src/controllers/objects.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { verifyToken, banCheck } from './user';
88
import axios from 'axios';
99
import moment from 'moment'
1010
import { UserData } from '@/Components/User';
11+
import { cacheMiddleware } from '../cache';
1112

1213
const allowedTags = ["Font", "Decoration", "Gameplay", "Art", "Structure", "Custom", "Icon", "Meme", "Technical", "Particles", "Triggers", "SFX", "Effects", "Auto Build", "Recreation"];
1314

@@ -57,6 +58,7 @@ enum ReviewStatus {
5758
interface WebhookObjData {
5859
account_name: string;
5960
name: string;
61+
id: number;
6062
description: string;
6163
tags: Array<string>;
6264
version: number;
@@ -81,6 +83,11 @@ function sendWebhook(object: WebhookObjData, status: ReviewStatus, reviewer?: st
8183
"value": object.account_name,
8284
"inline": true
8385
},
86+
{
87+
"name": "Object ID",
88+
"value": object.id,
89+
"inline": true
90+
},
8491
{
8592
"name": "Description",
8693
"value": object.description
@@ -186,6 +193,7 @@ oRouter.post('/objects/upload',
186193
timestamp: object.timestamp
187194
});
188195
sendWebhook({
196+
id: object.id,
189197
name,
190198
description,
191199
tags,
@@ -256,6 +264,7 @@ oRouter.post('/objects/:id/overwrite',
256264
}
257265
res.status(200).json({ message: "Object updated!" });
258266
sendWebhook({
267+
id: objData.id,
259268
name: objData.name,
260269
description: objData.description,
261270
tags: objData.tags,
@@ -277,7 +286,10 @@ oRouter.post('/objects/:id/overwrite',
277286
}
278287
);
279288

280-
oRouter.get('/objects/:id', param("id").isInt({min: 0, max: 2147483647}).notEmpty(), (req: Request, res: Response) => {
289+
oRouter.get('/objects/:id', param("id").isInt({min: 0, max: 2147483647}).notEmpty(), cacheMiddleware(600, (req) => {
290+
const category = parseInt(req.query.category as string) || 0;
291+
return !req.query["no-cache"] || category != 4;
292+
}), (req: Request, res: Response) => {
281293
const result = validationResult(req);
282294
if (!result.isEmpty()) return res.status(400).json({ errors: result.array() })
283295
const objectID = req.params.id;
@@ -615,19 +627,18 @@ oRouter.post('/objects/:id/favorite',
615627
const objExists = await pool.query("SELECT EXISTS (SELECT 1 FROM objects WHERE id = $1 AND status = 1)", [objectID])
616628
if (!objExists.rows[0].exists) return res.status(404).json({error: "Object not found."});
617629
let query = "";
618-
let query2 = "";
630+
619631
let message = ""
620632
if (verifyRes.user?.favorites.includes(parseInt(objectID))) {
621633
query = "UPDATE users SET favorites = ARRAY_REMOVE(favorites, $1) WHERE account_id = $2";
622-
query2 = "UPDATE objects SET favorites = favorites - 1 WHERE id = $1";
623634
message = "Unfavorited object!";
624635
} else {
625636
query = "UPDATE users SET favorites = ARRAY_APPEND(favorites, $1) WHERE account_id = $2";
626-
query2 = "UPDATE objects SET favorites = favorites + 1 WHERE id = $1";
627637
message = "Favorited object!";
628638
}
629639
await pool.query(query, [objectID, accountID]);
630-
await pool.query(query2, [objectID]);
640+
const favoriteCount = await pool.query("SELECT COUNT(*) as favorites FROM users WHERE favorites @> ARRAY[$1::integer]", [objectID]);
641+
await pool.query("UPDATE objects SET favorites = $1 WHERE id = $2", [favoriteCount.rows[0].favorites, objectID]);
631642
res.status(200).json({ message });
632643
} catch (e) {
633644
console.error(e);
@@ -823,6 +834,7 @@ oRouter.post('/objects/:id/accept',
823834
}
824835
await pool.query("UPDATE objects SET status = 1 WHERE id = $1", [objectID]);
825836
sendWebhook({
837+
id: objData.id,
826838
name: objData.name,
827839
description: objData.description,
828840
tags: objData.tags,
@@ -876,6 +888,7 @@ oRouter.post('/objects/:id/reject',
876888
await pool.query("DELETE FROM objects WHERE id = $1", [objectID]);
877889
res.status(200).json({ message: "Rejected!" });
878890
sendWebhook({
891+
id: objData.id,
879892
name: objData.name,
880893
description: objData.description,
881894
tags: objData.tags,
@@ -946,6 +959,9 @@ oRouter.post('/objects/:id/feature',
946959
oRouter.get('/user/:id',
947960
param('id').isInt({min: 0, max: 2147483647}).notEmpty(),
948961
query('page').isInt({min: 0, max: 2147483647}).optional(),
962+
cacheMiddleware(300, (req) => {
963+
return !req.query["no-cache"];
964+
}),
949965
async (req: Request, res: Response) => {
950966
const result = validationResult(req);
951967
if (!result.isEmpty()) return res.status(400).json({ errors: result.array() })
@@ -1098,6 +1114,9 @@ oRouter.get('/objects/:id/comments',
10981114
query('page').isInt({min: 1, max: 2147483647}).optional(),
10991115
query('limit').isInt({min: 1, max: 10}).optional(),
11001116
query('filter').isInt({min: 1, max: 2}).optional(),
1117+
cacheMiddleware(30, (req) => {
1118+
return !req.query["no-cache"];
1119+
}),
11011120
async (req: Request, res: Response) => {
11021121
const result = validationResult(req);
11031122
if (!result.isEmpty()) return res.status(400).json({ errors: result.array() })
@@ -1287,6 +1306,9 @@ oRouter.post('/user/@me/favorites',
12871306
body('token').notEmpty().isString(),
12881307
query('page').isInt({min: 1, max: 2147483647}).optional(),
12891308
query('limit').isInt({min: 1, max: 9}).optional(),
1309+
cacheMiddleware(300, (req) => {
1310+
return !req.query["no-cache"];
1311+
}),
12901312
async (req: Request, res: Response) => {
12911313
const result = validationResult(req);
12921314
if (!result.isEmpty()) return res.status(400).json({ errors: result.array() })
@@ -1357,6 +1379,10 @@ oRouter.get('/objects',
13571379
query('category').isInt({min: 0, max: 2147483647}).optional(),
13581380
query('limit').isInt({min: 1, max: 9}).optional(),
13591381
body('tags').optional().isString(),
1382+
cacheMiddleware(300, (req) => {
1383+
const category = parseInt(req.query.category as string) || 0;
1384+
return !req.query["no-cache"] || category == 4;
1385+
}),
13601386
async (req: Request, res: Response) => {
13611387
const result = validationResult(req);
13621388
if (!result.isEmpty()) return res.status(400).json({ errors: result.array() })

server/src/controllers/staff.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ sRouter.post("/case/create",
4343
}
4444
if (verifyRes.user) {
4545
const userData = verifyRes.user;
46-
if (userData.role < 2) return res.status(403).json({error: "No permission"})
46+
if (userData.role != 3) return res.status(403).json({error: "No permission"})
4747
const userID = parseInt(req.body.user as string);
4848
const caseType = parseInt(req.body.type as string);
4949
const reason = req.body.reason;

server/src/controllers/user.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,6 @@ interface DashAuth {
8181
}
8282
};
8383

84-
uRouter.get("/testverify", query('token').notEmpty().isUUID(4), async (req: Request, res: Response) => {
85-
const result = validationResult(req);
86-
if (!result.isEmpty()) return res.status(400).json({ errors: result.array() })
87-
axios.post("https://gd.figm.io/authentication/validate", `sessionID=${req.query.token}`).then(axiosRes => {
88-
res.status(200).json(axiosRes.data);
89-
})
90-
})
91-
9284
uRouter.post("/user/@me",
9385
body('token').notEmpty().isString().withMessage("Token is required"),
9486
async (req: Request, res: Response) => {
@@ -280,7 +272,6 @@ uRouter.post('/dashauth',
280272
const token = req.body.token as string;
281273
getCache().then(pool => {
282274
axios.post("https://dashend.firee.dev/api/v1/verify", {token}).then(axiosRes => {
283-
//axios.post("http://127.0.0.1:3001/api/v1/verify", {token}).then(axiosRes => {
284275
const dashAuthData = axiosRes.data as DashAuth;
285276
const data = dashAuthData.data;
286277
if (!process.env.PRODUCTION) {

server/src/postgres.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ let isReady: boolean;
55
async function getCache(): Promise<PoolClient> {
66
if (!isReady) {
77
let apool = new Pool({
8+
max: 20,
9+
idleTimeoutMillis: 20000,
10+
connectionTimeoutMillis: 2000,
811
user: 'myuser',
912
host: 'localhost',
1013
database: 'db',

src/ui/admin/RolePopup.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ bool RolePopup::setup(UserData user, std::function<void(int)> callback) {
1212
menu->setLayout(RowLayout::create());
1313
for (int i = 0; i < ROLE_COUNT; i++) {
1414
auto btn = CCMenuItemExt::createSpriteExtra(Utils::roleIDToSprite(i, 0.5F), [this](CCObject* sender) {
15-
if (auto item = typeinfo_cast<CCMenuItemSpriteExtra*>(sender)) {
15+
if (auto item = as<CCMenuItemSpriteExtra*>(sender)) {
1616
m_selectedRole = item->getTag();
1717
}
18+
ccColor3B col = true ? ccWHITE : {125, 125, 125};
1819
if (!m_roleBtns.empty()) {
1920
for (const auto& item : m_roleBtns) {
2021
if (item->getTag() == m_selectedRole) {

0 commit comments

Comments
 (0)