fix: replace Math.random() IDs with crypto.randomUUID() across all services

- billing-service: licenses, subscriptions (pay_, lic_)
- growth-service: invitations, referrals (inv_, ref_)
- platform-service: auth, audit (usr_, aud_)
- tracker-service: items, comments, votes, public (trk_, cmt_, vote_)
- Add votes.test.ts — closes the only missing module test
This commit is contained in:
saravanakumardb1 2026-02-12 13:03:09 -08:00
parent d39c447c52
commit 86a56339ab
11 changed files with 71 additions and 13 deletions

View File

@ -31,7 +31,7 @@ export async function licenseRoutes(app: FastifyInstance) {
const key = repo.generateKey();
const doc: LicenseDoc = {
id: `lic_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
id: `lic_${crypto.randomUUID()}`,
productId: PRODUCT_ID,
key,
userId: input.userId,

View File

@ -96,7 +96,7 @@ export async function subscriptionRoutes(app: FastifyInstance) {
}
const input = parsed.data;
const doc: PaymentDoc = {
id: `pay_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
id: `pay_${crypto.randomUUID()}`,
productId: PRODUCT_ID,
...input,
createdAt: new Date().toISOString(),

View File

@ -54,7 +54,7 @@ export async function invitationRoutes(app: FastifyInstance) {
const input = parsed.data;
const now = new Date().toISOString();
const doc: InvitationCodeDoc = {
id: `inv_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
id: `inv_${crypto.randomUUID()}`,
productId: PRODUCT_ID,
code: input.code.toUpperCase().replace(/[^A-Z0-9-]/g, ""),
description: input.description,
@ -126,7 +126,7 @@ export async function invitationRoutes(app: FastifyInstance) {
const input = parsed.data;
const now = new Date().toISOString();
const doc: InvitationCodeDoc = {
id: `inv_${Date.now()}_${i}_${Math.random().toString(36).slice(2, 8)}`,
id: `inv_${crypto.randomUUID()}`,
productId: PRODUCT_ID,
code: input.code.toUpperCase().replace(/[^A-Z0-9-]/g, ""),
description: input.description,

View File

@ -64,7 +64,7 @@ export async function referralRoutes(app: FastifyInstance) {
const now = new Date().toISOString();
const doc: ReferralDoc = {
id: `ref_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
id: `ref_${crypto.randomUUID()}`,
productId: PRODUCT_ID,
referrerId: input.referrerId,
referrerEmail: input.referrerEmail,

View File

@ -21,7 +21,7 @@ export async function auditRoutes(app: FastifyInstance) {
}
const input = parsed.data;
const doc: AuditDoc = {
id: `aud_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
id: `aud_${crypto.randomUUID()}`,
productId: PRODUCT_ID,
...input,
createdAt: new Date().toISOString(),

View File

@ -59,7 +59,7 @@ export async function authRoutes(app: FastifyInstance) {
const now = new Date().toISOString();
const user: UserDoc = {
id: `usr_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
id: `usr_${crypto.randomUUID()}`,
productId: PRODUCT_ID,
email: email.toLowerCase(),
passwordHash: await repo.hashPassword(password),

View File

@ -39,7 +39,7 @@ export async function commentRoutes(app: FastifyInstance) {
const now = new Date().toISOString();
const doc: CommentDoc = {
id: `cmt_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
id: `cmt_${crypto.randomUUID()}`,
itemId,
productId: item.productId,
authorId: auth.sub,

View File

@ -93,7 +93,7 @@ export async function itemRoutes(app: FastifyInstance) {
const now = new Date().toISOString();
const doc: TrackerItemDoc = {
id: `trk_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
id: `trk_${crypto.randomUUID()}`,
productId: pid,
type: input.type,
status: "open",

View File

@ -99,7 +99,7 @@ export async function publicRoutes(app: FastifyInstance) {
const now = new Date().toISOString();
const doc: TrackerItemDoc = {
id: `trk_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
id: `trk_${crypto.randomUUID()}`,
productId: pid,
type: input.type,
status: "open",
@ -123,7 +123,7 @@ export async function publicRoutes(app: FastifyInstance) {
// Auto-vote for the submitter
await voteRepo.create({
id: `vote_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
id: `vote_${crypto.randomUUID()}`,
itemId: created.id,
productId: pid,
userId: `email:${input.email}`,
@ -157,7 +157,7 @@ export async function publicRoutes(app: FastifyInstance) {
return { voted: false, voteCount: newCount };
} else {
await voteRepo.create({
id: `vote_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
id: `vote_${crypto.randomUUID()}`,
itemId: id,
productId: item.productId,
userId: voterId,

View File

@ -32,7 +32,7 @@ export async function voteRoutes(app: FastifyInstance) {
} else {
// Add vote
const doc: VoteDoc = {
id: `vote_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
id: `vote_${crypto.randomUUID()}`,
itemId,
productId: item.productId,
userId: auth.sub,

View File

@ -0,0 +1,58 @@
/**
* Votes module unit tests validates VoteDoc shape and route exports.
*/
import { describe, it, expect } from "vitest";
import type { VoteDoc } from "./types.js";
describe("VoteDoc", () => {
it("accepts a valid vote document", () => {
const doc: VoteDoc = {
id: `vote_${crypto.randomUUID()}`,
itemId: "trk_abc123",
productId: "lysnrai",
userId: "usr_xyz",
createdAt: new Date().toISOString(),
};
expect(doc.id).toMatch(/^vote_/);
expect(doc.itemId).toBe("trk_abc123");
expect(doc.productId).toBe("lysnrai");
expect(doc.userId).toBe("usr_xyz");
expect(doc.createdAt).toBeTruthy();
});
it("generates unique IDs with crypto.randomUUID", () => {
const ids = new Set(
Array.from({ length: 100 }, () => `vote_${crypto.randomUUID()}`)
);
expect(ids.size).toBe(100);
});
it("id format uses UUID (no Math.random)", () => {
const id = `vote_${crypto.randomUUID()}`;
// UUID v4 pattern after prefix
expect(id).toMatch(
/^vote_[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
);
});
it("createdAt is ISO 8601 format", () => {
const doc: VoteDoc = {
id: `vote_${crypto.randomUUID()}`,
itemId: "trk_test",
productId: "lysnrai",
userId: "usr_test",
createdAt: new Date().toISOString(),
};
// ISO 8601 pattern
expect(doc.createdAt).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
});
});
describe("voteRoutes export", () => {
it("exports voteRoutes function", async () => {
const mod = await import("./routes.js");
expect(typeof mod.voteRoutes).toBe("function");
});
});