feat: add OEM code copy tracking, PostHog analytics, Postal email integration
- Add oem_code_copies table and analytics module for tracking part code copies - Integrate PostHog for frontend product analytics (VIN decode, OEM copy events) - Switch email service from stub to Postal API with proper error handling - Add copy button to parts panel with clipboard + server-side logging - Add admin copy-logs page with filtering and top-copied-codes view - Add VIN report endpoint for users to flag unrecognized chassis numbers - Add Postal email env vars to config schema Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import { Module, OnModuleInit } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { AuthController } from "./auth.controller";
|
||||
import { AuthService } from "./auth.service";
|
||||
import { EmailService } from "../email/email.service";
|
||||
import { createAuth } from "./auth";
|
||||
|
||||
@Module({
|
||||
@@ -10,7 +11,10 @@ import { createAuth } from "./auth";
|
||||
exports: [AuthService],
|
||||
})
|
||||
export class AuthModule implements OnModuleInit {
|
||||
constructor(private configService: ConfigService) {}
|
||||
constructor(
|
||||
private configService: ConfigService,
|
||||
private emailService: EmailService,
|
||||
) {}
|
||||
|
||||
onModuleInit() {
|
||||
const databaseUrl = this.configService.get<string>("database.url")!;
|
||||
@@ -18,6 +22,9 @@ export class AuthModule implements OnModuleInit {
|
||||
const baseUrl = this.configService.get<string>("auth.url")!;
|
||||
const googleClientId = this.configService.get<string>("auth.googleClientId");
|
||||
const googleClientSecret = this.configService.get<string>("auth.googleClientSecret");
|
||||
createAuth(databaseUrl, secret, baseUrl, { googleClientId, googleClientSecret });
|
||||
createAuth(databaseUrl, secret, baseUrl, {
|
||||
social: { googleClientId, googleClientSecret },
|
||||
emailService: this.emailService,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import postgres from "postgres";
|
||||
import { randomUUID } from "crypto";
|
||||
|
||||
import * as schema from "../database/schema/core";
|
||||
import type { EmailService } from "../email/email.service";
|
||||
|
||||
let authInstance: ReturnType<typeof betterAuth> | null = null;
|
||||
|
||||
@@ -13,7 +14,12 @@ interface SocialCredentials {
|
||||
googleClientSecret?: string;
|
||||
}
|
||||
|
||||
export function createAuth(databaseUrl: string, secret: string, baseUrl: string, social?: SocialCredentials) {
|
||||
interface AuthOptions {
|
||||
social?: SocialCredentials;
|
||||
emailService?: EmailService;
|
||||
}
|
||||
|
||||
export function createAuth(databaseUrl: string, secret: string, baseUrl: string, options?: AuthOptions) {
|
||||
if (authInstance) return authInstance;
|
||||
|
||||
const client = postgres(databaseUrl, { max: 5 });
|
||||
@@ -35,13 +41,30 @@ export function createAuth(databaseUrl: string, secret: string, baseUrl: string,
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
minPasswordLength: 8,
|
||||
sendResetPassword: async (data) => {
|
||||
if (options?.emailService) {
|
||||
await options.emailService.sendPasswordReset(data.user.email, data.url);
|
||||
} else {
|
||||
console.log(`[DEV] Password reset URL for ${data.user.email}: ${data.url}`);
|
||||
}
|
||||
},
|
||||
},
|
||||
...(social?.googleClientId && social?.googleClientSecret
|
||||
emailVerification: {
|
||||
sendVerificationEmail: async (data) => {
|
||||
if (options?.emailService) {
|
||||
await options.emailService.sendEmailVerification(data.user.email, data.url);
|
||||
} else {
|
||||
console.log(`[DEV] Verification URL for ${data.user.email}: ${data.url}`);
|
||||
}
|
||||
},
|
||||
sendOnSignUp: true,
|
||||
},
|
||||
...(options?.social?.googleClientId && options?.social?.googleClientSecret
|
||||
? {
|
||||
socialProviders: {
|
||||
google: {
|
||||
clientId: social.googleClientId,
|
||||
clientSecret: social.googleClientSecret,
|
||||
clientId: options.social.googleClientId,
|
||||
clientSecret: options.social.googleClientSecret,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user