import type { NextApiRequest, NextApiResponse } from 'next'; import { Op } from 'sequelize'; import db from '../../database/database'; import Keyword from '../../database/models/keyword'; import { getAppSettings } from './settings'; import verifyUser from '../../utils/verifyUser'; import parseKeywords from '../../utils/parseKeywords'; import { integrateKeywordSCData, readLocalSCData } from '../../utils/searchConsole'; import refreshAndUpdateKeywords from '../../utils/refresh'; type KeywordsGetResponse = { keywords?: KeywordType[], error?: string|null, } type KeywordsDeleteRes = { domainRemoved?: number, keywordsRemoved?: number, error?: string|null, } export default async function handler(req: NextApiRequest, res: NextApiResponse) { await db.sync(); const authorized = verifyUser(req, res); if (authorized !== 'authorized') { return res.status(401).json({ error: authorized }); } if (req.method === 'GET') { return getKeywords(req, res); } if (req.method === 'POST') { return addKeywords(req, res); } if (req.method === 'DELETE') { return deleteKeywords(req, res); } if (req.method === 'PUT') { return updateKeywords(req, res); } return res.status(502).json({ error: 'Unrecognized Route.' }); } const getKeywords = async (req: NextApiRequest, res: NextApiResponse) => { if (!req.query.domain && typeof req.query.domain !== 'string') { return res.status(400).json({ error: 'Domain is Required!' }); } const domain = (req.query.domain as string).replaceAll('-', '.').replaceAll('_', '-'); const integratedSC = process.env.SEARCH_CONSOLE_PRIVATE_KEY && process.env.SEARCH_CONSOLE_CLIENT_EMAIL; const domainSCData = integratedSC ? await readLocalSCData(domain) : false; try { const allKeywords:Keyword[] = await Keyword.findAll({ where: { domain } }); const keywords: KeywordType[] = parseKeywords(allKeywords.map((e) => e.get({ plain: true }))); const processedKeywords = keywords.map((keyword) => { const historyArray = Object.keys(keyword.history).map((dateKey:string) => ({ date: new Date(dateKey).getTime(), dateRaw: dateKey, position: keyword.history[dateKey], })); const historySorted = historyArray.sort((a, b) => a.date - b.date); const lastWeekHistory :KeywordHistory = {}; historySorted.slice(-7).forEach((x:any) => { lastWeekHistory[x.dateRaw] = x.position; }); const keywordWithSlimHistory = { ...keyword, lastResult: [], history: lastWeekHistory }; const finalKeyword = domainSCData ? integrateKeywordSCData(keywordWithSlimHistory, domainSCData) : keywordWithSlimHistory; return finalKeyword; }); return res.status(200).json({ keywords: processedKeywords }); } catch (error) { console.log('[ERROR] Getting Domain Keywords for ', domain, error); return res.status(400).json({ error: 'Error Loading Keywords for this Domain.' }); } }; const addKeywords = async (req: NextApiRequest, res: NextApiResponse) => { const { keywords } = req.body; if (keywords && Array.isArray(keywords) && keywords.length > 0) { // const keywordsArray = keywords.replaceAll('\n', ',').split(',').map((item:string) => item.trim()); const keywordsToAdd: any = []; // QuickFIX for bug: https://github.com/sequelize/sequelize-typescript/issues/936 keywords.forEach((kwrd: KeywordAddPayload) => { const { keyword, device, country, domain, tags } = kwrd; const tagsArray = tags ? tags.split(',').map((item:string) => item.trim()) : []; const newKeyword = { keyword, device, domain, country, position: 0, updating: true, history: JSON.stringify({}), url: '', tags: JSON.stringify(tagsArray), sticky: false, lastUpdated: new Date().toJSON(), added: new Date().toJSON(), }; keywordsToAdd.push(newKeyword); }); try { const newKeywords:Keyword[] = await Keyword.bulkCreate(keywordsToAdd); const formattedkeywords = newKeywords.map((el) => el.get({ plain: true })); const keywordsParsed: KeywordType[] = parseKeywords(formattedkeywords); const settings = await getAppSettings(); refreshAndUpdateKeywords(newKeywords, settings); // Queue the SERP Scraping Process return res.status(201).json({ keywords: keywordsParsed }); } catch (error) { console.log('[ERROR] Adding New Keywords ', error); return res.status(400).json({ error: 'Could Not Add New Keyword!' }); } } else { return res.status(400).json({ error: 'Necessary Keyword Data Missing' }); } }; const deleteKeywords = async (req: NextApiRequest, res: NextApiResponse) => { if (!req.query.id && typeof req.query.id !== 'string') { return res.status(400).json({ error: 'keyword ID is Required!' }); } console.log('req.query.id: ', req.query.id); try { const keywordsToRemove = (req.query.id as string).split(',').map((item) => parseInt(item, 10)); const removeQuery = { where: { ID: { [Op.in]: keywordsToRemove } } }; const removedKeywordCount: number = await Keyword.destroy(removeQuery); return res.status(200).json({ keywordsRemoved: removedKeywordCount }); } catch (error) { console.log('[ERROR] Removing Keyword. ', error); return res.status(400).json({ error: 'Could Not Remove Keyword!' }); } }; const updateKeywords = async (req: NextApiRequest, res: NextApiResponse) => { if (!req.query.id && typeof req.query.id !== 'string') { return res.status(400).json({ error: 'keyword ID is Required!' }); } if (req.body.sticky === undefined && !req.body.tags === undefined) { return res.status(400).json({ error: 'keyword Payload Missing!' }); } const keywordIDs = (req.query.id as string).split(',').map((item) => parseInt(item, 10)); const { sticky, tags } = req.body; try { let keywords: KeywordType[] = []; if (sticky !== undefined) { await Keyword.update({ sticky }, { where: { ID: { [Op.in]: keywordIDs } } }); const updateQuery = { where: { ID: { [Op.in]: keywordIDs } } }; const updatedKeywords:Keyword[] = await Keyword.findAll(updateQuery); const formattedKeywords = updatedKeywords.map((el) => el.get({ plain: true })); keywords = parseKeywords(formattedKeywords); return res.status(200).json({ keywords }); } if (tags) { const tagsKeywordIDs = Object.keys(tags); const multipleKeywords = tagsKeywordIDs.length > 1; for (const keywordID of tagsKeywordIDs) { const selectedKeyword = await Keyword.findOne({ where: { ID: keywordID } }); const currentTags = selectedKeyword && selectedKeyword.tags ? JSON.parse(selectedKeyword.tags) : []; const mergedTags = Array.from(new Set([...currentTags, ...tags[keywordID]])); if (selectedKeyword) { await selectedKeyword.update({ tags: JSON.stringify(multipleKeywords ? mergedTags : tags[keywordID]) }); } } return res.status(200).json({ keywords }); } return res.status(400).json({ error: 'Invalid Payload!' }); } catch (error) { console.log('[ERROR] Updating Keyword. ', error); return res.status(200).json({ error: 'Error Updating keywords!' }); } };