@xtr-dev/rondevu-server 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ADVANCED.md +502 -0
- package/README.md +139 -251
- package/dist/index.js +715 -770
- package/dist/index.js.map +4 -4
- package/migrations/0006_service_offer_refactor.sql +40 -0
- package/migrations/0007_simplify_schema.sql +54 -0
- package/migrations/0008_peer_id_to_username.sql +67 -0
- package/migrations/fresh_schema.sql +81 -0
- package/package.json +2 -1
- package/src/app.ts +38 -677
- package/src/config.ts +0 -13
- package/src/crypto.ts +98 -133
- package/src/rpc.ts +725 -0
- package/src/storage/d1.ts +169 -182
- package/src/storage/sqlite.ts +142 -168
- package/src/storage/types.ts +51 -95
- package/src/worker.ts +0 -6
- package/wrangler.toml +3 -3
- package/src/middleware/auth.ts +0 -51
package/src/storage/sqlite.ts
CHANGED
|
@@ -9,9 +9,9 @@ import {
|
|
|
9
9
|
ClaimUsernameRequest,
|
|
10
10
|
Service,
|
|
11
11
|
CreateServiceRequest,
|
|
12
|
-
ServiceInfo,
|
|
13
12
|
} from './types.ts';
|
|
14
13
|
import { generateOfferHash } from './hash-id.ts';
|
|
14
|
+
import { parseServiceFqn } from '../crypto.ts';
|
|
15
15
|
|
|
16
16
|
const YEAR_IN_MS = 365 * 24 * 60 * 60 * 1000; // 365 days
|
|
17
17
|
|
|
@@ -39,30 +39,29 @@ export class SQLiteStorage implements Storage {
|
|
|
39
39
|
-- WebRTC signaling offers
|
|
40
40
|
CREATE TABLE IF NOT EXISTS offers (
|
|
41
41
|
id TEXT PRIMARY KEY,
|
|
42
|
-
|
|
42
|
+
username TEXT NOT NULL,
|
|
43
43
|
service_id TEXT,
|
|
44
44
|
sdp TEXT NOT NULL,
|
|
45
45
|
created_at INTEGER NOT NULL,
|
|
46
46
|
expires_at INTEGER NOT NULL,
|
|
47
47
|
last_seen INTEGER NOT NULL,
|
|
48
|
-
|
|
49
|
-
answerer_peer_id TEXT,
|
|
48
|
+
answerer_username TEXT,
|
|
50
49
|
answer_sdp TEXT,
|
|
51
50
|
answered_at INTEGER,
|
|
52
51
|
FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE
|
|
53
52
|
);
|
|
54
53
|
|
|
55
|
-
CREATE INDEX IF NOT EXISTS
|
|
54
|
+
CREATE INDEX IF NOT EXISTS idx_offers_username ON offers(username);
|
|
56
55
|
CREATE INDEX IF NOT EXISTS idx_offers_service ON offers(service_id);
|
|
57
56
|
CREATE INDEX IF NOT EXISTS idx_offers_expires ON offers(expires_at);
|
|
58
57
|
CREATE INDEX IF NOT EXISTS idx_offers_last_seen ON offers(last_seen);
|
|
59
|
-
CREATE INDEX IF NOT EXISTS idx_offers_answerer ON offers(
|
|
58
|
+
CREATE INDEX IF NOT EXISTS idx_offers_answerer ON offers(answerer_username);
|
|
60
59
|
|
|
61
60
|
-- ICE candidates table
|
|
62
61
|
CREATE TABLE IF NOT EXISTS ice_candidates (
|
|
63
62
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
64
63
|
offer_id TEXT NOT NULL,
|
|
65
|
-
|
|
64
|
+
username TEXT NOT NULL,
|
|
66
65
|
role TEXT NOT NULL CHECK(role IN ('offerer', 'answerer')),
|
|
67
66
|
candidate TEXT NOT NULL,
|
|
68
67
|
created_at INTEGER NOT NULL,
|
|
@@ -70,7 +69,7 @@ export class SQLiteStorage implements Storage {
|
|
|
70
69
|
);
|
|
71
70
|
|
|
72
71
|
CREATE INDEX IF NOT EXISTS idx_ice_offer ON ice_candidates(offer_id);
|
|
73
|
-
CREATE INDEX IF NOT EXISTS
|
|
72
|
+
CREATE INDEX IF NOT EXISTS idx_ice_username ON ice_candidates(username);
|
|
74
73
|
CREATE INDEX IF NOT EXISTS idx_ice_created ON ice_candidates(created_at);
|
|
75
74
|
|
|
76
75
|
-- Usernames table
|
|
@@ -87,36 +86,23 @@ export class SQLiteStorage implements Storage {
|
|
|
87
86
|
CREATE INDEX IF NOT EXISTS idx_usernames_expires ON usernames(expires_at);
|
|
88
87
|
CREATE INDEX IF NOT EXISTS idx_usernames_public_key ON usernames(public_key);
|
|
89
88
|
|
|
90
|
-
-- Services table (
|
|
89
|
+
-- Services table (new schema with extracted fields for discovery)
|
|
91
90
|
CREATE TABLE IF NOT EXISTS services (
|
|
92
91
|
id TEXT PRIMARY KEY,
|
|
93
|
-
username TEXT NOT NULL,
|
|
94
92
|
service_fqn TEXT NOT NULL,
|
|
93
|
+
service_name TEXT NOT NULL,
|
|
94
|
+
version TEXT NOT NULL,
|
|
95
|
+
username TEXT NOT NULL,
|
|
95
96
|
created_at INTEGER NOT NULL,
|
|
96
97
|
expires_at INTEGER NOT NULL,
|
|
97
|
-
is_public INTEGER NOT NULL DEFAULT 0,
|
|
98
|
-
metadata TEXT,
|
|
99
98
|
FOREIGN KEY (username) REFERENCES usernames(username) ON DELETE CASCADE,
|
|
100
|
-
UNIQUE(
|
|
99
|
+
UNIQUE(service_fqn)
|
|
101
100
|
);
|
|
102
101
|
|
|
103
|
-
CREATE INDEX IF NOT EXISTS idx_services_username ON services(username);
|
|
104
102
|
CREATE INDEX IF NOT EXISTS idx_services_fqn ON services(service_fqn);
|
|
103
|
+
CREATE INDEX IF NOT EXISTS idx_services_discovery ON services(service_name, version);
|
|
104
|
+
CREATE INDEX IF NOT EXISTS idx_services_username ON services(username);
|
|
105
105
|
CREATE INDEX IF NOT EXISTS idx_services_expires ON services(expires_at);
|
|
106
|
-
|
|
107
|
-
-- Service index table (privacy layer)
|
|
108
|
-
CREATE TABLE IF NOT EXISTS service_index (
|
|
109
|
-
uuid TEXT PRIMARY KEY,
|
|
110
|
-
service_id TEXT NOT NULL,
|
|
111
|
-
username TEXT NOT NULL,
|
|
112
|
-
service_fqn TEXT NOT NULL,
|
|
113
|
-
created_at INTEGER NOT NULL,
|
|
114
|
-
expires_at INTEGER NOT NULL,
|
|
115
|
-
FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
CREATE INDEX IF NOT EXISTS idx_service_index_username ON service_index(username);
|
|
119
|
-
CREATE INDEX IF NOT EXISTS idx_service_index_expires ON service_index(expires_at);
|
|
120
106
|
`);
|
|
121
107
|
|
|
122
108
|
// Enable foreign keys
|
|
@@ -139,8 +125,8 @@ export class SQLiteStorage implements Storage {
|
|
|
139
125
|
// Use transaction for atomic creation
|
|
140
126
|
const transaction = this.db.transaction((offersWithIds: (CreateOfferRequest & { id: string })[]) => {
|
|
141
127
|
const offerStmt = this.db.prepare(`
|
|
142
|
-
INSERT INTO offers (id,
|
|
143
|
-
VALUES (?, ?, ?, ?, ?, ?,
|
|
128
|
+
INSERT INTO offers (id, username, service_id, sdp, created_at, expires_at, last_seen)
|
|
129
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
144
130
|
`);
|
|
145
131
|
|
|
146
132
|
for (const offer of offersWithIds) {
|
|
@@ -149,24 +135,23 @@ export class SQLiteStorage implements Storage {
|
|
|
149
135
|
// Insert offer
|
|
150
136
|
offerStmt.run(
|
|
151
137
|
offer.id,
|
|
152
|
-
offer.
|
|
138
|
+
offer.username,
|
|
153
139
|
offer.serviceId || null,
|
|
154
140
|
offer.sdp,
|
|
155
141
|
now,
|
|
156
142
|
offer.expiresAt,
|
|
157
|
-
now
|
|
158
|
-
offer.secret || null
|
|
143
|
+
now
|
|
159
144
|
);
|
|
160
145
|
|
|
161
146
|
created.push({
|
|
162
147
|
id: offer.id,
|
|
163
|
-
|
|
148
|
+
username: offer.username,
|
|
164
149
|
serviceId: offer.serviceId || undefined,
|
|
150
|
+
serviceFqn: offer.serviceFqn,
|
|
165
151
|
sdp: offer.sdp,
|
|
166
152
|
createdAt: now,
|
|
167
153
|
expiresAt: offer.expiresAt,
|
|
168
154
|
lastSeen: now,
|
|
169
|
-
secret: offer.secret,
|
|
170
155
|
});
|
|
171
156
|
}
|
|
172
157
|
});
|
|
@@ -175,14 +160,14 @@ export class SQLiteStorage implements Storage {
|
|
|
175
160
|
return created;
|
|
176
161
|
}
|
|
177
162
|
|
|
178
|
-
async
|
|
163
|
+
async getOffersByUsername(username: string): Promise<Offer[]> {
|
|
179
164
|
const stmt = this.db.prepare(`
|
|
180
165
|
SELECT * FROM offers
|
|
181
|
-
WHERE
|
|
166
|
+
WHERE username = ? AND expires_at > ?
|
|
182
167
|
ORDER BY last_seen DESC
|
|
183
168
|
`);
|
|
184
169
|
|
|
185
|
-
const rows = stmt.all(
|
|
170
|
+
const rows = stmt.all(username, Date.now()) as any[];
|
|
186
171
|
return rows.map(row => this.rowToOffer(row));
|
|
187
172
|
}
|
|
188
173
|
|
|
@@ -201,13 +186,13 @@ export class SQLiteStorage implements Storage {
|
|
|
201
186
|
return this.rowToOffer(row);
|
|
202
187
|
}
|
|
203
188
|
|
|
204
|
-
async deleteOffer(offerId: string,
|
|
189
|
+
async deleteOffer(offerId: string, ownerUsername: string): Promise<boolean> {
|
|
205
190
|
const stmt = this.db.prepare(`
|
|
206
191
|
DELETE FROM offers
|
|
207
|
-
WHERE id = ? AND
|
|
192
|
+
WHERE id = ? AND username = ?
|
|
208
193
|
`);
|
|
209
194
|
|
|
210
|
-
const result = stmt.run(offerId,
|
|
195
|
+
const result = stmt.run(offerId, ownerUsername);
|
|
211
196
|
return result.changes > 0;
|
|
212
197
|
}
|
|
213
198
|
|
|
@@ -219,9 +204,8 @@ export class SQLiteStorage implements Storage {
|
|
|
219
204
|
|
|
220
205
|
async answerOffer(
|
|
221
206
|
offerId: string,
|
|
222
|
-
|
|
223
|
-
answerSdp: string
|
|
224
|
-
secret?: string
|
|
207
|
+
answererUsername: string,
|
|
208
|
+
answerSdp: string
|
|
225
209
|
): Promise<{ success: boolean; error?: string }> {
|
|
226
210
|
// Check if offer exists and is not expired
|
|
227
211
|
const offer = await this.getOfferById(offerId);
|
|
@@ -233,16 +217,8 @@ export class SQLiteStorage implements Storage {
|
|
|
233
217
|
};
|
|
234
218
|
}
|
|
235
219
|
|
|
236
|
-
// Verify secret if offer is protected
|
|
237
|
-
if (offer.secret && offer.secret !== secret) {
|
|
238
|
-
return {
|
|
239
|
-
success: false,
|
|
240
|
-
error: 'Invalid or missing secret'
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
|
|
244
220
|
// Check if offer already has an answerer
|
|
245
|
-
if (offer.
|
|
221
|
+
if (offer.answererUsername) {
|
|
246
222
|
return {
|
|
247
223
|
success: false,
|
|
248
224
|
error: 'Offer already answered'
|
|
@@ -252,11 +228,11 @@ export class SQLiteStorage implements Storage {
|
|
|
252
228
|
// Update offer with answer
|
|
253
229
|
const stmt = this.db.prepare(`
|
|
254
230
|
UPDATE offers
|
|
255
|
-
SET
|
|
256
|
-
WHERE id = ? AND
|
|
231
|
+
SET answerer_username = ?, answer_sdp = ?, answered_at = ?
|
|
232
|
+
WHERE id = ? AND answerer_username IS NULL
|
|
257
233
|
`);
|
|
258
234
|
|
|
259
|
-
const result = stmt.run(
|
|
235
|
+
const result = stmt.run(answererUsername, answerSdp, Date.now(), offerId);
|
|
260
236
|
|
|
261
237
|
if (result.changes === 0) {
|
|
262
238
|
return {
|
|
@@ -268,14 +244,14 @@ export class SQLiteStorage implements Storage {
|
|
|
268
244
|
return { success: true };
|
|
269
245
|
}
|
|
270
246
|
|
|
271
|
-
async getAnsweredOffers(
|
|
247
|
+
async getAnsweredOffers(offererUsername: string): Promise<Offer[]> {
|
|
272
248
|
const stmt = this.db.prepare(`
|
|
273
249
|
SELECT * FROM offers
|
|
274
|
-
WHERE
|
|
250
|
+
WHERE username = ? AND answerer_username IS NOT NULL AND expires_at > ?
|
|
275
251
|
ORDER BY answered_at DESC
|
|
276
252
|
`);
|
|
277
253
|
|
|
278
|
-
const rows = stmt.all(
|
|
254
|
+
const rows = stmt.all(offererUsername, Date.now()) as any[];
|
|
279
255
|
return rows.map(row => this.rowToOffer(row));
|
|
280
256
|
}
|
|
281
257
|
|
|
@@ -283,12 +259,12 @@ export class SQLiteStorage implements Storage {
|
|
|
283
259
|
|
|
284
260
|
async addIceCandidates(
|
|
285
261
|
offerId: string,
|
|
286
|
-
|
|
262
|
+
username: string,
|
|
287
263
|
role: 'offerer' | 'answerer',
|
|
288
264
|
candidates: any[]
|
|
289
265
|
): Promise<number> {
|
|
290
266
|
const stmt = this.db.prepare(`
|
|
291
|
-
INSERT INTO ice_candidates (offer_id,
|
|
267
|
+
INSERT INTO ice_candidates (offer_id, username, role, candidate, created_at)
|
|
292
268
|
VALUES (?, ?, ?, ?, ?)
|
|
293
269
|
`);
|
|
294
270
|
|
|
@@ -297,7 +273,7 @@ export class SQLiteStorage implements Storage {
|
|
|
297
273
|
for (let i = 0; i < candidates.length; i++) {
|
|
298
274
|
stmt.run(
|
|
299
275
|
offerId,
|
|
300
|
-
|
|
276
|
+
username,
|
|
301
277
|
role,
|
|
302
278
|
JSON.stringify(candidates[i]),
|
|
303
279
|
baseTimestamp + i
|
|
@@ -334,7 +310,7 @@ export class SQLiteStorage implements Storage {
|
|
|
334
310
|
return rows.map(row => ({
|
|
335
311
|
id: row.id,
|
|
336
312
|
offerId: row.offer_id,
|
|
337
|
-
|
|
313
|
+
username: row.username,
|
|
338
314
|
role: row.role,
|
|
339
315
|
candidate: JSON.parse(row.candidate),
|
|
340
316
|
createdAt: row.created_at,
|
|
@@ -427,87 +403,96 @@ export class SQLiteStorage implements Storage {
|
|
|
427
403
|
|
|
428
404
|
async createService(request: CreateServiceRequest): Promise<{
|
|
429
405
|
service: Service;
|
|
430
|
-
indexUuid: string;
|
|
431
406
|
offers: Offer[];
|
|
432
407
|
}> {
|
|
433
408
|
const serviceId = randomUUID();
|
|
434
|
-
const indexUuid = randomUUID();
|
|
435
409
|
const now = Date.now();
|
|
436
410
|
|
|
437
|
-
//
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
}
|
|
411
|
+
// Parse FQN to extract components
|
|
412
|
+
const parsed = parseServiceFqn(request.serviceFqn);
|
|
413
|
+
if (!parsed) {
|
|
414
|
+
throw new Error(`Invalid service FQN: ${request.serviceFqn}`);
|
|
415
|
+
}
|
|
416
|
+
if (!parsed.username) {
|
|
417
|
+
throw new Error(`Service FQN must include username: ${request.serviceFqn}`);
|
|
418
|
+
}
|
|
442
419
|
|
|
443
|
-
const
|
|
420
|
+
const { serviceName, version, username } = parsed;
|
|
444
421
|
|
|
445
422
|
const transaction = this.db.transaction(() => {
|
|
446
|
-
//
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
`);
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
const indexStmt = this.db.prepare(`
|
|
464
|
-
INSERT INTO service_index (uuid, service_id, username, service_fqn, created_at, expires_at)
|
|
465
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
466
|
-
`);
|
|
423
|
+
// Delete existing service with same (service_name, version, username) and its related offers (upsert behavior)
|
|
424
|
+
const existingService = this.db.prepare(`
|
|
425
|
+
SELECT id FROM services
|
|
426
|
+
WHERE service_name = ? AND version = ? AND username = ?
|
|
427
|
+
`).get(serviceName, version, username) as any;
|
|
428
|
+
|
|
429
|
+
if (existingService) {
|
|
430
|
+
// Delete related offers first (no FK cascade from offers to services)
|
|
431
|
+
this.db.prepare(`
|
|
432
|
+
DELETE FROM offers WHERE service_id = ?
|
|
433
|
+
`).run(existingService.id);
|
|
434
|
+
|
|
435
|
+
// Delete the service
|
|
436
|
+
this.db.prepare(`
|
|
437
|
+
DELETE FROM services WHERE id = ?
|
|
438
|
+
`).run(existingService.id);
|
|
439
|
+
}
|
|
467
440
|
|
|
468
|
-
|
|
469
|
-
|
|
441
|
+
// Insert new service with extracted fields
|
|
442
|
+
this.db.prepare(`
|
|
443
|
+
INSERT INTO services (id, service_fqn, service_name, version, username, created_at, expires_at)
|
|
444
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
445
|
+
`).run(
|
|
470
446
|
serviceId,
|
|
471
|
-
request.username,
|
|
472
447
|
request.serviceFqn,
|
|
448
|
+
serviceName,
|
|
449
|
+
version,
|
|
450
|
+
username,
|
|
473
451
|
now,
|
|
474
452
|
request.expiresAt
|
|
475
453
|
);
|
|
476
454
|
|
|
477
|
-
// Touch username to extend expiry
|
|
478
|
-
|
|
455
|
+
// Touch username to extend expiry (inline logic)
|
|
456
|
+
const expiresAt = now + YEAR_IN_MS;
|
|
457
|
+
this.db.prepare(`
|
|
458
|
+
UPDATE usernames
|
|
459
|
+
SET last_used = ?, expires_at = ?
|
|
460
|
+
WHERE username = ? AND expires_at > ?
|
|
461
|
+
`).run(now, expiresAt, username, now);
|
|
479
462
|
});
|
|
480
463
|
|
|
481
464
|
transaction();
|
|
482
465
|
|
|
466
|
+
// Create offers with serviceId (after transaction)
|
|
467
|
+
const offerRequests = request.offers.map(offer => ({
|
|
468
|
+
...offer,
|
|
469
|
+
serviceId,
|
|
470
|
+
}));
|
|
471
|
+
const offers = await this.createOffers(offerRequests);
|
|
472
|
+
|
|
483
473
|
return {
|
|
484
474
|
service: {
|
|
485
475
|
id: serviceId,
|
|
486
|
-
username: request.username,
|
|
487
476
|
serviceFqn: request.serviceFqn,
|
|
477
|
+
serviceName,
|
|
478
|
+
version,
|
|
479
|
+
username,
|
|
488
480
|
createdAt: now,
|
|
489
481
|
expiresAt: request.expiresAt,
|
|
490
|
-
isPublic: request.isPublic || false,
|
|
491
|
-
metadata: request.metadata,
|
|
492
482
|
},
|
|
493
|
-
indexUuid,
|
|
494
483
|
offers,
|
|
495
484
|
};
|
|
496
485
|
}
|
|
497
486
|
|
|
498
|
-
async
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
for (const request of requests) {
|
|
506
|
-
const result = await this.createService(request);
|
|
507
|
-
results.push(result);
|
|
508
|
-
}
|
|
487
|
+
async getOffersForService(serviceId: string): Promise<Offer[]> {
|
|
488
|
+
const stmt = this.db.prepare(`
|
|
489
|
+
SELECT * FROM offers
|
|
490
|
+
WHERE service_id = ? AND expires_at > ?
|
|
491
|
+
ORDER BY created_at ASC
|
|
492
|
+
`);
|
|
509
493
|
|
|
510
|
-
|
|
494
|
+
const rows = stmt.all(serviceId, Date.now()) as any[];
|
|
495
|
+
return rows.map(row => this.rowToOffer(row));
|
|
511
496
|
}
|
|
512
497
|
|
|
513
498
|
async getServiceById(serviceId: string): Promise<Service | null> {
|
|
@@ -525,14 +510,13 @@ export class SQLiteStorage implements Storage {
|
|
|
525
510
|
return this.rowToService(row);
|
|
526
511
|
}
|
|
527
512
|
|
|
528
|
-
async
|
|
513
|
+
async getServiceByFqn(serviceFqn: string): Promise<Service | null> {
|
|
529
514
|
const stmt = this.db.prepare(`
|
|
530
|
-
SELECT
|
|
531
|
-
|
|
532
|
-
WHERE si.uuid = ? AND s.expires_at > ?
|
|
515
|
+
SELECT * FROM services
|
|
516
|
+
WHERE service_fqn = ? AND expires_at > ?
|
|
533
517
|
`);
|
|
534
518
|
|
|
535
|
-
const row = stmt.get(
|
|
519
|
+
const row = stmt.get(serviceFqn, Date.now()) as any;
|
|
536
520
|
|
|
537
521
|
if (!row) {
|
|
538
522
|
return null;
|
|
@@ -541,47 +525,51 @@ export class SQLiteStorage implements Storage {
|
|
|
541
525
|
return this.rowToService(row);
|
|
542
526
|
}
|
|
543
527
|
|
|
544
|
-
async
|
|
528
|
+
async discoverServices(
|
|
529
|
+
serviceName: string,
|
|
530
|
+
version: string,
|
|
531
|
+
limit: number,
|
|
532
|
+
offset: number
|
|
533
|
+
): Promise<Service[]> {
|
|
534
|
+
// Query for unique services with available offers
|
|
535
|
+
// We join with offers and filter for available ones (answerer_username IS NULL)
|
|
545
536
|
const stmt = this.db.prepare(`
|
|
546
|
-
SELECT
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
537
|
+
SELECT DISTINCT s.* FROM services s
|
|
538
|
+
INNER JOIN offers o ON o.service_id = s.id
|
|
539
|
+
WHERE s.service_name = ?
|
|
540
|
+
AND s.version = ?
|
|
541
|
+
AND s.expires_at > ?
|
|
542
|
+
AND o.answerer_username IS NULL
|
|
543
|
+
AND o.expires_at > ?
|
|
550
544
|
ORDER BY s.created_at DESC
|
|
545
|
+
LIMIT ? OFFSET ?
|
|
551
546
|
`);
|
|
552
547
|
|
|
553
|
-
const rows = stmt.all(
|
|
554
|
-
|
|
555
|
-
return rows.map(row => ({
|
|
556
|
-
uuid: row.uuid,
|
|
557
|
-
isPublic: row.is_public === 1,
|
|
558
|
-
serviceFqn: row.is_public === 1 ? row.service_fqn : undefined,
|
|
559
|
-
metadata: row.is_public === 1 ? row.metadata || undefined : undefined,
|
|
560
|
-
}));
|
|
548
|
+
const rows = stmt.all(serviceName, version, Date.now(), Date.now(), limit, offset) as any[];
|
|
549
|
+
return rows.map(row => this.rowToService(row));
|
|
561
550
|
}
|
|
562
551
|
|
|
563
|
-
async
|
|
552
|
+
async getRandomService(serviceName: string, version: string): Promise<Service | null> {
|
|
553
|
+
// Get a random service with an available offer
|
|
564
554
|
const stmt = this.db.prepare(`
|
|
565
|
-
SELECT
|
|
566
|
-
INNER JOIN
|
|
567
|
-
WHERE
|
|
555
|
+
SELECT s.* FROM services s
|
|
556
|
+
INNER JOIN offers o ON o.service_id = s.id
|
|
557
|
+
WHERE s.service_name = ?
|
|
558
|
+
AND s.version = ?
|
|
559
|
+
AND s.expires_at > ?
|
|
560
|
+
AND o.answerer_username IS NULL
|
|
561
|
+
AND o.expires_at > ?
|
|
562
|
+
ORDER BY RANDOM()
|
|
563
|
+
LIMIT 1
|
|
568
564
|
`);
|
|
569
565
|
|
|
570
|
-
const row = stmt.get(
|
|
571
|
-
|
|
572
|
-
return row ? row.uuid : null;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
async findServicesByName(username: string, serviceName: string): Promise<Service[]> {
|
|
576
|
-
const stmt = this.db.prepare(`
|
|
577
|
-
SELECT * FROM services
|
|
578
|
-
WHERE username = ? AND service_fqn LIKE ? AND expires_at > ?
|
|
579
|
-
ORDER BY created_at DESC
|
|
580
|
-
`);
|
|
566
|
+
const row = stmt.get(serviceName, version, Date.now(), Date.now()) as any;
|
|
581
567
|
|
|
582
|
-
|
|
568
|
+
if (!row) {
|
|
569
|
+
return null;
|
|
570
|
+
}
|
|
583
571
|
|
|
584
|
-
return
|
|
572
|
+
return this.rowToService(row);
|
|
585
573
|
}
|
|
586
574
|
|
|
587
575
|
async deleteService(serviceId: string, username: string): Promise<boolean> {
|
|
@@ -612,14 +600,14 @@ export class SQLiteStorage implements Storage {
|
|
|
612
600
|
private rowToOffer(row: any): Offer {
|
|
613
601
|
return {
|
|
614
602
|
id: row.id,
|
|
615
|
-
|
|
603
|
+
username: row.username,
|
|
616
604
|
serviceId: row.service_id || undefined,
|
|
605
|
+
serviceFqn: row.service_fqn || undefined,
|
|
617
606
|
sdp: row.sdp,
|
|
618
607
|
createdAt: row.created_at,
|
|
619
608
|
expiresAt: row.expires_at,
|
|
620
609
|
lastSeen: row.last_seen,
|
|
621
|
-
|
|
622
|
-
answererPeerId: row.answerer_peer_id || undefined,
|
|
610
|
+
answererUsername: row.answerer_username || undefined,
|
|
623
611
|
answerSdp: row.answer_sdp || undefined,
|
|
624
612
|
answeredAt: row.answered_at || undefined,
|
|
625
613
|
};
|
|
@@ -631,26 +619,12 @@ export class SQLiteStorage implements Storage {
|
|
|
631
619
|
private rowToService(row: any): Service {
|
|
632
620
|
return {
|
|
633
621
|
id: row.id,
|
|
634
|
-
username: row.username,
|
|
635
622
|
serviceFqn: row.service_fqn,
|
|
623
|
+
serviceName: row.service_name,
|
|
624
|
+
version: row.version,
|
|
625
|
+
username: row.username,
|
|
636
626
|
createdAt: row.created_at,
|
|
637
627
|
expiresAt: row.expires_at,
|
|
638
|
-
isPublic: row.is_public === 1,
|
|
639
|
-
metadata: row.metadata || undefined,
|
|
640
628
|
};
|
|
641
629
|
}
|
|
642
|
-
|
|
643
|
-
/**
|
|
644
|
-
* Get all offers for a service
|
|
645
|
-
*/
|
|
646
|
-
async getOffersForService(serviceId: string): Promise<Offer[]> {
|
|
647
|
-
const stmt = this.db.prepare(`
|
|
648
|
-
SELECT * FROM offers
|
|
649
|
-
WHERE service_id = ? AND expires_at > ?
|
|
650
|
-
ORDER BY created_at ASC
|
|
651
|
-
`);
|
|
652
|
-
|
|
653
|
-
const rows = stmt.all(serviceId, Date.now()) as any[];
|
|
654
|
-
return rows.map(row => this.rowToOffer(row));
|
|
655
|
-
}
|
|
656
630
|
}
|