initial commit: rocket.new export of broadcastbeat
This commit is contained in:
62
src/app/api/marketplace/admin/notify/route.ts
Normal file
62
src/app/api/marketplace/admin/notify/route.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { createClient } from '@/lib/supabase/client';
|
||||
import {
|
||||
sendProfileApprovedEmail,
|
||||
sendProfileRejectedEmail,
|
||||
} from '@/lib/marketplace/emailService';
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const { profileId, action, reason } = await req.json();
|
||||
|
||||
if (!profileId || !action) {
|
||||
return NextResponse.json({ error: 'profileId and action required' }, { status: 400 });
|
||||
}
|
||||
|
||||
const supabase = createClient();
|
||||
|
||||
// Fetch profile
|
||||
const { data: profile } = await supabase
|
||||
.from('marketplace_profiles')
|
||||
.select('*')
|
||||
.eq('id', profileId)
|
||||
.maybeSingle();
|
||||
|
||||
if (!profile) {
|
||||
return NextResponse.json({ error: 'Profile not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
let email: string | null = null;
|
||||
let name = 'there';
|
||||
|
||||
if (profile.profile_type === 'crew') {
|
||||
const { data: details } = await supabase
|
||||
.from('marketplace_crew_details')
|
||||
.select('name')
|
||||
.eq('profile_id', profileId)
|
||||
.maybeSingle();
|
||||
if (details?.name) name = details.name;
|
||||
} else {
|
||||
const { data: details } = await supabase
|
||||
.from('marketplace_vendor_details')
|
||||
.select('company_name, contact_email')
|
||||
.eq('profile_id', profileId)
|
||||
.maybeSingle();
|
||||
if (details?.company_name) name = details.company_name;
|
||||
if (details?.contact_email) email = details.contact_email;
|
||||
}
|
||||
|
||||
if (email) {
|
||||
if (action === 'approve') {
|
||||
await sendProfileApprovedEmail(email, name);
|
||||
} else if (action === 'reject' && reason) {
|
||||
await sendProfileRejectedEmail(email, name, reason);
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (err: any) {
|
||||
console.error('Admin notify error:', err);
|
||||
return NextResponse.json({ error: err.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
34
src/app/api/marketplace/send-email/route.ts
Normal file
34
src/app/api/marketplace/send-email/route.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import nodemailer from 'nodemailer';
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const { to, subject, html } = await req.json();
|
||||
|
||||
if (!to || !subject || !html) {
|
||||
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
|
||||
}
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: process.env.SMTP_HOST,
|
||||
port: parseInt(process.env.SMTP_PORT || '587'),
|
||||
secure: false,
|
||||
auth: {
|
||||
user: process.env.SMTP_USER,
|
||||
pass: process.env.SMTP_PASS,
|
||||
},
|
||||
});
|
||||
|
||||
await transporter.sendMail({
|
||||
from: `"${process.env.SMTP_FROM_NAME || 'BroadcastBeat'}" <${process.env.SMTP_FROM_EMAIL}>`,
|
||||
to,
|
||||
subject,
|
||||
html,
|
||||
});
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (err: any) {
|
||||
console.error('Marketplace email error:', err);
|
||||
return NextResponse.json({ error: err.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
153
src/app/api/marketplace/typesense-index/route.ts
Normal file
153
src/app/api/marketplace/typesense-index/route.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { createClient as createBrowserClient } from '@/lib/supabase/client';
|
||||
|
||||
// Index a profile in Typesense after approval
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const { profileId } = await req.json();
|
||||
if (!profileId) {
|
||||
return NextResponse.json({ error: 'profileId required' }, { status: 400 });
|
||||
}
|
||||
|
||||
const supabase = createBrowserClient();
|
||||
|
||||
// Fetch profile
|
||||
const { data: profile } = await supabase
|
||||
.from('marketplace_profiles')
|
||||
.select('*')
|
||||
.eq('id', profileId)
|
||||
.maybeSingle();
|
||||
|
||||
if (!profile) {
|
||||
return NextResponse.json({ error: 'Profile not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
const TYPESENSE_HOST = process.env.TYPESENSE_HOST;
|
||||
const TYPESENSE_PORT = process.env.TYPESENSE_PORT || '8108';
|
||||
const TYPESENSE_PROTOCOL = process.env.TYPESENSE_PROTOCOL || 'http';
|
||||
const TYPESENSE_API_KEY = process.env.TYPESENSE_API_KEY;
|
||||
|
||||
if (!TYPESENSE_HOST || !TYPESENSE_API_KEY) {
|
||||
return NextResponse.json({ warning: 'Typesense not configured' }, { status: 200 });
|
||||
}
|
||||
|
||||
const baseUrl = `${TYPESENSE_PROTOCOL}://${TYPESENSE_HOST}:${TYPESENSE_PORT}`;
|
||||
const headers = {
|
||||
'X-TYPESENSE-API-KEY': TYPESENSE_API_KEY,
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
if (profile.profile_type === 'crew') {
|
||||
const { data: details } = await supabase
|
||||
.from('marketplace_crew_details')
|
||||
.select('*')
|
||||
.eq('profile_id', profileId)
|
||||
.maybeSingle();
|
||||
|
||||
if (!details) return NextResponse.json({ error: 'Crew details not found' }, { status: 404 });
|
||||
|
||||
await ensureCrewCollection(baseUrl, headers);
|
||||
|
||||
const doc = {
|
||||
id: profile.id,
|
||||
name: details.name,
|
||||
location: details.location,
|
||||
skills: details.skills || [],
|
||||
union_status: details.union_status || 'non_union',
|
||||
day_rate: details.day_rate || 0,
|
||||
bio: details.bio || '',
|
||||
slug: profile.slug,
|
||||
featured: profile.subscription_tier === 'featured',
|
||||
headshot_url: details.headshot_url || '',
|
||||
};
|
||||
|
||||
await fetch(`${baseUrl}/collections/crew_profiles/documents`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(doc),
|
||||
});
|
||||
} else {
|
||||
const { data: details } = await supabase
|
||||
.from('marketplace_vendor_details')
|
||||
.select('*')
|
||||
.eq('profile_id', profileId)
|
||||
.maybeSingle();
|
||||
|
||||
if (!details) return NextResponse.json({ error: 'Vendor details not found' }, { status: 404 });
|
||||
|
||||
await ensureVendorCollection(baseUrl, headers);
|
||||
|
||||
const doc = {
|
||||
id: profile.id,
|
||||
company_name: details.company_name,
|
||||
location: details.location,
|
||||
category: details.category || '',
|
||||
description: details.description || '',
|
||||
slug: profile.slug,
|
||||
featured: profile.subscription_tier === 'featured',
|
||||
logo_url: details.logo_url || '',
|
||||
};
|
||||
|
||||
await fetch(`${baseUrl}/collections/vendor_profiles/documents`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(doc),
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (err: any) {
|
||||
console.error('Typesense index error:', err);
|
||||
return NextResponse.json({ error: err.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureCrewCollection(baseUrl: string, headers: Record<string, string>) {
|
||||
const check = await fetch(`${baseUrl}/collections/crew_profiles`, { headers });
|
||||
if (check.ok) return;
|
||||
|
||||
await fetch(`${baseUrl}/collections`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
name: 'crew_profiles',
|
||||
fields: [
|
||||
{ name: 'id', type: 'string' },
|
||||
{ name: 'name', type: 'string' },
|
||||
{ name: 'location', type: 'string' },
|
||||
{ name: 'skills', type: 'string[]', facet: true },
|
||||
{ name: 'union_status', type: 'string', facet: true },
|
||||
{ name: 'day_rate', type: 'int32', optional: true },
|
||||
{ name: 'bio', type: 'string', optional: true },
|
||||
{ name: 'slug', type: 'string' },
|
||||
{ name: 'featured', type: 'bool', facet: true },
|
||||
{ name: 'headshot_url', type: 'string', optional: true },
|
||||
],
|
||||
default_sorting_field: 'day_rate',
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
async function ensureVendorCollection(baseUrl: string, headers: Record<string, string>) {
|
||||
const check = await fetch(`${baseUrl}/collections/vendor_profiles`, { headers });
|
||||
if (check.ok) return;
|
||||
|
||||
await fetch(`${baseUrl}/collections`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
name: 'vendor_profiles',
|
||||
fields: [
|
||||
{ name: 'id', type: 'string' },
|
||||
{ name: 'company_name', type: 'string' },
|
||||
{ name: 'location', type: 'string' },
|
||||
{ name: 'category', type: 'string', facet: true },
|
||||
{ name: 'description', type: 'string', optional: true },
|
||||
{ name: 'slug', type: 'string' },
|
||||
{ name: 'featured', type: 'bool', facet: true },
|
||||
{ name: 'logo_url', type: 'string', optional: true },
|
||||
],
|
||||
default_sorting_field: '',
|
||||
}),
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user