Přeskočit obsah
V
Pro vývojáře
Architektura, konvence, jádro systému a bezpečnost
Moduly / Inzerce

Inzerce#

Modul Bazaar — inzeráty s dynamickými poli, placené tarify, hlídací pes a odpovědi. Kód: app/UI/Api/Bazaar/, front app/UI/Front/Bazaar/, administrace app/UI/Admin/Bazaar/.

Z čeho se modul skládá#

Oblast Hlavní entity Poznámka
inzerát Advert + relace AdvertParameterRel, AdvertCategoryRel, AdvertAssetRel, AdvertFileRel téměř všechna data jsou v relacích, ne ve sloupcích
kategorie Category, CategoryText, CategoryPrice, CategoryParameterRel nested set
parametry Parameter, ParameterGroup, ParameterRule + ParameterRuleCondition/Source/Target pravidla řídí, které pole se kdy zobrazí
placení Tariff, TariffPrice, TariffQuickPrice, PaymentItem
interakce Response, AdvertFollower, AdvertReport, AdvertStatistic
hlídací pes Watchdog, WatchdogEmail + relace uložené hledání
moderace BlacklistedWord

Inzerát nemá pevná pole#

Název, popis, cena, e-mail i region jsou dynamické parametry (AdvertParameterRel). Sloupce v tabulce jsou jen denormalizované kopie kvůli řazení a filtrování — ty se totiž nad textovou hodnotou parametru dělat nedají.

Klíč parametru Denormalizovaný sloupec Kdo ho plní
price price (číslo, kvůli řazení a rozsahovému filtru) AdvertManager::beforeSave() z meta['parameters']
email email zápis inzerátu
regionId region_id zápis inzerátu

Přejmenování vyhrazeného klíče parametru tiše rozbije řazení

Nic to neohlásí — beforeSave() prostě nenajde, co má do sloupce zapsat. Řazení podle ceny přestane fungovat a odpovědi přestanou chodit, protože se nemá kam poslat e-mail. Vypadá to jako chyba výpisu.

Ceny v různých měnách se nepřepočítávají

Do sloupce jde holé číslo bez přepočtu. Řazení podle ceny tak míchá měny dohromady — 100 EUR se seřadí před 200 CZK.

Sloupec price má self-heal cron

/cron/bazaar/advert/recount-prices (à 10–15 minut) srovná nesrovnalosti po importech a ručních zásazích do databáze. Je to pojistka, ne primární plnění.

Sloupec active počítá databáze#

cms_mod_bazaar_adverts.active je generovaný sloupec (VIRTUAL):

active = CASE WHEN approved_at IS NOT NULL
              AND paid = 1 AND banned = 0 AND deleted = 0
              AND deactivated = 0 AND sold = 0
         THEN 1 ELSE 0 END

active nejde nastavit ani z administrace, ani z API

Mění se jedině zápisem některého z těch šesti příznaků. Entita ho má proto označený insertable: false, updatable: false a navíc #[ExternalReadonly] — bez toho druhého by setActive() přepsal hodnotu v paměti ještě před uložením a odpověď by vrátila klientem vymyšlený stav místo skutečného.

Expirace v tom vzorci NENÍ

Prošlý inzerát zůstává active = 1; z výpisů ho vyřazuje až aplikační filtr. Kdo si napíše vlastní dotaz jen na active = 1, dostane i inzeráty po expiraci.

Postup: přidávám dynamický parametr#

  1. Založ parametr a připoj ho ke kategorii (CategoryParameterRel).
  2. Volby výběru jsou děti parametru — parametr je kořen stromu, jeho potomci jsou jednotlivé volby.
  3. Rozhodni, jestli má mít denormalizovaný sloupec. Potřebuje ho jen to, podle čeho se řadí nebo filtruje rozsahem.
  4. Pravidla zobrazení (ParameterRule) řeší, kdy se pole ukáže — podmínka nad jiným parametrem.
  5. Prokliknij vložení i editaci inzerátu v té kategorii.

Smazání uzlu smaže celý podstrom

U parametru včetně všech jeho voleb — a s nimi hodnoty ve všech inzerátech, které tu volbu měly vyplněnou. Data se nedají vrátit.

Propsání cen nebo parametrů do podstromu je nevratné

Přepíše všechny podkategorie bez ptaní a bez zálohy. Na velkém stromu to znamená stovky přepsaných kategorií jedním kliknutím.

Denormalizovaná počítadla#

Hodnota Kdo ji srovná
advert.price cron /cron/bazaar/advert/recount-prices
category.advert_count cron /cron/bazaar/category/recount
advert.email, advert.region_id zápis inzerátu

Přepočet počtů je set-based UPDATE nad celým stromem — rodič počítá aktivní inzeráty ze svého celého podstromu, ne jen z přímých dětí.

Přepočet respektuje nastavení automatického schvalování

1/useAutoConfirmation mění, co se považuje za aktivní. Kdo počty ověřuje ručním SQL, musí ho vzít v úvahu — jinak mu čísla nebudou sedět a bude hledat chybu v cronu.

Hlídací pes#

Uložené hledání, které při novém inzerátu pošle e-mail.

Spouští se JEN při vložení inzerátu

Ne při editaci ani při přesunu do jiné kategorie. Inzerát, který se do kategorie dostane až později, hlídačům nikdy nepřijde — a nikomu nic nechybí, dokud se nezeptá inzerent.

Hlídač používá napevno zadaná id rodičovských parametrů

Po změně číselníků parametrů přestane sedět. Neprojeví se to chybou, jen tichým přestáním fungovat.

Zvýraznění a expirace#

toped_until řídí zvýraznění a řadí inzeráty ve výpisu. Prodloužení z platby je monotonní — jen prodlužuje, nikdy nezkracuje. Ruční zásah v administraci umí obojí.

Cron Co dělá Interval
/cron/bazaar/advert/notice-expiration upozorní N dní před expirací (5/expirationNoticeDays), nastaví příznak, aby se neopakovalo denně
/cron/bazaar/advert/delete-expired tiše označí deleted = 1 po M dnech od expirace (5/expirationDeleteDays) denně

Notifikace#

Sledující dostanou upozornění POUZE při změně cenovky

Porovnává se trojice cena, typ ceny a měna. Změna názvu, popisu ani fotek neupozorní nikoho — a je to schválně, jinak by úprava překlepu rozeslala stovky e-mailů.

Editace v administraci neposílá žádné e-maily

Ani aktivační, ani hlídačům, ani sledujícím. Je to záměr: administrátor opravuje cizí inzerát a nemá tím spouštět komunikaci jeho jménem.

Elasticsearch#

Inzerce je jeden ze zdrojů Elasticsearch — reindex /cron/bazaar/elastic/reindex, průběžná synchronizace /cron/bazaar/elastic/sync.

Hromadné operace mimo hooky index nevidí

Set-based UPDATE (přepočty, importy, ruční SQL) hook nespustí, takže se do indexu nepropíše. Konzistenci drží až noční reindex — do té doby hledání vrací stará data a nikde to není vidět.

Kam sáhnout#

Chci Kde
logiku inzerátu a denormalizace app/UI/Api/Bazaar/Models/Managers/AdvertManager.php
přepočet počtů v kategoriích app/UI/Api/Bazaar/Models/Managers/CategoryManager.phprecountAdverts()
hlídací pes app/UI/Api/Bazaar/Models/Managers/Watchdog*Manager.php
definici entity a jejích příznaků app/Core/Base/Shared/Bazaar/Models/Entities/Advert.php
cron úlohy app/UI/Cron/Bazaar/Presenters/
formulář inzerátu na frontu app/UI/Front/Bazaar/Components/AdvertFormComponent/

Navazující kapitoly: Katalog firem · Elasticsearch · Filtry · Naplánované úlohy