Initial commit: MOPC platform with Docker deployment setup

Full Next.js 15 platform with tRPC, Prisma, PostgreSQL, NextAuth.
Includes production Dockerfile (multi-stage, port 7600), docker-compose
with registry-based image pull, Gitea Actions CI workflow, nginx config
for portal.monaco-opc.com, deployment scripts, and DEPLOYMENT.md guide.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-30 13:41:32 +01:00
commit a606292aaa
290 changed files with 70691 additions and 0 deletions

View File

@@ -0,0 +1,187 @@
/**
* Meta WhatsApp Business Cloud API Provider
*
* Direct integration with Meta's Graph API for WhatsApp Business.
* Docs: https://developers.facebook.com/docs/whatsapp/cloud-api
*/
import type { WhatsAppProvider, WhatsAppResult } from './index'
const GRAPH_API_VERSION = 'v18.0'
const BASE_URL = `https://graph.facebook.com/${GRAPH_API_VERSION}`
export class MetaWhatsAppProvider implements WhatsAppProvider {
constructor(
private phoneNumberId: string,
private accessToken: string
) {}
/**
* Send a text message
*/
async sendText(to: string, body: string): Promise<WhatsAppResult> {
try {
const response = await fetch(
`${BASE_URL}/${this.phoneNumberId}/messages`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${this.accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
messaging_product: 'whatsapp',
recipient_type: 'individual',
to: formatPhoneNumber(to),
type: 'text',
text: { body },
}),
}
)
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.error?.message || `API error: ${response.status}`,
}
}
return {
success: true,
messageId: data.messages?.[0]?.id,
}
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
}
}
}
/**
* Send a template message
*
* Templates must be pre-approved by Meta.
* Params are passed as components to the template.
*/
async sendTemplate(
to: string,
template: string,
params: Record<string, string>
): Promise<WhatsAppResult> {
try {
// Build template components from params
const components = buildTemplateComponents(params)
const response = await fetch(
`${BASE_URL}/${this.phoneNumberId}/messages`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${this.accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
messaging_product: 'whatsapp',
recipient_type: 'individual',
to: formatPhoneNumber(to),
type: 'template',
template: {
name: template,
language: { code: 'en' },
components,
},
}),
}
)
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.error?.message || `API error: ${response.status}`,
}
}
return {
success: true,
messageId: data.messages?.[0]?.id,
}
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
}
}
}
/**
* Test the connection to Meta API
*/
async testConnection(): Promise<{ success: boolean; error?: string }> {
try {
// Try to get phone number info to verify credentials
const response = await fetch(
`${BASE_URL}/${this.phoneNumberId}`,
{
headers: {
Authorization: `Bearer ${this.accessToken}`,
},
}
)
if (!response.ok) {
const data = await response.json()
return {
success: false,
error: data.error?.message || `API error: ${response.status}`,
}
}
return { success: true }
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Connection failed',
}
}
}
}
/**
* Format phone number for WhatsApp API
* Removes + prefix and any non-digit characters
*/
function formatPhoneNumber(phone: string): string {
return phone.replace(/[^\d]/g, '')
}
/**
* Build template components from params
* Converts { param1: "value1", param2: "value2" } to WhatsApp component format
*/
function buildTemplateComponents(
params: Record<string, string>
): Array<{
type: 'body'
parameters: Array<{ type: 'text'; text: string }>
}> {
const paramValues = Object.values(params)
if (paramValues.length === 0) {
return []
}
return [
{
type: 'body',
parameters: paramValues.map((value) => ({
type: 'text',
text: value,
})),
},
]
}