@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.
@@ -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
- # Install production dependencies only
34
+ # Copy package files and install production deps
26
35
  COPY package*.json ./
27
36
  RUN npm ci --omit=dev && \
28
- npm cache clean --force
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=sqlite
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 git commit hash for version (like Cloudflare Workers deployment)
6
- let version = 'unknown';
7
- try {
8
- version = execSync('git rev-parse --short HEAD', { encoding: 'utf8' }).trim();
9
- } catch (e) {
10
- console.warn('Could not get git commit hash, using "unknown"');
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 ? "aa71918" : "unknown";
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: 3e4,
2952
- // 30 seconds TTL after offer is answered
3053
+ answeredOfferTtl: 6e4,
3054
+ // 60 seconds TTL after offer is answered
2953
3055
  cleanupInterval: 6e4,
2954
3056
  maxOffersPerRequest: 100,
2955
3057
  maxBatchSize: 100,