Skip to content

Commit c8a6059

Browse files
Harxhitclaude
andcommitted
feat(cards): add card sharing migration, update seed, fix lint
- Add migration for card sharing fields (slug, description, qrEnabled, viewCount, visibility) and the CardVisibility enum, plus supporting indexes; include prior card/auth migrations missing from history. - Update seed for the new schema: card slugs and a nested UserIdentity in place of the removed provider/providerId fields. - Resolve lint in cardService (no-shadow rename, explicit return types). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent e128aa5 commit c8a6059

10 files changed

Lines changed: 279 additions & 15 deletions

File tree

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
-- CreateEnum
2+
CREATE TYPE "TeamRole" AS ENUM ('OWNER', 'ADMIN', 'MEMBER');
3+
4+
-- CreateTable
5+
CREATE TABLE "Event" (
6+
"id" TEXT NOT NULL,
7+
"name" TEXT NOT NULL,
8+
"slug" TEXT NOT NULL,
9+
"location" TEXT NOT NULL,
10+
"description" TEXT,
11+
"organizerId" TEXT NOT NULL,
12+
"startDate" TIMESTAMP(3) NOT NULL,
13+
"endDate" TIMESTAMP(3) NOT NULL,
14+
"isPublic" BOOLEAN NOT NULL DEFAULT true,
15+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
16+
17+
CONSTRAINT "Event_pkey" PRIMARY KEY ("id")
18+
);
19+
20+
-- CreateTable
21+
CREATE TABLE "EventAttendee" (
22+
"id" TEXT NOT NULL,
23+
"userId" TEXT NOT NULL,
24+
"eventId" TEXT NOT NULL,
25+
"joinedAt" TIMESTAMP(3) NOT NULL,
26+
27+
CONSTRAINT "EventAttendee_pkey" PRIMARY KEY ("id")
28+
);
29+
30+
-- CreateTable
31+
CREATE TABLE "teams" (
32+
"id" TEXT NOT NULL,
33+
"name" TEXT NOT NULL,
34+
"slug" TEXT NOT NULL,
35+
"description" TEXT,
36+
"avatarUrl" TEXT,
37+
"ownerId" TEXT NOT NULL,
38+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
39+
"updatedAt" TIMESTAMP(3) NOT NULL,
40+
41+
CONSTRAINT "teams_pkey" PRIMARY KEY ("id")
42+
);
43+
44+
-- CreateTable
45+
CREATE TABLE "team_members" (
46+
"id" TEXT NOT NULL,
47+
"teamId" TEXT NOT NULL,
48+
"userId" TEXT NOT NULL,
49+
"role" "TeamRole" NOT NULL,
50+
"joinedAt" TIMESTAMP(3) NOT NULL,
51+
52+
CONSTRAINT "team_members_pkey" PRIMARY KEY ("id")
53+
);
54+
55+
-- CreateIndex
56+
CREATE UNIQUE INDEX "Event_slug_key" ON "Event"("slug");
57+
58+
-- CreateIndex
59+
CREATE UNIQUE INDEX "EventAttendee_userId_eventId_key" ON "EventAttendee"("userId", "eventId");
60+
61+
-- CreateIndex
62+
CREATE UNIQUE INDEX "teams_slug_key" ON "teams"("slug");
63+
64+
-- CreateIndex
65+
CREATE INDEX "teams_slug_idx" ON "teams"("slug");
66+
67+
-- CreateIndex
68+
CREATE INDEX "team_members_userId_idx" ON "team_members"("userId");
69+
70+
-- CreateIndex
71+
CREATE UNIQUE INDEX "team_members_userId_teamId_key" ON "team_members"("userId", "teamId");
72+
73+
-- AddForeignKey
74+
ALTER TABLE "Event" ADD CONSTRAINT "Event_organizerId_fkey" FOREIGN KEY ("organizerId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
75+
76+
-- AddForeignKey
77+
ALTER TABLE "EventAttendee" ADD CONSTRAINT "EventAttendee_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "Event"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
78+
79+
-- AddForeignKey
80+
ALTER TABLE "EventAttendee" ADD CONSTRAINT "EventAttendee_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
81+
82+
-- AddForeignKey
83+
ALTER TABLE "teams" ADD CONSTRAINT "teams_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
84+
85+
-- AddForeignKey
86+
ALTER TABLE "team_members" ADD CONSTRAINT "team_members_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "teams"("id") ON DELETE CASCADE ON UPDATE CASCADE;
87+
88+
-- AddForeignKey
89+
ALTER TABLE "team_members" ADD CONSTRAINT "team_members_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
Warnings:
3+
4+
- You are about to drop the column `provider` on the `users` table. All the data in the column will be lost.
5+
- You are about to drop the column `provider_id` on the `users` table. All the data in the column will be lost.
6+
- A unique constraint covering the columns `[phone_number]` on the table `users` will be added. If there are existing duplicate values, this will fail.
7+
8+
*/
9+
-- CreateEnum
10+
CREATE TYPE "Role" AS ENUM ('SUPERADMIN', 'ADMIN', 'USER');
11+
12+
-- DropIndex
13+
DROP INDEX "users_provider_provider_id_key";
14+
15+
-- AlterTable
16+
ALTER TABLE "users" DROP COLUMN "provider",
17+
DROP COLUMN "provider_id",
18+
ADD COLUMN "authRole" "Role" NOT NULL DEFAULT 'USER',
19+
ADD COLUMN "email_verified" BOOLEAN NOT NULL DEFAULT false,
20+
ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT false,
21+
ADD COLUMN "last_sign_in_at" TIMESTAMP(3),
22+
ADD COLUMN "phone_number" TEXT;
23+
24+
-- CreateTable
25+
CREATE TABLE "user_identities" (
26+
"id" TEXT NOT NULL,
27+
"user_id" TEXT NOT NULL,
28+
"provider" TEXT NOT NULL,
29+
"provider_id" TEXT NOT NULL,
30+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
31+
32+
CONSTRAINT "user_identities_pkey" PRIMARY KEY ("id")
33+
);
34+
35+
-- CreateTable
36+
CREATE TABLE "refresh_tokens" (
37+
"id" TEXT NOT NULL,
38+
"user_id" TEXT NOT NULL,
39+
"token_hash" TEXT NOT NULL,
40+
"family" TEXT NOT NULL,
41+
"expires_at" TIMESTAMP(3) NOT NULL,
42+
"revoked_at" TIMESTAMP(3),
43+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
44+
"user_agent" TEXT,
45+
"ip" TEXT,
46+
47+
CONSTRAINT "refresh_tokens_pkey" PRIMARY KEY ("id")
48+
);
49+
50+
-- CreateIndex
51+
CREATE INDEX "user_identities_user_id_idx" ON "user_identities"("user_id");
52+
53+
-- CreateIndex
54+
CREATE UNIQUE INDEX "user_identities_provider_provider_id_key" ON "user_identities"("provider", "provider_id");
55+
56+
-- CreateIndex
57+
CREATE UNIQUE INDEX "refresh_tokens_token_hash_key" ON "refresh_tokens"("token_hash");
58+
59+
-- CreateIndex
60+
CREATE INDEX "refresh_tokens_user_id_idx" ON "refresh_tokens"("user_id");
61+
62+
-- CreateIndex
63+
CREATE INDEX "refresh_tokens_family_idx" ON "refresh_tokens"("family");
64+
65+
-- CreateIndex
66+
CREATE UNIQUE INDEX "users_phone_number_key" ON "users"("phone_number");
67+
68+
-- AddForeignKey
69+
ALTER TABLE "user_identities" ADD CONSTRAINT "user_identities_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
70+
71+
-- AddForeignKey
72+
ALTER TABLE "refresh_tokens" ADD CONSTRAINT "refresh_tokens_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
Warnings:
3+
4+
- A unique constraint covering the columns `[slug]` on the table `cards` will be added. If there are existing duplicate values, this will fail.
5+
- Added the required column `slug` to the `cards` table without a default value. This is not possible if the table is not empty.
6+
7+
*/
8+
-- CreateEnum
9+
CREATE TYPE "CardVisibility" AS ENUM ('PUBLIC', 'UNLISTED', 'PRIVATE');
10+
11+
-- AlterTable
12+
ALTER TABLE "cards" ADD COLUMN "description" TEXT,
13+
ADD COLUMN "qrEnabled" BOOLEAN NOT NULL DEFAULT true,
14+
ADD COLUMN "slug" TEXT NOT NULL,
15+
ADD COLUMN "viewCount" INTEGER NOT NULL DEFAULT 0,
16+
ADD COLUMN "visibility" "CardVisibility" NOT NULL DEFAULT 'PUBLIC';
17+
18+
-- CreateIndex
19+
CREATE UNIQUE INDEX "cards_slug_key" ON "cards"("slug");
20+
21+
-- CreateIndex
22+
CREATE INDEX "cards_slug_idx" ON "cards"("slug");
23+
24+
-- CreateIndex
25+
CREATE INDEX "cards_viewCount_idx" ON "cards"("viewCount");
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-- DropIndex
2+
DROP INDEX "cards_slug_idx";
3+
4+
-- CreateIndex
5+
CREATE INDEX "card_views_card_id_idx" ON "card_views"("card_id");
6+
7+
-- CreateIndex
8+
CREATE INDEX "card_views_owner_id_idx" ON "card_views"("owner_id");
9+
10+
-- CreateIndex
11+
CREATE INDEX "cards_user_id_idx" ON "cards"("user_id");
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
Warnings:
3+
4+
- You are about to drop the column `description` on the `cards` table. All the data in the column will be lost.
5+
- You are about to drop the column `qrEnabled` on the `cards` table. All the data in the column will be lost.
6+
- You are about to drop the column `slug` on the `cards` table. All the data in the column will be lost.
7+
- You are about to drop the column `viewCount` on the `cards` table. All the data in the column will be lost.
8+
- You are about to drop the column `visibility` on the `cards` table. All the data in the column will be lost.
9+
10+
*/
11+
-- DropIndex
12+
DROP INDEX "card_views_card_id_idx";
13+
14+
-- DropIndex
15+
DROP INDEX "card_views_owner_id_idx";
16+
17+
-- DropIndex
18+
DROP INDEX "cards_slug_key";
19+
20+
-- DropIndex
21+
DROP INDEX "cards_user_id_idx";
22+
23+
-- DropIndex
24+
DROP INDEX "cards_viewCount_idx";
25+
26+
-- AlterTable
27+
ALTER TABLE "cards" DROP COLUMN "description",
28+
DROP COLUMN "qrEnabled",
29+
DROP COLUMN "slug",
30+
DROP COLUMN "viewCount",
31+
DROP COLUMN "visibility";
32+
33+
-- DropEnum
34+
DROP TYPE "CardVisibility";
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
-- CreateEnum
2+
CREATE TYPE "CardVisibility" AS ENUM ('PUBLIC', 'UNLISTED', 'PRIVATE');
3+
4+
-- AlterTable
5+
ALTER TABLE "cards" ADD COLUMN "description" TEXT,
6+
ADD COLUMN "qrEnabled" BOOLEAN NOT NULL DEFAULT true,
7+
ADD COLUMN "slug" TEXT NOT NULL,
8+
ADD COLUMN "viewCount" INTEGER NOT NULL DEFAULT 0,
9+
ADD COLUMN "visibility" "CardVisibility" NOT NULL DEFAULT 'PUBLIC';
10+
11+
-- CreateIndex
12+
CREATE INDEX "card_views_card_id_idx" ON "card_views"("card_id");
13+
14+
-- CreateIndex
15+
CREATE INDEX "card_views_owner_id_idx" ON "card_views"("owner_id");
16+
17+
-- CreateIndex
18+
CREATE UNIQUE INDEX "cards_slug_key" ON "cards"("slug");
19+
20+
-- CreateIndex
21+
CREATE INDEX "cards_user_id_idx" ON "cards"("user_id");
22+
23+
-- CreateIndex
24+
CREATE INDEX "cards_viewCount_idx" ON "cards"("viewCount");
25+

apps/backend/prisma/seed.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,13 @@ async function main() {
1919
company: 'OpenSource Inc.',
2020
avatarUrl: null,
2121
accentColor: '#6366f1',
22-
provider: 'github',
23-
providerId: 'demo-12345',
22+
emailVerified: true,
23+
identities: {
24+
create: {
25+
provider: 'github',
26+
providerId: 'demo-12345',
27+
},
28+
},
2429
},
2530
});
2631

@@ -109,6 +114,7 @@ async function main() {
109114
data: {
110115
userId: testUser.id,
111116
title: 'Professional',
117+
slug: 'devcard-demo-professional',
112118
isDefault: true,
113119
cardLinks: {
114120
create: [
@@ -125,6 +131,7 @@ async function main() {
125131
data: {
126132
userId: testUser.id,
127133
title: 'Hackathon',
134+
slug: 'devcard-demo-hackathon',
128135
isDefault: false,
129136
cardLinks: {
130137
create: [

apps/backend/src/routes/cards.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1+
12
import * as cardService from '../services/cardService'
23
import { handleDbError } from '../utils/error.util.js';
4+
import { hashIp } from '../utils/refreshToken';
35
import { createCardSchema ,updateCardSchema, addPlatformLinkSchema} from '../validations/card.validation';
46

57
import type { CardResponse, UpdateCardBody } from '../services/cardService';
68
import type { Card } from '@devcard/shared';
9+
import type { CardVisibility } from '@prisma/client';
710
import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
8-
import { CardVisibility } from '@prisma/client';
9-
import { hashIp } from '../utils/refreshToken';
1011

1112
export interface CreateCardBody {
1213
title: string;

apps/backend/src/services/cardService.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { Card, CardVisibility, Prisma } from '@prisma/client';
1+
import { type Card, CardVisibility, type Prisma } from '@prisma/client';
2+
import QRCode from 'qrcode';
23

34
import { generateUniqueSlug } from '../utils/slug';
45

56
import type { CreateCardBody } from '../routes/cards';
67
import type { FastifyInstance } from 'fastify';
7-
import QRCode from 'qrcode';
88

99
type CardLinkResponse = { platformLink: unknown };
1010
type RawCard = { id: string; title: string; isDefault: boolean; cardLinks: CardLinkResponse[] };
@@ -184,7 +184,7 @@ export async function setDefaultCard(app: FastifyInstance, userId: string, id: s
184184
}
185185

186186
//Adds platfrom link
187-
export async function addPlatFormLinks(app: FastifyInstance, userId: string, id:string, platformLinkId: string){
187+
export async function addPlatFormLinks(app: FastifyInstance, userId: string, id:string, platformLinkId: string): Promise<void> {
188188
const ownedCard = await app.prisma.card.findFirst({
189189
where: {
190190
id,
@@ -239,7 +239,7 @@ export async function addPlatFormLinks(app: FastifyInstance, userId: string, id:
239239
}
240240

241241
//Shares card
242-
export async function shareCard(app: FastifyInstance, userId:string, id: string){
242+
export async function shareCard(app: FastifyInstance, userId:string, id: string): Promise<{ shareUrl: string }> {
243243
const card = await app.prisma.card.findFirst({
244244
where:{
245245
id,
@@ -268,7 +268,7 @@ export async function shareCard(app: FastifyInstance, userId:string, id: string)
268268
}
269269

270270
//Gets share card
271-
export async function getSharedCard(app:FastifyInstance, slug:string){
271+
export async function getSharedCard(app:FastifyInstance, slug:string): Promise<Prisma.CardGetPayload<{ include: { cardLinks: { include: { platformLink: true } } } }>> {
272272
const card = await app.prisma.card.findUnique({
273273
where: {
274274
slug
@@ -293,7 +293,7 @@ export async function getSharedCard(app:FastifyInstance, slug:string){
293293
}
294294

295295
//Genreate qr
296-
export async function genrateQr(app: FastifyInstance,userId:string, id: string){
296+
export async function genrateQr(app: FastifyInstance,userId:string, id: string): Promise<Buffer> {
297297
const card = await app.prisma.card.findFirst({
298298
where:{
299299
id,
@@ -339,8 +339,8 @@ export async function genrateQr(app: FastifyInstance,userId:string, id: string){
339339
}
340340

341341
//TODO:Add pagination
342-
export async function cardAnalytics(app: FastifyInstance, userId:string, id: string){
343-
const cardAnalytics = await app.prisma.card.findFirst({
342+
export async function cardAnalytics(app: FastifyInstance, userId:string, id: string): Promise<Prisma.CardGetPayload<{ include: { views: { include: { viewer: { select: { id: true; username: true; avatarUrl: true; displayName: true; role: true; accentColor: true } } } } } }>> {
343+
const card = await app.prisma.card.findFirst({
344344
where: {
345345
id,
346346
userId
@@ -367,12 +367,12 @@ export async function cardAnalytics(app: FastifyInstance, userId:string, id: str
367367

368368
})
369369

370-
if (!cardAnalytics) {
370+
if (!card) {
371371
throw Object.assign(
372372
new Error('Card not found'),
373373
{ code: 'CARD_NOT_FOUND' }
374374
);
375375
}
376376

377-
return cardAnalytics
377+
return card
378378
}

apps/backend/src/utils/validators.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { z } from 'zod';
21
import { getPlatform } from '@devcard/shared';
2+
import { z } from 'zod';
33

44
export const updateProfileSchema = z.object({
55
displayName: z.string().min(1).max(100).optional(),

0 commit comments

Comments
 (0)