Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ADD]: wa webhook #98

Closed
wants to merge 3 commits into from
Closed
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
6 changes: 2 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
DATABASE_URL=

NEXTAUTH_SECRET=
NEXTAUTH_URL=
NEXT_PUBLIC_API_URL=

NEXT_CRON_SECRET=

Expand All @@ -14,8 +12,8 @@ UPSTASH_REDIS_REST_TOKEN=

BLOB_READ_WRITE_TOKEN=

AWS_S3_ACCESS_KEY_ID=
AWS_S3_SECRET_ACCESS_KEY=
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=

INVOICE_PARSER_ACCESS_TOKEN=
GEMINI_API_KEY=
27 changes: 27 additions & 0 deletions .github/workflow/development-pipeline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Ence Development Pipeline

env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}

on:
push:
branches:
- develop

jobs:
Deploy-Development:
runs-on: ubuntu-latest
steps:
- users: actions/checkout@v3
- name: Install Vercel CLI
run: npm install --global vercel

- name: Pull Vercel Environment Information
run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}

- name: Build Project Artifacts
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}

- name: Deploy Project Artifacts
run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }}
27 changes: 27 additions & 0 deletions .github/workflow/production-pipeline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Ence Production Pipeline

env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}

on:
push:
branches:
- main

jobs:
Deploy-Production:
runs-on: ubuntu-latest
steps:
- users: actions/checkout@v3
- name: Install Vercel CLI
run: npm install --global vercel

- name: Pull Vercel Environment Information
run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}

- name: Build Project Artifacts
run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}

- name: Deploy Project Artifacts
run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ yarn-error.log*
# local env files
.env*.local
.env

.env.production
# vercel
.vercel

Expand All @@ -36,4 +36,7 @@ yarn-error.log*
next-env.d.ts

# extras
.vscode
.vscode


docker-compose.production.yml
44 changes: 44 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
FROM node:18-alpine AS base

FROM base AS deps

RUN apk add --no-cache libc6-compat

WORKDIR /app

COPY package*.json ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.json ]; then yarn global add pnpm && pnpm i; \
else echo 'Lockfile not found' && exit 1; \
fi



FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

COPY .env.example .env.production
RUN npm install
RUN npx prisma generate
RUN npm run build

FROM base AS runner
WORKDIR /app

ENV NODE_ENV=production

RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001

COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/.next ./.next

EXPOSE 3000

CMD ["npm", "run", "start"]
72 changes: 36 additions & 36 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -118,38 +118,38 @@ model Organization {
}

model Invoice {
id String @id @default(uuid())
invoiceNumber Int
id String @id @default(uuid())
invoiceNumber Int
instantInvoiceLink String?
sendingMethod SendMethods @default(MAIL)
customerInfo Json
dateIssue DateTime
dueDate DateTime
shippingCharge Int?
adjustmentFee Int?
discount Int?
paymentMethod PaymentMethods @default(CASH)
paymentStatus PaymentStatus @default(DUE)
paymentTerms PaymentTerms @default(IMMEDIATE)
notes String?
items InvoiceItem[]
subTotal Float
invoiceTotal Float
totalAmount Float
dueAmount Float
approvalStatus InvoiceApprovalStatus @default(UNAPPROVED)
receiptSendStatus ReceiptSendStatus @default(NOT_SENT)
auditTrailEntries AuditTrail[]
relatedDocuments InvoiceRelatedDocument[]
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
sendingMethod SendMethods @default(MAIL)
customerInfo Json
dateIssue DateTime
dueDate DateTime
shippingCharge Int?
adjustmentFee Int?
discount Int?
paymentMethod PaymentMethods @default(CASH)
paymentStatus PaymentStatus @default(DUE)
paymentTerms PaymentTerms @default(IMMEDIATE)
notes String?
items InvoiceItem[]
subTotal Float
invoiceTotal Float
totalAmount Float
dueAmount Float
approvalStatus InvoiceApprovalStatus @default(UNAPPROVED)
receiptSendStatus ReceiptSendStatus @default(NOT_SENT)
auditTrailEntries AuditTrail[]
relatedDocuments InvoiceRelatedDocument[]
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
}

model InvoiceRelatedDocument {
id String @id @default(uuid())
documentLink String
invoiceId String
invoice Invoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
id String @id @default(uuid())
documentLink String
invoiceId String
invoice Invoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
}

model InvoiceItem {
Expand All @@ -163,15 +163,15 @@ model InvoiceItem {
}

model AuditTrail {
id String @id @default(uuid())
createdAt DateTime @default(now())
actionType ActionType
title String?
id String @id @default(uuid())
createdAt DateTime @default(now())
actionType ActionType
title String?
description String?
oldStatus String?
newStatus String?
invoiceId String
invoice Invoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
oldStatus String?
newStatus String?
invoiceId String
invoice Invoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
}

enum ActionType {
Expand Down
3 changes: 2 additions & 1 deletion src/app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ export const authOptions = {
}
return true
} // Todo: Currently in dev mode will be removed
}
},
secret: process.env.NEXTAUTH_SECRET
}

const handler = NextAuth(authOptions)
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/crons/invoices/update-status/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export async function GET(request: Request) {
})

await Promise.all(
overdueInvoices.map(async (invoice) => {
overdueInvoices.map(async (invoice: any) => {
await db.invoice.update({
where: {
id: invoice.id
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/invoice/overview/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export async function GET() {
})

const getTotalCount = (invoices: Invoice[], status: string) =>
invoices.filter((invoice) => invoice.paymentStatus === status).length
invoices.filter(invoice => invoice.paymentStatus === status).length

const totalCountCurrentWeek = {
paid: getTotalCount(currentWeekInvoices, 'PAID'),
Expand Down
5 changes: 5 additions & 0 deletions src/app/api/webhook/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import whatsappWebhook from '@/webhooks/whatsapp'

export async function POST(request: Request) {
whatsappWebhook(request)
}
4 changes: 2 additions & 2 deletions src/resources/s3.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import AWS from 'aws-sdk'

const s3 = new AWS.S3({
accessKeyId: process.env.AWS_S3_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_S3_SECRET_ACCESS_KEY,
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
region: 'us-east-1'
})

Expand Down
97 changes: 97 additions & 0 deletions src/webhooks/whatsapp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
const verify_token = process.env.VERIFY_TOKEN
const token = process.env.WHATSAPP_TOKEN

export default async function whatsappWebhook(request: Request) {
if (request.method === 'GET') {
let mode = request.query['hub.mode']
let token = request.query['hub.verify_token']
let challenge = request.query['hub.challenge']

if (mode && token) {
if (mode === 'subscribe' && token === verify_token) {
console.log('WEBHOOK_VERIFIED')
return new Response(`${challenge}`, { status: 200 })
} else {
return new Response('Failed to verify webhook', { status: 403 })
}
}
}
if (request.method === 'POST') {
console.log(JSON.stringify(request.body, null, 2))

if (request.body.object) {
if (
request.body.entry &&
request.body.entry[0].changes &&
request.body.entry[0].changes[0] &&
request.body.entry[0].changes[0].value.messages &&
request.body.entry[0].changes[0].value.messages[0]
) {
let phone_number_id =
request.body.entry[0].changes[0].value.metadata.phone_number_id
let from = request.body.entry[0].changes[0].value.messages[0].from
let msg_body =
request.body.entry[0].changes[0].value.messages[0].button.text

if (msg_body === 'Approve') {
console.log('---> in approve')
fetch(
'https://graph.facebook.com/v12.0/' +
phone_number_id +
'/messages?access_token=' +
token,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
messaging_product: 'whatsapp',
to: from,
text: {
body: 'https://ence.in'
}
})
}
)
.then(response => response.json())
.then(data => {
console.log('Approval response:', data)
})
.catch(error => {
console.error('Approval error:', error)
})
return
} else if (msg_body === 'Reject') {
fetch(
'https://graph.facebook.com/v12.0/' +
phone_number_id +
'/messages?access_token=' +
token,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
messaging_product: 'whatsapp',
to: from,
text: {
body: 'Sorry! We are improving our services each day.'
}
})
}
)
.then(response => response.json())
.then(data => {
console.log('Rejection response:', data)
})
.catch(error => {
console.error('Rejection error:', error)
})
return
}
return
}
return new Response('Success', { status: 200 })
}
} else {
return new Response('Not found', { status: 404 })
}
}