Skip to content

feat: add GET /api/nfc/payload endpoint for NFC tag payload generation#67

Open
SdSarthak wants to merge 2 commits into
Dev-Card:mainfrom
SdSarthak:feat/nfc-payload-endpoint
Open

feat: add GET /api/nfc/payload endpoint for NFC tag payload generation#67
SdSarthak wants to merge 2 commits into
Dev-Card:mainfrom
SdSarthak:feat/nfc-payload-endpoint

Conversation

@SdSarthak
Copy link
Copy Markdown

Summary

  • Add GET /api/nfc/payload endpoint that returns an NDEF-compatible URI record for NFC tag programming
  • Optional ?card=<cardId> query param returns a card-specific URL; without it, returns the user's profile URL (/u/<username>)
  • Card ownership is validated — returns 403 if the card doesn't belong to the requesting user
  • Route is protected by app.authenticate (JWT-based auth)
  • Unit tests cover: 401 unauthenticated, profile URL response, card URL response, 403 on unowned card

Test plan

  • GET /api/nfc/payload without auth → 401
  • GET /api/nfc/payload (authenticated, no query param) → { type: "URI", payload: "https://devcard.dev/u/<username>" }
  • GET /api/nfc/payload?card=<owned-card-id>{ type: "URI", payload: "https://devcard.dev/devcard/<cardId>" }
  • GET /api/nfc/payload?card=<other-user-card-id> → 403
  • Run pnpm test — all new tests pass

Closes #35

- Add apps/backend/src/routes/nfc.ts with authenticated GET /api/nfc/payload
  that returns an NDEF-compatible URI record for the user's profile page
- Optional ?card=<cardId> query param returns card-specific URL after
  validating that the card belongs to the requesting user (403 on failure)
- Register nfcRoutes in app.ts at prefix /api/nfc
- Add unit tests covering: 401 unauthenticated, profile URL, card URL,
  403 on unowned card

Closes Dev-Card#35
Copilot AI review requested due to automatic review settings May 10, 2026 18:29
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an authenticated backend endpoint to generate an NFC/NDEF-compatible URI payload pointing to either the requesting user’s profile URL or a specific owned card URL, and wires it into the backend app with unit tests.

Changes:

  • Added GET /api/nfc/payload (JWT-protected) to return { type: 'URI', payload: '<public URL>' } for profile or card.
  • Registered the new /api/nfc route group in the Fastify app.
  • Added Vitest coverage for unauthenticated access, profile payload, card payload, and unowned card access.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
apps/backend/src/routes/nfc.ts Implements the NFC payload generation endpoint with card ownership validation.
apps/backend/src/app.ts Registers the new nfcRoutes under /api/nfc.
apps/backend/src/tests/nfc.test.ts Adds unit tests for auth, default profile payload, card payload, and forbidden access.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/backend/src/routes/nfc.ts Outdated
Comment on lines +11 to +15
// Fetch user to get username for the profile URL
const user = await app.prisma.user.findUnique({
where: { id: userId },
select: { username: true },
});
Comment on lines +21 to +22
const appUrl = process.env.PUBLIC_APP_URL || 'https://devcard.dev';

Comment thread apps/backend/src/__tests__/nfc.test.ts Outdated
Comment on lines +35 to +51
const app = buildApp();
await app.ready();
const res = await app.inject({ method: 'GET', url: '/api/nfc/payload' });
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.type).toBe('URI');
expect(body.payload).toContain('/u/testuser');
});

it('returns card URI payload when cardId is provided and owned', async () => {
const app = buildApp();
await app.ready();
const res = await app.inject({ method: 'GET', url: '/api/nfc/payload?card=card-1' });
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.type).toBe('URI');
expect(body.payload).toContain('/devcard/card-1');
Comment thread apps/backend/src/__tests__/nfc.test.ts Outdated
Comment on lines +35 to +51
const app = buildApp();
await app.ready();
const res = await app.inject({ method: 'GET', url: '/api/nfc/payload' });
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.type).toBe('URI');
expect(body.payload).toContain('/u/testuser');
});

it('returns card URI payload when cardId is provided and owned', async () => {
const app = buildApp();
await app.ready();
const res = await app.inject({ method: 'GET', url: '/api/nfc/payload?card=card-1' });
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.type).toBe('URI');
expect(body.payload).toContain('/devcard/card-1');
@SdSarthak
Copy link
Copy Markdown
Author

All addressed in the follow-up commit:

  • Moved the user lookup below the cardId check — when a card URL is requested the user record is never fetched (saves a DB round-trip on the card path).
  • Tests now set process.env.PUBLIC_APP_URL to a fixed test value before running and restore it in afterAll, then assert the full expected URL string (e.g. https://test.devcard.dev/u/testuser) rather than just a path fragment.
  • Added a test that verifies the user lookup is skipped when cardId is provided.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

backend: implement NFC tag payload generation endpoint

2 participants