feat: implement newsletter subscription endpoint

This commit is contained in:
Hossein Mehrabi 2023-10-26 15:25:00 +03:30
parent 030239dd52
commit efaec2fe11
No known key found for this signature in database
GPG Key ID: 45C04964191AFAA1
5 changed files with 187 additions and 3 deletions

7
.env
View File

@ -4,4 +4,9 @@ SIMPLECAST_ACCESS_TOKEN=
REVALIDATE_WEBHOOK_TOKEN=
DISCORD_LOGS_WEBHOOK_URL=
NEXT_PUBLIC_SITE_URL=
FATHOM_SITE_ID=
FATHOM_SITE_ID=
ODOO_DB=
ODOO_USERNAME=
ODOO_BASE_URL=
ODOO_API_KEY=
ODOO_MAILING_LIST_ID=

View File

@ -51,6 +51,7 @@
"next": "13.3.0",
"next-query-params": "^4.2.3",
"nextjs-progressbar": "^0.0.16",
"odoo-await": "^3.4.1",
"react": "18.2.0",
"react-blurhash": "^0.3.0",
"react-dom": "18.2.0",
@ -59,7 +60,8 @@
"react-quick-pinch-zoom": "^4.9.0",
"react-use": "^17.4.0",
"typescript": "5.0.4",
"use-query-params": "^2.2.1"
"use-query-params": "^2.2.1",
"yup": "^1.3.2"
},
"devDependencies": {
"@graphql-codegen/cli": "5.0.0",

View File

@ -0,0 +1,109 @@
import type { NextApiRequest, NextApiResponse } from 'next'
import Odoo from 'odoo-await'
import * as yup from 'yup'
import { settle } from '../../../utils/promise.utils'
const formSchema = yup.object().shape({
name: yup.string().optional(),
email: yup.string().email().required(),
})
const {
ODOO_DB = '',
ODOO_USERNAME = '',
ODOO_BASE_URL = '',
ODOO_API_KEY = '',
} = process.env
const ODOO_MAILING_LIST_ID = parseInt(
process.env.ODOO_MAILING_LIST_ID || '0',
10,
)
const client = new Odoo({
db: ODOO_DB,
username: ODOO_USERNAME,
baseUrl: ODOO_BASE_URL,
password: ODOO_API_KEY,
})
const isSubscribed = async (
client: Odoo,
mailingListId: number,
email: string,
) => {
const [subscription] = await client.search('mailing.contact.subscription', [
['list_id', '=', mailingListId],
['contact_id.email', '=', email],
])
return [!!subscription, subscription]
}
const createContact = async (client: Odoo, name: string, email: string) => {
let [contact] = await client.search('mailing.contact', ['email', '=', email])
if (!contact) {
contact = await client.create('mailing.contact', {
name: name,
email: email,
})
}
return contact
}
const subscribe = async (
client: Odoo,
contactId: number,
mailingListId: number,
) => {
const subscription = await client.create('mailing.contact.subscription', {
contact_id: contactId,
list_id: mailingListId,
})
return subscription
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<any>,
) {
if (req.method !== 'POST')
return res.status(405).json({ message: 'Method not allowed' })
const [payload, err] = await settle(() => formSchema.validate(req.body))
if (err)
return res.status(400).json({
message: err.message,
data: {},
})
await client.connect()
const [subscribed] = await isSubscribed(
client,
ODOO_MAILING_LIST_ID,
payload.email,
)
if (subscribed) {
return res.status(200).json({
message: 'already subscribed',
data: {},
})
}
const contact = await createContact(
client,
payload.name || payload.email,
payload.email,
)
await subscribe(client, contact, ODOO_MAILING_LIST_ID)
res.status(200).json({
message: 'Subscribed successfully!',
})
}

View File

@ -79,6 +79,18 @@ export class ApiService {
console.error(e)
return { data: { posts: [], blocks: [] }, errors: JSON.stringify(e) }
})
subscribeToMailingList = async (email: string, name?: string) => {
const res = await fetch('/api/newsletter/subscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, name }),
})
return res.json()
}
}
export const api = new ApiService()

View File

@ -4469,6 +4469,14 @@ object.values@^1.1.6:
define-properties "^1.1.4"
es-abstract "^1.20.4"
odoo-await@^3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/odoo-await/-/odoo-await-3.4.1.tgz#657284d77ada3ebaded156710436ae382517c092"
integrity sha512-9IYiqyuakuebrXSPKo2JWcBgufucQTJ87WNMFsGSzuDOrBAPI4JG4IwEyiqY3jqEeJbLh4Ooy+qHh2pMOnz0Wg==
dependencies:
uuid "^8.3.1"
xmlrpc "^1.3.2"
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
@ -4744,6 +4752,11 @@ prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
object-assign "^4.1.1"
react-is "^16.13.1"
property-expr@^2.0.5:
version "2.0.6"
resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.6.tgz#f77bc00d5928a6c748414ad12882e83f24aec1e8"
integrity sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
@ -5071,7 +5084,7 @@ satori@0.7.2:
postcss-value-parser "^4.2.0"
yoga-wasm-web "^0.3.3"
sax@^1.2.4:
sax@1.2.x, sax@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
@ -5483,6 +5496,11 @@ through@^2.3.6, through@^2.3.8:
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==
tiny-case@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-case/-/tiny-case-1.0.3.tgz#d980d66bc72b5d5a9ca86fb7c9ffdb9c898ddd03"
integrity sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==
tiny-glob@^0.2.9:
version "0.2.9"
resolved "https://registry.yarnpkg.com/tiny-glob/-/tiny-glob-0.2.9.tgz#2212d441ac17928033b110f8b3640683129d31e2"
@ -5527,6 +5545,11 @@ toggle-selection@^1.0.6:
resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==
toposort@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330"
integrity sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==
tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
@ -5613,6 +5636,11 @@ type-fest@^0.21.3:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
type-fest@^2.19.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b"
integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==
typed-array-length@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb"
@ -5730,6 +5758,11 @@ util-deprecate@^1.0.1:
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
uuid@^8.3.1:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
value-or-promise@^1.0.11, value-or-promise@^1.0.12:
version "1.0.12"
resolved "https://registry.yarnpkg.com/value-or-promise/-/value-or-promise-1.0.12.tgz#0e5abfeec70148c78460a849f6b003ea7986f15c"
@ -5863,6 +5896,19 @@ xml-js@^1.6.11:
dependencies:
sax "^1.2.4"
xmlbuilder@8.2.x:
version "8.2.2"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-8.2.2.tgz#69248673410b4ba42e1a6136551d2922335aa773"
integrity sha512-eKRAFz04jghooy8muekqzo8uCSVNeyRedbuJrp0fovbLIi7wlsYtdUn3vBAAPq2Y3/0xMz2WMEUQ8yhVVO9Stw==
xmlrpc@^1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/xmlrpc/-/xmlrpc-1.3.2.tgz#26b2ea347848d028aac7e7514b5351976de3e83d"
integrity sha512-jQf5gbrP6wvzN71fgkcPPkF4bF/Wyovd7Xdff8d6/ihxYmgETQYSuTc+Hl+tsh/jmgPLro/Aro48LMFlIyEKKQ==
dependencies:
sax "1.2.x"
xmlbuilder "8.2.x"
y18n@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
@ -5956,6 +6002,16 @@ yoga-wasm-web@0.3.3, yoga-wasm-web@^0.3.3:
resolved "https://registry.yarnpkg.com/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz#eb8e9fcb18e5e651994732f19a220cb885d932ba"
integrity sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==
yup@^1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/yup/-/yup-1.3.2.tgz#afffc458f1513ed386e6aaf4bcaa4e67a9e270dc"
integrity sha512-6KCM971iQtJ+/KUaHdrhVr2LDkfhBtFPRnsG1P8F4q3uUVQ2RfEM9xekpha9aA4GXWJevjM10eDcPQ1FfWlmaQ==
dependencies:
property-expr "^2.0.5"
tiny-case "^1.0.3"
toposort "^2.0.2"
type-fest "^2.19.0"
zen-observable-ts@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.2.5.tgz#6c6d9ea3d3a842812c6e9519209365a122ba8b58"