Files
glitternetwork--pinme/bin/utils/history.ts
T
2026-07-13 12:30:28 +08:00

250 lines
7.1 KiB
TypeScript

import fs from 'fs-extra';
import path from 'path';
import os from 'os';
import dayjs from 'dayjs';
import chalk from 'chalk';
import { formatSize } from './uploadLimits';
import { getRootDomain } from './pinmeApi';
import tracker, { getTrackErrorReason } from './tracker';
import {
TRACK_EVENTS,
TRACK_PAGES,
resolveTrackAction,
} from './trackerEvents';
// history file path
const HISTORY_DIR = path.join(os.homedir(), '.pinme');
const HISTORY_FILE = path.join(HISTORY_DIR, 'upload-history.json');
interface UploadRecord {
timestamp: number;
date: string;
path: string;
filename: string;
contentHash: string;
previewHash: string | null;
size: number;
fileCount: number;
type: 'directory' | 'file';
shortUrl?: string | null;
pinmeUrl?: string | null;
dnsUrl?: string | null;
}
interface UploadHistory {
uploads: UploadRecord[];
}
interface UploadData {
path: string;
filename?: string;
contentHash: string;
previewHash?: string | null;
size: number;
fileCount?: number;
isDirectory?: boolean;
shortUrl?: string | null;
pinmeUrl?: string | null;
dnsUrl?: string | null;
}
// ensure the history directory exists
const ensureHistoryDir = (): void => {
if (!fs.existsSync(HISTORY_DIR)) {
fs.mkdirSync(HISTORY_DIR, { recursive: true });
}
if (!fs.existsSync(HISTORY_FILE)) {
fs.writeJsonSync(HISTORY_FILE, { uploads: [] });
}
};
// save the upload history
const saveUploadHistory = (uploadData: UploadData): boolean => {
try {
ensureHistoryDir();
const history = fs.readJsonSync(HISTORY_FILE) as UploadHistory;
// add new upload record
const newRecord: UploadRecord = {
timestamp: Date.now(),
date: dayjs().format('YYYY-MM-DD HH:mm:ss'),
path: uploadData.path,
filename: uploadData.filename || path.basename(uploadData.path),
contentHash: uploadData.contentHash,
previewHash: uploadData.previewHash,
size: uploadData.size,
fileCount: uploadData.fileCount || 1,
type: uploadData.isDirectory ? 'directory' : 'file',
shortUrl: uploadData?.shortUrl || null,
pinmeUrl: uploadData?.pinmeUrl || null,
dnsUrl: uploadData?.dnsUrl || null,
};
history.uploads.unshift(newRecord); // add to the beginning
// write to file
fs.writeJsonSync(HISTORY_FILE, history, { spaces: 2 });
return true;
} catch (error: any) {
console.error(chalk.red(`Error saving upload history: ${error.message}`));
return false;
}
};
// get the upload history
const getUploadHistory = (limit: number = 10): UploadRecord[] => {
try {
ensureHistoryDir();
const history = fs.readJsonSync(HISTORY_FILE) as UploadHistory;
return history.uploads.slice(0, limit);
} catch (error: any) {
console.error(chalk.red(`Error reading upload history: ${error.message}`));
return [];
}
};
async function formatHistoryUrl(
value?: string | null,
options?: { appendRootDomain?: boolean; rootDomain?: string | null },
): Promise<string | null> {
if (!value) {
return null;
}
const normalized = value.trim();
if (!normalized) {
return null;
}
if (/^https?:\/\//.test(normalized)) {
if (
options?.appendRootDomain &&
options.rootDomain &&
(() => {
try {
return !new URL(normalized).hostname.includes('.');
} catch {
return false;
}
})()
) {
const url = new URL(normalized);
url.hostname = `${url.hostname}.${options.rootDomain}`;
return url.toString().replace(/\/$/, '');
}
return normalized.replace(/\/$/, '');
}
if (normalized.includes('.')) {
return `https://${normalized}`;
}
if (options?.appendRootDomain && options.rootDomain) {
return `https://${normalized}.${options.rootDomain}`;
}
return `https://${normalized}`;
}
// display the upload history
const displayUploadHistory = async (limit: number = 10): Promise<void> => {
try {
const history = getUploadHistory(limit);
void tracker.trackEvent(TRACK_EVENTS.uploadHistoryViewed, TRACK_PAGES.upload, {
a: resolveTrackAction(TRACK_EVENTS.uploadHistoryViewed),
record_count: history.length,
limit,
});
if (history.length === 0) {
console.log(chalk.yellow('No upload history found.'));
return;
}
console.log(chalk.cyan('Upload History:'));
console.log(chalk.cyan('-'.repeat(80)));
let rootDomain: string | null = null;
try {
rootDomain = await getRootDomain();
} catch {
rootDomain = null;
}
// Display recent records, up to limit records
const recentHistory = history.slice(-limit);
for (const [index, item] of recentHistory.entries()) {
console.log(chalk.green(`${index + 1}. ${item.filename}`));
console.log(chalk.white(` Path: ${item.path}`));
console.log(chalk.white(` IPFS CID: ${item.contentHash}`));
const preferredUrl =
(await formatHistoryUrl(item.dnsUrl)) ||
(await formatHistoryUrl(item.pinmeUrl, {
appendRootDomain: true,
rootDomain,
})) ||
(await formatHistoryUrl(item.shortUrl, {
appendRootDomain: true,
rootDomain,
}));
if (preferredUrl) {
console.log(chalk.white(` URL: ${preferredUrl}`));
}
console.log(chalk.white(` Size: ${formatSize(item.size)}`));
console.log(chalk.white(` Files: ${item.fileCount}`));
console.log(chalk.white(` Type: ${item.type === 'directory' ? 'Directory' : 'File'}`));
if (item.timestamp) {
console.log(chalk.white(` Date: ${new Date(item.timestamp).toLocaleString()}`));
}
console.log(chalk.cyan('-'.repeat(80)));
}
// display the statistics
const totalSize = history.reduce((sum, record) => sum + record.size, 0);
const totalFiles = history.reduce((sum, record) => sum + record.fileCount, 0);
console.log(chalk.bold(`Total Uploads: ${history.length}`));
console.log(chalk.bold(`Total Files: ${totalFiles}`));
console.log(chalk.bold(`Total Size: ${formatSize(totalSize)}`));
} catch (error: any) {
void tracker.trackEvent(TRACK_EVENTS.uploadHistoryFailed, TRACK_PAGES.upload, {
a: resolveTrackAction(TRACK_EVENTS.uploadHistoryFailed),
action: 'view',
reason: getTrackErrorReason(error),
});
throw error;
}
};
// clear the upload history
const clearUploadHistory = (): boolean => {
try {
ensureHistoryDir();
fs.writeJsonSync(HISTORY_FILE, { uploads: [] });
void tracker.trackEvent(TRACK_EVENTS.uploadHistoryCleared, TRACK_PAGES.upload, {
a: resolveTrackAction(TRACK_EVENTS.uploadHistoryCleared),
cleared: true,
});
console.log(chalk.green('Upload history cleared successfully.'));
return true;
} catch (error: any) {
void tracker.trackEvent(TRACK_EVENTS.uploadHistoryFailed, TRACK_PAGES.upload, {
a: resolveTrackAction(TRACK_EVENTS.uploadHistoryFailed),
action: 'clear',
reason: getTrackErrorReason(error),
});
console.error(chalk.red(`Error clearing upload history: ${error.message}`));
return false;
}
};
export {
saveUploadHistory,
getUploadHistory,
displayUploadHistory,
clearUploadHistory,
formatHistoryUrl,
};