mirror of
https://github.com/arthur-pbty/links.git
synced 2026-08-01 20:29:27 +02:00
Refactor code structure and remove redundant sections for improved readability and maintainability
This commit is contained in:
+35
@@ -0,0 +1,35 @@
|
||||
import { createHash } from 'node:crypto';
|
||||
|
||||
function getRequiredEnv(name) {
|
||||
const value = process.env[name];
|
||||
if (!value) {
|
||||
throw new Error(`Missing required environment variable: ${name}`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export const ADMIN_USERNAME = getRequiredEnv('ADMIN_USERNAME');
|
||||
export const ADMIN_PASSWORD = getRequiredEnv('ADMIN_PASSWORD');
|
||||
export const ADMIN_SESSION_SECRET = getRequiredEnv('ADMIN_SESSION_SECRET');
|
||||
export const ADMIN_COOKIE = 'admin_session';
|
||||
|
||||
function hash(value) {
|
||||
return createHash('sha256').update(value).digest('hex');
|
||||
}
|
||||
|
||||
function expectedToken() {
|
||||
return hash(`${ADMIN_USERNAME}:${ADMIN_PASSWORD}:${ADMIN_SESSION_SECRET}`);
|
||||
}
|
||||
|
||||
export function isValidAdminCredentials(username, password) {
|
||||
return username === ADMIN_USERNAME && password === ADMIN_PASSWORD;
|
||||
}
|
||||
|
||||
export function createAdminSessionToken() {
|
||||
return expectedToken();
|
||||
}
|
||||
|
||||
export function isAdminRequest(request) {
|
||||
const token = request.cookies.get(ADMIN_COOKIE)?.value;
|
||||
return token === expectedToken();
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { socialLinks } from '../config';
|
||||
|
||||
const DATA_DIR = path.join(process.cwd(), 'data');
|
||||
const LINKS_FILE = path.join(DATA_DIR, 'links.json');
|
||||
const ANALYTICS_FILE = path.join(DATA_DIR, 'analytics.json');
|
||||
|
||||
function slugify(text) {
|
||||
return (text || '')
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '')
|
||||
.slice(0, 50);
|
||||
}
|
||||
|
||||
function withId(link, index) {
|
||||
return {
|
||||
id: link.id || `${slugify(link.name)}-${index + 1}`,
|
||||
name: link.name,
|
||||
url: link.url,
|
||||
icon: link.icon,
|
||||
description: link.description || '',
|
||||
category: link.category || 'Autres'
|
||||
};
|
||||
}
|
||||
|
||||
async function fileExists(filePath) {
|
||||
try {
|
||||
await readFile(filePath, 'utf-8');
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function readJson(filePath, fallback) {
|
||||
try {
|
||||
const content = await readFile(filePath, 'utf-8');
|
||||
return JSON.parse(content);
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
async function writeJson(filePath, value) {
|
||||
await writeFile(filePath, JSON.stringify(value, null, 2), 'utf-8');
|
||||
}
|
||||
|
||||
export async function ensureDataFiles() {
|
||||
await mkdir(DATA_DIR, { recursive: true });
|
||||
|
||||
if (!(await fileExists(LINKS_FILE))) {
|
||||
const initialLinks = socialLinks.map(withId);
|
||||
await writeJson(LINKS_FILE, initialLinks);
|
||||
}
|
||||
|
||||
if (!(await fileExists(ANALYTICS_FILE))) {
|
||||
await writeJson(ANALYTICS_FILE, {});
|
||||
}
|
||||
}
|
||||
|
||||
export async function getLinks() {
|
||||
await ensureDataFiles();
|
||||
return readJson(LINKS_FILE, []);
|
||||
}
|
||||
|
||||
export async function addLink(payload) {
|
||||
await ensureDataFiles();
|
||||
const links = await getLinks();
|
||||
|
||||
const newLink = {
|
||||
id: randomUUID(),
|
||||
name: payload.name,
|
||||
url: payload.url,
|
||||
icon: payload.icon || '/github.png',
|
||||
description: payload.description || '',
|
||||
category: payload.category || 'Autres'
|
||||
};
|
||||
|
||||
const updatedLinks = [...links, newLink];
|
||||
await writeJson(LINKS_FILE, updatedLinks);
|
||||
return newLink;
|
||||
}
|
||||
|
||||
export async function removeLink(linkId) {
|
||||
await ensureDataFiles();
|
||||
const links = await getLinks();
|
||||
const nextLinks = links.filter((link) => link.id !== linkId);
|
||||
|
||||
if (nextLinks.length === links.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await writeJson(LINKS_FILE, nextLinks);
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function incrementClick(linkId) {
|
||||
await ensureDataFiles();
|
||||
const analytics = await readJson(ANALYTICS_FILE, {});
|
||||
analytics[linkId] = (analytics[linkId] || 0) + 1;
|
||||
await writeJson(ANALYTICS_FILE, analytics);
|
||||
}
|
||||
|
||||
export async function getAnalyticsForLinks() {
|
||||
await ensureDataFiles();
|
||||
const [links, analytics] = await Promise.all([
|
||||
readJson(LINKS_FILE, []),
|
||||
readJson(ANALYTICS_FILE, {})
|
||||
]);
|
||||
|
||||
return links.map((link) => ({
|
||||
id: link.id,
|
||||
name: link.name,
|
||||
url: link.url,
|
||||
clicks: analytics[link.id] || 0
|
||||
}));
|
||||
}
|
||||
Reference in New Issue
Block a user