mirror of
https://github.com/HeyPuter/puter.git
synced 2026-05-04 08:30:39 +00:00
fix: redis cache deletions (#2518)
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2024-present Puter Technologies Inc.
|
||||
*
|
||||
* This file is part of Puter.
|
||||
*
|
||||
* Puter is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { redisClient } from './redisSingleton.js';
|
||||
|
||||
export const deleteRedisKeys = async (...keysInput: string[]) => {
|
||||
const keys = keysInput
|
||||
.map(key => key === null || key === undefined ? '' : String(key))
|
||||
.filter(Boolean);
|
||||
|
||||
if ( keys.length === 0 ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let deleted = 0;
|
||||
for ( const key of new Set(keys) ) {
|
||||
deleted += await redisClient.del(key);
|
||||
}
|
||||
return deleted;
|
||||
};
|
||||
@@ -20,6 +20,7 @@ const { origin_from_url } = require('../../util/urlutil');
|
||||
const { DB_READ } = require('../../services/database/consts');
|
||||
const BaseService = require('../../services/BaseService');
|
||||
const { redisClient } = require('../../clients/redis/redisSingleton');
|
||||
const { deleteRedisKeys } = require('../../clients/redis/deleteRedisKeys.js');
|
||||
const { AppRedisCacheSpace } = require('./AppRedisCacheSpace.js');
|
||||
|
||||
// Currently leaks memory (not sure why yet, but icons are a factor)
|
||||
@@ -215,97 +216,97 @@ class AppInformationService extends BaseService {
|
||||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
|
||||
switch ( period ) {
|
||||
case 'today':
|
||||
return {
|
||||
start: today.getTime(),
|
||||
end: now.getTime(),
|
||||
};
|
||||
case 'yesterday': {
|
||||
const yesterday = new Date(today);
|
||||
case 'today':
|
||||
return {
|
||||
start: today.getTime(),
|
||||
end: now.getTime(),
|
||||
};
|
||||
case 'yesterday': {
|
||||
const yesterday = new Date(today);
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
return {
|
||||
start: yesterday.getTime(),
|
||||
end: today.getTime() - 1,
|
||||
};
|
||||
}
|
||||
case '7d': {
|
||||
const weekAgo = new Date(now);
|
||||
}
|
||||
case '7d': {
|
||||
const weekAgo = new Date(now);
|
||||
weekAgo.setDate(weekAgo.getDate() - 7);
|
||||
return {
|
||||
start: weekAgo.getTime(),
|
||||
end: now.getTime(),
|
||||
};
|
||||
}
|
||||
case '30d': {
|
||||
const monthAgo = new Date(now);
|
||||
}
|
||||
case '30d': {
|
||||
const monthAgo = new Date(now);
|
||||
monthAgo.setDate(monthAgo.getDate() - 30);
|
||||
return {
|
||||
start: monthAgo.getTime(),
|
||||
end: now.getTime(),
|
||||
};
|
||||
}
|
||||
case 'this_week': {
|
||||
const firstDayOfWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - now.getDay());
|
||||
return {
|
||||
start: firstDayOfWeek.getTime(),
|
||||
end: now.getTime(),
|
||||
};
|
||||
}
|
||||
case 'last_week': {
|
||||
const firstDayOfLastWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - now.getDay() - 7);
|
||||
const firstDayOfThisWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - now.getDay());
|
||||
return {
|
||||
start: firstDayOfLastWeek.getTime(),
|
||||
end: firstDayOfThisWeek.getTime() - 1,
|
||||
};
|
||||
}
|
||||
case 'this_month': {
|
||||
const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
return {
|
||||
start: firstDayOfMonth.getTime(),
|
||||
end: now.getTime(),
|
||||
};
|
||||
}
|
||||
case 'last_month': {
|
||||
const firstDayOfLastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
const firstDayOfThisMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
return {
|
||||
start: firstDayOfLastMonth.getTime(),
|
||||
end: firstDayOfThisMonth.getTime() - 1,
|
||||
};
|
||||
}
|
||||
case 'this_year': {
|
||||
const firstDayOfYear = new Date(now.getFullYear(), 0, 1);
|
||||
return {
|
||||
start: firstDayOfYear.getTime(),
|
||||
end: now.getTime(),
|
||||
};
|
||||
}
|
||||
case 'last_year': {
|
||||
const firstDayOfLastYear = new Date(now.getFullYear() - 1, 0, 1);
|
||||
const firstDayOfThisYear = new Date(now.getFullYear(), 0, 1);
|
||||
return {
|
||||
start: firstDayOfLastYear.getTime(),
|
||||
end: firstDayOfThisYear.getTime() - 1,
|
||||
};
|
||||
}
|
||||
case '12m': {
|
||||
const twelveMonthsAgo = new Date(now);
|
||||
}
|
||||
case 'this_week': {
|
||||
const firstDayOfWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - now.getDay());
|
||||
return {
|
||||
start: firstDayOfWeek.getTime(),
|
||||
end: now.getTime(),
|
||||
};
|
||||
}
|
||||
case 'last_week': {
|
||||
const firstDayOfLastWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - now.getDay() - 7);
|
||||
const firstDayOfThisWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - now.getDay());
|
||||
return {
|
||||
start: firstDayOfLastWeek.getTime(),
|
||||
end: firstDayOfThisWeek.getTime() - 1,
|
||||
};
|
||||
}
|
||||
case 'this_month': {
|
||||
const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
return {
|
||||
start: firstDayOfMonth.getTime(),
|
||||
end: now.getTime(),
|
||||
};
|
||||
}
|
||||
case 'last_month': {
|
||||
const firstDayOfLastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
const firstDayOfThisMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
return {
|
||||
start: firstDayOfLastMonth.getTime(),
|
||||
end: firstDayOfThisMonth.getTime() - 1,
|
||||
};
|
||||
}
|
||||
case 'this_year': {
|
||||
const firstDayOfYear = new Date(now.getFullYear(), 0, 1);
|
||||
return {
|
||||
start: firstDayOfYear.getTime(),
|
||||
end: now.getTime(),
|
||||
};
|
||||
}
|
||||
case 'last_year': {
|
||||
const firstDayOfLastYear = new Date(now.getFullYear() - 1, 0, 1);
|
||||
const firstDayOfThisYear = new Date(now.getFullYear(), 0, 1);
|
||||
return {
|
||||
start: firstDayOfLastYear.getTime(),
|
||||
end: firstDayOfThisYear.getTime() - 1,
|
||||
};
|
||||
}
|
||||
case '12m': {
|
||||
const twelveMonthsAgo = new Date(now);
|
||||
twelveMonthsAgo.setMonth(twelveMonthsAgo.getMonth() - 12);
|
||||
return {
|
||||
start: twelveMonthsAgo.getTime(),
|
||||
end: now.getTime(),
|
||||
};
|
||||
}
|
||||
case 'all':{
|
||||
const start = new Date(app_creation_ts);
|
||||
return {
|
||||
start: start.getTime(),
|
||||
end: now.getTime(),
|
||||
};
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
case 'all':{
|
||||
const start = new Date(app_creation_ts);
|
||||
return {
|
||||
start: start.getTime(),
|
||||
end: now.getTime(),
|
||||
};
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -796,7 +797,7 @@ class AppInformationService extends BaseService {
|
||||
.filter(Boolean)
|
||||
.map(ext => AppRedisCacheSpace.associationAppsKey(ext));
|
||||
if ( associationKeys.length ) {
|
||||
await redisClient.del(...associationKeys);
|
||||
await deleteRedisKeys(associationKeys);
|
||||
}
|
||||
|
||||
// remove from recent
|
||||
@@ -836,29 +837,29 @@ class AppInformationService extends BaseService {
|
||||
while ( currentDate <= endDate ) {
|
||||
let period;
|
||||
switch ( grouping ) {
|
||||
case 'hour':
|
||||
period = `${currentDate.toISOString().slice(0, 13) }:00:00`;
|
||||
case 'hour':
|
||||
period = `${currentDate.toISOString().slice(0, 13) }:00:00`;
|
||||
currentDate.setHours(currentDate.getHours() + 1);
|
||||
break;
|
||||
case 'day':
|
||||
period = currentDate.toISOString().slice(0, 10);
|
||||
break;
|
||||
case 'day':
|
||||
period = currentDate.toISOString().slice(0, 10);
|
||||
currentDate.setDate(currentDate.getDate() + 1);
|
||||
break;
|
||||
case 'week': {
|
||||
break;
|
||||
case 'week': {
|
||||
// Get the ISO week number
|
||||
const weekNum = String(this.getWeekNumber(currentDate)).padStart(2, '0');
|
||||
period = `${currentDate.getFullYear()}-${weekNum}`;
|
||||
const weekNum = String(this.getWeekNumber(currentDate)).padStart(2, '0');
|
||||
period = `${currentDate.getFullYear()}-${weekNum}`;
|
||||
currentDate.setDate(currentDate.getDate() + 7);
|
||||
break;
|
||||
}
|
||||
case 'month':
|
||||
period = currentDate.toISOString().slice(0, 7);
|
||||
}
|
||||
case 'month':
|
||||
period = currentDate.toISOString().slice(0, 7);
|
||||
currentDate.setMonth(currentDate.getMonth() + 1);
|
||||
break;
|
||||
case 'year':
|
||||
period = currentDate.getFullYear().toString();
|
||||
break;
|
||||
case 'year':
|
||||
period = currentDate.getFullYear().toString();
|
||||
currentDate.setFullYear(currentDate.getFullYear() + 1);
|
||||
break;
|
||||
break;
|
||||
}
|
||||
periods.push({ period, count: 0 });
|
||||
}
|
||||
@@ -887,23 +888,23 @@ class AppInformationService extends BaseService {
|
||||
// For ClickHouse results, convert the timestamp to match the expected format
|
||||
if ( item.period instanceof Date ) {
|
||||
switch ( stats_grouping ) {
|
||||
case 'hour':
|
||||
period = `${item.period.toISOString().slice(0, 13) }:00:00`;
|
||||
break;
|
||||
case 'day':
|
||||
period = item.period.toISOString().slice(0, 10);
|
||||
break;
|
||||
case 'week': {
|
||||
const weekNum = String(this.getWeekNumber(item.period)).padStart(2, '0');
|
||||
period = `${item.period.getFullYear()}-${weekNum}`;
|
||||
break;
|
||||
}
|
||||
case 'month':
|
||||
period = item.period.toISOString().slice(0, 7);
|
||||
break;
|
||||
case 'year':
|
||||
period = item.period.getFullYear().toString();
|
||||
break;
|
||||
case 'hour':
|
||||
period = `${item.period.toISOString().slice(0, 13) }:00:00`;
|
||||
break;
|
||||
case 'day':
|
||||
period = item.period.toISOString().slice(0, 10);
|
||||
break;
|
||||
case 'week': {
|
||||
const weekNum = String(this.getWeekNumber(item.period)).padStart(2, '0');
|
||||
period = `${item.period.getFullYear()}-${weekNum}`;
|
||||
break;
|
||||
}
|
||||
case 'month':
|
||||
period = item.period.toISOString().slice(0, 7);
|
||||
break;
|
||||
case 'year':
|
||||
period = item.period.getFullYear().toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return [period, parseInt(item.count)];
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { redisClient } from '../../clients/redis/redisSingleton.js';
|
||||
import { deleteRedisKeys } from '../../clients/redis/deleteRedisKeys.js';
|
||||
|
||||
const appFullNamespace = 'apps';
|
||||
const appLiteNamespace = 'apps:lite';
|
||||
@@ -101,7 +102,7 @@ export const AppRedisCacheSpace = {
|
||||
keys.push(...AppRedisCacheSpace.statsKeys(app.uid));
|
||||
}
|
||||
if ( keys.length ) {
|
||||
await redisClient.del(...keys);
|
||||
await deleteRedisKeys(keys);
|
||||
}
|
||||
},
|
||||
invalidateCachedAppName: async (name, { rawIconVariants = [true, false] } = {}) => {
|
||||
@@ -111,10 +112,10 @@ export const AppRedisCacheSpace = {
|
||||
value: name,
|
||||
rawIcon,
|
||||
}));
|
||||
await redisClient.del(...keys);
|
||||
await deleteRedisKeys(keys);
|
||||
},
|
||||
invalidateAppStats: async (uid) => {
|
||||
if ( ! uid ) return;
|
||||
await redisClient.del(...AppRedisCacheSpace.statsKeys(uid));
|
||||
await deleteRedisKeys(AppRedisCacheSpace.statsKeys(uid));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import APIError from '../../api/APIError.js';
|
||||
import { redisClient } from '../../clients/redis/redisSingleton.js';
|
||||
import { deleteRedisKeys } from '../../clients/redis/deleteRedisKeys.js';
|
||||
import config from '../../config.js';
|
||||
import { AppRedisCacheSpace } from '../apps/AppRedisCacheSpace.js';
|
||||
import { NodeInternalIDSelector } from '../../filesystem/node/selectors.js';
|
||||
@@ -1166,7 +1166,7 @@ export default class AppService extends BaseService {
|
||||
if ( ! normalizedNew.length ) {
|
||||
const affectedExtensions = new Set(normalizedOld);
|
||||
if ( affectedExtensions.size ) {
|
||||
await redisClient.del(...Array.from(affectedExtensions)
|
||||
await deleteRedisKeys(Array.from(affectedExtensions)
|
||||
.map(ext => AppRedisCacheSpace.associationAppsKey(ext)));
|
||||
}
|
||||
return;
|
||||
@@ -1180,7 +1180,7 @@ export default class AppService extends BaseService {
|
||||
|
||||
const affectedExtensions = new Set([...normalizedOld, ...normalizedNew]);
|
||||
if ( affectedExtensions.size ) {
|
||||
await redisClient.del(...Array.from(affectedExtensions)
|
||||
await deleteRedisKeys(Array.from(affectedExtensions)
|
||||
.map(ext => AppRedisCacheSpace.associationAppsKey(ext)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
const APIError = require('../../api/APIError');
|
||||
const { AppRedisCacheSpace } = require('../../modules/apps/AppRedisCacheSpace.js');
|
||||
const { redisClient } = require('../../clients/redis/redisSingleton');
|
||||
const { deleteRedisKeys } = require('../../clients/redis/deleteRedisKeys.js');
|
||||
const config = require('../../config');
|
||||
const { app_name_exists } = require('../../helpers');
|
||||
const { AppUnderUserActorType } = require('../../services/auth/Actor');
|
||||
@@ -181,7 +181,7 @@ class AppES extends BaseES {
|
||||
...normalizedNewAssociations,
|
||||
]);
|
||||
if ( affectedAssociationExtensions.size ) {
|
||||
await redisClient.del(...Array.from(affectedAssociationExtensions)
|
||||
await deleteRedisKeys(Array.from(affectedAssociationExtensions)
|
||||
.map(ext => AppRedisCacheSpace.associationAppsKey(ext)));
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { redisClient } from '../clients/redis/redisSingleton.js';
|
||||
import { deleteRedisKeys } from '../clients/redis/deleteRedisKeys.js';
|
||||
|
||||
const userKeyPrefix = 'users';
|
||||
const defaultUserIdProperties = ['username', 'uuid', 'email', 'id', 'referral_code'];
|
||||
@@ -65,7 +66,7 @@ const UserRedisCacheSpace = {
|
||||
invalidateUser: async (user, props = defaultUserIdProperties) => {
|
||||
const keys = UserRedisCacheSpace.keysForUser(user, props);
|
||||
if ( keys.length ) {
|
||||
await redisClient.del(...keys);
|
||||
await deleteRedisKeys(keys);
|
||||
}
|
||||
},
|
||||
invalidateById: async (id, props = defaultUserIdProperties) => {
|
||||
|
||||
@@ -28,6 +28,7 @@ const { UserActorType, Actor, AppUnderUserActorType } = require('./Actor');
|
||||
const { PERM_KEY_PREFIX, MANAGE_PERM_PREFIX } = require('./permissionConts.mjs');
|
||||
const { PermissionUtil, PermissionExploder, PermissionImplicator, PermissionRewriter } = require('./permissionUtils.mjs');
|
||||
const { spanify } = require('../../util/otelutil');
|
||||
const { deleteRedisKeys } = require('../../clients/redis/deleteRedisKeys.js');
|
||||
const { redisClient } = require('../../clients/redis/redisSingleton');
|
||||
const { PermissionScanRedisCacheSpace } = require('./PermissionScanRedisCacheSpace.js');
|
||||
const { Context } = require('../../util/context');
|
||||
@@ -277,7 +278,7 @@ class PermissionService extends BaseService {
|
||||
cursor = next_cursor;
|
||||
if ( keys?.length ) toDelete.push(...keys);
|
||||
} while ( cursor !== '0' );
|
||||
if ( toDelete.length ) await redisClient.del(...toDelete);
|
||||
if ( toDelete.length ) await deleteRedisKeys(toDelete);
|
||||
}
|
||||
|
||||
async validateUserPerms ({ actor, permissions }) {
|
||||
|
||||
Reference in New Issue
Block a user