Skip to content

Commit

Permalink
fix: Resolves the app crash issue when there is no database.
Browse files Browse the repository at this point in the history
closes #161, #162
  • Loading branch information
towfiqi committed Feb 13, 2024
1 parent c3ddb9d commit e5dd411
Show file tree
Hide file tree
Showing 7 changed files with 270 additions and 24 deletions.
44 changes: 35 additions & 9 deletions database/migrations/1707068556345-add-new-keyword-fields.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,44 @@

// CLI Migration
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.sequelize.transaction(async (t) => {
await queryInterface.addColumn('keyword', 'city', { type: Sequelize.DataTypes.STRING }, { transaction: t });
await queryInterface.addColumn('keyword', 'latlong', { type: Sequelize.DataTypes.STRING }, { transaction: t });
await queryInterface.addColumn('keyword', 'settings', { type: Sequelize.DataTypes.STRING }, { transaction: t });
});
up: async (queryInterface, Sequelize) => {
return queryInterface.sequelize.transaction(async (t) => {
try {
const keywordTableDefinition = await queryInterface.describeTable('keyword');
if (keywordTableDefinition) {
if (!keywordTableDefinition.city) {
await queryInterface.addColumn('keyword', 'city', { type: Sequelize.DataTypes.STRING }, { transaction: t });
}
if (!keywordTableDefinition.latlong) {
await queryInterface.addColumn('keyword', 'latlong', { type: Sequelize.DataTypes.STRING }, { transaction: t });
}
if (!keywordTableDefinition.settings) {
await queryInterface.addColumn('keyword', 'settings', { type: Sequelize.DataTypes.STRING }, { transaction: t });
}
}
} catch (error) {
console.log('error :', error);
}
});
},
down: (queryInterface) => {
return queryInterface.sequelize.transaction(async (t) => {
await queryInterface.removeColumn('keyword', 'city', { transaction: t });
await queryInterface.removeColumn('keyword', 'latlong', { transaction: t });
await queryInterface.removeColumn('keyword', 'settings', { transaction: t });
try {
const keywordTableDefinition = await queryInterface.describeTable('keyword');
if (keywordTableDefinition) {
if (keywordTableDefinition.city) {
await queryInterface.removeColumn('keyword', 'city', { transaction: t });
}
if (keywordTableDefinition.latlong) {
await queryInterface.removeColumn('keyword', 'latlong', { transaction: t });
}
if (keywordTableDefinition.latlong) {
await queryInterface.removeColumn('keyword', 'settings', { transaction: t });
}
}
} catch (error) {
console.log('error :', error);
}
});
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,26 @@
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.sequelize.transaction(async (t) => {
await queryInterface.addColumn('domain', 'search_console', { type: Sequelize.DataTypes.STRING }, { transaction: t });
try {
const domainTableDefinition = await queryInterface.describeTable('domain');
if (domainTableDefinition && !domainTableDefinition.search_console) {
await queryInterface.addColumn('domain', 'search_console', { type: Sequelize.DataTypes.STRING }, { transaction: t });
}
} catch (error) {
console.log('error :', error);
}
});
},
down: (queryInterface) => {
return queryInterface.sequelize.transaction(async (t) => {
await queryInterface.removeColumn('domain', 'search_console', { transaction: t });
try {
const domainTableDefinition = await queryInterface.describeTable('domain');
if (domainTableDefinition && domainTableDefinition.search_console) {
await queryInterface.removeColumn('domain', 'search_console', { transaction: t });
}
} catch (error) {
console.log('error :', error);
}
});
},
};
130 changes: 120 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
"build": "next build",
"start": "next start",
"cron": "node cron.js",
"prestart": "npm run db:migrate",
"start:all": "concurrently npm:start npm:cron",
"lint": "next lint",
"lint:css": "stylelint styles/*.css",
Expand Down Expand Up @@ -48,7 +47,8 @@
"reflect-metadata": "^0.1.13",
"sequelize": "^6.34.0",
"sequelize-typescript": "^2.1.6",
"sqlite3": "^5.1.6"
"sqlite3": "^5.1.6",
"umzug": "^3.6.1"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.1.4",
Expand Down
53 changes: 53 additions & 0 deletions pages/api/dbmigrate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Sequelize } from 'sequelize';
import { Umzug, SequelizeStorage } from 'umzug';
import type { NextApiRequest, NextApiResponse } from 'next';
import db from '../../database/database';
import verifyUser from '../../utils/verifyUser';

type MigrationGetResponse = {
hasMigrations: boolean,
}

type MigrationPostResponse = {
migrated: boolean,
erroor?: string
}

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const authorized = verifyUser(req, res);
if (authorized === 'authorized' && req.method === 'GET') {
await db.sync();
return getMigrationStatus(req, res);
}
if (authorized === 'authorized' && req.method === 'POST') {
return migrateDatabase(req, res);
}
return res.status(401).json({ error: authorized });
}

const getMigrationStatus = async (req: NextApiRequest, res: NextApiResponse<MigrationGetResponse>) => {
const sequelize = new Sequelize({ dialect: 'sqlite', storage: './data/database.sqlite', logging: false });
const umzug = new Umzug({
migrations: { glob: 'database/migrations/*.js' },
context: sequelize.getQueryInterface(),
storage: new SequelizeStorage({ sequelize }),
logger: undefined,
});
const migrations = await umzug.pending();
// console.log('migrations :', migrations);
// const migrationsExceuted = await umzug.executed();
return res.status(200).json({ hasMigrations: migrations.length > 0 });
};

const migrateDatabase = async (req: NextApiRequest, res: NextApiResponse<MigrationPostResponse>) => {
const sequelize = new Sequelize({ dialect: 'sqlite', storage: './data/database.sqlite', logging: false });
const umzug = new Umzug({
migrations: { glob: 'database/migrations/*.js' },
context: sequelize.getQueryInterface(),
storage: new SequelizeStorage({ sequelize }),
logger: undefined,
});
const migrations = await umzug.up();
console.log('[Updated] migrations :', migrations);
return res.status(200).json({ migrated: true });
};
11 changes: 10 additions & 1 deletion pages/domains/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import toast, { Toaster } from 'react-hot-toast';
import TopBar from '../../components/common/TopBar';
import AddDomain from '../../components/domains/AddDomain';
import Settings from '../../components/settings/Settings';
import { useFetchSettings } from '../../services/settings';
import { useCheckMigrationStatus, useFetchSettings } from '../../services/settings';
import { fetchDomainScreenshot, useFetchDomains } from '../../services/domains';
import DomainItem from '../../components/domains/DomainItem';
import Icon from '../../components/common/Icon';
Expand All @@ -22,6 +22,9 @@ const Domains: NextPage = () => {
const [domainThumbs, setDomainThumbs] = useState<thumbImages>({});
const { data: appSettingsData, isLoading: isAppSettingsLoading } = useFetchSettings();
const { data: domainsData, isLoading } = useFetchDomains(router, true);
const { data: migrationStatus } = useCheckMigrationStatus();
// const { mutate: updateDatabaseMutate, isLoading: isUpdatingDB } = useMigrateDatabase((res:Object) => { window.location.reload(); });

const appSettings:SettingsType = appSettingsData?.settings || {};
const { scraper_type = '' } = appSettings;

Expand Down Expand Up @@ -78,6 +81,12 @@ const Domains: NextPage = () => {
A Scrapper/Proxy has not been set up Yet. Open Settings to set it up and start using the app.
</div>
)}
{migrationStatus?.hasMigrations && (
<div className=' p-3 bg-black text-white text-sm text-center'>
You need to Update your database. Stop Serpbear and run this command to update your database:
<code className=' bg-gray-700 px-2 py-0 ml-1'>npm run db:migrate</code>
</div>
)}
<Head>
<title>Domains - SerpBear</title>
</Head>
Expand Down
Loading

0 comments on commit e5dd411

Please sign in to comment.