feat(integration): ✨ Add Google Sheets integration
This commit is contained in:
62
apps/builder/pages/api/credentials/google-sheets/callback.ts
Normal file
62
apps/builder/pages/api/credentials/google-sheets/callback.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { oauth2Client } from 'libs/google-sheets'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { getSession } from 'next-auth/react'
|
||||
import { CredentialsType, Prisma, User } from 'db'
|
||||
import prisma from 'libs/prisma'
|
||||
import { googleSheetsScopes } from './consent-url'
|
||||
import { stringify } from 'querystring'
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const session = await getSession({ req })
|
||||
const { redirectUrl, stepId } = JSON.parse(
|
||||
Buffer.from(req.query.state.toString(), 'base64').toString()
|
||||
)
|
||||
if (req.method === 'GET') {
|
||||
const code = req.query.code.toString()
|
||||
if (!code)
|
||||
return res.status(400).send({ message: "Bad request, couldn't get code" })
|
||||
if (!session?.user)
|
||||
return res.status(401).json({ message: 'Not authenticated' })
|
||||
const user = session.user as User
|
||||
const { tokens } = await oauth2Client.getToken(code)
|
||||
if (!tokens?.access_token) {
|
||||
console.error('Error getting oAuth tokens:')
|
||||
throw new Error('ERROR')
|
||||
}
|
||||
oauth2Client.setCredentials(tokens)
|
||||
const { email, scopes } = await oauth2Client.getTokenInfo(
|
||||
tokens.access_token
|
||||
)
|
||||
if (!email)
|
||||
return res
|
||||
.status(400)
|
||||
.send({ message: "Couldn't get email from getTokenInfo" })
|
||||
if (googleSheetsScopes.some((scope) => !scopes.includes(scope)))
|
||||
return res
|
||||
.status(400)
|
||||
.send({ message: "User didn't accepted required scopes" })
|
||||
const credentials = {
|
||||
name: email,
|
||||
type: CredentialsType.GOOGLE_SHEETS,
|
||||
ownerId: user.id,
|
||||
data: tokens as Prisma.InputJsonValue,
|
||||
}
|
||||
const { id: credentialsId } = await prisma.credentials.upsert({
|
||||
create: credentials,
|
||||
update: credentials,
|
||||
where: {
|
||||
name_type_ownerId: {
|
||||
name: credentials.name,
|
||||
type: credentials.type,
|
||||
ownerId: user.id,
|
||||
},
|
||||
},
|
||||
})
|
||||
const queryParams = stringify({ stepId, credentialsId })
|
||||
return res.redirect(
|
||||
`${redirectUrl}?${queryParams}` ?? `${process.env.NEXTAUTH_URL}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default handler
|
@ -0,0 +1,22 @@
|
||||
import { oauth2Client } from 'libs/google-sheets'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
|
||||
export const googleSheetsScopes = [
|
||||
'https://www.googleapis.com/auth/userinfo.email',
|
||||
'https://www.googleapis.com/auth/spreadsheets',
|
||||
'https://www.googleapis.com/auth/drive.readonly',
|
||||
]
|
||||
|
||||
const handler = (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (req.method === 'GET') {
|
||||
const url = oauth2Client.generateAuthUrl({
|
||||
access_type: 'offline',
|
||||
scope: googleSheetsScopes,
|
||||
prompt: 'consent',
|
||||
state: Buffer.from(JSON.stringify(req.query)).toString('base64'),
|
||||
})
|
||||
return res.status(301).redirect(url)
|
||||
}
|
||||
}
|
||||
|
||||
export default handler
|
@ -0,0 +1,30 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { drive } from '@googleapis/drive'
|
||||
import { getAuthenticatedGoogleClient } from 'libs/google-sheets'
|
||||
import { methodNotAllowed } from 'utils'
|
||||
import { getSession } from 'next-auth/react'
|
||||
import { User } from 'db'
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const session = await getSession({ req })
|
||||
|
||||
if (!session?.user)
|
||||
return res.status(401).json({ message: 'Not authenticated' })
|
||||
|
||||
const user = session.user as User
|
||||
if (req.method === 'GET') {
|
||||
const credentialsId = req.query.credentialsId.toString()
|
||||
const auth = await getAuthenticatedGoogleClient(user.id, credentialsId)
|
||||
const { data } = await drive({
|
||||
version: 'v3',
|
||||
auth,
|
||||
}).files.list({
|
||||
q: "mimeType='application/vnd.google-apps.spreadsheet'",
|
||||
fields: 'nextPageToken, files(id, name)',
|
||||
})
|
||||
return res.send(data)
|
||||
}
|
||||
return methodNotAllowed(res)
|
||||
}
|
||||
|
||||
export default handler
|
@ -0,0 +1,41 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { GoogleSpreadsheet } from 'google-spreadsheet'
|
||||
import { getAuthenticatedGoogleClient } from 'libs/google-sheets'
|
||||
import { methodNotAllowed } from 'utils'
|
||||
import { getSession } from 'next-auth/react'
|
||||
import { User } from 'db'
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const session = await getSession({ req })
|
||||
|
||||
if (!session?.user)
|
||||
return res.status(401).json({ message: 'Not authenticated' })
|
||||
|
||||
const user = session.user as User
|
||||
if (req.method === 'GET') {
|
||||
const credentialsId = req.query.credentialsId.toString()
|
||||
|
||||
const spreadsheetId = req.query.id.toString()
|
||||
const doc = new GoogleSpreadsheet(spreadsheetId)
|
||||
doc.useOAuth2Client(
|
||||
await getAuthenticatedGoogleClient(user.id, credentialsId)
|
||||
)
|
||||
await doc.loadInfo()
|
||||
return res.send({
|
||||
sheets: await Promise.all(
|
||||
Array.from(Array(doc.sheetCount)).map(async (_, idx) => {
|
||||
const sheet = doc.sheetsByIndex[idx]
|
||||
await sheet.loadHeaderRow()
|
||||
return {
|
||||
id: sheet.sheetId,
|
||||
name: sheet.title,
|
||||
columns: sheet.headerValues,
|
||||
}
|
||||
})
|
||||
),
|
||||
})
|
||||
}
|
||||
return methodNotAllowed(res)
|
||||
}
|
||||
|
||||
export default handler
|
25
apps/builder/pages/api/users/[id]/credentials.ts
Normal file
25
apps/builder/pages/api/users/[id]/credentials.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { User } from 'db'
|
||||
import prisma from 'libs/prisma'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { getSession } from 'next-auth/react'
|
||||
import { methodNotAllowed } from 'utils'
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const session = await getSession({ req })
|
||||
|
||||
if (!session?.user)
|
||||
return res.status(401).json({ message: 'Not authenticated' })
|
||||
|
||||
const user = session.user as User
|
||||
const id = req.query.id.toString()
|
||||
if (user.id !== id) return res.status(401).send({ message: 'Forbidden' })
|
||||
if (req.method === 'GET') {
|
||||
const credentials = await prisma.credentials.findMany({
|
||||
where: { ownerId: user.id },
|
||||
})
|
||||
return res.send({ credentials })
|
||||
}
|
||||
return methodNotAllowed(res)
|
||||
}
|
||||
|
||||
export default handler
|
Reference in New Issue
Block a user