Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/const/localization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export const localization = {
mb: 'MB',
nameConflict:
'Database with this name already exists. Please delete it first',
finalDatabaseName: 'Final database name:',
container: 'Container',
noLogsAvailable: 'No logs available for this container',
failedToDownload: 'Failed to download',
Expand Down
11 changes: 11 additions & 0 deletions app/lib/databaseNameHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Generate a database name with today's date suffix
* Format: {baseName}_YYYY_MM_DD
*/
export function generateDatabaseNameWithDate(baseName: string): string {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
return `${baseName}_${year}_${month}_${day}`;
}
26 changes: 13 additions & 13 deletions app/pages/api/databases/[name]/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,12 @@ export default async function handler(
const sanitizedFilename = String(databaseName).replace(/[^\w.-]/g, '_');

try {
res.setHeader(
'Content-Disposition',
`attachment; filename="${sanitizedFilename}.sql"`
);

const child = spawn(
'mysqldump',
const result = await spawn(
'mariadb-dump',
[
`-u${process.env.MYSQL_USERNAME}`,
`-p${process.env.MYSQL_PASSWORD}`,
`-h${process.env.MYSQL_HOST}`,
`--user=${process.env.MYSQL_USERNAME}`,
`--password=${process.env.MYSQL_PASSWORD}`,
`--host=${process.env.MYSQL_HOST}`,
`--databases ${databaseName}`,
'--no-create-db',
],
Expand All @@ -44,11 +39,16 @@ export default async function handler(
}
);

child.stdout.pipe(res);
child.stderr.on('data', (error) => {
res.setHeader(
'Content-Disposition',
`attachment; filename="${sanitizedFilename}.sql"`
);

result.stdout.pipe(res);
result.stderr.on('data', (error) => {
throw new Error(error);
});
await new Promise((resolve) => child.stdout.on('exit', resolve));
await new Promise((resolve) => result.stdout.on('exit', resolve));
} catch (error) {
res.status(500).json({
error: (error as object).toString(),
Expand Down
11 changes: 7 additions & 4 deletions app/pages/api/databases/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import path from 'node:path';

import { getUser, run } from '../../../lib/apiUtils';
import { connectToDatabase } from '../../../lib/database';
import { generateDatabaseNameWithDate } from '../../../lib/databaseNameHelper';

// First we need to disable the default body parser
export const config = {
Expand Down Expand Up @@ -45,14 +46,16 @@ export default async function handler(

if (typeof data === 'string') return res.status(400).json({ error: data });

const databaseName = data.fields.databaseName as string | undefined;
const databaseNameForm = data.fields.databaseName as string | undefined;

if (!databaseName)
if (!databaseNameForm)
return res.status(400).json({ error: 'Database name is required' });

if (databaseName.match(/^\w+$/) === null)
if (databaseNameForm.match(/^\w+$/) === null)
return res.status(400).json({ error: 'Database name is invalid' });

const databaseName = generateDatabaseNameWithDate(databaseNameForm);

const file = data.files.file as File | undefined;

if (typeof file === 'undefined' || file.newFilename === null)
Expand Down Expand Up @@ -111,7 +114,7 @@ export default async function handler(
await connection.execute(`CREATE DATABASE \`${databaseName}\``);
await run(
[
`mysql -u${process.env.MYSQL_USERNAME} `,
`mariadb -u${process.env.MYSQL_USERNAME} `,
`-p${process.env.MYSQL_PASSWORD} `,
`-h${process.env.MYSQL_HOST} `,
`--database "${databaseName}" < ${filePath}`,
Expand Down
10 changes: 9 additions & 1 deletion app/pages/api/dockerhub/[image].ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ type Response = {
readonly name: string;
readonly last_updated: string;
readonly digest: string;
readonly images: RA<{
readonly architecture: string;
}>;
}>;
readonly next: string | undefined;
};
Expand All @@ -35,7 +38,12 @@ const processTagsResponse = (tags: Response['results']): IR<DockerHubTag> =>
Object.fromEntries(
tags
// Latest is an unpredictable branch, thus will exclude it
.filter(({ name }) => !name.startsWith('sha-') && name !== 'latest')
.filter(
({ name, images }) =>
!name.startsWith('sha-') &&
name !== 'latest' &&
images.some(({ architecture }) => architecture === 'arm64')
)
.map(({ name, last_updated, digest }) => [
name,
{
Expand Down
14 changes: 13 additions & 1 deletion app/pages/databases/upload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Layout from '../../components/Layout';
import { useApi } from '../../components/useApi';
import { useDatabases } from '../index';
import { localization } from '../../const/localization';
import { generateDatabaseNameWithDate } from '../../lib/databaseNameHelper';

const bytesToMb = (size: number): number =>
Math.round((size / 1024 / 1024) * 100) / 100;
Expand All @@ -22,7 +23,8 @@ export default function Index(): JSX.Element {

const isConflict =
typeof databases === 'object' &&
databases.some(({ name }) => name === databaseName);
Boolean(databaseName) &&
databases.some(({ name }) => name === generateDatabaseNameWithDate(databaseName));

return (
<Layout title={localization.uploadNewDatabase} protected>
Expand Down Expand Up @@ -91,6 +93,16 @@ export default function Index(): JSX.Element {
diskUsage.data.free + fileSize >= diskUsage.data.size * 0.99 ? (
<p>{localization.notEnoughSpace}</p>
) : undefined}
{databaseName && (
<div className="flex flex-col gap-y-1">
<label className="text-sm font-medium text-gray-700">
{localization.finalDatabaseName}
</label>
<div className="rounded bg-gray-100 p-2 font-mono text-sm">
{generateDatabaseNameWithDate(databaseName)}
</div>
</div>
)}
<input
className={`cursor-pointer rounded-xl bg-green-500 p-3
hover:bg-green-800`}
Expand Down