Skip to content

Commit c5334b0

Browse files
committed
feat: introduce notification selection via driver
1 parent d5cbc24 commit c5334b0

File tree

8 files changed

+151
-1
lines changed

8 files changed

+151
-1
lines changed

packages/backend/src/CoreModule.js

+14
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* along with this program. If not, see <https://www.gnu.org/licenses/>.
1818
*/
1919
const { AdvancedBase } = require("@heyputer/puter-js-common");
20+
const { NotificationES } = require("./om/entitystorage/NotificationES");
2021
const { Context } = require('./util/context');
2122

2223

@@ -89,6 +90,8 @@ const install = async ({ services, app, useapi }) => {
8990
const SubdomainES = require('./om/entitystorage/SubdomainES');
9091
const { MaxLimitES } = require('./om/entitystorage/MaxLimitES');
9192
const { AppLimitedES } = require('./om/entitystorage/AppLimitedES');
93+
const { ReadOnlyES } = require('./om/entitystorage/ReadOnlyES');
94+
const { OwnerLimitedES } = require('./om/entitystorage/OwnerLimitedES');
9295
const { ESBuilder } = require('./om/entitystorage/ESBuilder');
9396
const { Eq, Or } = require('./om/query/query');
9497
const { TrackSpendingService } = require('./services/TrackSpendingService');
@@ -165,6 +168,17 @@ const install = async ({ services, app, useapi }) => {
165168
MaxLimitES, { max: 5000 },
166169
]),
167170
});
171+
services.registerService('es:notification', EntityStoreService, {
172+
entity: 'notification',
173+
upstream: ESBuilder.create([
174+
SQLES, { table: 'notification', debug: true },
175+
NotificationES,
176+
OwnerLimitedES,
177+
ReadOnlyES,
178+
SetOwnerES,
179+
MaxLimitES, { max: 50 },
180+
]),
181+
})
168182
services.registerService('rate-limit', RateLimitService);
169183
services.registerService('monthly-usage', MonthlyUsageService);
170184
services.registerService('auth', AuthService);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright (C) 2024 Puter Technologies Inc.
3+
*
4+
* This file is part of Puter.
5+
*
6+
* Puter is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as published
8+
* by the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
*/
19+
const { Eq } = require("../query/query");
20+
const { BaseES } = require("./BaseES");
21+
22+
class NotificationES extends BaseES {
23+
static METHODS = {
24+
async create_predicate (id) {
25+
if ( id === 'unread' ) {
26+
return new Eq({
27+
key: 'read',
28+
value: 0,
29+
});
30+
}
31+
if ( id === 'read' ) {
32+
return new Eq({
33+
key: 'read',
34+
value: 1,
35+
});
36+
}
37+
},
38+
async read_transform (entity) {
39+
await entity.set('value', JSON.parse(await entity.get('value') ?? '{}'));
40+
}
41+
}
42+
}
43+
44+
module.exports = { NotificationES };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
const { AppUnderUserActorType, UserActorType } = require("../../services/auth/Actor");
2+
const context = require("../../util/context");
3+
const { Context } = require("../../util/context");
4+
const { Eq, Or } = require("../query/query");
5+
const { BaseES } = require("./BaseES");
6+
const { Entity } = require("./Entity");
7+
8+
class OwnerLimitedES extends BaseES {
9+
// Limit selection to entities owned by the app of the current actor.
10+
async select (options) {
11+
const actor = Context.get('actor');
12+
13+
if ( ! (actor.type instanceof UserActorType) ) {
14+
return [];
15+
}
16+
17+
let condition = new Eq({
18+
key: 'owner',
19+
value: actor.type.user.id,
20+
});
21+
22+
options.predicate = options.predicate?.and
23+
? options.predicate.and(condition)
24+
: condition;
25+
26+
return await this.upstream.select(options);
27+
}
28+
29+
// Limit read to entities owned by the app of the current actor.
30+
async read (uid) {
31+
const actor = Context.get('actor');
32+
if ( ! (actor.type instanceof UserActorType) ) {
33+
return null;
34+
}
35+
36+
const entity = await this.upstream.read(uid);
37+
if ( ! entity ) return null;
38+
39+
const entity_owner = await entity.get('owner');
40+
let owner_id = entity_owner?.id;
41+
if ( entity_owner.id !== actor.type.user.id ) {
42+
return null;
43+
}
44+
45+
return entity;
46+
}
47+
}
48+
49+
module.exports = {
50+
OwnerLimitedES,
51+
};
52+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const APIError = require("../../api/APIError");
2+
const { BaseES } = require("./BaseES");
3+
4+
class ReadOnlyES extends BaseES {
5+
async upsert () {
6+
throw APIError.create('forbidden');
7+
}
8+
async delete () {
9+
throw APIError.create('forbidden');
10+
}
11+
}
12+
13+
module.exports = ReadOnlyES;

packages/backend/src/om/mappings/__all__.js

+1
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@
1919
module.exports = {
2020
app: require('./app'),
2121
subdomain: require('./subdomain'),
22+
notification: require('./notification'),
2223
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module.exports = {
2+
sql: {
3+
table_name: 'notification'
4+
},
5+
primary_identifier: 'uid',
6+
properties: {
7+
uid: { type: 'uuid' },
8+
value: { type: 'json' },
9+
read: { type: 'flag' },
10+
owner: {
11+
type: 'reference',
12+
to: 'user',
13+
permissions: ['read'],
14+
permissible_subproperties: ['username', 'uuid'],
15+
sql: {
16+
use_id: true,
17+
column_name: 'user_id',
18+
}
19+
}
20+
}
21+
};

packages/backend/src/services/SelfhostedService.js

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class SelfhostedService extends BaseService {
1515
svc_driver.register_driver('puter-kvstore', new DBKVStore());
1616
svc_driver.register_driver('puter-apps', new EntityStoreImplementation({ service: 'es:app' }));
1717
svc_driver.register_driver('puter-subdomains', new EntityStoreImplementation({ service: 'es:subdomain' }));
18+
svc_driver.register_driver('puter-notifications', new EntityStoreImplementation({ service: 'es:notification' }));
1819
}
1920
}
2021

packages/backend/src/services/drivers/interfaces.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -270,5 +270,9 @@ module.exports = {
270270
'puter-subdomains': {
271271
...ENTITY_STORAGE_INTERFACE,
272272
description: 'Manage subdomains on Puter.',
273-
}
273+
},
274+
'puter-notifications': {
275+
...ENTITY_STORAGE_INTERFACE,
276+
description: 'Read notifications on Puter.',
277+
},
274278
};

0 commit comments

Comments
 (0)