import type { NextApiRequest, NextApiResponse } from 'next'; import { Op } from 'sequelize'; import db from '../../database/database'; import Keyword from '../../database/models/keyword'; import { refreshAndUpdateKeywords } from './refresh'; import { getAppSettings } from './settings'; import verifyUser from '../../utils/verifyUser'; import parseKeywords from '../../utils/parseKeywords'; 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('_', '-'); try { const allKeywords:Keyword[] = await Keyword.findAll({ where: { domain } }); const keywords: KeywordType[] = parseKeywords(allKeywords.map((e) => e.get({ plain: true }))); const slimKeywords = 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; }); return { ...keyword, lastResult: [], history: lastWeekHistory }; }); console.log('getKeywords: ', keywords.length); return res.status(200).json({ keywords: slimKeywords }); } catch (error) { console.log(error); return res.status(400).json({ error: 'Error Loading Keywords for this Domain.' }); } }; const addKeywords = async (req: NextApiRequest, res: NextApiResponse) => { const { keywords, device, country, domain, tags } = req.body; if (keywords && device && country) { const keywordsArray = keywords.replaceAll('\n', ',').split(',').map((item:string) => item.trim()); const tagsArray = tags ? tags.split(',').map((item:string) => item.trim()) : []; const keywordsToAdd: any = []; // QuickFIX for bug: https://github.com/sequelize/sequelize-typescript/issues/936 keywordsArray.forEach((keyword: string) => { 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) { 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) { 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); for (const keywordID of tagsKeywordIDs) { const response = await Keyword.findOne({ where: { ID: keywordID } }); if (response) { await response.update({ tags: JSON.stringify(tags[keywordID]) }); } } return res.status(200).json({ keywords }); } return res.status(400).json({ error: 'Invalid Payload!' }); } catch (error) { console.log('ERROR updateKeywords: ', error); return res.status(200).json({ error: 'Error Updating keywords!' }); } };