feat: photo-search, system settings, ROADMAP

- PhotoSearchModal: Yandex photo-search с профилями доменов
- SystemSettings: управление app_settings (admin-only, /system)
- ROADMAP.md: актуальный план фич P1-P10
- Header, ChannelView, session: поддержка is_admin
This commit is contained in:
Nik (Claude)
2026-06-07 14:04:14 +03:00
parent 76eb519018
commit 2e550d2993
14 changed files with 931 additions and 38 deletions
+16
View File
@@ -0,0 +1,16 @@
import { NextResponse } from 'next/server';
import { requireAdmin } from '@/lib/session';
import { engine } from '@/lib/engine';
export async function PUT(req, { params }) {
const admin = await requireAdmin();
if (!admin) return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
try {
const { key } = await params;
const body = await req.json();
const row = await engine.updateSetting(key, body?.value);
return NextResponse.json(row);
} catch (err) {
return NextResponse.json({ error: err.message }, { status: err.status || 500 });
}
}
+17
View File
@@ -0,0 +1,17 @@
import { NextResponse } from 'next/server';
import { requireAdmin } from '@/lib/session';
import { engine } from '@/lib/engine';
// GET /api/admin/settings?category=photo_search
export async function GET(req) {
const admin = await requireAdmin();
if (!admin) return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
try {
const { searchParams } = new URL(req.url);
const category = searchParams.get('category') || undefined;
const rows = await engine.listSettings(category);
return NextResponse.json(rows);
} catch (err) {
return NextResponse.json({ error: err.message }, { status: err.status || 500 });
}
}
+11 -3
View File
@@ -16,19 +16,23 @@ export async function POST(req) {
}
const hash = await bcrypt.hash(password, 10);
const { rows } = await q(
`INSERT INTO users (email,password) VALUES ($1,$2) RETURNING id,email,name`,
`INSERT INTO users (email,password) VALUES ($1,$2) RETURNING id,email,name,is_admin`,
[email, hash]
);
const user = rows[0];
const s = await getSession();
s.userId = user.id;
s.email = user.email;
s.isAdmin = !!user.is_admin;
await s.save();
return NextResponse.json({ ok: true, user });
}
// login
const { rows } = await q(`SELECT id,email,password,name FROM users WHERE email=$1`, [email]);
const { rows } = await q(
`SELECT id,email,password,name,is_admin FROM users WHERE email=$1`,
[email]
);
if (!rows.length) {
return NextResponse.json({ error: 'Неверный email или пароль' }, { status: 401 });
}
@@ -41,6 +45,10 @@ export async function POST(req) {
s.userId = user.id;
s.email = user.email;
s.name = user.name;
s.isAdmin = !!user.is_admin;
await s.save();
return NextResponse.json({ ok: true, user: { id: user.id, email: user.email, name: user.name } });
return NextResponse.json({
ok: true,
user: { id: user.id, email: user.email, name: user.name, isAdmin: !!user.is_admin },
});
}
+18
View File
@@ -0,0 +1,18 @@
import { NextResponse } from 'next/server';
import { requireUser } from '@/lib/session';
import { engine } from '@/lib/engine';
export async function POST(req) {
const user = await requireUser();
if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
try {
const body = await req.json();
const data = await engine.photoSearchByQuery(body);
return NextResponse.json(data);
} catch (err) {
return NextResponse.json(
{ error: err.message, code: err.code },
{ status: err.status || 500 }
);
}
}
+14
View File
@@ -0,0 +1,14 @@
import { NextResponse } from 'next/server';
import { requireUser } from '@/lib/session';
import { engine } from '@/lib/engine';
export async function GET() {
const user = await requireUser();
if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
try {
const data = await engine.photoSearchProfiles();
return NextResponse.json(data);
} catch (err) {
return NextResponse.json({ error: err.message }, { status: err.status || 500 });
}
}
+14
View File
@@ -0,0 +1,14 @@
import { NextResponse } from 'next/server';
import { requireUser } from '@/lib/session';
import { engine } from '@/lib/engine';
export async function GET() {
const user = await requireUser();
if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
try {
const data = await engine.photoSearchQuota();
return NextResponse.json(data);
} catch (err) {
return NextResponse.json({ error: err.message }, { status: err.status || 500 });
}
}