@xtr-dev/rondevu-server 0.5.20 → 0.5.25
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/.github/workflows/docker-publish.yml +59 -0
- package/Dockerfile +21 -10
- package/build.js +10 -6
- package/dist/index.js +106 -4
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
- package/src/config.ts +1 -1
- package/src/rpc.ts +30 -1
- package/src/storage/d1.ts +69 -40
- package/src/storage/memory.ts +39 -0
- package/src/storage/mysql.ts +23 -0
- package/src/storage/postgres.ts +23 -0
- package/src/storage/sqlite.ts +25 -0
- package/src/storage/types.ts +8 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
name: Build and Publish Docker Image
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
tags: ['v*']
|
|
7
|
+
pull_request:
|
|
8
|
+
branches: [main]
|
|
9
|
+
|
|
10
|
+
env:
|
|
11
|
+
REGISTRY: ghcr.io
|
|
12
|
+
IMAGE_NAME: ${{ github.repository }}
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
build-and-push:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
permissions:
|
|
18
|
+
contents: read
|
|
19
|
+
packages: write
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- name: Checkout repository
|
|
23
|
+
uses: actions/checkout@v4
|
|
24
|
+
|
|
25
|
+
- name: Set up Docker Buildx
|
|
26
|
+
uses: docker/setup-buildx-action@v3
|
|
27
|
+
|
|
28
|
+
- name: Log in to Container Registry
|
|
29
|
+
if: github.event_name != 'pull_request'
|
|
30
|
+
uses: docker/login-action@v3
|
|
31
|
+
with:
|
|
32
|
+
registry: ${{ env.REGISTRY }}
|
|
33
|
+
username: ${{ github.actor }}
|
|
34
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
35
|
+
|
|
36
|
+
- name: Extract metadata for Docker
|
|
37
|
+
id: meta
|
|
38
|
+
uses: docker/metadata-action@v5
|
|
39
|
+
with:
|
|
40
|
+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
41
|
+
tags: |
|
|
42
|
+
type=ref,event=branch
|
|
43
|
+
type=ref,event=pr
|
|
44
|
+
type=semver,pattern={{version}}
|
|
45
|
+
type=semver,pattern={{major}}.{{minor}}
|
|
46
|
+
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
|
47
|
+
type=sha,prefix=
|
|
48
|
+
|
|
49
|
+
- name: Build and push Docker image
|
|
50
|
+
uses: docker/build-push-action@v5
|
|
51
|
+
with:
|
|
52
|
+
context: .
|
|
53
|
+
push: ${{ github.event_name != 'pull_request' }}
|
|
54
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
55
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
56
|
+
build-args: |
|
|
57
|
+
VERSION=${{ github.sha }}
|
|
58
|
+
cache-from: type=gha
|
|
59
|
+
cache-to: type=gha,mode=max
|
package/Dockerfile
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
# Build stage
|
|
2
2
|
FROM node:20-alpine AS builder
|
|
3
3
|
|
|
4
|
+
# Version is passed as build arg (from git commit hash)
|
|
5
|
+
ARG VERSION=unknown
|
|
6
|
+
|
|
7
|
+
# Install build tools for better-sqlite3
|
|
8
|
+
RUN apk add --no-cache python3 make g++
|
|
9
|
+
|
|
4
10
|
WORKDIR /app
|
|
5
11
|
|
|
6
12
|
# Copy package files
|
|
7
13
|
COPY package*.json ./
|
|
8
14
|
|
|
9
|
-
# Install dependencies
|
|
15
|
+
# Install dependencies (including native modules)
|
|
10
16
|
RUN npm ci
|
|
11
17
|
|
|
12
18
|
# Copy source files
|
|
@@ -14,22 +20,30 @@ COPY tsconfig.json ./
|
|
|
14
20
|
COPY build.js ./
|
|
15
21
|
COPY src ./src
|
|
16
22
|
|
|
17
|
-
# Build TypeScript
|
|
18
|
-
RUN npm run build
|
|
23
|
+
# Build TypeScript with version embedded
|
|
24
|
+
RUN VERSION=$VERSION npm run build
|
|
19
25
|
|
|
20
26
|
# Production stage
|
|
21
27
|
FROM node:20-alpine
|
|
22
28
|
|
|
29
|
+
# Install build tools for better-sqlite3 native module
|
|
30
|
+
RUN apk add --no-cache python3 make g++
|
|
31
|
+
|
|
23
32
|
WORKDIR /app
|
|
24
33
|
|
|
25
|
-
#
|
|
34
|
+
# Copy package files and install production deps
|
|
26
35
|
COPY package*.json ./
|
|
27
36
|
RUN npm ci --omit=dev && \
|
|
28
|
-
npm
|
|
37
|
+
npm rebuild better-sqlite3 && \
|
|
38
|
+
npm cache clean --force && \
|
|
39
|
+
apk del python3 make g++
|
|
29
40
|
|
|
30
41
|
# Copy built files from builder
|
|
31
42
|
COPY --from=builder /app/dist ./dist
|
|
32
43
|
|
|
44
|
+
# Copy migrations for schema setup
|
|
45
|
+
COPY migrations ./migrations
|
|
46
|
+
|
|
33
47
|
# Create data directory for SQLite
|
|
34
48
|
RUN mkdir -p /app/data && \
|
|
35
49
|
chown -R node:node /app
|
|
@@ -39,12 +53,9 @@ USER node
|
|
|
39
53
|
|
|
40
54
|
# Environment variables with defaults
|
|
41
55
|
ENV PORT=3000
|
|
42
|
-
ENV STORAGE_TYPE=
|
|
43
|
-
ENV STORAGE_PATH=/app/data/sessions.db
|
|
44
|
-
ENV SESSION_TIMEOUT=300000
|
|
45
|
-
ENV CODE_CHARS=0123456789
|
|
46
|
-
ENV CODE_LENGTH=9
|
|
56
|
+
ENV STORAGE_TYPE=memory
|
|
47
57
|
ENV CORS_ORIGINS=*
|
|
58
|
+
ENV NODE_ENV=production
|
|
48
59
|
|
|
49
60
|
# Expose port
|
|
50
61
|
EXPOSE 3000
|
package/build.js
CHANGED
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
const esbuild = require('esbuild');
|
|
3
3
|
const { execSync } = require('child_process');
|
|
4
4
|
|
|
5
|
-
// Use
|
|
6
|
-
let version =
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
// Use VERSION env var first (for Docker builds), then fall back to git commit hash
|
|
6
|
+
let version = process.env.VERSION;
|
|
7
|
+
if (!version || version === 'unknown') {
|
|
8
|
+
try {
|
|
9
|
+
version = execSync('git rev-parse --short HEAD', { encoding: 'utf8' }).trim();
|
|
10
|
+
} catch (e) {
|
|
11
|
+
console.warn('Could not get git commit hash, using "unknown"');
|
|
12
|
+
version = 'unknown';
|
|
13
|
+
}
|
|
11
14
|
}
|
|
15
|
+
console.log(`Building with version: ${version}`);
|
|
12
16
|
|
|
13
17
|
esbuild.build({
|
|
14
18
|
entryPoints: ['src/index.ts'],
|
package/dist/index.js
CHANGED
|
@@ -354,6 +354,38 @@ var init_memory = __esm({
|
|
|
354
354
|
const candidates = this.iceCandidates.get(offerId);
|
|
355
355
|
return candidates ? candidates.length : 0;
|
|
356
356
|
}
|
|
357
|
+
async countOffersByTags(tags, unique = false) {
|
|
358
|
+
const result = /* @__PURE__ */ new Map();
|
|
359
|
+
if (tags.length === 0) return result;
|
|
360
|
+
const now = Date.now();
|
|
361
|
+
for (const tag of tags) {
|
|
362
|
+
const offerIds = this.offersByTag.get(tag);
|
|
363
|
+
if (!offerIds) {
|
|
364
|
+
result.set(tag, 0);
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
if (unique) {
|
|
368
|
+
const uniquePublicKeys = /* @__PURE__ */ new Set();
|
|
369
|
+
for (const offerId of offerIds) {
|
|
370
|
+
const offer = this.offers.get(offerId);
|
|
371
|
+
if (offer && offer.expiresAt > now && !offer.answererPublicKey) {
|
|
372
|
+
uniquePublicKeys.add(offer.publicKey);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
result.set(tag, uniquePublicKeys.size);
|
|
376
|
+
} else {
|
|
377
|
+
let count = 0;
|
|
378
|
+
for (const offerId of offerIds) {
|
|
379
|
+
const offer = this.offers.get(offerId);
|
|
380
|
+
if (offer && offer.expiresAt > now && !offer.answererPublicKey) {
|
|
381
|
+
count++;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
result.set(tag, count);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return result;
|
|
388
|
+
}
|
|
357
389
|
// ===== Helper Methods =====
|
|
358
390
|
removeOfferFromIndexes(offer) {
|
|
359
391
|
const publicKeyOffers = this.offersByPublicKey.get(offer.publicKey);
|
|
@@ -793,6 +825,24 @@ var init_sqlite = __esm({
|
|
|
793
825
|
const result = this.db.prepare("SELECT COUNT(*) as count FROM ice_candidates WHERE offer_id = ?").get(offerId);
|
|
794
826
|
return result.count;
|
|
795
827
|
}
|
|
828
|
+
async countOffersByTags(tags, unique = false) {
|
|
829
|
+
const result = /* @__PURE__ */ new Map();
|
|
830
|
+
if (tags.length === 0) return result;
|
|
831
|
+
const now = Date.now();
|
|
832
|
+
const countColumn = unique ? "COUNT(DISTINCT o.public_key)" : "COUNT(DISTINCT o.id)";
|
|
833
|
+
const stmt = this.db.prepare(`
|
|
834
|
+
SELECT ${countColumn} as count
|
|
835
|
+
FROM offers o, json_each(o.tags) as t
|
|
836
|
+
WHERE t.value = ?
|
|
837
|
+
AND o.expires_at > ?
|
|
838
|
+
AND o.answerer_public_key IS NULL
|
|
839
|
+
`);
|
|
840
|
+
for (const tag of tags) {
|
|
841
|
+
const row = stmt.get(tag, now);
|
|
842
|
+
result.set(tag, row.count);
|
|
843
|
+
}
|
|
844
|
+
return result;
|
|
845
|
+
}
|
|
796
846
|
// ===== Helper Methods =====
|
|
797
847
|
/**
|
|
798
848
|
* Helper method to convert database row to Offer object
|
|
@@ -1181,6 +1231,24 @@ var init_mysql = __esm({
|
|
|
1181
1231
|
);
|
|
1182
1232
|
return Number(rows[0].count);
|
|
1183
1233
|
}
|
|
1234
|
+
async countOffersByTags(tags, unique = false) {
|
|
1235
|
+
const result = /* @__PURE__ */ new Map();
|
|
1236
|
+
if (tags.length === 0) return result;
|
|
1237
|
+
const now = Date.now();
|
|
1238
|
+
for (const tag of tags) {
|
|
1239
|
+
const countColumn = unique ? "COUNT(DISTINCT public_key)" : "COUNT(DISTINCT id)";
|
|
1240
|
+
const [rows] = await this.pool.query(
|
|
1241
|
+
`SELECT ${countColumn} as count
|
|
1242
|
+
FROM offers
|
|
1243
|
+
WHERE JSON_CONTAINS(tags, ?)
|
|
1244
|
+
AND expires_at > ?
|
|
1245
|
+
AND answerer_public_key IS NULL`,
|
|
1246
|
+
[JSON.stringify(tag), now]
|
|
1247
|
+
);
|
|
1248
|
+
result.set(tag, Number(rows[0].count));
|
|
1249
|
+
}
|
|
1250
|
+
return result;
|
|
1251
|
+
}
|
|
1184
1252
|
// ===== Helper Methods =====
|
|
1185
1253
|
rowToOffer(row) {
|
|
1186
1254
|
return {
|
|
@@ -1581,6 +1649,24 @@ var init_postgres = __esm({
|
|
|
1581
1649
|
);
|
|
1582
1650
|
return Number(result.rows[0].count);
|
|
1583
1651
|
}
|
|
1652
|
+
async countOffersByTags(tags, unique = false) {
|
|
1653
|
+
const result = /* @__PURE__ */ new Map();
|
|
1654
|
+
if (tags.length === 0) return result;
|
|
1655
|
+
const now = Date.now();
|
|
1656
|
+
for (const tag of tags) {
|
|
1657
|
+
const countColumn = unique ? "COUNT(DISTINCT public_key)" : "COUNT(DISTINCT id)";
|
|
1658
|
+
const queryResult = await this.pool.query(
|
|
1659
|
+
`SELECT ${countColumn} as count
|
|
1660
|
+
FROM offers
|
|
1661
|
+
WHERE tags ? $1
|
|
1662
|
+
AND expires_at > $2
|
|
1663
|
+
AND answerer_public_key IS NULL`,
|
|
1664
|
+
[tag, now]
|
|
1665
|
+
);
|
|
1666
|
+
result.set(tag, Number(queryResult.rows[0].count));
|
|
1667
|
+
}
|
|
1668
|
+
return result;
|
|
1669
|
+
}
|
|
1584
1670
|
// ===== Helper Methods =====
|
|
1585
1671
|
rowToOffer(row) {
|
|
1586
1672
|
return {
|
|
@@ -2365,6 +2451,22 @@ var handlers = {
|
|
|
2365
2451
|
expiresAt: offer.expiresAt
|
|
2366
2452
|
};
|
|
2367
2453
|
},
|
|
2454
|
+
/**
|
|
2455
|
+
* Count available offers by tags
|
|
2456
|
+
*/
|
|
2457
|
+
async countOffersByTags(params, publicKey, timestamp, signature, storage, config, request) {
|
|
2458
|
+
const { tags, unique } = params;
|
|
2459
|
+
const tagsValidation = validateTags(tags);
|
|
2460
|
+
if (!tagsValidation.valid) {
|
|
2461
|
+
throw new RpcError(ErrorCodes.INVALID_TAG, tagsValidation.error || "Invalid tags");
|
|
2462
|
+
}
|
|
2463
|
+
const counts = await storage.countOffersByTags(tags, unique === true);
|
|
2464
|
+
const result = {};
|
|
2465
|
+
for (const [tag, count] of counts) {
|
|
2466
|
+
result[tag] = count;
|
|
2467
|
+
}
|
|
2468
|
+
return { counts: result };
|
|
2469
|
+
},
|
|
2368
2470
|
/**
|
|
2369
2471
|
* Publish offers with tags
|
|
2370
2472
|
*/
|
|
@@ -2661,7 +2763,7 @@ var handlers = {
|
|
|
2661
2763
|
};
|
|
2662
2764
|
}
|
|
2663
2765
|
};
|
|
2664
|
-
var UNAUTHENTICATED_METHODS = /* @__PURE__ */ new Set(["discover"]);
|
|
2766
|
+
var UNAUTHENTICATED_METHODS = /* @__PURE__ */ new Set(["discover", "countOffersByTags"]);
|
|
2665
2767
|
async function handleRpc(requests, ctx, storage, config) {
|
|
2666
2768
|
const responses = [];
|
|
2667
2769
|
const clientIp = ctx.req.header("cf-connecting-ip") || ctx.req.header("x-real-ip") || ctx.req.header("x-forwarded-for")?.split(",")[0].trim() || void 0;
|
|
@@ -2898,7 +3000,7 @@ function createApp(storage, config) {
|
|
|
2898
3000
|
}
|
|
2899
3001
|
|
|
2900
3002
|
// src/config.ts
|
|
2901
|
-
var BUILD_VERSION = true ? "
|
|
3003
|
+
var BUILD_VERSION = true ? "33a7f9c" : "unknown";
|
|
2902
3004
|
function loadConfig() {
|
|
2903
3005
|
function parsePositiveInt(value, defaultValue, name, min = 1) {
|
|
2904
3006
|
const parsed = parseInt(value || defaultValue, 10);
|
|
@@ -2948,8 +3050,8 @@ var CONFIG_DEFAULTS = {
|
|
|
2948
3050
|
offerDefaultTtl: 6e4,
|
|
2949
3051
|
offerMaxTtl: 864e5,
|
|
2950
3052
|
offerMinTtl: 6e4,
|
|
2951
|
-
answeredOfferTtl:
|
|
2952
|
-
//
|
|
3053
|
+
answeredOfferTtl: 6e4,
|
|
3054
|
+
// 60 seconds TTL after offer is answered
|
|
2953
3055
|
cleanupInterval: 6e4,
|
|
2954
3056
|
maxOffersPerRequest: 100,
|
|
2955
3057
|
maxBatchSize: 100,
|