mirror of
https://github.com/towfiqi/serpbear
synced 2025-06-26 18:15:54 +00:00
173 lines
7.5 KiB
TypeScript
173 lines
7.5 KiB
TypeScript
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<KeywordsGetResponse>) => {
|
|
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<KeywordsGetResponse>) => {
|
|
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<KeywordsDeleteRes>) => {
|
|
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<KeywordsGetResponse>) => {
|
|
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!' });
|
|
}
|
|
};
|