Compare commits
No commits in common. "ai-dev" and "master" have entirely different histories.
@ -1,25 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
up: async (queryInterface, Sequelize) => {
|
|
||||||
await queryInterface.changeColumn('text_files', 'epoch_millis', {
|
|
||||||
type: Sequelize.BIGINT,
|
|
||||||
});
|
|
||||||
await queryInterface.changeColumn('text_files', 'size_bytes', {
|
|
||||||
type: Sequelize.BIGINT,
|
|
||||||
});
|
|
||||||
await queryInterface.changeColumn('rename_jobs', 'epoch_millis', {
|
|
||||||
type: Sequelize.BIGINT,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: async (queryInterface, Sequelize) => {
|
|
||||||
await queryInterface.changeColumn('text_files', 'epoch_millis', {
|
|
||||||
type: Sequelize.INTEGER,
|
|
||||||
});
|
|
||||||
await queryInterface.changeColumn('text_files', 'size_bytes', {
|
|
||||||
type: Sequelize.INTEGER,
|
|
||||||
});
|
|
||||||
await queryInterface.changeColumn('rename_jobs', 'epoch_millis', {
|
|
||||||
type: Sequelize.INTEGER,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@ -29,7 +29,7 @@ new_filename: {
|
|||||||
},
|
},
|
||||||
|
|
||||||
epoch_millis: {
|
epoch_millis: {
|
||||||
type: DataTypes.BIGINT,
|
type: DataTypes.INTEGER,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -29,14 +29,14 @@ content: {
|
|||||||
},
|
},
|
||||||
|
|
||||||
size_bytes: {
|
size_bytes: {
|
||||||
type: DataTypes.BIGINT,
|
type: DataTypes.INTEGER,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
epoch_millis: {
|
epoch_millis: {
|
||||||
type: DataTypes.BIGINT,
|
type: DataTypes.INTEGER,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const cors = require('cors');
|
const cors = require('cors');
|
||||||
const app = express();
|
const app = express();
|
||||||
@ -86,8 +87,7 @@ app.use('/api-docs', function (req, res, next) {
|
|||||||
app.use(cors({origin: true}));
|
app.use(cors({origin: true}));
|
||||||
require('./auth/auth');
|
require('./auth/auth');
|
||||||
|
|
||||||
app.use(bodyParser.json({ limit: '10GB' }));
|
app.use(bodyParser.json());
|
||||||
app.use(bodyParser.urlencoded({ limit: '10GB', extended: true }));
|
|
||||||
|
|
||||||
app.use('/api/auth', authRoutes);
|
app.use('/api/auth', authRoutes);
|
||||||
app.use('/api/file', fileRoutes);
|
app.use('/api/file', fileRoutes);
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
const util = require('util');
|
const util = require('util');
|
||||||
const Multer = require('multer');
|
const Multer = require('multer');
|
||||||
// Set to a very large number to satisfy "unlimited" feel for prototype
|
const maxSize = 10 * 1024 * 1024;
|
||||||
// 100 GB as a practical "very large" limit for the VM
|
|
||||||
const maxSize = 100 * 1024 * 1024 * 1024;
|
|
||||||
|
|
||||||
let processFile = Multer({
|
let processFile = Multer({
|
||||||
storage: Multer.memoryStorage(),
|
storage: Multer.memoryStorage(),
|
||||||
|
|||||||
@ -7,12 +7,7 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
icon: icon.mdiViewDashboardOutline,
|
icon: icon.mdiViewDashboardOutline,
|
||||||
label: 'Dashboard',
|
label: 'Dashboard',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
href: '/text_files/processor',
|
|
||||||
label: 'Epoch Processor',
|
|
||||||
icon: icon.mdiFileDocumentEdit,
|
|
||||||
permissions: 'CREATE_TEXT_FILES'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
href: '/users/users-list',
|
href: '/users/users-list',
|
||||||
label: 'Users',
|
label: 'Users',
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton
|
|||||||
import BaseIcon from "../components/BaseIcon";
|
import BaseIcon from "../components/BaseIcon";
|
||||||
import { getPageTitle } from '../config'
|
import { getPageTitle } from '../config'
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import BaseButton from '../components/BaseButton';
|
|
||||||
|
|
||||||
import { hasPermission } from "../helpers/userPermissions";
|
import { hasPermission } from "../helpers/userPermissions";
|
||||||
import { fetchWidgets } from '../stores/roles/rolesSlice';
|
import { fetchWidgets } from '../stores/roles/rolesSlice';
|
||||||
@ -100,24 +99,6 @@ const Dashboard = () => {
|
|||||||
{''}
|
{''}
|
||||||
</SectionTitleLineWithButton>
|
</SectionTitleLineWithButton>
|
||||||
|
|
||||||
{/* Quick Action Card */}
|
|
||||||
<div className={`mb-6 p-6 ${corners !== 'rounded-full' ? corners : 'rounded-3xl'} bg-gradient-to-r from-blue-600 to-purple-600 text-white shadow-lg`}>
|
|
||||||
<div className="flex flex-col md:flex-row items-center justify-between">
|
|
||||||
<div className="mb-4 md:mb-0">
|
|
||||||
<h2 className="text-2xl font-bold mb-2">Welcome to Epoch Renamer</h2>
|
|
||||||
<p className="opacity-90">Start processing your text files with unique epoch timestamps in seconds.</p>
|
|
||||||
</div>
|
|
||||||
<Link href="/text_files/processor">
|
|
||||||
<BaseButton
|
|
||||||
label="Launch Processor"
|
|
||||||
color="white"
|
|
||||||
icon={icon.mdiFlash}
|
|
||||||
className="bg-white text-blue-600 hover:bg-blue-50 border-none px-8 py-3 font-bold shadow-md"
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{hasPermission(currentUser, 'CREATE_ROLES') && <WidgetCreator
|
{hasPermission(currentUser, 'CREATE_ROLES') && <WidgetCreator
|
||||||
currentUser={currentUser}
|
currentUser={currentUser}
|
||||||
isFetchingQuery={isFetchingQuery}
|
isFetchingQuery={isFetchingQuery}
|
||||||
|
|||||||
@ -1,126 +0,0 @@
|
|||||||
import { mdiFileDocumentEdit, mdiSwapHorizontal, mdiDownload } from '@mdi/js';
|
|
||||||
import Head from 'next/head';
|
|
||||||
import React, { ReactElement, useState } from 'react';
|
|
||||||
import axios from 'axios';
|
|
||||||
import CardBox from '../../components/CardBox';
|
|
||||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
|
||||||
import SectionMain from '../../components/SectionMain';
|
|
||||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
|
||||||
import FormField from '../../components/FormField';
|
|
||||||
import BaseButton from '../../components/BaseButton';
|
|
||||||
import BaseButtons from '../../components/BaseButtons';
|
|
||||||
import { getPageTitle } from '../../config';
|
|
||||||
import { useAppSelector } from '../../stores/hooks';
|
|
||||||
|
|
||||||
const ProcessorPage = () => {
|
|
||||||
const [content, setContent] = useState('');
|
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
|
||||||
const [result, setResult] = useState<{ id: string, filename: string } | null>(null);
|
|
||||||
const { currentUser } = useAppSelector((state) => state.auth);
|
|
||||||
|
|
||||||
const handleSaveAndRename = async () => {
|
|
||||||
if (!content.trim()) return;
|
|
||||||
setIsSaving(true);
|
|
||||||
try {
|
|
||||||
const epochMillis = Date.now();
|
|
||||||
const filename = `${epochMillis}.txt`;
|
|
||||||
|
|
||||||
// 1. Create Text File
|
|
||||||
const textFileResponse = await axios.post('/text_files', {
|
|
||||||
data: {
|
|
||||||
filename,
|
|
||||||
content,
|
|
||||||
epoch_millis: epochMillis,
|
|
||||||
size_bytes: new Blob([content]).size,
|
|
||||||
uploader: currentUser?.id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 2. Create Rename Job (optional but requested in domain)
|
|
||||||
await axios.post('/rename_jobs', {
|
|
||||||
data: {
|
|
||||||
original_filename: 'new_file.txt',
|
|
||||||
new_filename: filename,
|
|
||||||
epoch_millis: epochMillis,
|
|
||||||
status: 'completed',
|
|
||||||
completed_at: new Date(),
|
|
||||||
target_file: textFileResponse.data.id,
|
|
||||||
performed_by: currentUser?.id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setResult({ id: textFileResponse.data.id, filename });
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error saving file:', error);
|
|
||||||
} finally {
|
|
||||||
setIsSaving(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDownload = () => {
|
|
||||||
if (!result) return;
|
|
||||||
const element = document.createElement('a');
|
|
||||||
const file = new Blob([content], { type: 'text/plain' });
|
|
||||||
element.href = URL.createObjectURL(file);
|
|
||||||
element.download = result.filename;
|
|
||||||
document.body.appendChild(element);
|
|
||||||
element.click();
|
|
||||||
document.body.removeChild(element);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Head>
|
|
||||||
<title>{getPageTitle('Epoch File Processor')}</title>
|
|
||||||
</Head>
|
|
||||||
<SectionMain>
|
|
||||||
<SectionTitleLineWithButton icon={mdiFileDocumentEdit} title='Epoch File Processor' main>
|
|
||||||
{''}
|
|
||||||
</SectionTitleLineWithButton>
|
|
||||||
|
|
||||||
<CardBox className="mb-6">
|
|
||||||
<FormField label="Text Content" help="Type or paste your text here to process it.">
|
|
||||||
<textarea
|
|
||||||
className="px-3 py-2 max-w-full focus:ring focus:outline-none border-gray-700 rounded w-full dark:bg-slate-800 h-64"
|
|
||||||
value={content}
|
|
||||||
onChange={(e) => setContent(e.target.value)}
|
|
||||||
placeholder="Start typing..."
|
|
||||||
/>
|
|
||||||
</FormField>
|
|
||||||
|
|
||||||
<BaseButtons>
|
|
||||||
<BaseButton
|
|
||||||
label={isSaving ? 'Processing...' : 'Rename to Epoch & Save'}
|
|
||||||
color="info"
|
|
||||||
icon={mdiSwapHorizontal}
|
|
||||||
onClick={handleSaveAndRename}
|
|
||||||
disabled={isSaving || !content.trim()}
|
|
||||||
/>
|
|
||||||
{result && (
|
|
||||||
<BaseButton
|
|
||||||
label={`Download ${result.filename}`}
|
|
||||||
color="success"
|
|
||||||
icon={mdiDownload}
|
|
||||||
onClick={handleDownload}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</BaseButtons>
|
|
||||||
</CardBox>
|
|
||||||
|
|
||||||
{result && (
|
|
||||||
<CardBox className="bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800">
|
|
||||||
<p className="text-green-700 dark:text-green-400 font-semibold">
|
|
||||||
Success! File has been processed and saved as: <code className="bg-green-100 dark:bg-green-800 px-2 py-1 rounded">{result.filename}</code>
|
|
||||||
</p>
|
|
||||||
</CardBox>
|
|
||||||
)}
|
|
||||||
</SectionMain>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ProcessorPage.getLayout = function getLayout(page: ReactElement) {
|
|
||||||
return <LayoutAuthenticated>{page}</LayoutAuthenticated>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProcessorPage;
|
|
||||||
Loading…
x
Reference in New Issue
Block a user