Pins + metadata
Obsah není jen obrázek; je propojený s vlastníkem, tagy a boardy.

web s přepínáním vzhledu, pro sdílení a komentování fotek (aktuální verze nemá nastavenou konfiguraci na wloudinary, tudíž všechen odkaz je buď externí nebo uložený v Mongo v rámci limitů)
Uživatel vytvoří pin, přidá titul, popis a tagy, cizí pin uloží do boardu, okomentuje ho a sdílí přes vlastní URL. Vyhledávání nehledá jen v názvu, ale i v popisech, tazích, boardech a u uživatelů.
Pin v GaleryPin není jen obrázek, je to propojený dokument. Čtyři vrstvy níže ukazují, co všechno se k němu váže.
Obsah není jen obrázek; je propojený s vlastníkem, tagy a boardy.
Upload je oddělený do storage vrstvy, takže není natvrdo spojený s UI.
Ukládání cizího obsahu a interakce vyžadují vazby mezi dokumenty.
Vyhledávání spojuje více typů textových dat do jednoho výsledku.
Vazby mezi obsahem, médii, sociálními prvky a vyhledáváním určují, jak vypadá backend. Uvidíš to hned v kapitole o architektuře.
Backend je schovaný uvnitř Next.js v jednom catch-all route. Nasazení je díky tomu jednodušší, ale na jednom místě se sejde hodně zodpovědností: autentizace, databáze, CORS i upload.
async function connectToMongo() {
if (db) return db
if (!connectPromise) {
connectPromise = (async () => {
client = new MongoClient(process.env.MONGO_URL)
await client.connect()
db = client.db(process.env.DB_NAME || 'galeripin')
return db
})()
}
return connectPromise
}
const clean = (doc) => {
if (!doc) return doc
const { _id, passwordHash, securityAnswerHash, resetToken, resetExpires, ...rest } = doc
return rest
}
export async function uploadImage(dataUrl) {
const cloud = process.env.CLOUDINARY_CLOUD_NAME
const apiKey = process.env.CLOUDINARY_API_KEY
const apiSecret = process.env.CLOUDINARY_API_SECRET
const folder = process.env.CLOUDINARY_FOLDER || 'galeripin'
if (cloud && apiKey && apiSecret) {
const timestamp = Math.floor(Date.now() / 1000)
const paramsToSign = `folder=${folder}×tamp=${timestamp}`
const signature = crypto.createHash('sha1').update(paramsToSign + apiSecret).digest('hex')
const form = new FormData()
form.append('file', dataUrl)
form.append('api_key', apiKey)
form.append('timestamp', String(timestamp))
form.append('folder', folder)
form.append('signature', signature)
if (route === '/auth/register' && method === 'POST') {
const body = await request.json()
const acceptedTerms = body.acceptedTerms === true
if (!acceptedTerms) {
return cors(NextResponse.json(
{ error: 'Pro registraci musíš souhlasit s podmínkami používání' },
{ status: 400 }
))
}
const username = (body.username || '').trim().toLowerCase().replace(/[^a-z0-9_.]/g, '')
const password = body.password || ''
const email = (body.email || '').trim().toLowerCase() || null
if (username.length < 2) return cors(NextResponse.json(
{ error: 'Přezdívka musí mít alespoň 2 znaky (a-z, 0-9, _ .)' },
{ status: 400 }
))
Frontend používá React Query pro serverový stav, ThemeProvider pro dark/light mode a Sonner pro notifikace. Pro appku, kde se pořád pracuje s daty a přitom má být interakce svižná, mi to přijde rozumná kombinace.
Frontend řeší data, vzhled i zpětnou vazbu pro uživatele a každá z těch částí má svou knihovnu.
Cache a refetch nejsou rozptýlené po každém komponentu.
Dark/light režim je řešený centrálně.
Výsledky operací se propisují do uživatelských notifikací.
Díky tomu není cache, motiv ani notifikace rozeseté po komponentách, ale řeší se to centrálně. Míň chaosu, víc klidu.
Největší riziko nebyl grid obrázků. Byl to fakt, že se tu potkávají účty, soubory a veřejně sdílený obsah. Proto jsou zajímavější věci jako čištění API odpovědí, hashování hesel, storage abstrakce nebo registrace než vzhled karty pinu.
V repu už jsou připravené follows, likes, notifikace, AI recommendations nebo moderace. Proto je důležitější základ než jednorázový design: nové sociální vazby budou přibývat nad stejným datovým modelem.
Žádná technologie tu není jen pro parádu. Tady je rychlý přehled, kdo za co odpovídá.
Nejde mi jen o to, jak to vypadá. Baví mě hlavně to, co je pod tím: kudy data tečou, co se validuje, co může selhat a proč jsem to udělala právě takhle.
Napiš mi