Add production deployment, photorealistic movement walls, and fix duplicate influence links.

Configure hosting for gallery.mysuperlab.netcraze.pro and LAN access, enhance movement gallery textures with period-appropriate painted materials, dedupe influenced-by API responses via painting_influence_sources, and refresh several painting image files.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Danila Khodjaef
2026-06-21 23:00:13 +03:00
co-authored by Cursor
parent df29848d89
commit 4eead54062
161 changed files with 668 additions and 187 deletions
+37 -41
View File
@@ -9,7 +9,13 @@ const { ensurePaintingImages, preloadArtistImagesLocal, replacePaintingImageFrom
const { searchGoogleImagesFirst, searchArtistPortraitFirst, searchPaintingImagesMany, searchArtistPortraitMany, fetchImageBuffer, friendlyImageFetchError, pickExt } = require('../scripts/image-fetcher');
const app = express();
const PORT = process.env.PORT || 3001;
const HOST = process.env.HOST || '0.0.0.0';
const PORT = Number(process.env.PORT) || 3001;
const PUBLIC_URL = process.env.PUBLIC_URL || '';
if (process.env.TRUST_PROXY === '1' || process.env.TRUST_PROXY === 'true') {
app.set('trust proxy', 1);
}
app.use(cors());
app.use(express.json({ limit: '20mb' }));
@@ -19,9 +25,6 @@ const INFLUENCE_LINKS_EXISTS = `
EXISTS (
SELECT 1 FROM painting_influence_sources pis
WHERE pis.painting_id = p.id OR pis.source_painting_id = p.id
) OR EXISTS (
SELECT 1 FROM painting_influences pi
WHERE pi.painting_id = p.id OR pi.influenced_by_painting_id = p.id
)`;
const INFLUENCED_BY_SQL = `
@@ -59,6 +62,28 @@ const INFLUENCED_BY_SQL = `
ORDER BY
CASE pis.source_type WHEN 'painting' THEN 1 WHEN 'artist' THEN 2 ELSE 3 END,
COALESCE(p.year, pis.period_start_year) NULLS LAST`;
const INFLUENCED_SQL = `
SELECT
pis.notes,
pis.source,
pis.aspects,
pis.quote,
pis.source_author,
pis.source_url,
'painting' AS source_type,
p.id,
p.title,
p.year,
p.image_path,
a.name AS artist_name,
a.id AS artist_id
FROM painting_influence_sources pis
JOIN paintings p ON pis.painting_id = p.id
JOIN artists a ON p.artist_id = a.id
WHERE pis.source_painting_id = $1 AND pis.source_type = 'painting'
ORDER BY p.year NULLS LAST, p.title`;
app.get('/api/timeline', async (req, res) => {
try {
const { start, end } = req.query;
@@ -221,16 +246,7 @@ app.get('/api/artists/:id/navigation', async (req, res) => {
LEFT JOIN paintings p_pred ON pis.source_painting_id = p_pred.id
LEFT JOIN artists a ON a.id = COALESCE(p_pred.artist_id, pis.source_artist_id)
LEFT JOIN art_movements m ON a.movement_id = m.id
WHERE p_work.artist_id = $1 AND a.id IS NOT NULL AND a.id <> $1
UNION
SELECT DISTINCT a.id, a.name, a.birth_year, a.death_year, a.portrait_path,
m.id AS movement_id, m.name AS movement_name, m.color AS movement_color
FROM painting_influences pi
JOIN paintings p_work ON pi.painting_id = p_work.id
JOIN paintings p_pred ON pi.influenced_by_painting_id = p_pred.id
JOIN artists a ON p_pred.artist_id = a.id
LEFT JOIN art_movements m ON a.movement_id = m.id
WHERE p_work.artist_id = $1 AND a.id <> $1`;
WHERE p_work.artist_id = $1 AND a.id IS NOT NULL AND a.id <> $1`;
const successorSql = `
SELECT DISTINCT a.id, a.name, a.birth_year, a.death_year, a.portrait_path,
@@ -244,12 +260,11 @@ app.get('/api/artists/:id/navigation', async (req, res) => {
UNION
SELECT DISTINCT a.id, a.name, a.birth_year, a.death_year, a.portrait_path,
m.id AS movement_id, m.name AS movement_name, m.color AS movement_color
FROM painting_influences pi
JOIN paintings p_src ON pi.influenced_by_painting_id = p_src.id
JOIN paintings p_work ON pi.painting_id = p_work.id
FROM painting_influence_sources pis
JOIN paintings p_work ON pis.painting_id = p_work.id
JOIN artists a ON p_work.artist_id = a.id
LEFT JOIN art_movements m ON a.movement_id = m.id
WHERE p_src.artist_id = $1 AND a.id <> $1`;
WHERE pis.source_artist_id = $1 AND a.id <> $1`;
const [predecessors, successors] = await Promise.all([
pool.query(
@@ -610,27 +625,7 @@ app.get('/api/paintings/:id', async (req, res) => {
const [influencedBy, influenced, annotations] = await Promise.all([
pool.query(INFLUENCED_BY_SQL, [id]),
pool.query(
`SELECT * FROM (
SELECT pi.notes, pi.source, pi.aspects, pi.quote, pi.source_author, pi.source_url,
'painting' AS source_type,
p.id, p.title, p.year, p.image_path, a.name as artist_name, a.id as artist_id
FROM painting_influences pi
JOIN paintings p ON pi.painting_id = p.id
JOIN artists a ON p.artist_id = a.id
WHERE pi.influenced_by_painting_id = $1
UNION ALL
SELECT pis.notes, pis.source, pis.aspects, pis.quote, pis.source_author, pis.source_url,
'painting' AS source_type,
p.id, p.title, p.year, p.image_path, a.name as artist_name, a.id as artist_id
FROM painting_influence_sources pis
JOIN paintings p ON pis.painting_id = p.id
JOIN artists a ON p.artist_id = a.id
WHERE pis.source_painting_id = $1 AND pis.source_type = 'painting'
) influenced_works
ORDER BY year NULLS LAST`,
[id]
),
pool.query(INFLUENCED_SQL, [id]),
pool.query(
`SELECT id, label, body, category, pos_x, pos_y, source_author, source, source_url, sort_order, confidence
FROM painting_annotations
@@ -861,6 +856,7 @@ if (fs.existsSync(CLIENT_DIST)) {
});
}
app.listen(PORT, () => {
console.log(`Gallery running on http://localhost:${PORT}`);
app.listen(PORT, HOST, () => {
console.log(`Gallery listening on http://${HOST}:${PORT}`);
if (PUBLIC_URL) console.log(`Public URL: ${PUBLIC_URL}`);
});