Vodič za integraciju događaja
Ovaj vodič objašnjava kako da Digital Academy prikaže RU4M događaje, a da prijava i plaćanje ostanu u RU4M aplikaciji. Partner-feed pristup se uključuje pojedinačno za RU4M nalog.
Sadržaj vodiča
1. Pregled
RU4M partnerski feed objavljuje javne, objavljene događaje kojima upravlja vaš RU4M nalog ili kompanija čiji ste administrator/moderator. Vaš sajt preuzima podatke i prikazuje karticu. Posetilac bira „Prijavi se” i nastavlja preko RU4M linka: aplikacija se otvara ako je instalirana; u suprotnom se otvara prodavnica aplikacija, a Branch prosleđuje korisnika do događaja nakon instalacije. Prijava i plaćanje preko Monri-ja ostaju u RU4M aplikaciji — ne pravite sopstveni tok prijave ili naplate.
za vaš nalog
na kontrolnoj tabli
ključ ostaje server-side
Kako dobijate pristup
- RU4M platform administrator (nalog sa ulogom ADMIN ili SUPER_ADMIN) uključi „API pristup” za vaš nalog.
- Prijavite se u RU4M kontrolnu tablu i napravite svoj API ključ.
- Sačuvajte ključ na svom serveru i pozivajte feed server-to-server.
2. Dobijanje API ključa — korak po korak
Ključ počinje sa ru4m_ i pripada vašem RU4M nalogu. RU4M vam ne šalje produkcijski ključ: pošto je pristup uključen, sami ga napravite u kontrolnoj tabli.
2.1 Preduslovi
Koristite RU4M nalog sa potvrđenom email adresom kojim pravite događaje. Ako događaj pripada kompaniji, vaš nalog mora biti administrator ili moderator te kompanije. Feed proverava vlasništvo i članstvo; događaji drugog naloga/kompanije se neće pojaviti.
2.2 Zatražite API pristup
Pošaljite RU4M timu email adresu svog RU4M naloga i zamolite da uključe API pristup. Kontakt: [kontakt RU4M tima]. API pristup je podrazumevano isključen; dok ga tim ne uključi, stavka „API ključevi” neće biti dostupna u meniju kontrolne table. Uključenje prikazuje poruku „API pristup je uključen”. Ako pristup nije uključen, kontrolna tabla prikazuje naslov „API pristup nije uključen za vaš nalog” i tekst „API pristup uključuje RU4M tim. Javite nam se ako želite da prikazujete RU4M događaje na svom sajtu ili u aplikaciji.”
2.3 Prijavite se u RU4M kontrolnu tablu
Otvorite https://app.ru4m.com i prijavite se uobičajenim RU4M nalogom. Kada je pristup uključen, u navigaciji izaberite API ključevi.
2.4 Napravite ključ
Izaberite Novi API ključ i popunite polja tačno kako su prikazana:
| Polje | Šta unosite | Pravila iz aplikacije |
|---|---|---|
| Naziv | Naziv integracije, na primer „Digital Academy sajt – produkcija”. | Obavezno, 1–60 znakova. Validacione poruke: „Unesite naziv.” i „Naziv može imati najviše 60 znakova.” |
| Kanal (opciono) | Oznaka izvora poseta za Branch, na primer digital_academy. | 2–40 malih slova, cifara ili donjih crta: ^[a-z0-9_]{2,40}$. Poruka validacije je „Koristite 2–40 malih slova, cifara ili donjih crta.” Ako ga ostavite praznim, kanal je partner_feed. Izabrani kanal ulazi u Branch ~channel i web utm_source linka, pa se klikovi mogu pripisati tom izvoru. |
| Ističe | Odaberite koliko dugo ključ važi. | Bez isteka, Za 30 dana, Za 90 dana, Za 180 dana ili Za 365 dana. Za stabilnu produkcionu integraciju preporuka je Bez isteka ili Za 365 dana; za test izaberite Za 30 dana. |
Pritisnite Napravi ključ. Možete imati najviše pet aktivnih ključeva. Kada je limit dostignut, aplikacija prikazuje: „Možete imati najviše 5 aktivnih ključeva. Opozovite jedan da biste napravili novi.”
2.5 Sačuvajte ključ
U prozoru Vaš novi API ključ pritisnite Kopiraj (potvrda: „Kopirano u privremenu memoriju”) i sačuvajte vrednost u password manager-u ili tajnom server-side podešavanju. Poruka aplikacije glasi „Ovaj ključ se prikazuje samo jednom”: po zatvaranju prozora ključ više nije moguće prikazati. Ako ga izgubite, opozovite ga i napravite novi.
2.6 Pregledajte ključeve
U odeljku Vaši ključevi videćete sledeće kolone: Naziv, Ključ (prikazuje sačuvani prefix i elipsu, nikada punu vrednost), Kanal, Napravljen, Poslednje korišćenje, Ističe, Status i Akcije. Statusi su Aktivan, Opozvan i Istekao. Početna lista prikazuje aktivne ključeve; uključite Prikaži opozvane i istekle ({count}) da vidite ostale. Poslednje korišćenje prikazuje Nikad dok ključ nije uspešno korišćen; vreme se osvežava najviše jednom u minutu. Za ranije unete, neizmenjene naloge Administracija prikazuje Nikad menjano.
2.7 Opozovite stari ključ pri rotaciji
U aplikaciji nema automatskog „rotiranja” ključa. Napravite novi ključ, zamenite staru vrednost na svom serveru, proverite da se kod novog ključa promenilo Poslednje korišćenje, pa pritisnite Opozovi pored starog ključa. Potvrdite u prozoru Opozvati API ključ? izborom Opozovi. Aplikacija upozorava: „„{name}” odmah prestaje da radi i integracije koje ga koriste gube pristup feedu. Ovo ne može da se poništi.” Za planiranu rotaciju ostavite stari ključ aktivan najviše sedam dana dok prelazite, pa ga opozovite sami.
2.8 Ako se API pristup isključi
RU4M može isključiti pristup u okviru Administracija → API pristup. Isključivanje odmah opoziva sve aktivne ključeve tog naloga; sledeći feed zahtev vraća 401. Administrator potvrđuje izbor u prozoru „Isključiti API pristup?” dugmetom „Isključi pristup”; uspešna promena prikazuje „API pristup je isključen”. Obratite se RU4M timu da proveri pristup. U tom panelu statusi su Uključen i Isključen, a administrator koristi Samo uključeni za filtriranje. Potvrda isključenja prikazuje naslov „Isključiti API pristup?”, dugme „Otkaži” i potvrdu „Isključi pristup”. Poruke su: „Korisnik nema aktivnih ključeva. Više neće moći da pravi nove.”; „Aktivni ključ korisnika biće odmah opozvan i integracije koje ga koriste prestaće da rade.”; „Svih {count} aktivna ključa biće odmah opozvana i integracije koje ih koriste prestaće da rade.”; „Svih {count} aktivnih ključeva biće odmah opozvano i integracije koje ih koriste prestaće da rade.” U tabeli su statusi „Uključen” i „Isključen”; kontrola je označena „API pristup za {user}”, a filter se zove „Samo uključeni”.
API pristup
Samo uključeni
Isključivanje traži potvrdu: „Isključiti API pristup?” → „Isključi pristup”.
API ključevi
{count} od 5 aktivnih ključeva
Naziv
Kanal (opciono)
Ističe
Kopirajte ključ i sačuvajte ga na svom serveru.
Kada zatvorite ovaj prozor, ključ više ne može da se prikaže.
Snimci ekrana – uskoro
Ove HTML/CSS ilustracije pokazuju nazive i tok; stvarni snimci ekrana biće dodati kada produkcioni prikaz bude dostupan.
3. Adrese feedova
| Okruženje | JSON Feed 1.1 | RSS 2.0 | Status |
|---|---|---|---|
| Produkcija | https://api.ru4m.com/feeds/events.json | https://api.ru4m.com/feeds/events.rss | U pripremi — još nije pušteno. |
| Sandbox | https://fuzzywork.shop/feed.json | https://fuzzywork.shop/feed.rss | Za testiranje; zatražite testni ključ od RU4M tima. |
Sandbox je odvojeno testno okruženje sa uzornim događajima. Vraća JSON Feed 1.1 i RSS 2.0 i zahteva testni ključ od RU4M tima. Sandbox prototip može se razlikovati i po nekim nazivima polja ili porukama; produkcijski ugovor i tabele u ovom vodiču prate API kod, ne stariji sandbox payload. API host u produkciji je https://api.ru4m.com; potvrđen je dostupnim API resursom, ali partner feed rute još nisu deploy-ovane.
Podržan je samo parametar locale: sr ili en, podrazumevano sr. Primer: https://api.ru4m.com/feeds/events.json?locale=en. Kanal i organizacija se čitaju iz API ključa; nemojte ih slati u query parametrima. Sačuvajte URL feeda kao podešavanje u svom sistemu, ne kao vrednost ugrađenu u više stranica.
4. Autentifikacija
Ključ se šalje sa svakim serverskim zahtevom. Preporučeno je x-api-key; podržani su i Bearer zaglavlje i query ključ. Prednost ima redom x-api-key, Bearer, pa ?key=.
curl -i -H 'x-api-key: <VAŠ_KLJUČ>' https://api.ru4m.com/feeds/events.json
curl -i -H 'Authorization: Bearer <VAŠ_KLJUČ>' https://api.ru4m.com/feeds/events.json
curl -i 'https://api.ru4m.com/feeds/events.json?key=<VAŠ_KLJUČ>'Query varijantu koristite samo kada alat ne može da pošalje zaglavlje: ključ u URL-u može završiti u logovima servera/proksija. Nikada ga ne stavljajte u link objavljen na stranici. U aplikaciji stoji upozorenje: „U krajnjoj nuždi možete dodati ?key= sa svojim ključem na URL feeda. Ključ tada završava u logovima servera, proksija i pregledača, zato ga opozovite i pređite na zaglavlje čim budete mogli.”
| Status | Odgovor | Napomena |
|---|---|---|
| 200 | JSON Feed 1.1 ili RSS 2.0 | Primer oblika JSON-a (polja su iz produkcijskog koda; event vrednosti ispod su sandbox primer): {"version":"https://jsonfeed.org/version/1.1","title":"RU4M Partner Events","items":[{"id":"c493a00e-ad7d-45fc-b09a-1dca37d051c1","title":"Career Days Niš","content_text":"Career Days Niš povezuje talente i poslodavce kroz prilike za profesionalni razvoj, zapošljavanje i karijeru.","date_published":"2026-10-10T11:25:11.292Z"}]} |
| 401 | {"error":"missing_api_key"} | Ključ nije poslat. Oba 401 slučaja postavljaju WWW-Authenticate: Bearer realm="ru4m-feed". |
| 401 | {"error":"invalid_api_key"} | Ključ je pogrešan, istekao/opozvan, nema scope-a ili je API pristup isključen. Slučajevi su namerno ujednačeni. |
| 429 | {"error":"rate_limit_exceeded"} | Više od 60 zahteva u 60 sekundi po ključu; odgovor sadrži Retry-After u sekundama. |
| 503 | {"error":"temporarily_unavailable"} | Redis ograničenja zahteva nisu dostupna. Retry-After: 30; pokušajte ponovo posle 30 sekundi. |
| 400 | {"error":"invalid_locale"} | locale nije sr ili en. |
| 500 | {"error":"feed_unavailable"} | Greška pri učitavanju ili sastavljanju feeda. |
Uspešan odgovor ima Cache-Control: private, max-age=300. Greške imaju Cache-Control: no-store. Ovaj endpoint ne koristi 403 za nevažeći ključ ili nedostajući feed:read; oba se vraćaju kao 401.
Ključ držite isključivo na serveru: WordPress wp-config.php ili promenljiva okruženja RU4M_FEED_KEY. Nikada u JavaScript-u/HTML-u browsera, mobilnoj aplikaciji, javnom repozitorijumu ili javno objavljenom URL-u. Ako ključ procuri, odmah ga opozovite u „API ključevi” i obavestite RU4M tim.
5. Polja i formati
Produkcijska tabela prati partnerFeedRoutes.ts. Primeri događaja preuzeti su iz sandbox/REST događaja „Career Days Niš”; sandbox i produkcija mogu imati različit tekst poruke dok produkcijski endpoint ne bude objavljen.
JSON Feed 1.1
| Putanja | Tip | Primer / opis | Namena |
|---|---|---|---|
version | string | https://jsonfeed.org/version/1.1 | Verzija formata. |
title | string | RU4M Partner Events | Naziv feeda. |
home_page_url | URL | RU4M appUrl | Početna URL vrednost iz API runtime konfiguracije. |
feed_url | URL | {appUrl}/feeds/events.json?locale=sr | Self URL sastavljen od runtime konfiguracije i izabranog jezika. Za integraciju koristite objavljenu API adresu iz odeljka 3. |
items[] | niz objekata, najviše 100 | Redosled po startDate rastuće | Javni, objavljeni događaji koji nisu završeni i dostupni su nalogu. |
items[].id | string | c493a00e-ad7d-45fc-b09a-1dca37d051c1 | ID događaja; ne sastavljajte URL od njega. |
items[].title | string | Career Days Niš | Naslov. |
items[].content_text | string | Career Days Niš povezuje talente i poslodavce kroz prilike za profesionalni razvoj, zapošljavanje i karijeru. | Opis u običnom tekstu. |
items[].date_published | ISO 8601 UTC | 2026-10-10T11:25:11.292Z | Vrednost je event.updatedAt. |
items[].url | URL | Isto kao _ru4m.branchUrl | Glavni CTA „Prijavi se”. Branch URL ako postoji sharableUrl; u suprotnom web fallback. |
items[].tags | niz stringova | Kategorije događaja | Naslovi kategorija; u RSS-u postaju ponovljeni <category>. |
items[]._ru4m.startDate, endDate | ISO 8601 UTC string | 2026-10-14T09:00:00.000Z; 2026-10-14T15:30:00.000Z | Početak i kraj događaja. |
items[]._ru4m.timezone | IANA timezone | Europe/Belgrade | Vremenska zona za prikaz. |
items[]._ru4m.location | string ili null | Aleksandra Medvedeva 2a, Niš, Serbia | Vrednost lokacije događaja. |
items[]._ru4m.address | string | Sastavljen od adrese, grada i države | Pražnjeni delovi se izostavljaju; može biti prazan tekst. |
items[]._ru4m.organizer | objekat ili null | {"id":1762,"name":"AIESEC","image":"…"} | Organizator: ID, naziv i slika. |
items[]._ru4m.coverImage | URL ili null | Naslovna slika tipa COVER | Slika kartice. |
items[]._ru4m.branchUrl | URL | Link za isti događaj kao items[].url | Partner-parametri se dodaju registrovanom Branch linku. |
items[]._ru4m.webUrl | URL | appUrl/{locale}/events/{slug} sa UTM parametrima | Direktna RU4M web stranica za „Više detalja”. |
items[]._ru4m.qrUrl | URL ili null | URL event QR slike | Zvanična QR slika događaja. |
items[]._ru4m.priceFrom, currency | broj/string ili null | Najniža cena karata; null kada nema pricing reda | Najniža cena i šifra valute. |
items[]._ru4m.phase | upcoming ili live | upcoming | live kada je startDate već prošao; samo događaji sa budućim endDate ulaze u feed. |
items[]._schema | Schema.org Event | @type: Event | eventStatus je EventInProgress ili EventScheduled; url je webUrl, a ne Branch link. |
RSS 2.0
| Element | Namespace | Mapiranje / značenje |
|---|---|---|
item/title, item/link, item/guid | RSS 2.0 | title, Branch url, id (guid nije permalink). |
item/pubDate, item/description | RSS 2.0 | updatedAt u RFC 822; opis u CDATA. |
item/category | RSS 2.0 | Jedan element po kategoriji, iz tags. |
ev:startdate, ev:enddate | http://purl.org/rss/1.0/modules/event/ | ISO 8601 početak i kraj. |
ev:location, ev:organizer, ev:type | Event namespace | Lokacija, naziv organizatora i kategorije spojene zarezom. |
media:content atribut url | http://search.yahoo.com/mrss/ | Naslovna slika; atribut medium="image". |
ru4m:webUrl, ru4m:qrUrl | https://ru4m.com/ns/feed | Web stranica i QR slika. |
ru4m:priceFrom, ru4m:currency, ru4m:status | RU4M namespace | Cena, valuta i upcoming/live. |
atom:link | http://www.w3.org/2005/Atom | Self link na RSS feed sa locale query parametrom. |
RSS namespace-i su definisani URI-jem, ne samo prefix-om. RSS ne prenosi JSON timezone polje, zato prikažite ISO vreme u očekivanoj vremenskoj zoni ili koristite JSON Feed.
6. Pravila za linkove
items[].url (RSS <link>). Ne zamenjujte ga sa webUrl.- „Prijavi se” koristi feed vrednost
urltačno onakvu kakva jeste. Ne skraćujte je, ne provlačite kroz sopstveni redirect i ne sklapajte je od ID-a. - „Više detalja” vodi na
_ru4m.webUrl. - Ako feed
urlima registrovani Branch link, on nosi~channel, app/store fallback i odredišni događaj. Ako event nema Branch link, API koristi web URL kao fallback. - Channel ključa postaje Branch
~channel. Ako želite dodatni placement tracking, jedini dodatak jeutm_content, dodat URL API-jem, nikada konkatenacijom stringa. target="_blank"je dozvoljen uzrel="noopener"; to sprečava novu stranicu da pristupiwindow.opener, a ne menja URL.
Preusmeravanje ili menjanje linka može prekinuti Branch otvaranje događaja i atribuciju. QR/CTA koji treba da otvori aplikaciju mora biti napravljen od url, ne od webUrl.
const signupUrl = new URL(item.url);
if (!signupUrl.searchParams.has("utm_content")) signupUrl.searchParams.set("utm_content", "events_list");
const href = signupUrl.toString();7. Načini integracije
A. WordPress shortcode — preporuka za WordPress
U wp-config.php čuvajte URL i ključ. Postavite produkcijski URL kada endpoint bude objavljen; za sandbox koristite https://fuzzywork.shop/feed.json.
define('RU4M_FEED_URL', 'https://api.ru4m.com/feeds/events.json');
define('RU4M_FEED_KEY', '<VAŠ_KLJUČ>');Dodajte sledeći kod u functions.php child teme ili u mali plugin; na stranicu dodajte shortcode [ru4m_events]. Keš traje pet minuta, a poslednji ispravan odgovor se čuva kao fallback 30 dana.
function da_partner_get_feed() {
$cached = get_transient('da_partner_feed');
if (is_array($cached)) return $cached;
$response = wp_remote_get(RU4M_FEED_URL, array(
'timeout' => 5,
'headers' => array('x-api-key' => RU4M_FEED_KEY),
));
if (!is_wp_error($response) && 200 === wp_remote_retrieve_response_code($response)) {
$feed = json_decode(wp_remote_retrieve_body($response), true);
if (is_array($feed) && isset($feed['items']) && is_array($feed['items'])) {
set_transient('da_partner_feed', $feed, 5 * MINUTE_IN_SECONDS);
set_transient('da_partner_feed_last_good', $feed, 30 * DAY_IN_SECONDS);
return $feed;
}
}
$last_good = get_transient('da_partner_feed_last_good');
return is_array($last_good) ? $last_good : array('items' => array());
}
function da_partner_events_shortcode() {
$feed = da_partner_get_feed();
if (empty($feed['items'])) return '<p>Uskoro novi događaji.</p>';
$html = '<div class="da-ru4m-events">';
foreach ($feed['items'] as $item) {
$meta = isset($item['_ru4m']) && is_array($item['_ru4m']) ? $item['_ru4m'] : array();
$title = $item['title'] ?? '';
$html .= '<article class="da-ru4m-card">';
if (!empty($meta['coverImage'])) {
$html .= '<img src="' . esc_url($meta['coverImage']) . '" alt="' . esc_attr($title) . '" loading="lazy">';
} else {
$html .= '<div class="image-fallback" aria-hidden="true"></div>';
}
$html .= '<h3>' . esc_html($title) . '</h3>';
if (($meta['phase'] ?? '') === 'live') $html .= '<strong>U TOKU</strong>';
if (!empty($meta['startDate'])) {
$date_label = $meta['startDate'];
try {
$date_label = (new DateTimeImmutable($meta['startDate']))
->setTimezone(new DateTimeZone($meta['timezone'] ?? 'Europe/Belgrade'))
->format('d.m.Y. H:i');
} catch (Exception $error) {
$date_label = $meta['startDate'];
}
$html .= '<p>' . esc_html($date_label) . ' ' . esc_html($meta['timezone'] ?? '') . '</p>';
}
if (array_key_exists('priceFrom', $meta)) {
$price = $meta['priceFrom'];
$html .= null === $price ? '<p>Besplatno</p>' : '<p>' . esc_html((string) $price . ' ' . ($meta['currency'] ?? '')) . '</p>';
}
$html .= '<p>' . esc_html($item['content_text'] ?? '') . '</p>';
if (!empty($item['url'])) $html .= '<a href="' . esc_url($item['url']) . '" target="_blank" rel="noopener">Prijavi se</a>';
if (!empty($meta['webUrl'])) $html .= ' <a href="' . esc_url($meta['webUrl']) . '">Više detalja</a>';
$html .= '</article>';
}
return $html . '</div>';
}
add_shortcode('ru4m_events', 'da_partner_events_shortcode');esc_url() u WordPress core-u dozvoljava ~ i %, pa čuva i literalni tilde i sekvencu %7E. HTML escape ampersanda u atributu je normalan; browser ga pravilno obrađuje. Nemojte ponovo kodirati ceo URL. Minimalni CSS:
.da-ru4m-events{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:1rem}
.da-ru4m-card{overflow:hidden;border:1px solid #ddd;border-radius:12px;padding:1rem}
.da-ru4m-card img{width:100%;aspect-ratio:16/9;object-fit:cover}
.da-ru4m-card .image-fallback{aspect-ratio:16/9;background:#eee}
B. WordPress plugin bez koda
RSS agregator koji ne podržava zaglavlje može koristiti https://api.ru4m.com/feeds/events.rss?key=<VAŠ_KLJUČ>; dok se produkcija ne objavi, zamenite domen sandbox adresom. Ograničenja: plugin možda ne prikazuje datum ili custom namespace polja, a <link> mora ostati neizmenjen Branch URL.
x-api-key.C. Browser JavaScript preko vašeg backend-a
Browser nikada ne poziva RU4M feed direktno i nikada ne dobija partner ključ. Klijentski kod zove samo vaš endpoint, ovde /wp-json/da/v1/events. Vaš server dodaje x-api-key, kešira pet minuta i može da vrati poslednju uspešnu verziju.
<section id="ru4m-events">
<h2>Događaji</h2>
<div class="ru4m-grid" aria-live="polite">
<article class="ru4m-skeleton" aria-label="Učitavanje događaja"></article>
</div>
</section>
<script>
const FEED_URL = "/wp-json/da/v1/events";
const grid = document.querySelector("#ru4m-events .ru4m-grid");
function cardFor(item) {
const meta = item._ru4m || {};
const card = document.createElement("article");
card.className = "ru4m-card";
if (meta.coverImage) {
const image = document.createElement("img");
image.src = meta.coverImage; image.alt = item.title; image.loading = "lazy"; card.append(image);
} else {
const fallback = document.createElement("div"); fallback.className = "image-fallback"; fallback.setAttribute("aria-hidden", "true"); card.append(fallback);
}
const title = document.createElement("h3"); title.textContent = item.title; card.append(title);
if (meta.phase === "live") { const badge = document.createElement("strong"); badge.textContent = "U TOKU"; card.append(badge); }
if (meta.startDate) {
const date = document.createElement("p");
date.textContent = new Intl.DateTimeFormat("sr-Latn", {dateStyle:"medium",timeStyle:"short",timeZone:meta.timezone || "Europe/Belgrade"}).format(new Date(meta.startDate));
card.append(date);
}
if (Object.hasOwn(meta, "priceFrom")) {
const price = document.createElement("p");
price.textContent = meta.priceFrom == null ? "Besplatno" : `${meta.priceFrom} ${meta.currency || ""}`;
card.append(price);
}
const description = document.createElement("p"); description.textContent = item.content_text || ""; card.append(description);
const signup = document.createElement("a"); signup.href = item.url; signup.textContent = "Prijavi se"; signup.target = "_blank"; signup.rel = "noopener"; card.append(signup);
if (meta.webUrl) { const more = document.createElement("a"); more.href = meta.webUrl; more.textContent = "Više detalja"; card.append(" ", more); }
return card;
}
async function loadEvents() {
const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch(FEED_URL, {signal:controller.signal});
if (!response.ok) throw new Error(`Events HTTP ${response.status}`);
const feed = await response.json(); grid.replaceChildren();
if (!Array.isArray(feed.items) || !feed.items.length) { grid.textContent = "Uskoro novi događaji."; return; }
feed.items.forEach((item) => grid.append(cardFor(item)));
} catch (error) {
grid.textContent = "Događaji trenutno nisu dostupni.";
console.error("RU4M partner feed nije dostupan", error);
} finally { clearTimeout(timer); }
}
loadEvents();
</script>
Pošto se kartice tek u browseru generišu JavaScript-om, crawler ili preview koji ga ne izvršava možda neće videti događaje; za SEO je bolji serverski render.
Minimalna WordPress REST proxy ruta. Definišite RU4M_FEED_KEY i RU4M_FEED_URL u wp-config.php; ruta je javni endpoint vašeg sajta, ali tajni ključ nikad ne napušta server.
add_action('rest_api_init', function () {
register_rest_route('da/v1', '/events', array(
'methods' => 'GET',
'permission_callback' => '__return_true',
'callback' => function () {
$cached = get_transient('da_partner_proxy_feed');
if (is_array($cached)) return rest_ensure_response($cached);
$response = wp_remote_get(RU4M_FEED_URL, array(
'timeout' => 5,
'headers' => array('x-api-key' => RU4M_FEED_KEY),
));
if (!is_wp_error($response) && 200 === wp_remote_retrieve_response_code($response)) {
$feed = json_decode(wp_remote_retrieve_body($response), true);
if (is_array($feed) && isset($feed['items'])) {
set_transient('da_partner_proxy_feed', $feed, 5 * MINUTE_IN_SECONDS);
set_transient('da_partner_proxy_last_good', $feed, 30 * DAY_IN_SECONDS);
return rest_ensure_response($feed);
}
}
$last_good = get_transient('da_partner_proxy_last_good');
if (is_array($last_good)) return rest_ensure_response($last_good);
return new WP_Error('partner_feed_unavailable', 'Događaji trenutno nisu dostupni.', array('status' => 503));
},
));
});Klijentski kod kešira samo u browser memoriji; glavno osvežavanje i fallback radi backend ruta.
D. Serverska strana — Node / Next.js
Next.js server komponenta šalje ključ iz process.env.RU4M_FEED_KEY i revalidira na pet minuta. Ne dodajte "use client" i ne nazivajte promenljivu NEXT_PUBLIC_*.
const RU4M_FEED_URL = process.env.RU4M_FEED_URL || "https://api.ru4m.com/feeds/events.json";
export default async function PartnerEvents() {
const response = await fetch(RU4M_FEED_URL, {
headers: {"x-api-key": process.env.RU4M_FEED_KEY},
next: {revalidate: 300}
});
if (!response.ok) return <p>Događaji trenutno nisu dostupni.</p>;
const feed = await response.json();
if (!feed.items?.length) return <p>Uskoro novi događaji.</p>;
return <div>{feed.items.map((item) => <article key={item.id}>
{item._ru4m.coverImage ? <img src={item._ru4m.coverImage} alt={item.title} /> : <div className="image-fallback" aria-hidden="true" />}
<h2>{item.title}</h2>
<p>{new Intl.DateTimeFormat("sr-Latn", {dateStyle:"medium",timeStyle:"short",timeZone:item._ru4m.timezone}).format(new Date(item._ru4m.startDate))}</p>
{item._ru4m.phase === "live" ? <strong>U TOKU</strong> : null}
{Object.hasOwn(item._ru4m, "priceFrom") ? <p>{item._ru4m.priceFrom == null ? "Besplatno" : `${item._ru4m.priceFrom} ${item._ru4m.currency || ""}`}</p> : null}
<a href={item.url} target="_blank" rel="noopener">Prijavi se</a>
{item._ru4m.webUrl ? <a href={item._ru4m.webUrl}>Više detalja</a> : null}
</article>)}</div>;
}Za RSS u Node-u koristite XML parser u serverskom okruženju i zadržite namespace-e. Primer sa fast-xml-parser:
import { XMLParser } from "fast-xml-parser";
const RU4M_FEED_URL = process.env.RU4M_FEED_URL || "https://api.ru4m.com/feeds/events.rss";
const response = await fetch(RU4M_FEED_URL, {headers:{"x-api-key":process.env.RU4M_FEED_KEY}});
if (!response.ok) throw new Error(`RSS HTTP ${response.status}`);
const parser = new XMLParser({ignoreAttributes:false,attributeNamePrefix:"@_",removeNSPrefix:false});
const rss = parser.parse(await response.text());
const raw = rss.rss.channel.item;
const items = Array.isArray(raw) ? raw : [raw];
const events = items.map((item) => ({
title:item.title, branchUrl:item.link, id:item.guid["#text"] || item.guid,
startDate:item["ev:startdate"], endDate:item["ev:enddate"],
location:item["ev:location"], organizer:item["ev:organizer"],
coverImage:item["media:content"]?.["@_url"], webUrl:item["ru4m:webUrl"],
qrUrl:item["ru4m:qrUrl"], priceFrom:item["ru4m:priceFrom"],
currency:item["ru4m:currency"], phase:item["ru4m:status"]
}));Proverite konkretan oblik guid u izabranom parseru. JSON Feed daje strukturisaniji organizer, tags i Schema.org objekat.
8. Prikaz događaja
- Preporučena kartica: naslov, cover slika 16:9, datum/vreme, lokacija, organizator, opis i jasna CTA dugmad.
phase="live"prikažite kao oznaku „U TOKU”.priceFrom: nullznači da nema evidentirane cene karte; prikažite „Besplatno” samo kada je to potvrđeno vašim pravilom prikaza.- Feed je sortiran po
startDaterastuće i vraća najviše 100 događaja; zadržite redosled. - Ako
coverImagenedostaje ili se slika ne učita, prikažite neutralnu lokalnu zamenu; ne pravite image URL od ID-a. - Prazan feed: „Uskoro novi događaji.” Koristite smislen
alt, vidljiv fokus tastature i linkove „Prijavi se” / „Više detalja”.
9. Keširanje i osvežavanje
Uspešan produkcijski odgovor ima Cache-Control: private, max-age=300: osvežava se na pet minuta. Keširajte ga i na svom serveru; ne tražite feed češće od jednom u minuti. Postavite timeout na pet sekundi i pri grešci poslužite poslednju ispravnu verziju.
Greške imaju Cache-Control: no-store. Kod 429 sačekajte Retry-After; kod 503 sa Redis rate-limit nedostupnošću zaglavlje je 30 sekundi. Ne keširajte 400, 401, 429, 500 ili 503 kao feed podatke.
Novi događaj se pojavljuje nakon objave i sledećeg uspešnog osvežavanja. Završeni događaj izlazi kada mu endDate prođe; privatni, nacrt ili obrisani događaji se ne vraćaju. Ako događaj pripada drugom nalogu ili kompaniji u kojoj niste administrator/moderator, neće se pojaviti.
10. QR kodovi
qrUrl je URL zvanične slike QR koda događaja. Za štampu možete koristiti tu sliku. Ona nije partnerski link sa vašim ključnim kanalom. Za QR koji treba da nosi Branch partner atribuciju, generišite QR od url (ili od new URL(item.url) sa utm_content=print dodatim preko URL API-ja). Ne koristite webUrl kao prijavni QR.
11. Analitika i UTM
channel iz API ključa ulazi u Branch ~channel kada događaj ima registrovan Branch link; isti kanal se koristi u web UTM parametrima. Ostavljen prazan kanal API ključa koristi partner_feed. Na svom sajtu zabeležite klik, ID događaja i poziciju:
gtag("event", "ru4m" + "_event_click", {event_id:item.id, placement:"events_list"});Branch izveštaji mere klikove i prelazak u aplikaciju/prodavnicu; vaša analitika meri prikaz i klik na sajtu. UTM vrednosti opisuju izvor, medijum, kampanju i placement. Izveštaje razmenjujte kroz dogovoreni RU4M dashboard.
12. Testiranje
Koristite sandbox ključ koji dobijete od RU4M tima do produkcijske objave:
curl -i https://fuzzywork.shop/feed.json
curl -i -H 'x-api-key: <VAŠ_KLJUČ>' https://fuzzywork.shop/feed.json| Scenario | Uređaj / provera | Očekivani rezultat |
|---|---|---|
| Nedostaje ključ / pogrešan ključ | Terminal | 401; bez ključa body je missing_api_key. |
| Važeći ključ | Terminal | 200; broj događaja odgovara feedu. |
| Ključ se koristi | Kontrolna tabla → API ključevi | Poslednje korišćenje se pojavi nakon uspešnog feed zahteva. |
| Aplikacija je instalirana | iPhone / Android | „Prijavi se” otvara tačan događaj u RU4M aplikaciji. |
| Aplikacija nije instalirana | iPhone / Android | Branch otvara odgovarajuću prodavnicu aplikacija, pa događaj nakon instalacije. |
| Desktop QR | Desktop + telefon | QR se skenira i otvara odgovarajući Branch događaj/link. |
| In-app browser | Instagram / Facebook | Proveriti otvaranje; po potrebi nastaviti u sistemskom browseru. |
| Stari ključ posle opoziva | Terminal | 401 invalid_api_key. |
- ☐ Produkcijski URL je zamenjen tek nakon RU4M objave endpointa.
- ☐ Ključ se čuva samo u server-side tajni.
- ☐ „Prijavi se” koristi
url/<link>, a „Više detalja”webUrl. - ☐ Testirani su datum/timezone, prazan feed, 401, 429 i fallback na keš.
- ☐ Provereno je polje „Poslednje korišćenje” posle uspešnog zahteva.
13. Česte greške i pitanja
| Pitanje / greška | Odgovor |
|---|---|
| 401 missing_api_key | Nije poslato zaglavlje/query ključ. Preporuka je x-api-key. |
| Ne vidim API ključevi u meniju | API pristup je podrazumevano isključen. Pošaljite email RU4M naloga RU4M timu i zamolite da uključi pristup. |
| Izgubio sam ključ | Ključ se prikazuje samo jednom. Napravite novi u „API ključevi”, zamenite ga na serveru, pa opozovite stari. |
| 401 invalid_api_key | Ključ je pogrešan, istekao, opozvan, nema feed:read, nalog nije potvrđen ili je API pristup isključen. |
| 403 Forbidden | Trenutna feed ruta ove greške normalizuje na 401; ne očekujte 403. |
| 401 iako je ključ tačan | Proverite istek, opoziv, feed:read, potvrdu email-a i da li je RU4M isključio API pristup. |
| Feed je prazan | Događaji mogu biti privatni, nacrti, završeni, obrisani ili van naloga/kompanije kojom upravljate. |
| 429 Too Many Requests | Prekoračeno je 60 zahteva u minuti po ključu. Poštujte Retry-After i povećajte keš. |
| 503 temporarily_unavailable | Redis rate-limit servis nije dostupan; sačekajte 30 sekundi iz Retry-After. |
| Događaj se ne pojavljuje | Proverite da je javno objavljen, da nije privatan ili završen, da je kreiran vašim nalogom ili kompanijom kojom upravljate, i da pripada prvih 100 rezultata. |
| Dugme ne otvara aplikaciju | Proverite da „Prijavi se” koristi feed url / RSS <link>, a ne webUrl. Uklonite shortener i sopstveni redirect. |
| Ključ je procureo | Odmah ga opozovite u RU4M „API ključi” i obavestite RU4M tim. |
| Pristup je isključen | RU4M isključivanje opoziva aktivne ključeve odmah; kontaktirajte RU4M tim da ponovo uključi API pristup. |
Za pomoć: [kontakt RU4M tima].