forked from admin/zeropost-tool
feat: zeropost-tool — Next.js 16 кабинет
- Auth: iron-session, регистрация/логин по email+password - Дашборд со списком каналов - 3-шаговая анкета создания канала (база/стиль/примеры+табу) - Страница канала с генератором постов через polling - Тёмная тема, Tailwind 3.4, accent emerald - Прокси-API к zeropost-engine с x-user-id - Совместимость с Next 16 async cookies/params
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Прямой клиент к БД zeropost (для авторизации — engine не даёт login роута)
|
||||
*/
|
||||
import { Pool } from 'pg';
|
||||
|
||||
let pool;
|
||||
export function getPool() {
|
||||
if (!pool) {
|
||||
pool = new Pool({
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
port: parseInt(process.env.DB_PORT || 5432),
|
||||
database: process.env.DB_NAME || 'zeropost',
|
||||
user: process.env.DB_USER || 'postgres',
|
||||
password: process.env.DB_PASS || 'postgres',
|
||||
});
|
||||
}
|
||||
return pool;
|
||||
}
|
||||
|
||||
export const q = (text, params) => getPool().query(text, params);
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Engine client — единая точка вызовов к zeropost-engine
|
||||
*/
|
||||
const ENGINE_URL = process.env.ENGINE_URL || 'http://127.0.0.1:3040';
|
||||
const ENGINE_SECRET = process.env.ENGINE_SECRET || 'zeropost_internal_2026';
|
||||
|
||||
async function call(path, options = {}) {
|
||||
const { userId, body, method = 'GET' } = options;
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'x-internal-secret': ENGINE_SECRET,
|
||||
};
|
||||
if (userId) headers['x-user-id'] = String(userId);
|
||||
|
||||
const url = `${ENGINE_URL}${path}`;
|
||||
const res = await fetch(url, {
|
||||
method,
|
||||
headers,
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
cache: 'no-store',
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({ error: res.statusText }));
|
||||
throw new Error(err.error || `Engine ${res.status}`);
|
||||
}
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export const engine = {
|
||||
// Channels
|
||||
listChannels: (userId) => call('/api/channels/', { userId }),
|
||||
getChannel: (userId, id) => call(`/api/channels/${id}`, { userId }),
|
||||
createChannel: (userId, data) => call('/api/channels/', { userId, method: 'POST', body: data }),
|
||||
updateChannel: (userId, id, data) => call(`/api/channels/${id}`, { userId, method: 'PATCH', body: data }),
|
||||
deleteChannel: (userId, id) => call(`/api/channels/${id}`, { userId, method: 'DELETE' }),
|
||||
|
||||
// Generation
|
||||
generate: (userId, data) => call('/api/generate/', { userId, method: 'POST', body: data }),
|
||||
getJob: (userId, id) => call(`/api/generate/${id}`, { userId }),
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
import { cookies } from 'next/headers';
|
||||
import { getIronSession } from 'iron-session';
|
||||
|
||||
const sessionOptions = {
|
||||
cookieName: 'zeropost_session',
|
||||
password: process.env.SESSION_SECRET || 'this_is_a_dev_secret_change_in_prod_at_least_32_chars',
|
||||
cookieOptions: {
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
httpOnly: true,
|
||||
maxAge: 60 * 60 * 24 * 30,
|
||||
},
|
||||
};
|
||||
|
||||
export async function getSession() {
|
||||
const cookieStore = await cookies();
|
||||
return getIronSession(cookieStore, sessionOptions);
|
||||
}
|
||||
|
||||
export async function requireUser() {
|
||||
const s = await getSession();
|
||||
if (!s.userId) return null;
|
||||
return { id: s.userId, email: s.email, name: s.name };
|
||||
}
|
||||
Reference in New Issue
Block a user