Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ RUN npm run build
EXPOSE 3000
ENV NEXT_TELEMETRY_DISABLED=1

CMD npx prisma migrate deploy && npm start
CMD npx prisma migrate deploy && npx prisma db seed && npm start
3 changes: 1 addition & 2 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,12 @@ model Transaction {
id Int @id @default(autoincrement())
userId String
provider SubscriptionProvider
transactionId String
transactionId String @unique
createdAt DateTime @default(now())

user User @relation(fields: [userId], references: [id], onDelete: Cascade)

@@index([userId])
@@index([transactionId])
}

model Project {
Expand Down
10 changes: 2 additions & 8 deletions src/app/api/apple/verify/route.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { NextRequest } from "next/server";
import { decodeJwt } from "jose";
import prisma from "@src/server/db";
import { apiHandler, AuthApiContext } from "@src/lib/utils/api-handler";
import { BodyFieldError, ForbiddenError, Success } from "@src/lib/utils/api-utils";
import * as UserService from "@src/server/service/user-service";
import * as TransactionService from "@src/server/service/transaction-service";

const APPLE_PRODUCT_ID = "app.scriptio.pro.monthly";
const APPLE_BUNDLE_IDS = ["app.scriptio", "app.scriptio.staging"];
Expand Down Expand Up @@ -46,13 +46,7 @@ async function verifyApplePurchase(req: NextRequest, { user }: AuthApiContext) {
isSubscriptionCancelled: false,
});

await prisma.transaction.create({
data: {
userId: user.id,
provider: "APPLE",
transactionId: payload.transactionId,
},
});
await TransactionService.createTransactionIfNotExists(user.id, "APPLE", payload.transactionId);

return Success(null);
}
Expand Down
4 changes: 4 additions & 0 deletions src/app/api/projects/[projectId]/saves/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ async function listSaves(req: NextRequest, { routeParams, user }: AuthApiContext
}

const res = await forwardToWorker(projectId, "GET", "/saves");
if (!res.ok) {
const text = await res.text();
throw new Error(`Worker error listing saves: ${text}`);
}
const data = await res.json();
return Success(data);
}
Expand Down
20 changes: 9 additions & 11 deletions src/app/api/webhooks/apple/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from "next/server";
import { decodeJwt } from "jose";
import prisma from "@src/server/db";
import * as UserService from "@src/server/service/user-service";
import * as TransactionService from "@src/server/service/transaction-service";

interface AppleNotificationPayload {
notificationType: string;
Expand All @@ -27,9 +27,13 @@ interface AppleRenewalInfo {
}

async function findUser(transaction: AppleTransactionInfo): Promise<{ id: string } | null> {
if (!transaction.appAccountToken) return null;
const user = await UserService.getUserFromId(transaction.appAccountToken);
return user ? { id: user.id } : null;
if (transaction.appAccountToken) {
const user = await UserService.getUserFromId(transaction.appAccountToken);
if (user) return { id: user.id };
}
// Fallback: look up via the transaction stored during initial purchase verification
const tx = await TransactionService.findUserByTransactionId(transaction.transactionId);
return tx ? { id: tx.userId } : null;
}

export async function POST(req: NextRequest) {
Expand Down Expand Up @@ -59,13 +63,7 @@ export async function POST(req: NextRequest) {
subscriptionProvider: "APPLE",
isSubscriptionCancelled: false,
});
await prisma.transaction.create({
data: {
userId: user.id,
provider: "APPLE",
transactionId: transaction.transactionId,
},
});
await TransactionService.createTransactionIfNotExists(user.id, "APPLE", transaction.transactionId);
break;
}

Expand Down
4 changes: 2 additions & 2 deletions src/generated/client/internal/class.ts

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/generated/client/models/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,15 +230,15 @@ export type TransactionOrderByWithRelationInput = {

export type TransactionWhereUniqueInput = Prisma.AtLeast<{
id?: number
transactionId?: string
AND?: Prisma.TransactionWhereInput | Prisma.TransactionWhereInput[]
OR?: Prisma.TransactionWhereInput[]
NOT?: Prisma.TransactionWhereInput | Prisma.TransactionWhereInput[]
userId?: Prisma.StringFilter<"Transaction"> | string
provider?: Prisma.EnumSubscriptionProviderFilter<"Transaction"> | $Enums.SubscriptionProvider
transactionId?: Prisma.StringFilter<"Transaction"> | string
createdAt?: Prisma.DateTimeFilter<"Transaction"> | Date | string
user?: Prisma.XOR<Prisma.UserScalarRelationFilter, Prisma.UserWhereInput>
}, "id">
}, "id" | "transactionId">

export type TransactionOrderByWithAggregationInput = {
id?: Prisma.SortOrder
Expand Down
16 changes: 16 additions & 0 deletions src/server/repository/transaction-repository.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { SubscriptionProvider } from "../../generated/client/client";
import prisma from "../db";

export class TransactionRepository {
Expand All @@ -23,4 +24,19 @@ export class TransactionRepository {
where: { createdAt: { gte: since } },
});
}

findByTransactionId(transactionId: string) {
return prisma.transaction.findFirst({
where: { transactionId },
select: { userId: true },
});
}

createIfNotExists(userId: string, provider: SubscriptionProvider, transactionId: string) {
return prisma.transaction.upsert({
where: { transactionId },
update: {},
create: { userId, provider, transactionId },
});
}
}
13 changes: 13 additions & 0 deletions src/server/service/transaction-service.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { SubscriptionProvider } from "../../generated/client/client";
import { TransactionRepository } from "../repository/transaction-repository";

const repository = new TransactionRepository();
Expand All @@ -13,3 +14,15 @@ export const countTransactionsByUser = async (userId: string) => {
export const countTransactionsSince = async (since: Date) => {
return repository.countSince(since);
};

export const findUserByTransactionId = async (transactionId: string) => {
return repository.findByTransactionId(transactionId);
};

export const createTransactionIfNotExists = async (
userId: string,
provider: SubscriptionProvider,
transactionId: string,
) => {
return repository.createIfNotExists(userId, provider, transactionId);
};
Loading