Bump Npgsql.EntityFrameworkCore.PostgreSQL from 9.0.4 to 10.0.3 - #16
Closed
dependabot[bot] wants to merge 130 commits into
Closed
dependabot[bot] wants to merge 130 commits into
dependabot[bot] wants to merge 130 commits into
Conversation
…atic-file caching
… limiting Track play counts via ListeningHistory (one count per user per song, enforced by an existence check plus a unique-index fallback for races), expose them on SongDto, and add GET /api/stats/top-songs. Rate-limit /api/history to curb listen-count inflation, and stop combining artist+album into a single iTunes query so album search returns full tracklists again.
Rebalance the light theme's palette away from stark white and high contrast toward warmer, muted tones, and add a neutral gray theme between dark and light. Replace the theme/language toggle buttons with dropdown menus that list all options with the active one marked.
…vance Cover the logic most likely to silently break: unique-listener dedup in HistoryController, GetPlayCountsAsync/GetSongDtosByIdsAsync in MusicService, top-songs ordering in StatsController, and the tiered relevance sort plus artistHint tie-break in ExternalMusicSearchService (against a faked iTunes response, no network needed). Exclude Tests/ from MusicDB.Api's default file glob (it otherwise double- compiles the test project's .cs files), and point CI's build/publish/test steps at explicit project files so the Azure deploy package doesn't end up bundling xunit and other test-only assemblies.
Bring in the Expo/React Native mobile client that talks to the same MusicDB.Api backend, so it's tracked alongside the web app and API in one repo. node_modules/.expo are excluded via its own nested .gitignore; the .NET build/CI is untouched since dotnet only looks at project files.
iTunes's /search endpoint is a fuzzy relevance search over the whole
catalog, not a guaranteed complete tracklist for one album — confirmed
against the real API: a 14-track album ("Кишлак — Эскапист") came back
with only 4 tracks via term search, and a 10-track album similarly
dropped 3 tracks. For the top relevant album matches, now follow up
with /lookup?id={collectionId}, which is authoritative and returns the
album in full; term-search results are kept only as a fallback if a
lookup fails. Shared by web and mobile (both call /api/external-search),
so this fixes album autofill on both without touching either frontend.
The mobile theme file only had dark/light, copied from the web app before its light palette was rebalanced — it still had the old bright-white colors. Worse, toggleTheme/toggleLang existed in SettingsContext but were never wired to any screen, so users had no way to actually change theme or language in the app. Add the same gray theme as web, refresh light to the new muted palette, replace the unused toggle functions with direct setThemeMode/setLang setters, and add a SegmentedPicker control (reused for both) on the settings screen so both are actually selectable and persist via AsyncStorage like the server address already does.
Most people never type "ё" (they type "е" instead), and typos happen. The search's local relevance filtering used exact Contains/Equals, so a single ё-vs-е difference between what the user typed and what iTunes has on file could completely change which album/artist floated to the top — confirmed live: searching an album with an artist hint typed with vs. without "ё" returned two different top results for the same intent. Add FuzzyText (normalizes ё→е, tolerates small edit-distance typos via Levenshtein) and use it everywhere external search compares user input against candidates: artistTerm/songTerm filtering, album relevance tiering, and the artistHint tie-break. No UI/"did you mean" step — matching is corrected transparently, per request.
The video-selection heuristic only scored candidates by how "official" they looked (channel name, "official video" in the title, bad-word penalties) — it never checked whether a candidate was actually about the requested song. For less popular tracks (a lot of which just got added), a poor YouTube search could return nothing relevant in the top results, and the "most official-looking" pick would be a completely unrelated video/artist. Now candidates are first filtered to ones whose title contains most of the song title's significant words (diacritic/case-insensitive), with a second search attempt (without "official video") if nothing matches. Only the remaining official-ness heuristic runs on that relevant set. If nothing is relevant, play shows "video not found" instead of the wrong song.
Real-world duration audit (comparing DB duration against the actual picked YouTube video's length) surfaced that the relevance filter added earlier still let through live performances, interviews, "making of" clips, and teasers — their titles legitimately mention the song, so they passed the word-overlap check, but they're not the studio track and often run a very different length. Add these to the existing bad-word penalty list so the official studio video is preferred over them when both are candidates, and they're scored low enough to lose to a genuine official upload.
The popup video was already meant to be silent by design — audio always comes from the hidden main player, and the popup gets mute()/setVolume(0) plus a re-mute every 5s because YouTube's own volume slider kept un-muting it behind the app's back (that's what the periodic re-mute workaround was compensating for). Instead of reacting to that, remove the native control bar entirely (controls:0, disablekb:1) so there's nothing in the popup for a user to touch in the first place. Playback (play/pause/seek/volume) stays solely owned by the player bar — one source of truth for volume across the app, the same model Spotify uses across its own views.
Add a native horizontal resize handle (CSS resize:horizontal, height follows via the existing aspect-ratio on the video frame), an expand button that jumps between compact and a larger size in one click, and pointer-based dragging from the header so the popup can be moved anywhere on screen instead of being pinned to the corner.
YouTube Data API quota (10000 units/day) is per Google Cloud project, not per key, so a single key has a hard ceiling that one heavy day (or a diagnostic script, as just happened) can exhaust for the whole site. /config now returns youtubeApiKeys (an array, one key per project) instead of a single youtubeApiKey; YouTube:ApiKey stays as a fallback for single-key setups. The frontend tries the current key and transparently advances to the next one on 403/429 before giving up, so exhausting one project's quota no longer breaks video search for everyone until the next day.
- New fullscreen button uses the Fullscreen API on the popup element; :fullscreen CSS makes it fill the viewport (width/height 100vw/100vh, frame flexes to fill remaining height instead of staying locked to the 16:9-derived height). !important on the fullscreen width/height since a prior manual resize or "expand" click can leave an inline style.width that would otherwise win over the stylesheet rule. - Synced via the fullscreenchange event too, so Esc/exiting fullscreen through the browser (not just our button) still updates the icon state. - Raised the manual resize handle's max-width from ~720px to 98vw, so dragging it can stretch the popup close to full screen width as well.
Native CSS resize only offers a single bottom-right handle and locks height to the video's aspect ratio. Replace it with 8 custom pointer- driven handles (4 edges + 4 corners); the opposite edge/corner stays anchored while dragging, matching normal window-resize behavior. The video frame switches from aspect-ratio-derived height to flex:1, so width and height now resize independently (the YouTube iframe already letterboxes internally when the container's aspect doesn't match the video's). The expand button now sets both dimensions instead of just width, since height is no longer auto-derived.
The Fullscreen API only renders the fullscreen element and its DOM descendants — everything else in the document (including the player bar, which lives elsewhere in the DOM) is simply not shown while another element is fullscreen. Since the popup itself has no playback controls by design (audio/controls are owned solely by the player bar), that made it impossible to press play, pause, or seek while the video was fullscreen. On fullscreenchange, physically move the player bar's DOM node into the popup (and back out on exit) — same element, same handlers, just a different parent — so it renders pinned to the bottom of the fullscreen view instead of disappearing.
Clicking the popup video sends onStateChange(PLAYING) exactly like the spurious PLAYING event YouTube fires right after our own seekTo() calls (popup init, periodic drift-sync). The handler used to tell them apart by checking userIntendedPlaying — but that flag is false right after both a real pause AND during those spurious events, so a genuine "resume by clicking the video" click was mistaken for a spurious sync artifact and immediately paused itself again. Track an explicit _popupProgrammaticSeek flag around our own seekTo() calls instead, and only suppress the PLAYING handler when it's set. A real click (no seek in flight) now sets userIntendedPlaying=true and resumes the main player as expected.
Closing the popup (its × button, or playerClose()) set display:none on it
via classList.remove('open') without first checking whether it was the
active fullscreen element. Hiding the fullscreen element without exiting
fullscreen first leaves the browser in a broken fullscreen state — the
page freezes and stops responding until the user manually toggles
fullscreen (F11) to force it out.
closeVideoPopup() now calls exitFullscreen() first when the popup is
currently fullscreen, and only clears/hides it once that resolves.
… rotation
Every play re-ran a YouTube search (100 quota units) even for a song
already played before. Add Music.YoutubeVideoId (nullable, migrated on
Neon separately) and PUT /api/songs/{id}/youtube-video to cache the first
confirmed match — write-once (first result wins) so a stray request can't
clobber an already-correct cached video. Web and mobile both check the
cached id before searching, and save it after a fresh search succeeds.
Also discovered mobile's video search was silently broken since the
earlier key-rotation change: it still read the old singular
config.youtubeApiKey, which no longer exists (/config now returns
youtubeApiKeys). Ported the same array + 403/429 rotation logic used on
web to mobile's fetchVideoId.
Top 10 -> Top 100 everywhere it's surfaced (backend default limit, web
and mobile UI text and API calls).
The write-once cache (first confirmed match wins) means a wrong-but- plausible pick — an author's "Remastered"/"Reimagined" reissue, say, which still clears the relevance and officialness checks — gets stuck permanently with no way to retry. Add a YouTube video field to the admin edit-song form (accepts a bare video ID or a full URL) so it can be corrected or cleared by hand. UpdateSongDto's YoutubeVideoId distinguishes "field omitted" (leave the cached value alone) from "sent as an empty string" (explicit clear) — needed because other update callers, like the mobile app's edit form, don't send this field at all yet and shouldn't wipe the cache on every unrelated edit.
- Один SVG-спрайт (30 символів: check/trophy/arrow/mic/settings/...) у <body> — усі emoji-іконки по сайту (91 входження, ~30 унікальних символів) замінено на <svg class="icon"><use href="#icon-X">. .icon — 1em-розмір, currentColor, theme-aware без додаткових правил. - 18 i18n-ключів мали emoji "запечений" прямо в перекладений рядок (el.textContent = t(key) заміняв усе одразу) — розділив іконку й текст на окремі елементи, прибрав emoji з обох мовних словників. - 2 функції (normalizeGenres, artist follow-btn) писали в btn.textContent напряму — перевів на innerHTML, інакше перший виклик стирав іконку назавжди. - Inter — новий шрифт для основного тексту/таблиць (readability на довгих списках пісень); DM Mono лишився на чіп/кнопках/навбарі через наявні font-family-оверрайди, Playfair Display не чіпав. - Токени --shadow-sm/md/lg, --radius у :root — база для наступного проходу з анімаціями/полішем. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Сплеш-екран (#app-splash) — чистий HTML/CSS, малюється браузером одразу, до виконання app.js; ховається з initApp() коли перші дані готові, з мінімальним часом показу (350мс, щоб не блимнути на швидкому з'єднанні) і жорстким запобіжником на 8с (якщо initApp впаде/зависне — сплеш все одно ховається, а не блокує сайт назавжди). - Переходи між сторінками — showPage() через document.startViewTransition() (нативний крос-фейд у Chrome/Edge/Electron; без підтримки — той самий миттєвий перехід, що й був, без регресій). - Вихід з усіх 7 модалок сайту (delete/edit-song/edit-request/add-to- playlist/battle-setup/battle/graph) тепер симетричний до входу — спільний хелпер _closeModalAnimated() програє modalIn у зворотньому напрямку через CSS замість дублювання анімації в кожному close*(). - ::view-transition-* явно вимкнено під prefers-reduced-motion (окремий псевдоелементний простір, не *::before/::after). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
_loadCurrent() оптимістично показував <img> і ховав плейсхолдер одразу при спробі завантаження, не чекаючи результату — якщо мініатюра 404-ила (рідкісні/видалені відео), лишався битий значок браузера замість ноти. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…w Transitions _updateNavIndicator() викликався ОДРАЗУ за startViewTransition(doSwitch), не чекаючи його — startViewTransition не гарантує виклик колбека в тому самому такті, тож індикатор іноді читав ще стару активну вкладку (підсвічена попередня сторінка замість щойно обраної). Переніс виклик усередину doSwitch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Глянцевий оверлей диска (.wheel-wrap::after, z-index:3) малювався поверх підписів жанрів (.wheel-seg-label не мав z-index узагалі) — тепер підписи на z-index:4, над оверлеєм, як хаб і стрілка. - Лого на сплеші — той самий майже чорний силует, що й губився на хабі колеса (задуманий як водяний знак) — тепер той самий brightness(0) invert(1) фікс, що вже є для хаба. Додав назву "N'Owl" під лого. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Попередній фікс (z-index:4 на .wheel-seg-label) не спрацював — підписи жанрів зникли ПОВНІСТЮ (overflow:hidden на #wheel-disc, судячи з усього, перекреслював z-index-порядок для нащадків). Надійніше рішення: глянцевий шар (світлова пляма + затемнення до країв) тепер один з шарів у background самого диска, поруч із conic-gradient — фон за визначенням завжди позаду контенту, жодних ігор з z-index не потрібно. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Два послідовні виправлення видимості підписів жанрів (z-index, потім перенесення глянцевого ефекту у background) не допомогли — користувач підтвердив, що проблема лишається. Оскільки сама логіка розташування підписів (_wheelLabelFontSize/startR/renderWheelDisc) жодного разу не змінювалась апгрейдом, повертаю решту косметики (палітра, стрілка, глянцевий оверлей, спалах при зупинці, конфеті, аудіо-тіки, грід легенди) до версії, що передувала "Прокачати UI колеса фортуни", як і попросив користувач, замість продовжувати вгадувати наосліп. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Справжня причина невидимих підписів: startR рахувався як Math.min(discR*0.42, n*2.6) — n*2.6 давав абсолютні пікселі (26px для n=10), що для типових n=5-15 завжди програвало порівнянню й збивало всі підписи впритул до хаба. Ця формула не змінювалась жодним з попередніх "апгрейдів", тож обидва попередні виправлення (z-index, потім background-шар) не могли на неї вплинути. Тепер startR — частка радіуса диска (0.6), а не від n. - Легенда жанрів — окрема картка зліва від колеса (було: список під колесом), колесо звужене (стеля 620px замість 1000px) під новий 3-колонковий розклад. - Параметри (кількість жанрів, тривалість) — картка-панель у стилі form-card замість голих рядків; поля отримали власну (раніше повністю відсутню) стилізацію під тему сайту. - Кнопка "Крутити" — на всю ширину картки, золотий градієнт, більший розмір. - Плейлист після зупинки — гортаючийся список (max-height + sticky thead через наявний .scroll-table) замість подовження сторінки. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…миттям
- .wheel-page-layout: justify-content space-between замість center — на
широких екранах легенда й картка параметрів роз'їжджаються до країв,
лишаючи колесу більше місця в центрі.
- Стеля розміру колеса піднята з 620px/52vmin до 760px/62vmin.
- Результат ("Випав жанр: X") перенесено з блоку під колесом у кругла
картку-оверлей поверх хаба в самому центрі диска (z-index 6, з появою
через keyframe). Диск отримує клас .revealed (filter: blur) на час
показу результату — знімається одразу, як користувач крутить наново.
- Хаб отримав max-width/height (118px) — на більшому колесі 22% давали б
завеликий хаб, що підступав до перших символів підписів; startR-запас
до хаба також збільшено (+28 замість +16) для гарантованого зазору.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…сю ширину - Хрестик на картці результату (closeWheelResult()) — ховає картку й одразу знімає .revealed з диска. - filter-transition тепер оголошений лише в .wheel-disc.revealed (не в базовому .wheel-disc): поява розмиття плавна, а зняття класу (закриття картки або новий спін) миттєве — правило .wheel-disc без .revealed просто не містить transition для filter. - #page-wheel .container: max-width: none — єдина сторінка, що навмисно ігнорує звичайну стелю контейнера (1300-1600px) і займає всю ширину екрана; легенда/картка параметрів і стеля розміру колеса (860px/66vmin) розширені відповідно. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…грейд
- Справжня причина стійкого бага "підписи впритул до хаба", що поверталась
попри правильну формулу: clientWidth диска читався одразу після
showPage()/View Transition, коли розмір ще не завжди встиг влаштуватись
на фінальний — формула рахувала на заниженому discR. Тепер позиціювання
підписів винесене в окрему _renderWheelLabels(), яку викликає
ResizeObserver на #wheel-disc — перемальовується щоразу, як диск
насправді змінює розмір, а не один раз одразу після рендеру.
- Палітра — приглушені "коштовні" тони (аметист, смарагд, бордо, мідь...)
у гамі золотого акценту сайту замість яскравих кольорів звичайного
колеса фортуни.
- Диск — подвійна рамка (темна+золота) і м'яке навколишнє світіння замість
плаского box-shadow ("медальйон", а не наклейка).
- Стрілка — SVG-крапля (Material "place"-контур) із золотим градієнтом
замість CSS-трикутника.
- Хаб — глянцевий radial-gradient замість пласкої заливки.
- Свотчі легенди — круглі, в тон коловій темі колеса.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…леєра YouTube IFrame Player API підвантажує www-widgetapi.js у БАТЬКІВСЬКИЙ документ (не в сам iframe) з домену s.ytimg.com — script-src дозволяв лише www.youtube.com, тому цей скрипт мовчки блокувався. YT.Player при цьому створювався без помилок, але керування (play/pause/стан) не працювало — звідси "музика не відтворюється" без жодної видимої помилки в мережі. Заголовок безпеки додано ще в bce6cb5 і відтоді жодного разу не був перевірений реальним відтворенням. Заодно додав www.youtube.com у connect-src про всяк випадок для внутрішніх запитів widget API. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
#ms-anchor (беззвучний data:audio/wav, що утримує media session на нашій сторінці, а не на чужому youtube.com iframe) блокувався CSP — media-src дозволяв лише 'self' і youtube.com, без data: схеми. Підтверджено консольним логом браузера: "Loading media from 'data:audio/wav...' violates ... media-src". Не пов'язано з основним багом відтворення (той був через s.ytimg.com, вже виправлено), але теж реальне порушення CSP, що варто було закрити. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ні ресурси
Знайдено через консоль браузера користувача: sw.js перехоплював УСІ fetch,
включно з крос-доменними (шрифти Google, мініатюри YouTube, signalr з
jsdelivr), і перевідправляв їх через fetch() ВСЕРЕДИНІ самого SW. Такий
виклик підпадає під CSP connect-src, а не під img-src/style-src/script-src,
де ці домени явно дозволені — тож вужчий connect-src ('self' +
googleapis.com + youtube.com) блокував усе інше. Це й було справжньою
причиною "музика не грає": не окремі прогалини в CSP (ті теж були реальні
й виправлені раніше), а сам SW, що перевідкривав кожен запит під невірну
директиву.
Пояснює й "працює лише після Ctrl+Shift+R" — хард-релоад у Chrome обходить
уже активний SW для цієї навігації, тож сторінка вантажилась як без SW
узагалі; за звичайного переходу застарілий SW, що встиг захопити контроль
раніше (до цього фіксу), продовжував ламати запити.
Фікс — обмежити SW лише своїм origin; крос-доменні запити тепер узагалі не
чіпаються (event.respondWith не викликається), тож підпадають під звичайні,
правильні CSP-директиви як завжди.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Поля "від"/"до" валідувались незалежно — можна було виставити 4 і 3, показуючи невалідний діапазон (сам spinWheel() це тихо підстраховував, підіймаючи max до min при обчисленні, але поля в UI лишались суперечливими). На blur тепер підтягуємо ІНШЕ поле до щойно відредагованого, якщо після зміни min > max. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Картки плейлистів на сторінці — іконка-чип, hover-підйом, акцентна рамка (золото для "моїх", фіолет для спільноти) замість голого .ext-search-item. - VS — кругла золота емблема з м'яким світінням замість дрібного тексту. - Стрічка прогресу турніру (16→8→4→2→1), поточний раунд підсвічений, пройдені позначені. - Анімація вибору: обрана сторона коротко спалахує золотою рамкою, програна тьмяніє й стискається, тоді підʼїжджає наступна пара — замість миттєвої підміни контенту. - Власна play-кнопка поверх відео (замість чужого червоного брендингу YouTube), синхронізована зі станом плеєра. - Кнопки вибору — на всю ширину сторони, золотий градієнт, як wheel-spin-btn; плюс клавіші ←/1 та →/2 для вибору без миші. - Екран чемпіона — конфеті-вибух, пульсуюче світіння на імені переможця, кнопка "Слухати переможця" одразу вмикає пісню в основному плеєрі. - Мобільна адаптація: більші тач-зони кнопок і стрічки прогресу. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…лами
Попередній оверлей ("варіант 2" по суті) лежав ПОВЕРХ чужого controls:1 —
під час програвання все одно лишались YouTube-скрубер, лого й брендинг.
Тепер controls:0 на обох YT.Player, і власні play/pause-кнопка та
прогрес-бар (клікабельний для перемотки) повністю керують відтворенням
через API (playVideo/pauseVideo/seekTo/getCurrentTime/getDuration) —
жодного чужого UI не лишається взагалі, лише наша золота палітра.
- Кнопка тепер toggle (play↔pause), а не лише "play"; іконка перемикається
через новий #icon-pause у спільному SVG-спрайті.
- Під час програвання кнопка гасне (клік по відео так само ставить паузу)
і повертається на hover — як у звичайних відеоплеєрів.
- Один спільний тік (250мс) оновлює прогрес-бар того боку, що зараз грає.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
На прохання користувача — controls:1 назад, власна play/pause-кнопка, прогрес-бар та їхня JS-логіка (_battleTogglePlay/_battleSeek/тік) прибрані повністю, а не просто заховані. Хроніка: controls:1 із чужим брендингом → власний оверлей ПОВЕРХ controls:1 → controls:0 з повністю власним плеєром → це фінальне рішення: назад до controls:1, нічого свого поверх немає. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Розклад розбитий на два ряди: .battle-media-row (обидва відео + VS) окремо від .battle-details-row (інфо + кнопка). Раніше VS центрувався по висоті ВСІЄЇ колонки (відео+текст+кнопка разом), тому візуально сидів нижче середини самого відео — тепер він центрується саме в ряду з відео, як і просили. - VS-емблема більша (58px замість 40px), подвійне кільце, контрастний діагональний градієнт, жирний курсивний шрифт, легкий пульс — агресивніше/помітніше замість м'якого малого кола. - Анімація вибору (winner-flash/loser-fade) тепер підсвічує ОБИДВА елементи боку — відео в media-row і інфо/кнопку в details-row разом. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
splashPulse крутив opacity 0.85→1→0.85 щоцикл, тож лого ніколи не було одразу на повній непрозорості — читалось як поступова поява. Тепер статичне, без анімації, і більше (128px замість 72px). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
loadBattleMatch() (єдине місце, що перемальовувало стрічку) більше не викликається після визначення чемпіона, тож сегмент "1" лишався непідсвіченим — стрічка застигала на стані "2 → 1" матчу. Тепер showBattleChampion() окремо позначає останній сегмент класом .winner (золотий, пульсуюче світіння) замість покладатись на .current. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ше 16/8/4 Попередній фікс знімав .current із сегмента "2", але не додавав йому .done — лишався взагалі без класу, тому виглядав інакше за 16/8/4 (ті мали .done ще з останнього реального рендеру loadBattleMatch). Тепер showBattleChampion явно проставляє .done усім, крім останнього, і .winner останньому. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ар і теми
Головна таблиця_2 — пісні від ком'юніті (та сама lab.music з колонкою source):
файл пісні до 25 МБ або посилання на YouTube, нік автора, увесь функціонал
каталогу. Заявка й адмінське додавання — з вибором таблиці.
- Навбар: "Головна" + випадаюче меню + глобальний пошук (пісні, виконавці,
люди); адмін-панель — у меню профілю.
- Сповіщення адмінів: нові заявки й дії інших адмінів ("hito схвалює запит").
- Особисті повідомлення (друзям вільно, іншим — через запит на листування)
і гілки обговорень, реалтайм через SignalR-групи.
- Оцінки 0–100 і рецензії.
- Батл рояль: міні-плеєр для треків ком'юніті без відео; "Слухати переможця"
грає саме переможця.
- Файли пісень — Cloudflare R2 (редирект на підписане посилання), якщо
задано Uploads:R2, інакше диск.
- Мобільний застосунок: таблиця_2, відтворення файлів, заявки з файлом,
оцінки, вкладка "Спільнота", глобальний пошук, сповіщення адмінів.
- Редизайн тем сайту (токени кольорів, контраст).
Міграція: db/create_community_features.sql (поза репо; прод уже мігровано).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
--- updated-dependencies: - dependency-name: Npgsql.EntityFrameworkCore.PostgreSQL dependency-version: 10.0.3 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
dependabot
Bot
force-pushed
the
dependabot/nuget/Npgsql.EntityFrameworkCore.PostgreSQL-10.0.3
branch
from
September 23, 2026 11:29
e571a5c to
23265b2
Compare
Author
|
OK, I won't notify you again about this release, but will get in touch when a new version is available. If you'd rather skip all updates until the next major or minor version, let me know by commenting If you change your mind, just re-open this PR and I'll resolve any conflicts on it. |
dependabot
Bot
deleted the
dependabot/nuget/Npgsql.EntityFrameworkCore.PostgreSQL-10.0.3
branch
September 24, 2026 22:07
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Updated Npgsql.EntityFrameworkCore.PostgreSQL from 9.0.4 to 10.0.3.
Release notes
Sourced from Npgsql.EntityFrameworkCore.PostgreSQL's releases.
10.0.3
What's Changed
Full Changelog: npgsql/efcore.pg@v10.0.2...v10.0.3
10.0.2
Milestone issue
What's Changed
bytea.Any()aslength > 0by @georg-jung in Translatebytea.Any()aslength > 0npgsql/efcore.pg#3817Full Changelog: npgsql/efcore.pg@v10.0.1...v10.0.2
10.0.0
See the release notes.
The full list of changes is available here.
What's Changed
... (truncated)
10.0.0-preview.7
10.0.0-preview.2
10.0.0-preview.1
Commits viewable in compare view.