Skip to content

Commit 428efd3

Browse files
authored
Implement v2 invites API with metadata support and tests (#140)
Add GET /api/v2/invites/:slug endpoint that decodes base64url-encoded protobuf invites with signature verification and returns metadata fields (name, description, imageURL). Key changes: - Add v2 invite protobuf schema with optional metadata fields - Implement slug decoding with secp256k1 signature recovery - Add 2048-char slug length limit for DoS protection (413 response) - Add test suite covering error cases and security - Fix Docker build to install devDependencies for code generation - Update Claude Code workflow to use CLAUDE_MODEL repo var
1 parent 83672b0 commit 428efd3

18 files changed

Lines changed: 448 additions & 359 deletions

File tree

.github/workflows/claude-code-review.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ jobs:
3939
with:
4040
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
4141

42-
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4)
43-
# model: "claude-opus-4-20250514"
42+
# Optional: Specify model (defaults to Claude Sonnet)
43+
model: ${{ vars.CLAUDE_MODEL || 'claude-sonnet-4-5-20250929' }}
4444

4545
# Direct prompt for automated review (no @claude mention needed)
4646
direct_prompt: |

.github/workflows/claude.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ jobs:
4040
additional_permissions: |
4141
actions: read
4242
43-
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4)
44-
# model: "claude-opus-4-20250514"
43+
# Optional: Specify model (defaults to Claude Sonnet)
44+
model: ${{ vars.CLAUDE_MODEL || 'claude-sonnet-4-5-20250929' }}
4545

4646
# Optional: Customize the trigger phrase (default: @claude)
4747
# trigger_phrase: "/claude"

.github/workflows/validate-code-quality.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ jobs:
1818
with:
1919
bun-version-file: ".bun-version"
2020
- run: bun install
21+
- run: bun run buf:generate
2122
- run: ./dev/up
2223
env:
2324
TEST_NOTIFICATION_DELIVERY_URL: "http://host.docker.internal:8081"

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ logs
6464
# Prisma generated files
6565
/prisma/generated/
6666

67+
# Protobuf generated files
68+
/src/gen/
69+
6770
# Claude
6871
.claude
69-

Dockerfile

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,28 @@ RUN curl -fsSL https://bun.sh/install | bash -s "bun-v1.2.2" && \
1111
ln -s $HOME/.bun/bin/bun /usr/local/bin/bun
1212

1313
RUN mkdir -p /temp/prod
14-
COPY package.json bun.lock tsconfig.json /temp/prod/
14+
COPY package.json bun.lock tsconfig.json buf.yaml buf.gen.yaml /temp/prod/
1515
RUN mkdir -p /temp/prod/src
1616
COPY src /temp/prod/src
1717
RUN mkdir -p /temp/prod/prisma
1818
COPY prisma /temp/prod/prisma
19-
RUN cd /temp/prod && bun install --frozen-lockfile --production
19+
RUN mkdir -p /temp/prod/proto
20+
COPY proto /temp/prod/proto
21+
22+
# Install all dependencies (including devDependencies needed for code generation)
23+
RUN cd /temp/prod && bun install --frozen-lockfile
2024

2125
ENV NODE_ENV=production
2226

27+
# generate protobuf types
28+
RUN cd /temp/prod && bun run buf:generate
29+
2330
# generate Prisma client
2431
RUN cd /temp/prod && bun prisma generate
2532

33+
# Remove devDependencies to keep the image small
34+
RUN cd /temp/prod && bun install --frozen-lockfile --production
35+
2636
FROM base AS release
2737

2838
# install bun

buf.gen.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
version: v2
22
plugins:
33
- local: protoc-gen-es
4-
out: src/notifications/gen
4+
out: src/gen
55
include_imports: true
66
opt: target=ts

bun.lock

Lines changed: 21 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

eslint.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const gitignorePath = path.resolve(__dirname, ".gitignore");
1414
export default tseslint.config(
1515
includeIgnoreFile(gitignorePath),
1616
{
17-
ignores: [".yarn/**/*", "src/notifications/gen/**"],
17+
ignores: [".yarn/**/*", "src/gen/**"],
1818
},
1919
eslint.configs.recommended,
2020
...tseslint.configs.strictTypeChecked,

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252
"pino": "^9.6.0",
5353
"pino-http": "^10.4.0",
5454
"pino-pretty": "^13.0.0",
55+
"protobufjs": "^7.5.4",
56+
"secp256k1": "^5.0.1",
5557
"thirdweb": "^5.88.2",
5658
"uint8array-extras": "^1.4.0",
5759
"uuid": "^11.0.5",

proto/invite/v2/invite.proto

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
syntax = "proto3";
2+
3+
package invite.v2;
4+
5+
// InvitePayload represents the core invite data
6+
message InvitePayload {
7+
// The encrypted conversation id
8+
string conversationToken = 1;
9+
10+
// The creator's inbox ID
11+
string creator_inbox_id = 2;
12+
13+
// The tag to mark which conversation this corresponds to, lives in `ConversationCustomMetadata`
14+
string tag = 3;
15+
16+
// optional conversation metadata
17+
optional string name = 4;
18+
optional string description = 5;
19+
optional string imageURL = 6;
20+
}
21+
22+
// SignedInvite represents an invite with its cryptographic signature
23+
message SignedInvite {
24+
// The invite payload containing the actual invite data
25+
InvitePayload payload = 1;
26+
27+
// The cryptographic signature (typically 65 bytes for ECDSA with recovery)
28+
bytes signature = 2;
29+
}

0 commit comments

Comments
 (0)