feat(settings): add about system information
This commit is contained in:
@@ -1 +1 @@
|
|||||||
0.1.0
|
0.2.0
|
||||||
|
|||||||
4
project/apps/admin/package-lock.json
generated
4
project/apps/admin/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@mercadodevida/admin",
|
"name": "@mercadodevida/admin",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@mercadodevida/admin",
|
"name": "@mercadodevida/admin",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lexical/history": "^0.49.0",
|
"@lexical/history": "^0.49.0",
|
||||||
"@lexical/html": "^0.49.0",
|
"@lexical/html": "^0.49.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mercadodevida/admin",
|
"name": "@mercadodevida/admin",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --port 3001",
|
"dev": "next dev --port 3001",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { settingsApi, type StoreSettings } from '@/lib/api-client';
|
import { settingsApi, type AboutInfo, type StoreSettings } from '@/lib/api-client';
|
||||||
|
|
||||||
type FormData = StoreSettings;
|
type FormData = StoreSettings;
|
||||||
|
|
||||||
@@ -12,6 +12,7 @@ const TABS = [
|
|||||||
{ id: 'ai', label: 'IA para SEO', icon: '✨' },
|
{ id: 'ai', label: 'IA para SEO', icon: '✨' },
|
||||||
{ id: 'smtp', label: 'SMTP / Email', icon: '✉️' },
|
{ id: 'smtp', label: 'SMTP / Email', icon: '✉️' },
|
||||||
{ id: 'couriers', label: 'Transportistas', icon: '🚚' },
|
{ id: 'couriers', label: 'Transportistas', icon: '🚚' },
|
||||||
|
{ id: 'about', label: 'About', icon: 'ℹ️' },
|
||||||
] as const;
|
] as const;
|
||||||
type TabId = (typeof TABS)[number]['id'];
|
type TabId = (typeof TABS)[number]['id'];
|
||||||
|
|
||||||
@@ -24,12 +25,28 @@ export default function SettingsPage() {
|
|||||||
const [err, setErr] = useState('');
|
const [err, setErr] = useState('');
|
||||||
const [tab, setTab] = useState<TabId>('general');
|
const [tab, setTab] = useState<TabId>('general');
|
||||||
const [couriersText, setCouriersText] = useState('');
|
const [couriersText, setCouriersText] = useState('');
|
||||||
|
const [about, setAbout] = useState<AboutInfo | null>(null);
|
||||||
|
const [aboutLoading, setAboutLoading] = useState(false);
|
||||||
|
|
||||||
|
const loadAbout = async () => {
|
||||||
|
setAboutLoading(true);
|
||||||
|
try {
|
||||||
|
setAbout(await settingsApi.about());
|
||||||
|
} catch {
|
||||||
|
setErr('Error al cargar información del sistema');
|
||||||
|
} finally {
|
||||||
|
setAboutLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
settingsApi.get().then(d => {
|
Promise.all([
|
||||||
setData(d); setForm(d);
|
settingsApi.get().then(d => {
|
||||||
setCouriersText((d.couriers ?? []).join('\n'));
|
setData(d); setForm(d);
|
||||||
}).catch(() => setErr('Error al cargar ajustes')).finally(() => setLoading(false));
|
setCouriersText((d.couriers ?? []).join('\n'));
|
||||||
|
}),
|
||||||
|
loadAbout(),
|
||||||
|
]).catch(() => setErr('Error al cargar ajustes')).finally(() => setLoading(false));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleCouriersChange = (text: string) => {
|
const handleCouriersChange = (text: string) => {
|
||||||
@@ -38,6 +55,22 @@ export default function SettingsPage() {
|
|||||||
setForm(f => f ? { ...f, couriers: list } : f);
|
setForm(f => f ? { ...f, couriers: list } : f);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatDuration = (seconds: number) => {
|
||||||
|
const days = Math.floor(seconds / 86400);
|
||||||
|
const hours = Math.floor((seconds % 86400) / 3600);
|
||||||
|
const minutes = Math.floor((seconds % 3600) / 60);
|
||||||
|
if (days > 0) return `${days}d ${hours}h ${minutes}m`;
|
||||||
|
if (hours > 0) return `${hours}h ${minutes}m`;
|
||||||
|
return `${minutes}m`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const infoRow = (label: string, value: string | number) => (
|
||||||
|
<div className="rounded-xl border border-gray-100 bg-gray-50 px-4 py-3">
|
||||||
|
<p className="text-xs font-medium uppercase tracking-wide text-gray-400">{label}</p>
|
||||||
|
<p className="mt-1 break-words text-sm font-semibold text-gray-800">{value}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const handleSave = async (e: React.FormEvent) => {
|
const handleSave = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!form) return;
|
if (!form) return;
|
||||||
@@ -228,6 +261,81 @@ export default function SettingsPage() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{tab === 'about' && (
|
||||||
|
<>
|
||||||
|
<div className="px-6 py-4 bg-gray-50 border-b border-gray-200 flex items-center justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-base font-semibold text-gray-800">About / Sistema</h2>
|
||||||
|
<p className="text-xs text-gray-400 mt-0.5">Versiones de servicios e información segura del servidor.</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => void loadAbout()}
|
||||||
|
disabled={aboutLoading}
|
||||||
|
className="rounded-xl border border-gray-300 px-3 py-2 text-xs font-semibold text-gray-700 hover:bg-white disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{aboutLoading ? 'Actualizando…' : 'Actualizar'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="p-6 space-y-6">
|
||||||
|
{about ? (
|
||||||
|
<>
|
||||||
|
<div className="rounded-2xl border border-[#2D6A4F]/20 bg-[#2D6A4F]/5 p-5">
|
||||||
|
<p className="text-xs font-medium uppercase tracking-wide text-[#2D6A4F]">Versión producto</p>
|
||||||
|
<p className="mt-1 text-3xl font-bold text-[#1B4332]">v{about.productVersion}</p>
|
||||||
|
<p className="mt-2 text-xs text-gray-500">Generado: {new Date(about.generatedAt).toLocaleString('es-ES')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h3 className="mb-3 text-sm font-semibold text-gray-800">Servicios</h3>
|
||||||
|
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
||||||
|
{about.services.map(service => (
|
||||||
|
<div key={service.id} className="rounded-xl border border-gray-200 p-4">
|
||||||
|
<p className="text-sm font-semibold text-gray-900">{service.name}</p>
|
||||||
|
<p className="mt-1 font-mono text-xs text-gray-500">{service.id}</p>
|
||||||
|
<p className="mt-3 inline-flex rounded-full bg-gray-100 px-2.5 py-1 font-mono text-xs font-semibold text-gray-700">v{service.version}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h3 className="mb-3 text-sm font-semibold text-gray-800">Servidor</h3>
|
||||||
|
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
||||||
|
{infoRow('Host', about.server.hostname)}
|
||||||
|
{infoRow('Sistema', `${about.server.platform} ${about.server.arch}`)}
|
||||||
|
{infoRow('Kernel / Release', about.server.release)}
|
||||||
|
{infoRow('Node.js', about.server.nodeVersion)}
|
||||||
|
{infoRow('PID backend', about.server.pid)}
|
||||||
|
{infoRow('Zona horaria', about.server.timezone)}
|
||||||
|
{infoRow('Uptime host', formatDuration(about.server.uptimeSeconds))}
|
||||||
|
{infoRow('Uptime proceso', formatDuration(about.server.processUptimeSeconds))}
|
||||||
|
{infoRow('Arranque proceso', new Date(about.server.processStartedAt).toLocaleString('es-ES'))}
|
||||||
|
{infoRow('CPU cores', about.server.cpuCount)}
|
||||||
|
{infoRow('RAM total', `${about.server.totalMemoryMb} MB`)}
|
||||||
|
{infoRow('RAM libre', `${about.server.freeMemoryMb} MB`)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h3 className="mb-3 text-sm font-semibold text-gray-800">Proceso y base de datos</h3>
|
||||||
|
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
||||||
|
{infoRow('RSS proceso', `${about.server.processMemoryMb.rss} MB`)}
|
||||||
|
{infoRow('Heap usado', `${about.server.processMemoryMb.heapUsed} MB`)}
|
||||||
|
{infoRow('Heap total', `${about.server.processMemoryMb.heapTotal} MB`)}
|
||||||
|
{infoRow('PostgreSQL', about.database.postgresVersion)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="rounded-xl border border-gray-200 p-8 text-center text-sm text-gray-500">
|
||||||
|
{aboutLoading ? 'Cargando información del sistema…' : 'No se pudo cargar la información del sistema.'}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{tab === 'footer' && (
|
{tab === 'footer' && (
|
||||||
<>
|
<>
|
||||||
<div className="px-6 py-4 bg-gray-50 border-b border-gray-200">
|
<div className="px-6 py-4 bg-gray-50 border-b border-gray-200">
|
||||||
@@ -244,12 +352,14 @@ export default function SettingsPage() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="px-6 py-5 bg-gray-50 border-t border-gray-200 flex justify-end">
|
{tab !== 'about' && (
|
||||||
<button type="submit" disabled={saving || !form}
|
<div className="px-6 py-5 bg-gray-50 border-t border-gray-200 flex justify-end">
|
||||||
className="px-6 py-2.5 bg-[#2D6A4F] hover:bg-[#1B4332] disabled:opacity-50 text-white text-sm font-semibold rounded-xl transition-colors">
|
<button type="submit" disabled={saving || !form}
|
||||||
{saving ? 'Guardando...' : 'Guardar cambios'}
|
className="px-6 py-2.5 bg-[#2D6A4F] hover:bg-[#1B4332] disabled:opacity-50 text-white text-sm font-semibold rounded-xl transition-colors">
|
||||||
</button>
|
{saving ? 'Guardando...' : 'Guardar cambios'}
|
||||||
</div>
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -395,7 +395,31 @@ export interface StoreSettings {
|
|||||||
couriers?: string[];
|
couriers?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AboutInfo {
|
||||||
|
productVersion: string;
|
||||||
|
generatedAt: string;
|
||||||
|
services: Array<{ id: string; name: string; version: string }>;
|
||||||
|
server: {
|
||||||
|
hostname: string;
|
||||||
|
platform: string;
|
||||||
|
arch: string;
|
||||||
|
release: string;
|
||||||
|
uptimeSeconds: number;
|
||||||
|
processUptimeSeconds: number;
|
||||||
|
processStartedAt: string;
|
||||||
|
nodeVersion: string;
|
||||||
|
pid: number;
|
||||||
|
cpuCount: number;
|
||||||
|
totalMemoryMb: number;
|
||||||
|
freeMemoryMb: number;
|
||||||
|
processMemoryMb: { rss: number; heapUsed: number; heapTotal: number };
|
||||||
|
timezone: string;
|
||||||
|
};
|
||||||
|
database: { postgresVersion: string };
|
||||||
|
}
|
||||||
|
|
||||||
export const settingsApi = {
|
export const settingsApi = {
|
||||||
get: () => api.get<StoreSettings>('/api/admin/settings'),
|
get: () => api.get<StoreSettings>('/api/admin/settings'),
|
||||||
update: (data: Partial<StoreSettings>) => api.patch<StoreSettings>('/api/admin/settings', data),
|
update: (data: Partial<StoreSettings>) => api.patch<StoreSettings>('/api/admin/settings', data),
|
||||||
|
about: () => api.get<AboutInfo>('/api/admin/about'),
|
||||||
};
|
};
|
||||||
|
|||||||
4
project/apps/pos/package-lock.json
generated
4
project/apps/pos/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "mercadodevida-pos",
|
"name": "mercadodevida-pos",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mercadodevida-pos",
|
"name": "mercadodevida-pos",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"next": "^16.3.1",
|
"next": "^16.3.1",
|
||||||
"react": "^19.2.8",
|
"react": "^19.2.8",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mercadodevida-pos",
|
"name": "mercadodevida-pos",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --port 3002",
|
"dev": "next dev --port 3002",
|
||||||
|
|||||||
4
project/frontend/package-lock.json
generated
4
project/frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"next": "16.3.1",
|
"next": "16.3.1",
|
||||||
"react": "19.2.8",
|
"react": "19.2.8",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
|||||||
4
project/package-lock.json
generated
4
project/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "mercadodevida-backend",
|
"name": "mercadodevida-backend",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mercadodevida-backend",
|
"name": "mercadodevida-backend",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fastify/cookie": "^11.1.2",
|
"@fastify/cookie": "^11.1.2",
|
||||||
"@fastify/cors": "^11.3.0",
|
"@fastify/cors": "^11.3.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mercadodevida-backend",
|
"name": "mercadodevida-backend",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "mercadodevida vNext backend - modular monolith skeleton",
|
"description": "mercadodevida vNext backend - modular monolith skeleton",
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
import os from 'node:os';
|
||||||
|
import path from 'node:path';
|
||||||
import type { FastifyInstance } from 'fastify';
|
import type { FastifyInstance } from 'fastify';
|
||||||
import type { FastifySchema } from 'fastify';
|
import type { FastifySchema } from 'fastify';
|
||||||
import type pg from 'pg';
|
import type pg from 'pg';
|
||||||
@@ -62,6 +65,50 @@ export function parseCouriers(raw: string | undefined | null): string[] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ServiceVersion = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function readTextIfExists(filePath: string): string | null {
|
||||||
|
try {
|
||||||
|
return readFileSync(filePath, 'utf8').trim();
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readPackageVersion(id: string, label: string, packagePath: string): ServiceVersion {
|
||||||
|
try {
|
||||||
|
const raw = readFileSync(packagePath, 'utf8');
|
||||||
|
const pkg = JSON.parse(raw) as { name?: string; version?: string };
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name: pkg.name ?? label,
|
||||||
|
version: pkg.version ?? 'unknown',
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
return { id, name: label, version: 'unknown' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectServiceVersions(): { productVersion: string; services: ServiceVersion[] } {
|
||||||
|
const root = process.cwd();
|
||||||
|
return {
|
||||||
|
productVersion: readTextIfExists(path.join(root, 'VERSION')) ?? 'unknown',
|
||||||
|
services: [
|
||||||
|
readPackageVersion('backend', 'Backend API', path.join(root, 'package.json')),
|
||||||
|
readPackageVersion('admin', 'Admin', path.join(root, 'apps/admin/package.json')),
|
||||||
|
readPackageVersion('tpv', 'TPV', path.join(root, 'apps/pos/package.json')),
|
||||||
|
readPackageVersion('frontend', 'Frontend', path.join(root, 'frontend/package.json')),
|
||||||
|
readPackageVersion('storefront', 'Storefront SEO', path.join(root, 'storefront/package.json')),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const PROCESS_STARTED_AT = new Date();
|
||||||
|
|
||||||
const SETTING_KEYS: Record<string, string> = {
|
const SETTING_KEYS: Record<string, string> = {
|
||||||
storeName: 'store_name',
|
storeName: 'store_name',
|
||||||
storeTagline: 'store_tagline',
|
storeTagline: 'store_tagline',
|
||||||
@@ -96,6 +143,53 @@ export async function registerStoreSettingsRoutes(
|
|||||||
app: FastifyInstance,
|
app: FastifyInstance,
|
||||||
deps: StoreSettingsRoutesDeps,
|
deps: StoreSettingsRoutesDeps,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
// GET /admin/about — versions and safe server diagnostics for admin UI
|
||||||
|
const getAboutSchema: FastifySchema = {
|
||||||
|
tags: ['Admin'],
|
||||||
|
summary: 'Get service versions and server info (admin)',
|
||||||
|
response: { 401: errorSchema, 403: errorSchema },
|
||||||
|
};
|
||||||
|
app.get('/admin/about', { schema: getAboutSchema }, async (request, reply) => {
|
||||||
|
const user = await deps.authenticate(request);
|
||||||
|
requireRole(user, 'admin');
|
||||||
|
|
||||||
|
const { productVersion, services } = collectServiceVersions();
|
||||||
|
const dbVersion = await deps.pool
|
||||||
|
.query<{ server_version: string }>('SHOW server_version')
|
||||||
|
.then((result) => result.rows[0]?.server_version ?? 'unknown')
|
||||||
|
.catch(() => 'unavailable');
|
||||||
|
const memory = process.memoryUsage();
|
||||||
|
|
||||||
|
return reply.send({
|
||||||
|
productVersion,
|
||||||
|
generatedAt: new Date().toISOString(),
|
||||||
|
services,
|
||||||
|
server: {
|
||||||
|
hostname: os.hostname(),
|
||||||
|
platform: os.platform(),
|
||||||
|
arch: os.arch(),
|
||||||
|
release: os.release(),
|
||||||
|
uptimeSeconds: Math.floor(os.uptime()),
|
||||||
|
processUptimeSeconds: Math.floor(process.uptime()),
|
||||||
|
processStartedAt: PROCESS_STARTED_AT.toISOString(),
|
||||||
|
nodeVersion: process.version,
|
||||||
|
pid: process.pid,
|
||||||
|
cpuCount: os.cpus().length,
|
||||||
|
totalMemoryMb: Math.round(os.totalmem() / 1024 / 1024),
|
||||||
|
freeMemoryMb: Math.round(os.freemem() / 1024 / 1024),
|
||||||
|
processMemoryMb: {
|
||||||
|
rss: Math.round(memory.rss / 1024 / 1024),
|
||||||
|
heapUsed: Math.round(memory.heapUsed / 1024 / 1024),
|
||||||
|
heapTotal: Math.round(memory.heapTotal / 1024 / 1024),
|
||||||
|
},
|
||||||
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||||
|
},
|
||||||
|
database: {
|
||||||
|
postgresVersion: dbVersion,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// GET /admin/settings — fetch all settings
|
// GET /admin/settings — fetch all settings
|
||||||
const getSettingsSchema: FastifySchema = {
|
const getSettingsSchema: FastifySchema = {
|
||||||
tags: ['Admin'],
|
tags: ['Admin'],
|
||||||
|
|||||||
4
project/storefront/package-lock.json
generated
4
project/storefront/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "mercadodevida-storefront",
|
"name": "mercadodevida-storefront",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mercadodevida-storefront",
|
"name": "mercadodevida-storefront",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/postcss": "^4.1.17",
|
"@tailwindcss/postcss": "^4.1.17",
|
||||||
"next": "^16.0.5",
|
"next": "^16.0.5",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mercadodevida-storefront",
|
"name": "mercadodevida-storefront",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "mercadodevida customer storefront shell",
|
"description": "mercadodevida customer storefront shell",
|
||||||
|
|||||||
Reference in New Issue
Block a user