[{"data":1,"prerenderedAt":1367},["ShallowReactive",2],{"blog-uk-duplicate-key-value-violates-unique-constraint":3,"blog-index-uk":839},{"id":4,"title":5,"body":6,"category":799,"cover":800,"coverAlt":801,"description":802,"draft":803,"extension":804,"faq":805,"image":818,"keywords":819,"meta":828,"navigation":829,"ogTitle":5,"path":830,"published":831,"seo":832,"stem":833,"tldr":834,"updated":831,"__hash__":838},"blog_uk\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint.md","Supabase \"duplicate key value violates unique constraint\": пояснення",{"type":7,"value":8,"toc":787},"minimark",[9,13,24,31,34,39,42,45,48,167,180,184,193,267,270,278,292,296,299,302,324,337,343,366,379,385,389,392,399,407,413,419,432,443,447,450,477,514,520,540,556,559,564,568,571,592,607,615,631,634,640,661,666,670,673,682,705,714,732,736,777],[10,11,12],"p",{},"Хтось заповнює форму вашого списку очікування, чекає й натискає кнопку ще раз.\nПерше натискання вже пройшло. Друге повертається помилкою, і консоль браузера\nпоказує її повністю:",[14,15,20],"pre",{"className":16,"code":18,"language":19},[17],"language-text","{\n  \"code\": \"23505\",\n  \"details\": \"Key (email)=(maria@example.com) already exists.\",\n  \"hint\": null,\n  \"message\": \"duplicate key value violates unique constraint \\\"waitlist_email_key\\\"\"\n}\n","text",[21,22,18],"code",{"__ignoreMap":23},"",[10,25,26,27,30],{},"Supabase \"duplicate key value violates unique constraint\" означає, що Postgres\nвідмовився додати рядок, бо інший рядок уже має те саме значення в стовпці, який\nприймає кожне значення лише раз. Тут це стовпець ",[21,28,29],{},"email",", і Марія вже є у вашому\nсписку.",[10,32,33],{},"Порада, яку для цієї помилки передають з рук у руки, звучить так: замінити insert\nна upsert. Іноді це правильно. Але на таблиці, куди відвідувачі можуть лише\nдодавати, upsert із браузера потребує правил, які дозволяють їм читати таблицю, а\nрядок з адресою Марії ваш застосунок не мав би показувати відвідувачеві взагалі.",[35,36,38],"h2",{"id":37},"що-означає-duplicate-key-value-violates-unique-constraint","Що означає \"duplicate key value violates unique constraint\"?",[10,40,41],{},"Значення, яке ви хотіли зберегти, уже стоїть у стовпці, що приймає кожне значення\nодин раз, тож Postgres відхилив увесь рядок. Нічого не записано, а рядок, який був\nтам першим, не змінився.",[10,43,44],{},"Уявіть таблицю як список гостей біля дверей, по одному рядку на ім'я. Обмеження\nунікальності працює як правило охоронця: ім'я потрапляє в список лише раз. Ваш\nзастосунок прийшов з іменем, яке там уже записане, і охоронець його розвернув.",[10,46,47],{},"Кожна частина помилки каже одну річ:",[49,50,51,67],"table",{},[52,53,54],"thead",{},[55,56,57,61,64],"tr",{},[58,59,60],"th",{},"Поле",[58,62,63],{},"Приклад",[58,65,66],{},"Що воно вам каже",[68,69,70,96,111,126,141],"tbody",{},[55,71,72,77,82],{},[73,74,75],"td",{},[21,76,21],{},[73,78,79],{},[21,80,81],{},"23505",[73,83,84,85,92,93],{},"Код Postgres для цієї помилки, у ",[86,87,91],"a",{"href":88,"rel":89},"https:\u002F\u002Fgithub.com\u002Fpostgres\u002Fpostgres\u002Fblob\u002Fmaster\u002Fsrc\u002Fbackend\u002Futils\u002Ferrcodes.txt",[90],"nofollow","його списку кодів"," він називається ",[21,94,95],{},"unique_violation",[55,97,98,103,108],{},[73,99,100],{},[21,101,102],{},"message",[73,104,105],{},[21,106,107],{},"duplicate key value violates unique constraint \"waitlist_email_key\"",[73,109,110],{},"Яке правило відхилило рядок, на ім'я",[55,112,113,118,123],{},[73,114,115],{},[21,116,117],{},"details",[73,119,120],{},[21,121,122],{},"Key (email)=(maria@example.com) already exists.",[73,124,125],{},"Стовпець і значення, що зіткнулися, якщо Postgres вирішить, що тому, хто викликає, можна їх бачити",[55,127,128,133,138],{},[73,129,130],{},[21,131,132],{},"hint",[73,134,135],{},[21,136,137],{},"null",[73,139,140],{},"Для цієї помилки додати нічого",[55,142,143,146,151],{},[73,144,145],{},"Статус HTTP",[73,147,148],{},[21,149,150],{},"409 Conflict",[73,152,153,154,156,157,160,161,166],{},"Так про ",[21,155,81],{}," повідомляє PostgREST, частина Supabase, яка перетворює виклики ",[21,158,159],{},"supabase.from(...)"," на SQL (",[86,162,165],{"href":163,"rel":164},"https:\u002F\u002Fgithub.com\u002FPostgREST\u002Fpostgrest\u002Fblob\u002Fmain\u002Fsrc\u002Flibrary\u002FPostgREST\u002FError.hs",[90],"його код",")",[10,168,169,170,175,176,179],{},"Формулювання належить самому Postgres.\n",[86,171,174],{"href":172,"rel":173},"https:\u002F\u002Fgithub.com\u002Fpostgres\u002Fpostgres\u002Fblob\u002Fmaster\u002Fsrc\u002Fbackend\u002Faccess\u002Fnbtree\u002Fnbtinsert.c",[90],"Код, який викидає помилку",",\nдодає рядок ",[21,177,178],{},"Key ... already exists"," лише тоді, коли має опис ключа, і розділ\nнижче пояснює, коли опису немає.",[35,181,183],{"id":182},"який-стовпець-спричинив-помилку","Який стовпець спричинив помилку?",[10,185,186,187,192],{},"Це каже назва в лапках, бо Postgres складає її з таблиці й стовпців, якщо ніхто\nне вибрав іншої.\n",[86,188,191],{"href":189,"rel":190},"https:\u002F\u002Fgithub.com\u002Fpostgres\u002Fpostgres\u002Fblob\u002Fmaster\u002Fsrc\u002Fbackend\u002Fcommands\u002Findexcmds.c",[90],"Його код для назв","\nдає три форми:",[49,194,195,207],{},[52,196,197],{},[55,198,199,202,205],{},[58,200,201],{},"Назва закінчується на",[58,203,204],{},"Це",[58,206,63],{},[68,208,209,233,251],{},[55,210,211,216,221],{},[73,212,213],{},[21,214,215],{},"_key",[73,217,218,219],{},"обмеження унікальності на стовпцях між назвою таблиці та ",[21,220,215],{},[73,222,223,226,227,230,231],{},[21,224,225],{},"waitlist_email_key",": таблиця ",[21,228,229],{},"waitlist",", стовпець ",[21,232,29],{},[55,234,235,240,246],{},[73,236,237],{},[21,238,239],{},"_pkey",[73,241,242,243],{},"первинний ключ таблиці, зазвичай ",[21,244,245],{},"id",[73,247,248],{},[21,249,250],{},"orders_pkey",[55,252,253,259,262],{},[73,254,255,258],{},[21,256,257],{},"_idx"," або щось інше",[73,260,261],{},"унікальний індекс, створений вручну, або обмеження, якому хтось дав назву",[73,263,264],{},[21,265,266],{},"profiles_username_idx",[10,268,269],{},"Повідомлення каже \"unique constraint\" в усіх трьох випадках. Щоб побачити, що саме\nохоплює назва, виконайте це в SQL Editor з назвою з вашого повідомлення:",[14,271,276],{"className":272,"code":274,"language":275,"meta":23},[273],"language-sql","select tablename, indexdef\nfrom pg_indexes\nwhere indexname = 'waitlist_email_key';\n","sql",[21,277,274],{"__ignoreMap":23},[10,279,280,283,284,287,288,291],{},[21,281,282],{},"indexdef"," повернеться приблизно таким:\n",[21,285,286],{},"CREATE UNIQUE INDEX waitlist_email_key ON public.waitlist USING btree (email)",",\nі стовпці в дужках не можуть повторюватися. Правило на двох стовпцях, як-от\n",[21,289,290],{},"(team_id, email)",", пускає ту саму адресу у дві команди й відхиляє її вдруге в\nтій самій.",[35,293,295],{"id":294},"key-id1-already-exists-хоча-ви-ніколи-не-надсилали-id","\"Key (id)=(1) already exists\", хоча ви ніколи не надсилали id",[10,297,298],{},"Ця помилка означає, що лічильник id таблиці відстає від рядків, які в ній уже є,\nтож він видав зайнятий номер. Ваш застосунок не надіслав жодного id. Postgres\nузяв наступний номер із лічильника, а рядок із таким номером уже існував.",[10,300,301],{},"У списку гостей охоронець ще й роздає нумеровані браслети з рулону. Хтось\nпереписав перших двісті гостей вручну, разом із номерами браслетів, і ніхто не\nпрокрутив рулон далі. Він і далі починає з 1.",[10,303,304,305,310,311,316,317,319,320,323],{},"Стовпець, який нумерує себе сам, бере кожен номер із послідовності, так Postgres\nназиває цей лічильник.\n",[86,306,309],{"href":307,"rel":308},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Fddl-identity-columns.html",[90],"Сторінка Postgres про identity-стовпці","\nкаже, що такий стовпець \"does not guarantee uniqueness\", і серед причин називає\nсаме цей випадок: значення \"could be inserted manually into the identity column\".\nРядки приходять із уже заповненими номерами з імпорту CSV, де є стовпець id, з\nкопії з іншого проєкту або з seed-скрипта. Лічильник не дізнається про жоден із\nних.\n",[86,312,315],{"href":313,"rel":314},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Ftroubleshooting\u002Fwhy-are-there-gaps-in-my-postgres-id-sequence-Frifus",[90],"Сторінка Supabase з усунення проблем про послідовності id","\nрозбирає саме це повідомлення на ",[21,318,239],{},", і її виправлення теж зводиться до\n",[21,321,322],{},"setval",".",[10,325,326,327,330,331,333,334,336],{},"Виправлення займає одну інструкцію. Підставте свою таблицю й стовпець замість\n",[21,328,329],{},"orders"," і ",[21,332,245],{},"; таблиця стоїть у назві перед ",[21,335,239],{},":",[14,338,341],{"className":339,"code":340,"language":275,"meta":23},[273],"select setval(\n  pg_get_serial_sequence('public.orders', 'id'),\n  (select max(id) from public.orders)\n);\n",[21,342,340],{"__ignoreMap":23},[10,344,345,348,349,354,355,361,362,323],{},[21,346,347],{},"pg_get_serial_sequence"," знаходить лічильник за стовпцем, і\n",[86,350,353],{"href":351,"rel":352},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Ffunctions-info.html",[90],"документація Postgres","\nкаже, що функція працює для обох видів: \"If the column is an identity column, the\nassociated sequence is the sequence internally created for that column.\" Далі\n",[86,356,359],{"href":357,"rel":358},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Ffunctions-sequence.html",[90],[21,360,322],{}," із\nдвома аргументами ставить лічильник так, що \"the next nextval will advance the\nsequence before returning a value\". Наступний новий рядок отримує найбільший id\nплюс один. Якщо Postgres відповідає, що relation не існує, назву таблиці написано\nінакше, вона має велику літеру або лежить в іншій схемі, і\n",[86,363,365],{"href":364},"\u002Fblog\u002Fsupabase-relation-does-not-exist","про цю помилку є окремий допис",[10,367,368,369,372,373,378],{},"Відновлення з виводу ",[21,370,371],{},"pg_dump"," сюди не приводить.\n",[86,374,377],{"href":375,"rel":376},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Fapp-pgdump.html",[90],"Його документація","\nперелічує, що містить дамп даних: \"Table data, large objects, and sequence values\nare dumped.\" Кожен лічильник повертається разом зі своїми рядками.",[380,381],"diagram",{"alt":382,"caption":383,"src":384},"Список із п'яти нумерованих рядків, від 1 до 5, поруч із лічильником. Ліворуч лічильник показує 1 і вказує на рядок 1, підсвічений бурштиновим, що закінчується табличкою Key (id)=(1). Праворуч лічильник показує 6 і вказує на нове ім'я в шостому рядку, під ним стоїть галочка.","Рядки, скопійовані з власними id, лишають лічильник там, де він був. setval переводить його за найбільший id, і наступний рядок отримує вільний номер.","\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint\u002Fthe-counter-is-behind-1600x640.png",[35,386,388],{"id":387},"три-способи-впоратися-з-дублікатом","Три способи впоратися з дублікатом",[10,390,391],{},"Вирішіть, що має статися, коли значення вже є: відхилити новий рядок і сказати\nпро це, дозволити йому замінити старий або взагалі його не надсилати.",[10,393,394,398],{},[395,396,397],"strong",{},"Відхиліть його власними словами."," Це підходить, коли другий рядок був би\nпомилкою: адреса вже зареєстрована, ім'я користувача вже зайняте. Insert падає,\nваш застосунок читає код, а людина бачить речення, яке написали ви:",[14,400,405],{"className":401,"code":403,"language":404,"meta":23},[402],"language-js","const { error } = await supabase.from('profiles').insert({ id: user.id, username })\n\nif (error?.code === '23505') {\n  setMessage(\"Це ім'я користувача вже зайняте. Спробуйте інше.\")\n} else if (error) {\n  setMessage('Щось пішло не так. Спробуйте ще раз.')\n}\n","js",[21,406,403],{"__ignoreMap":23},[10,408,409,412],{},[395,410,411],{},"Дозвольте йому замінити старий."," Це підходить, коли має перемогти найновіша\nверсія: рядок налаштувань для кожного користувача, кошик для кожної сесії. Це\nupsert, і наступний розділ пояснює, коли він ламається.",[10,414,415,418],{},[395,416,417],{},"Не надсилайте його."," Іноді другого рядка взагалі не мало бути, і він приходить\nз одного з таких місць:",[420,421,422,426,429],"ul",{},[423,424,425],"li",{},"кнопка, яка надсилає форму вдруге, коли її натискають удруге. Вимкніть її, доки\nне відповість перший запит.",[423,427,428],{},"повторна спроба після повільної відповіді, коли перша вже пройшла",[423,430,431],{},"два записувачі для одного рядка, як-от тригер, що створює профіль для кожного\nнового користувача, і код застосунку, що вставляє той самий профіль після\nреєстрації",[10,433,434,435,439,440,442],{},"Випадок із тригером варто впізнавати з першого погляду. Тригер, який копіює нових\nкористувачів у вашу таблицю, падає з цією помилкою, коли значення, яке він копіює,\nтам уже є, і Supabase повідомляє про всю реєстрацію як\n",[86,436,438],{"href":437},"\u002Fblog\u002Fdatabase-error-saving-new-user","\"Database error saving new user\"",". Сам\n",[21,441,81],{}," лежить у логах Postgres, і той допис показує, де саме.",[35,444,446],{"id":445},"чи-варто-використовувати-upsert","Чи варто використовувати upsert?",[10,448,449],{},"Використовуйте його, коли новий рядок має замінити старий, і перевірте три речі,\nперш ніж на нього покладатися.",[10,451,452,455,456,461,462,465,466,469,470,473,474,323],{},[21,453,454],{},".upsert()"," у supabase-js працює як insert, що робить щось інше, коли рядок уже\nіснує. ",[86,457,460],{"href":458,"rel":459},"https:\u002F\u002Fgithub.com\u002Fsupabase\u002Fsupabase-js\u002Fblob\u002Fmaster\u002Fpackages\u002Fcore\u002Fpostgrest-js\u002Fsrc\u002FPostgrestQueryBuilder.ts",[90],"Його вихідний код","\nкаже це у двох рядках: \"If ",[21,463,464],{},"true",", duplicate rows are ignored. If ",[21,467,468],{},"false",",\nduplicate rows are merged with existing rows.\" Це опція ",[21,471,472],{},"ignoreDuplicates",", і\nPostgres виконує обидві версії як ",[21,475,476],{},"insert ... on conflict",[10,478,479,486,491,492,495,496,498,499,501,502,504,505,507,508,513],{},[395,480,481,482,485],{},"1. ",[21,483,484],{},"onConflict"," має називати стовпець із вашої помилки.",[86,487,490],{"href":488,"rel":489},"https:\u002F\u002Fdocs.postgrest.org\u002Fen\u002Fstable\u002Freferences\u002Fapi\u002Ftables_views.html",[90],"Документація PostgREST","\nкаже: \"By default, upsert operates based on the primary key columns\". Upsert\n",[21,493,494],{},"{ email }"," у таблицю з первинним ключем ",[21,497,245],{}," шукає зіткнення по ",[21,500,245],{},", не\nзнаходить його, бо id новий, вставляє рядок і натрапляє на правило для ",[21,503,29],{}," з\nтим самим ",[21,506,81],{},".\n",[86,509,512],{"href":510,"rel":511},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Fsql-insert.html",[90],"Довідка Postgres про INSERT","\nкаже, що альтернативна дія спрацьовує, \"if an arbiter constraint or index\nspecified by conflict_target is violated\". Зіткнення в будь-якому іншому стовпці\nвідхиляє рядок, як і раніше.",[14,515,518],{"className":516,"code":517,"language":404,"meta":23},[402],"await supabase\n  .from('settings')\n  .upsert({ user_id: user.id, theme: 'dark' }, { onConflict: 'user_id' })\n",[21,519,517],{"__ignoreMap":23},[10,521,522,525,526,529,530,533,534,539],{},[395,523,524],{},"2. Цьому стовпцю потрібне власне правило унікальності."," Без нього Postgres\nнема з чим зіставляти, і він відповідає\n",[21,527,528],{},"there is no unique or exclusion constraint matching the ON CONFLICT specification",",\nкод ",[21,531,532],{},"42P10","\n(",[86,535,538],{"href":536,"rel":537},"https:\u002F\u002Fgithub.com\u002Fpostgres\u002Fpostgres\u002Fblob\u002Fmaster\u002Fsrc\u002Fbackend\u002Foptimizer\u002Futil\u002Fplancat.c",[90],"рядок, який це викидає",").",[10,541,542,545,546,551,552,323],{},[395,543,544],{},"3. З браузера Row Level Security читає більше ваших правил."," Row Level Security\nохоплює правила для окремих рядків, які Supabase перевіряє в кожному запиті з\nвашим публічним ключем. Для upsert\n",[86,547,550],{"href":548,"rel":549},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Fsql-createpolicy.html",[90],"довідка Postgres про policies","\nкаже: \"SELECT permissions are required on the relation, and the rows proposed for\ninsertion are checked using the relation's SELECT policies.\" Коли рядок існує, а\nupsert зливає дані, наявний рядок ще й перевіряють за правилами оновлення таблиці.\nРядок, що не проходить будь-яку з цих перевірок, завершує запит помилкою\n",[86,553,555],{"href":554},"\u002Fblog\u002Fnew-row-violates-row-level-security-policy","new row violates row-level security policy",[10,557,558],{},"Тож у списку очікування upsert із браузера падає, доки таблиця не матиме правила\nчитання, яке проходить відвідувач. Правило читання, яке незнайомець проходить для\nбудь-якої адреси, дозволяє незнайомцям читати ваш список. Біля дверей переписати\nрядок можна лише тоді, коли вам спершу показали список. Для такої таблиці краще\nвідхиляйте дублікат або скористайтеся функцією з наступного розділу.",[380,560],{"alt":561,"caption":562,"src":563},"Два запити до однієї таблиці. Insert проходить одну браму, insert, і досягає таблиці. Upsert має пройти три брами поспіль, insert, select і update; брама select зачинена й намальована бурштиновим, тож upsert зупиняється біля неї.","З браузера upsert перевіряють за правилами читання й оновлення таблиці, а не лише за правилом вставки. На таблиці, куди відвідувачі можуть лише додавати, його зупиняє правило читання.","\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint\u002Fupsert-reads-three-rules-1600x660.png",[35,565,567],{"id":566},"чи-безпечно-показувати-цю-помилку-користувачам","Чи безпечно показувати цю помилку користувачам?",[10,569,570],{},"Ні. Показуйте власне речення, а сиру помилку тримайте в логах, із двох причин:\nрядок деталей може повернути значення, а сама помилка підтверджує, що значення\nіснує, хоч би що ви показали.",[10,572,573,576,577,580,581,588,589,323],{},[395,574,575],{},"Рядок деталей повторює значення."," ",[21,578,579],{},"Key (email)=(maria@example.com)"," нешкідливий\nна екрані самої Марії, але геть інша річ на скриншоті у форумі підтримки, у\nсистемі відстеження помилок або на екрані того, хто ввів у вашу форму чужу\nадресу. Postgres уже його оберігає. Функція, яка будує цей рядок\n(",[86,582,585],{"href":583,"rel":584},"https:\u002F\u002Fgithub.com\u002Fpostgres\u002Fpostgres\u002Fblob\u002Fmaster\u002Fsrc\u002Fbackend\u002Faccess\u002Findex\u002Fgenam.c",[90],[21,586,587],{},"BuildIndexValueDescription","),\nспершу перевіряє: \"if RLS is enabled for the relation. If so, return NULL to avoid\nleaking data.\" Запит із вашим публічним ключем до таблиці з увімкненою Row Level\nSecurity отримує ",[21,590,591],{},"\"details\": null",[10,593,594,595,598,599,601,602,606],{},"Якщо прочитати навпаки, рядок каже вам щось про таблицю. Якщо консоль браузера\nпоказує ",[21,596,597],{},"Key (...)=(...)"," для запиту, який ваш застосунок зробив із публічним\nключем, Row Level Security на цій таблиці вимкнена. Єдиний виняток становить\nзапит, який ваш застосунок надсилає через серверний код із секретним ключем, що\nповністю обходить правила. ",[21,600,137],{}," доводить менше: Postgres прибирає рядок ще й\nтоді, коли той, хто викликає, не може читати кожен стовпець ключа.\n",[86,603,605],{"href":604},"\u002Fblog\u002Fenable-rls-on-every-supabase-table","Як увімкнути Row Level Security на кожній таблиці","\nрозповідає про перемикач і перше правило.",[10,608,609,610,614],{},"Таблиця з вимкненими правилами відповідає незнайомцеві так само охоче, як і вам.\nНаше ",[86,611,613],{"href":612},"\u002Fsecurity-scanner","безкоштовне сканування"," читає ваш живий застосунок тим\nсамим публічним ключем, який отримує відвідувач, повідомляє, які таблиці віддають\nрядки, і не потребує облікового запису.",[10,616,617,620,621,624,625,630],{},[395,618,619],{},"Помилка відповідає на запитання, на яке ви, можливо, не хочете відповідати.","\nНавіть без рядка деталей ",[21,622,623],{},"409"," каже тому, хто надіслав рядок, що значення вже є.\n",[86,626,629],{"href":627,"rel":628},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Fddl-rowsecurity.html",[90],"Сторінка Postgres про безпеку рядків","\nкаже, що обмеження унікальності \"always bypass row security to ensure that data\nintegrity is maintained\", і просить проєктувати таблиці й правила так, щоб ці\nперевірки не ставали прихованим каналом. У списку очікування будь-хто може\nдізнатися, чи є адреса у вашому списку, просто спробувавши її додати. Охоронець\nніколи не читає ім'я вголос, а коли він когось розвертає, черга однаково знає, що\nтаке ім'я там було.",[10,632,633],{},"Якщо для вашого списку це важливо, давайте браузерові однакову відповідь в обох\nвипадках. Функція в базі може додати адресу, пропустити її, якщо вона вже є, і\nнічого не повертати в жодному з випадків:",[14,635,638],{"className":636,"code":637,"language":275,"meta":23},[273],"create function public.join_waitlist(new_email text)\nreturns void\nlanguage sql\nsecurity definer\nset search_path = ''\nas $$\n  insert into public.waitlist (email)\n  values (new_email)\n  on conflict (email) do nothing;\n$$;\n",[21,639,637],{"__ignoreMap":23},[10,641,642,643,507,646,649,650,655,656,660],{},"Ваш застосунок викликає ",[21,644,645],{},"supabase.rpc('join_waitlist', { new_email: email })",[21,647,648],{},"security definer"," виконує insert від імені власника функції, тож відвідувачам не\nпотрібне жодне правило на таблиці, а правило, яке дозволяло їм вставляти, можна\nприбрати, інакше старий шлях лишиться відкритим поруч із новим.\n",[86,651,652,653],{"href":437},"Що \"Database error saving new user\" каже про ",[21,654,648],{},"\nпояснює, що він передає, а\n",[86,657,659],{"href":658},"\u002Fblog\u002Fsupabase-function-not-found","як викликати функцію Supabase"," показує, як\nузгодити назви аргументів.",[380,662],{"alt":663,"caption":664,"src":665},"Той самий дублікат надіслано до двох таблиць. Ліва таблиця має навісний замок, і її відповідь каже 23505 і 409 з порожнім рядком details. Права таблиця замка не має, і її відповідь несе бурштиновий рядок details із Key (email)=(…).","Postgres пише значення в рядок деталей лише для того, на кого Row Level Security не поширюється. Якщо ви бачите його з браузера, правила таблиці вимкнені.","\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint\u002Fthe-detail-line-and-rls-1600x700.png",[35,667,669],{"id":668},"перш-ніж-змінювати-як-ваш-застосунок-записує-дані","Перш ніж змінювати, як ваш застосунок записує дані",[10,671,672],{},"Збережіть копію бази з часу до того, як перейдете з insert на upsert або\nпідправите лічильник, і дізнайтеся, коли якась таблиця почне відповідати\nнезнайомцям.",[10,674,675],{},[395,676,677,681],{},[86,678,680],{"href":679},"\u002Fsupabase-backups","Reeve Care"," зберігає копію вашої бази даних Supabase:",[420,683,684,687,690,693,696,699,702],{},[423,685,686],{},"зашифрована копія за розкладом, у власному сховищі Reeve в ЄС, поза вашим обліковим записом Supabase",[423,688,689],{},"кожна таблиця з рядками такими, якими вони були до того, як злиття їх переписало",[423,691,692],{},"лічильник id кожної таблиці повертається разом із її рядками під час відновлення, тож наступний новий рядок отримує вільний номер",[423,694,695],{},"кожна копія перевірена, перш ніж її зараховують, підрахунком рядків у кожній таблиці",[423,697,698],{},"ваші завантажені файли теж, щойно ви підключите облікові дані Storage",[423,700,701],{},"відновлення одним кліком, перед яким береться страхувальна копія поточного стану",[423,703,704],{},"усе, що робить Monitor",[10,706,707],{},[395,708,709,713],{},[86,710,712],{"href":711},"\u002Fwebsite-monitoring","Reeve Monitor"," і далі перевіряє ззовні:",[420,715,716,719,722,729],{},[423,717,718],{},"чи доступний ваш застосунок, з перевіркою щохвилини",[423,720,721],{},"повна перевірка безпеки щогодини, до трьох застосунків",[423,723,724,725,728],{},"повідомлення, коли результат ",[395,726,727],{},"змінюється",", тож таблиця, яка після деплою почала відповідати незнайомцям, дійде до вас, і вам не доведеться йти дивитися",[423,730,731],{},"щомісячний звіт простою мовою про те, що він побачив",[35,733,735],{"id":734},"що-зробити-зараз","Що зробити зараз",[737,738,739],"key-takeaways",{},[420,740,741,750,759,765,771],{},[423,742,743,744,746,747,749],{},"Прочитайте назву в лапках. Таблиця й стовпець стоять перед ",[21,745,215],{},", а ",[21,748,239],{}," означає первинний ключ.",[423,751,752,753,755,756,758],{},"Якщо назва закінчується на ",[21,754,239],{},", а ваш застосунок не надсилає id, підправте лічильник інструкцією ",[21,757,322],{}," вище.",[423,760,761,762,764],{},"Перехоплюйте ",[21,763,81],{}," у застосунку й показуйте речення, яке написали ви, а не сире повідомлення.",[423,766,767,768,770],{},"Використовуйте upsert лише там, де новий рядок має замінити старий, з ",[21,769,484],{}," на стовпці з помилки.",[423,772,773,774,776],{},"Якщо консоль браузера показує рядок ",[21,775,597],{}," для запиту з вашим публічним ключем, увімкніть Row Level Security на цій таблиці.",[10,778,779,780,782,783,786],{},"Почніть із назви в лапках, бо вона каже, яке правило відхилило рядок і який\nстовпець це правило стереже. Перш ніж запускати ",[21,781,322],{},", upsert чи щось інше, що\nпереписує рядки, на які покладаються люди,\n",[86,784,785],{"href":679},"збережіть копію бази"," з часу до зміни.",{"title":23,"searchDepth":788,"depth":788,"links":789},3,[790,792,793,794,795,796,797,798],{"id":37,"depth":791,"text":38},2,{"id":182,"depth":791,"text":183},{"id":294,"depth":791,"text":295},{"id":387,"depth":791,"text":388},{"id":445,"depth":791,"text":446},{"id":566,"depth":791,"text":567},{"id":668,"depth":791,"text":669},{"id":734,"depth":791,"text":735},"Основи безпеки","\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint\u002Fcover-1200x630.png","Список імен на планшеті, один рядок бірюзовий, а перед ним папірець із тим самим іменем бурштиновим, який не пускають у список.","Supabase \"duplicate key value violates unique constraint\": значення вже зайняте. Знайдіть стовпець, оберіть виправлення й сховайте значення від очей.",false,"md",[806,809,811,813,816],{"q":807,"a":808},"Що означає duplicate key value violates unique constraint?","Це означає, що Postgres відмовився додати чи змінити рядок, бо інший рядок уже має те саме значення в стовпці або групі стовпців, які приймають кожне значення лише раз. Нічого не записано. Код помилки 23505, а із застосунку на Supabase вона приходить як HTTP 409.",{"q":183,"a":810},"Це каже назва правила в лапках. Postgres називає обмеження унікальності за таблицею й стовпцями, із _key у кінці, тож waitlist_email_key означає стовпець email таблиці waitlist, а назва із закінченням _pkey означає первинний ключ, зазвичай id. Рядок деталей, коли він є, називає стовпець і значення.",{"q":446,"a":812},"Лише тоді, коли новий рядок має замінити старий, як рядок налаштувань для кожного користувача. Вкажіть в onConflict стовпець із помилки, бо upsert у Supabase за замовчуванням перевіряє первинний ключ. З браузера upsert потребує ще й правил читання, які рядок проходить, тож на таблиці, куди відвідувачі можуть лише додавати, він падає або змушує вас відкрити таблицю для читання.",{"q":814,"a":815},"Чому це стається під час реєстрації?","Туди ведуть два шляхи: тригер копіює кожного нового користувача у вашу таблицю, а значення, яке він копіює, там уже є, або ваш застосунок вставляє той самий рядок профілю, який тригер уже створив. Supabase повідомляє про обидва як \"Database error saving new user\", а сам 23505 лежить у логах Postgres.",{"q":567,"a":817},"Ні. Рядок деталей може повторити значення, повідомлення називає вашу таблицю й стовпець, а сама помилка каже тому, хто надіслав рядок, що таке значення існує. Перехоплюйте код 23505, показуйте речення, яке написали ви, а сиру помилку тримайте у власних логах.","\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint\u002Fcard-800x500.png",[820,821,822,823,824,825,826,827],"duplicate key value violates unique constraint","supabase помилка дубліката ключа","postgres 23505","supabase upsert дублікат","key already exists","duplicate key value violates unique constraint pkey","there is no unique or exclusion constraint matching the on conflict specification","supabase onconflict",{},true,"\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint","2026-10-24",{"title":5,"description":802},"blog\u002Fduplicate-key-value-violates-unique-constraint",[835,836,837],"Supabase \"duplicate key value violates unique constraint\" (код Postgres 23505, HTTP 409 у вашому застосунку) означає, що якийсь рядок уже має це значення в стовпці, який приймає кожне значення лише раз. Назва в лапках каже, який це стовпець.","\"Key (id)=(1) already exists\", хоча ваш застосунок не надсилає id, означає, що лічильник id таблиці відстає від її рядків. Один рядок із setval це виправляє.","Upsert допомагає, лише коли onConflict називає цей стовпець, а з браузера йому потрібні ще й правила читання таблиці. Показуйте користувачам власне речення: сире повідомлення може повернути значення.","y-04aRWZ4kw9LfN-DmJtIo9XVyN-Az03S6sBruKhOsQ",[840,846,852,858,864,870,876,882,888,894,900,907,913,919,925,931,937,938,943,948,954,960,966,972,978,984,990,995,1001,1007,1013,1019,1025,1031,1037,1043,1049,1055,1061,1067,1073,1079,1085,1091,1097,1103,1108,1114,1120,1126,1132,1138,1144,1150,1156,1162,1168,1174,1180,1186,1192,1198,1203,1209,1215,1221,1227,1233,1239,1245,1251,1257,1263,1269,1275,1281,1286,1292,1298,1304,1310,1316,1322,1328,1333,1339,1345,1351,1356,1362],{"path":841,"title":842,"description":843,"published":844,"category":799,"image":845,"draft":803},"\u002Fblog\u002Fmax-client-connections-reached","Supabase \"max client connections reached\": пояснення","Supabase \"max client connections reached\": у пулера немає місця ще для одного клієнта. Ліміт визначає порт у вашому рядку підключення.","2026-11-09","\u002Fblog\u002Fmax-client-connections-reached\u002Fcard-800x500.png",{"path":847,"title":848,"description":849,"published":850,"category":799,"image":851,"draft":803},"\u002Fblog\u002Funsupported-provider-provider-is-not-enabled","Помилка Supabase: \"Unsupported provider: provider is not enabled\"","\"Unsupported provider: provider is not enabled\": вхід через Google вимкнено у вашому проєкті Supabase. Як увімкнути та виправити redirect_uri_mismatch.","2026-11-08","\u002Fblog\u002Funsupported-provider-provider-is-not-enabled\u002Fcard-800x500.png",{"path":853,"title":854,"description":855,"published":856,"category":799,"image":857,"draft":803},"\u002Fblog\u002Fviolates-foreign-key-constraint","Помилка Supabase \"violates foreign key constraint\" під час вставки","Supabase \"insert or update on table violates foreign key constraint\": ваш рядок посилається на рядок, якого немає. Який, чому і як це виправити.","2026-11-07","\u002Fblog\u002Fviolates-foreign-key-constraint\u002Fcard-800x500.png",{"path":859,"title":860,"description":861,"published":862,"category":799,"image":863,"draft":803},"\u002Fblog\u002Fnull-value-violates-not-null-constraint","Помилка Supabase \"null value in column violates not-null constraint\"","Supabase \"null value in column violates not-null constraint\": обовʼязковий стовпець прийшов порожнім. Звідки взявся null і як це виправити.","2026-11-06","\u002Fblog\u002Fnull-value-violates-not-null-constraint\u002Fcard-800x500.png",{"path":865,"title":866,"description":867,"published":868,"category":799,"image":869,"draft":803},"\u002Fblog\u002Fauth-session-missing","Supabase \"Auth session missing!\": куди поділася сесія","\"Auth session missing!\" означає, що supabase-js не знайшла жодної сесії й навіть не спитала Supabase. Куди поділася сесія і як це виправити.","2026-11-05","\u002Fblog\u002Fauth-session-missing\u002Fcard-800x500.png",{"path":871,"title":872,"description":873,"published":874,"category":799,"image":875,"draft":803},"\u002Fblog\u002Finvalid-claim-missing-sub-claim","Supabase \"invalid claim: missing sub claim\": ніхто не ввійшов","\"invalid claim: missing sub claim\": Supabase Auth отримав справжній токен без жодного користувача, ключ проєкту замість сесії.","2026-11-04","\u002Fblog\u002Finvalid-claim-missing-sub-claim\u002Fcard-800x500.png",{"path":877,"title":878,"description":879,"published":880,"category":799,"image":881,"draft":803},"\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function","\"Failed to send a request to the Edge Function\": справжня причина","\"Failed to send a request to the Edge Function\": браузер не отримав відповіді, яку можна прочитати. Його консоль каже, чому, і чи ваша функція запускалася.","2026-11-03","\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function\u002Fcard-800x500.png",{"path":883,"title":884,"description":885,"published":886,"category":799,"image":887,"draft":803},"\u002Fblog\u002Fsupabase-auth-redirects-to-localhost","Чому Supabase перенаправляє посилання з листів на localhost:3000","Якщо Supabase перенаправляє на localhost:3000, Site URL так і не змінили або вашої адреси немає в Redirect URLs. Як виправити і якої маски уникати.","2026-11-02","\u002Fblog\u002Fsupabase-auth-redirects-to-localhost\u002Fcard-800x500.png",{"path":889,"title":890,"description":891,"published":892,"category":799,"image":893,"draft":803},"\u002Fblog\u002Femail-link-is-invalid-or-has-expired","Supabase \"Email link is invalid or has expired\" на новому посиланні","На посиланні, надісланому хвилину тому, \"Email link is invalid or has expired\" у Supabase зазвичай означає, що поштовий фільтр використав його першим.","2026-11-01","\u002Fblog\u002Femail-link-is-invalid-or-has-expired\u002Fcard-800x500.png",{"path":895,"title":896,"description":897,"published":898,"category":799,"image":899,"draft":803},"\u002Fblog\u002Fcanceling-statement-due-to-statement-timeout","Supabase \"canceling statement due to statement timeout\": пояснення","Supabase \"canceling statement due to statement timeout\": запит перевищив ліміт своєї ролі, 3 секунди для відвідувача. Спершу перевірте політику RLS.","2026-10-31","\u002Fblog\u002Fcanceling-statement-due-to-statement-timeout\u002Fcard-800x500.png",{"path":901,"title":902,"description":903,"published":904,"category":905,"image":906,"draft":803},"\u002Fblog\u002Fdatabase-error-deleting-user","\"Database error deleting user\" у Supabase і що зникає разом","\"Database error deleting user\" у Supabase: ваша база відмовилася видаляти. Причина в логах, а виправлення вирішує, що ще буде видалено.","2026-10-30","Backups","\u002Fblog\u002Fdatabase-error-deleting-user\u002Fcard-800x500.png",{"path":908,"title":909,"description":910,"published":911,"category":799,"image":912,"draft":803},"\u002Fblog\u002Fstripe-webhook-no-signatures-found","\"No signatures found matching the expected signature\" у Supabase","Вебхук Stripe в Edge Function Supabase пише \"No signatures found matching the expected signature\"? Причин дві. Не вимикайте перевірку.","2026-10-29","\u002Fblog\u002Fstripe-webhook-no-signatures-found\u002Fcard-800x500.png",{"path":914,"title":915,"description":916,"published":917,"category":799,"image":918,"draft":803},"\u002Fblog\u002Fsupabase-invalid-login","\"Invalid login credentials\" у Supabase: три причини","Supabase відповідає \"Invalid login credentials\" на неправильний пароль, невідому пошту й акаунт Google. Як зрозуміти, що саме.","2026-10-28","\u002Fblog\u002Fsupabase-invalid-login\u002Fcard-800x500.png",{"path":920,"title":921,"description":922,"published":923,"category":799,"image":924,"draft":803},"\u002Fblog\u002Fedge-function-returned-a-non-2xx-status-code","\"Edge Function returned a non-2xx status code\": справжня помилка","\"Edge Function returned a non-2xx status code\" означає, що ваша функція Supabase відповіла помилкою. Ось де прочитати справжню.","2026-10-27","\u002Fblog\u002Fedge-function-returned-a-non-2xx-status-code\u002Fcard-800x500.png",{"path":926,"title":927,"description":928,"published":929,"category":799,"image":930,"draft":803},"\u002Fblog\u002Fcertificate-not-trusted","Сертифікат не довірений: три причини, крім закінчення строку","Сертифікат не довірений, хоча дата в порядку? Він називає іншу адресу, підписав сам себе або в його ланцюжку бракує ланки. Як зрозуміти, що саме.","2026-10-26","\u002Fblog\u002Fcertificate-not-trusted\u002Fcard-800x500.png",{"path":932,"title":933,"description":934,"published":935,"category":799,"image":936,"draft":803},"\u002Fblog\u002Finvalid-input-syntax-for-type-uuid","Supabase \"invalid input syntax for type uuid\": що в лапках","Supabase \"invalid input syntax for type uuid\" означає, що застосунок надіслав щось, що не є id. Прочитайте лапки, притримайте запит, залиште колонку.","2026-10-25","\u002Fblog\u002Finvalid-input-syntax-for-type-uuid\u002Fcard-800x500.png",{"path":830,"title":5,"description":802,"published":831,"category":799,"image":818,"draft":803},{"path":364,"title":939,"description":940,"published":941,"category":799,"image":942,"draft":803},"Supabase \"relation does not exist\", хоча таблиця на місці","Supabase \"relation does not exist\" або PGRST205: жодна таблиця не має такої назви в цій схемі. Чотири перевірки, і на що зважити, відкриваючи схему.","2026-10-23","\u002Fblog\u002Fsupabase-relation-does-not-exist\u002Fcard-800x500.png",{"path":658,"title":944,"description":945,"published":946,"category":799,"image":947,"draft":803},"Supabase PGRST202: \"Could not find the function in the schema cache\"","Supabase \"Could not find the function in the schema cache\" показує назви аргументів, які надіслав ваш застосунок. Порівняйте їх із функцією.","2026-10-22","\u002Fblog\u002Fsupabase-function-not-found\u002Fcard-800x500.png",{"path":949,"title":950,"description":951,"published":952,"category":799,"image":953,"draft":803},"\u002Fblog\u002Fsupabase-schema-cache-column-error","Supabase PGRST204: \"Could not find the column in the schema cache\"","Supabase \"Could not find the column in the schema cache\" зазвичай означає, що назва не збігається. Чотири перевірки і рядок для перезавантаження.","2026-10-21","\u002Fblog\u002Fsupabase-schema-cache-column-error\u002Fcard-800x500.png",{"path":955,"title":956,"description":957,"published":958,"category":799,"image":959,"draft":803},"\u002Fblog\u002Fsupabase-email-rate-limit-exceeded","Supabase \"Email Rate Limit Exceeded\": два ліміти, один код помилки","Supabase \"Email Rate Limit Exceeded\" обмежує кожен лист вашого проєкту: 2 на годину з вбудованим відправником. Що враховується і як підняти ліміт.","2026-10-20","\u002Fblog\u002Fsupabase-email-rate-limit-exceeded\u002Fcard-800x500.png",{"path":961,"title":962,"description":963,"published":964,"category":799,"image":965,"draft":803},"\u002Fblog\u002Fsupabase-users-logged-out","Supabase \"Invalid Refresh Token: Refresh Token Not Found\": 3 причини","Supabase \"Invalid Refresh Token: Refresh Token Not Found\" виводить ваших користувачів із системи. Три причини і як лог Auth їх розрізняє.","2026-10-19","\u002Fblog\u002Fsupabase-users-logged-out\u002Fcard-800x500.png",{"path":967,"title":968,"description":969,"published":970,"category":799,"image":971,"draft":803},"\u002Fblog\u002Fpermission-denied-for-schema-public","Supabase \"permission denied for schema public\": три причини","Supabase \"permission denied for schema public\" означає, що бракує GRANT на схему. Що його прибрало і який рядок його повертає.","2026-10-18","\u002Fblog\u002Fpermission-denied-for-schema-public\u002Fcard-800x500.png",{"path":973,"title":974,"description":975,"published":976,"category":799,"image":977,"draft":803},"\u002Fblog\u002Fsupabase-bucket-not-found","Supabase \"Bucket not found\": три речі, які це може означати","Supabase \"Bucket not found\" означає одне з трьох: публічне посилання на приватний бакет, неточну назву або не той проєкт.","2026-10-17","\u002Fblog\u002Fsupabase-bucket-not-found\u002Fcard-800x500.png",{"path":979,"title":980,"description":981,"published":982,"category":799,"image":983,"draft":803},"\u002Fblog\u002Fsupabase-invalid-jwt","Supabase invalid JWT: який ключ перестав працювати?","Помилка Supabase invalid JWT — це PGRST301: підписаний токен не пройшов перевірку. У новому проєкті йдеться про сесію, а не про ключ API.","2026-10-16","\u002Fblog\u002Fsupabase-invalid-jwt\u002Fcard-800x500.png",{"path":985,"title":986,"description":987,"published":988,"category":799,"image":989,"draft":803},"\u002Fblog\u002Fsupabase-single-row-error","Supabase \"multiple (or no) rows returned\" майже завжди означає нуль","Supabase повертає PGRST116, \"JSON object requested, multiple (or no) rows returned\", коли запит нічого не знайшов. Прихований рядок має вигляд відсутнього.","2026-10-15","\u002Fblog\u002Fsupabase-single-row-error\u002Fcard-800x500.png",{"path":437,"title":991,"description":992,"published":993,"category":799,"image":994,"draft":803},"\"Database error saving new user\" у Supabase: де шукати","\"Database error saving new user\" означає, що щось причеплене до вашої реєстрації в Supabase впало. Справжня помилка лежить у ваших логах.","2026-10-14","\u002Fblog\u002Fdatabase-error-saving-new-user\u002Fcard-800x500.png",{"path":996,"title":997,"description":998,"published":999,"category":799,"image":1000,"draft":803},"\u002Fblog\u002Fwhat-is-supabase","Що таке Supabase? База даних, яку обрав ваш білдер","Що таке Supabase: база даних, вхід і сховище файлів за вашим застосунком. Supabase тримає сервери, а правила про те, хто читає ваші рядки, ваші.","2026-10-13","\u002Fblog\u002Fwhat-is-supabase\u002Fcard-800x500.png",{"path":1002,"title":1003,"description":1004,"published":1005,"category":799,"image":1006,"draft":803},"\u002Fblog\u002Faws-access-key-exposed-in-frontend","Відкритий у фронтенді ключ AWS це не те саме, що ключ OpenAI","Ключ доступу AWS, відкритий у вашому фронтенді, читає і пише ваше сховище. Рішення зазвичай presigned URL, а не новий ключ.","2026-10-12","\u002Fblog\u002Faws-access-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":1008,"title":1009,"description":1010,"published":1011,"category":799,"image":1012,"draft":803},"\u002Fblog\u002Fstorage-upload-violates-row-level-security","\"Row-level security policy for table objects\" при вивантаженні","\"New row violates row-level security policy for table objects\" означає, що вашому вивантаженню бракує правила insert. Публічний бакет його не додає.","2026-10-11","\u002Fblog\u002Fstorage-upload-violates-row-level-security\u002Fcard-800x500.png",{"path":1014,"title":1015,"description":1016,"published":1017,"category":799,"image":1018,"draft":803},"\u002Fblog\u002Fbase44-security-scan","Сканування безпеки Base44: те, що бачить лише воно","Сканування безпеки Base44 перевіряє сім видів проблем зсередини. Ось половина, яку читає тільки воно, і половина, на яку воно ніколи не дивиться.","2026-10-10","\u002Fblog\u002Fbase44-security-scan\u002Fcard-800x500.png",{"path":1020,"title":1021,"description":1022,"published":1023,"category":799,"image":1024,"draft":803},"\u002Fblog\u002Fapi-key-leaked-what-to-do","Ваш API-ключ витік. Порядок дій","API-ключ витік, і ви не знаєте, що робити першим. Не кожен ключ у вашому фронтенді є витоком, а порядок важить більше за швидкість.","2026-10-09","\u002Fblog\u002Fapi-key-leaked-what-to-do\u002Fcard-800x500.png",{"path":1026,"title":1027,"description":1028,"published":1029,"category":799,"image":1030,"draft":803},"\u002Fblog\u002Finfinite-recursion-in-policy-for-relation","\"Infinite recursion detected in policy\" без вимкнення RLS","\"Infinite recursion detected in policy for relation\" означає, що ваша policy запитала таблицю, яку вона захищає. Ось як розірвати коло.","2026-10-08","\u002Fblog\u002Finfinite-recursion-in-policy-for-relation\u002Fcard-800x500.png",{"path":1032,"title":1033,"description":1034,"published":1035,"category":799,"image":1036,"draft":803},"\u002Fblog\u002Fno-api-key-found-in-request","\"No API key found in request\" у Supabase, і хибне виправлення","\"No API key found in request\" означає, що запит дійшов до Supabase без ключа. Більшість відповідей натомість вказують на правила вашої бази.","2026-10-07","\u002Fblog\u002Fno-api-key-found-in-request\u002Fcard-800x500.png",{"path":1038,"title":1039,"description":1040,"published":1041,"category":799,"image":1042,"draft":803},"\u002Fblog\u002Fis-base44-safe","Чи безпечний Base44? Дані 5 442 застосунків","Чи безпечний Base44? Ми прогнали дев’ять перевірок на 5 442 робочих застосунках Base44. Майже всі отримали B з причин, яких власник ніколи не обирав.","2026-10-06","\u002Fblog\u002Fis-base44-safe\u002Fcard-800x500.png",{"path":1044,"title":1045,"description":1046,"published":1047,"category":799,"image":1048,"draft":803},"\u002Fblog\u002Fmove-a-secret-to-an-edge-function","Сховати ключ API: перенесіть його в Supabase Edge Function","Сховати ключ API означає прибрати його з браузера, а Supabase Edge Function це найменше місце, куди його покласти. Два кроки поруч важать більше.","2026-10-05","\u002Fblog\u002Fmove-a-secret-to-an-edge-function\u002Fcard-800x500.png",{"path":1050,"title":1051,"description":1052,"published":1053,"category":799,"image":1054,"draft":803},"\u002Fblog\u002Fenv-file-exposed-on-your-server","Чи відкритий ваш файл .env? Дванадцять шляхів для перевірки","Чи відкритий ваш файл .env на вашому власному вебсервері? Дванадцять адрес скажуть це за хвилину, а влучання означає, що все в ньому вже публічне.","2026-10-04","\u002Fblog\u002Fenv-file-exposed-on-your-server\u002Fcard-800x500.png",{"path":1056,"title":1057,"description":1058,"published":1059,"category":799,"image":1060,"draft":803},"\u002Fblog\u002Fis-v0-safe","Безпека v0: усі 1 790 просканованих застосунків v0 отримали A","Безпека v0 на 1 790 живих застосунках v0: усі отримали A. Лише 17 згадували базу даних, і саме це здебільшого вимірює оцінка A.","2026-10-03","\u002Fblog\u002Fis-v0-safe\u002Fcard-800x500.png",{"path":1062,"title":1063,"description":1064,"published":1065,"category":799,"image":1066,"draft":803},"\u002Fblog\u002Fis-bolt-safe","Чи безпечний Bolt? Що показали 1 123 застосунки Bolt","Чи безпечний Bolt? Ми прогнали 1 123 живі застосунки Bolt через дев’ять перевірок. Хостинг був чистим. Знахідки: ключі й відкриті таблиці в самих застосунках.","2026-10-02","\u002Fblog\u002Fis-bolt-safe\u002Fcard-800x500.png",{"path":1068,"title":1069,"description":1070,"published":1071,"category":799,"image":1072,"draft":803},"\u002Fblog\u002Fis-supabase-down-or-your-app","Supabase не працює чи проблема у вашому застосунку? Як розрізнити","Supabase не працює чи зламався ваш застосунок? Пауза, повна база й ваша власна помилка виглядають однаково. Чотири перевірки їх розрізняють.","2026-10-01","\u002Fblog\u002Fis-supabase-down-or-your-app\u002Fcard-800x500.png",{"path":1074,"title":1075,"description":1076,"published":1077,"category":799,"image":1078,"draft":803},"\u002Fblog\u002Fdoes-supabase-encrypt-my-data","Чи шифрує Supabase мої дані? Так. Ось від чого це захищає","Чи шифрує Supabase дані? Так: AES-256 у стані спокою, TLS під час передавання, SOC 2 та ISO 27001. Що покриває кожне і який витік не зупиняє жодне.","2026-09-30","\u002Fblog\u002Fdoes-supabase-encrypt-my-data\u002Fcard-800x500.png",{"path":1080,"title":1081,"description":1082,"published":1083,"category":799,"image":1084,"draft":803},"\u002Fblog\u002Fsupabase-free-plan-limits","Ліміти безкоштовного тарифу Supabase і що стається на кожному","Ліміти безкоштовного тарифу Supabase: 500 МБ бази, 5 ГБ egress і жодних резервних копій. Що стається на кожній межі і що змінює Pro за $25.","2026-09-29","\u002Fblog\u002Fsupabase-free-plan-limits\u002Fcard-800x500.png",{"path":1086,"title":1087,"description":1088,"published":1089,"category":905,"image":1090,"draft":803},"\u002Fblog\u002Ftest-your-supabase-backup","Перевірте резервну копію Supabase до того, як вона знадобиться","Як перевірити резервну копію Supabase: відновити в тестовий проєкт, порахувати рядки, увійти й знайти рядок, якого бракує обрізаному файлу.","2026-09-28","\u002Fblog\u002Ftest-your-supabase-backup\u002Fcard-800x500.png",{"path":1092,"title":1093,"description":1094,"published":1095,"category":905,"image":1096,"draft":803},"\u002Fblog\u002Fsupabase-backup-github-action","Безкоштовна резервна копія Supabase через GitHub Action і підступ","Резервна копія Supabase через GitHub Action безкоштовна й годиться багатьом застосункам. Робочий процес, потрібний рядок підключення та egress.","2026-09-27","\u002Fblog\u002Fsupabase-backup-github-action\u002Fcard-800x500.png",{"path":1098,"title":1099,"description":1100,"published":1101,"category":905,"image":1102,"draft":803},"\u002Fblog\u002Fdownload-your-supabase-backup","Чому ви не можете завантажити резервну копію Supabase","Завантажити резервну копію Supabase на сучасному проєкті не вийде: щоденна копія там фізична. Як це перевірити і як тримати власну копію.","2026-09-26","\u002Fblog\u002Fdownload-your-supabase-backup\u002Fcard-800x500.png",{"path":1104,"title":1105,"description":1106,"published":1101,"category":799,"image":1107,"draft":803},"\u002Fblog\u002Fsupabase-permission-denied-for-table","Supabase \"permission denied for table\": бракує GRANT","З 30 жовтня нова таблиця в Supabase відповідає \"permission denied for table\", доки ви не дасте доступ. Лист показує лише половину виправлення.","\u002Fblog\u002Fsupabase-permission-denied-for-table\u002Fcard-800x500.png",{"path":1109,"title":1110,"description":1111,"published":1112,"category":799,"image":1113,"draft":803},"\u002Fblog\u002Fopen-api-endpoint-exposed","Чи є відкритий API-ендпоінт проблемою безпеки? Читайте JSON","Ваше сканування позначило відкритий API-ендпоінт. Чи це важливо, залежить від того, що повернулося, і більшість знайдених належали платформі.","2026-09-25","\u002Fblog\u002Fopen-api-endpoint-exposed\u002Fcard-800x500.png",{"path":1115,"title":1116,"description":1117,"published":1118,"category":799,"image":1119,"draft":803},"\u002Fblog\u002Flovable-security-scan","Сканування безпеки Lovable: чого воно не доведе","Сканування безпеки Lovable: що перевіряють Quick scan і Deep scan, коли запускається кожне, і єдине, чого не доведе жодне сканування зсередини.","2026-09-24","\u002Fblog\u002Flovable-security-scan\u002Fcard-800x500.png",{"path":1121,"title":1122,"description":1123,"published":1124,"category":905,"image":1125,"draft":803},"\u002Fblog\u002Fsupabase-backup-auth-users","Чому у вашому дампі Supabase немає користувачів","Запустіть supabase db dump окремо і отримаєте форму вашої бази та жодного її рядка, без схеми auth, у якій живуть ваші користувачі.","2026-09-23","\u002Fblog\u002Fsupabase-backup-auth-users\u002Fcard-800x500.png",{"path":1127,"title":1128,"description":1129,"published":1130,"category":799,"image":1131,"draft":803},"\u002Fblog\u002Fdomain-and-certificate-expiry","Домен закінчився, сайт лежить: що буде далі","Ваш домен закінчився і сайт лежить. Ось який годинник уже пішов, чому прострочений сертифікат це менша з двох бід, і як перевірити обидва.","2026-09-22","\u002Fblog\u002Fdomain-and-certificate-expiry\u002Fcard-800x500.png",{"path":1133,"title":1134,"description":1135,"published":1136,"category":799,"image":1137,"draft":803},"\u002Fblog\u002Fis-lovable-safe","Чи безпечний Lovable? Що показали 18 554 застосунки Lovable","Чи безпечний Lovable? Ми прогнали 18 554 живі застосунки Lovable через дев’ять перевірок. Платформа виявилася найчистішою з п’яти. Знахідки були в застосунках.","2026-09-21","\u002Fblog\u002Fis-lovable-safe\u002Fcard-800x500.png",{"path":1139,"title":1140,"description":1141,"published":1142,"category":799,"image":1143,"draft":803},"\u002Fblog\u002Fvibe-coded-app-security-checklist","Чек-лист безпеки для vibe coding, у девʼяти перевірках","Чек-лист безпеки для vibe coding із девʼяти пунктів, кожен з яких будь-хто може перевірити у вашому живому застосунку ззовні, і кожен має тест на один рядок.","2026-09-20","\u002Fblog\u002Fvibe-coded-app-security-checklist\u002Fcard-800x500.png",{"path":1145,"title":1146,"description":1147,"published":1148,"category":799,"image":1149,"draft":803},"\u002Fblog\u002Fstripe-secret-key-in-frontend","Секретний ключ Stripe у фронтенді може рухати гроші","Секретний ключ Stripe, відкритий у вашому фронтенді, може повертати кошти, списувати й читати кожну картку клієнта. А ключ pk_live_ там і має бути.","2026-09-19","\u002Fblog\u002Fstripe-secret-key-in-frontend\u002Fcard-800x500.png",{"path":1151,"title":1152,"description":1153,"published":1154,"category":799,"image":1155,"draft":803},"\u002Fblog\u002Fvite-and-next-public-env-vars","Змінні оточення Vite відкриті: префікс означає опублікувати це","Змінні оточення Vite відкриті у вашому застосунку, бо так просив префікс. VITE_ і NEXT_PUBLIC_ означають опублікувати це, а ШІ, який їх додав, не знав ціни.","2026-09-18","\u002Fblog\u002Fvite-and-next-public-env-vars\u002Fcard-800x500.png",{"path":1157,"title":1158,"description":1159,"published":1160,"category":799,"image":1161,"draft":803},"\u002Fblog\u002Fbase44-source-maps","Безпека Base44: що позначає скан і що з цього виправляти вам","Безпека Base44 на 5 438 просканованих застосунках: три знахідки майже на кожному належать платформі, source map є бейджем Base44, а ваша частина коротка.","2026-09-17","\u002Fblog\u002Fbase44-source-maps\u002Fcard-800x500.png",{"path":1163,"title":1164,"description":1165,"published":1166,"category":799,"image":1167,"draft":803},"\u002Fblog\u002Fis-cursor-ai-safe","Чи безпечний Cursor AI? Редактор, код і опублікований застосунок","Чи безпечний Cursor AI? Три запитання в одному пошуку: що Cursor зберігає, у чому помиляється його код і чи відкритий застосунок, який ви опублікували.","2026-09-16","\u002Fblog\u002Fis-cursor-ai-safe\u002Fcard-800x500.png",{"path":1169,"title":1170,"description":1171,"published":1172,"category":799,"image":1173,"draft":803},"\u002Fblog\u002Fis-replit-safe","Чи безпечний Replit? Що ми знайшли в 3 042 живих застосунках Replit","Чи безпечний Replit? Ми прогнали 3 042 живі застосунки Replit через дев’ять зовнішніх перевірок. Знахідки були в опублікованому застосунку, а не в хостингу.","2026-09-15","\u002Fblog\u002Fis-replit-safe\u002Fcard-800x500.png",{"path":1175,"title":1176,"description":1177,"published":1178,"category":905,"image":1179,"draft":803},"\u002Fblog\u002Fsupabase-storage-backup","Supabase storage backup: у копії бази даних немає жодного файлу","Supabase storage backup це окрема робота. Копії бази даних зберігають список ваших файлів і жодного з них, тож після відновлення кожне завантаження зламане.","2026-09-14","\u002Fblog\u002Fsupabase-storage-backup\u002Fcard-800x500.png",{"path":1181,"title":1182,"description":1183,"published":1184,"category":905,"image":1185,"draft":803},"\u002Fblog\u002Fsupabase-point-in-time-recovery","Point-in-time recovery у Supabase: ціна та межі","Point-in-time recovery у Supabase відмотує базу на будь-яку секунду за останній тиждень. Коштує $100 на місяць понад Pro і покриває лише базу даних.","2026-09-13","\u002Fblog\u002Fsupabase-point-in-time-recovery\u002Fcard-800x500.png",{"path":1187,"title":1188,"description":1189,"published":1190,"category":905,"image":1191,"draft":803},"\u002Fblog\u002Fsupabase-project-paused-recover","Проєкт Supabase на паузі? Ваші дані нікуди не зникли","Supabase поставив ваш проєкт на паузу після тижня без активності. Нічого не видалено, кнопка Restore стоїть поруч із назвою, і у вас є рік, щоб натиснути.","2026-09-12","\u002Fblog\u002Fsupabase-project-paused-recover\u002Fcard-800x500.png",{"path":1193,"title":1194,"description":1195,"published":1196,"category":799,"image":1197,"draft":803},"\u002Fblog\u002Fsafest-ai-app-builder","Який білдер застосунків з ШІ найбезпечніший? 30 998 тестів","Який білдер застосунків з ШІ найбезпечніший? Ми просканували 30 998 живих застосунків з Lovable, Base44, Replit, v0 і Bolt. Оцінку вирішує не білдер.","2026-09-11","\u002Fblog\u002Fsafest-ai-app-builder\u002Fcard-800x500.png",{"path":604,"title":1199,"description":1200,"published":1201,"category":799,"image":1202,"draft":803},"Увімкніть Row Level Security на кожній таблиці Supabase і перевірте","Row Level Security у Supabase без політики закриває таблицю повністю. Політика без увімкненого перемикача не робить нічого. Ось SQL, і ось перевірка.","2026-09-10","\u002Fblog\u002Fenable-rls-on-every-supabase-table\u002Fcard-800x500.png",{"path":1204,"title":1205,"description":1206,"published":1207,"category":799,"image":1208,"draft":803},"\u002Fblog\u002Fsupabase-rls-disabled-in-public","Supabase \"RLS disabled in public\": чого не бачить попередження","Supabase позначає \"RLS disabled in public\" як помилку. Про політику читання, яка лишає вашу таблицю так само відкритою, він не каже нічого.","2026-09-09","\u002Fblog\u002Fsupabase-rls-disabled-in-public\u002Fcard-800x500.png",{"path":1210,"title":1211,"description":1212,"published":1213,"category":799,"image":1214,"draft":803},"\u002Fblog\u002Frotate-supabase-service-role-key","Як замінити злитий ключ service_role у Supabase","Supabase каже спершу закрити витік. Інші посібники кажуть міняти негайно. Що правильно, залежить від того, куди саме потрапив ваш ключ service_role.","2026-09-08","\u002Fblog\u002Frotate-supabase-service-role-key\u002Fcard-800x500.png",{"path":1216,"title":1217,"description":1218,"published":1219,"category":799,"image":1220,"draft":803},"\u002Fblog\u002Fvibe-coding-security-scanners-compared","Сканери безпеки для vibe coding у порівнянні, наш теж","Найкращий сканер безпеки для vibe coding залежить від трьох питань, на які не відповідає жоден перелік функцій. Вісім інструментів і ціни.","2026-09-07","\u002Fblog\u002Fvibe-coding-security-scanners-compared\u002Fcard-800x500.png",{"path":1222,"title":1223,"description":1224,"published":1225,"category":799,"image":1226,"draft":803},"\u002Fblog\u002Fsupabase-security-checker","Supabase security checker: п’ять перевірок, які зробите самі","Supabase security checker читає ваш опублікований застосунок, а не налаштування проєкту. Ось п’ять перевірок і як виконати кожну самостійно.","2026-09-06","\u002Fblog\u002Fsupabase-security-checker\u002Fcard-800x500.png",{"path":1228,"title":1229,"description":1230,"published":1231,"category":799,"image":1232,"draft":803},"\u002Fblog\u002Fvibe-coding-security-scanner","Сканер безпеки для vibe coding: чого не бачить скан URL","Сканер безпеки для vibe coding читає ваш робочий застосунок ззовні. Що це охоплює, чотири речі, яких він не бачить, і як читати результат.","2026-09-05","\u002Fblog\u002Fvibe-coding-security-scanner\u002Fcard-800x500.png",{"path":1234,"title":1235,"description":1236,"published":1237,"category":799,"image":1238,"draft":803},"\u002Fblog\u002Fmissing-security-headers","Відсутні заголовки безпеки: коли це справді важливо","Відсутні заголовки безпеки трапляються в наших звітах найчастіше. Від чого вони захищають і коли це найменш терміновий рядок вашого звіту.","2026-09-04","\u002Fblog\u002Fmissing-security-headers\u002Fcard-800x500.png",{"path":1240,"title":1241,"description":1242,"published":1243,"category":799,"image":1244,"draft":803},"\u002Fblog\u002Fopenai-api-key-exposed-in-frontend","Ваш ключ API OpenAI відкритий у фронтенді. Замініть його.","Ключ API OpenAI, відкритий у фронтенді, не можна прив'язати до домену. Замініть його сьогодні, перенесіть виклик на свій сервер і обмежте витрати.","2026-09-03","\u002Fblog\u002Fopenai-api-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":1246,"title":1247,"description":1248,"published":1249,"category":799,"image":1250,"draft":803},"\u002Fblog\u002Fwhat-secrets-leak-from-vibe-coded-apps","Ключ API у фронтенді: що насправді віддають 30 998 застосунків","Ключ API, відкритий у вашому фронтенді, зазвичай є ключем Google Maps. Ми просканували 30 998 vibe-coded застосунків і порахували, що витікає насправді.","2026-09-02","\u002Fblog\u002Fwhat-secrets-leak-from-vibe-coded-apps\u002Fcard-800x500.png",{"path":1252,"title":1253,"description":1254,"published":1255,"category":905,"image":1256,"draft":803},"\u002Fblog\u002Fsupabase-backup-tools-compared","Інструменти резервного копіювання Supabase, і наш серед них","Чотири види інструментів резервного копіювання Supabase, що копіює кожен і коли безкоштовна GitHub Action виграє в будь-якої оплати, нашої теж.","2026-09-01","\u002Fblog\u002Fsupabase-backup-tools-compared\u002Fcard-800x500.png",{"path":1258,"title":1259,"description":1260,"published":1261,"category":799,"image":1262,"draft":803},"\u002Fblog\u002Freplit-secrets-explained","Як користуватися секретами в Replit і що все одно публікується","Як користуватися секретами в Replit: додати, прочитати і усунути дві причини undefined. А ще ключі, які цей інструмент не здатен зберегти в таємниці.","2026-08-31","\u002Fblog\u002Freplit-secrets-explained\u002Fcard-800x500.png",{"path":1264,"title":1265,"description":1266,"published":1267,"category":905,"image":1268,"draft":803},"\u002Fblog\u002Fback-up-supabase-free-tier","Резервні копії на безкоштовному тарифі Supabase без терміналу","На безкоштовному тарифі Supabase резервних копій немає: копію робите ви. Як зробити її з панелі та що втрачає шлях через CSV.","2026-08-30","\u002Fblog\u002Fback-up-supabase-free-tier\u002Fcard-800x500.png",{"path":1270,"title":1271,"description":1272,"published":1273,"category":799,"image":1274,"draft":803},"\u002Fblog\u002Fis-supabase-secure","Чи безпечний Supabase? Так. Ваш проєкт це інше питання","Чи безпечний Supabase? Платформа проходить аудити, шифрує дані й тримає пентести. Їхня документація каже, де це закінчується і починаються ваші налаштування.","2026-08-29","\u002Fblog\u002Fis-supabase-secure\u002Fcard-800x500.png",{"path":1276,"title":1277,"description":1278,"published":1279,"category":799,"image":1280,"draft":803},"\u002Fblog\u002Fwhere-to-find-supabase-api-keys","Де знайти API-ключі Supabase: anon, service_role і URL","URL проєкту, ключ anon і ключ service_role лежать на одній сторінці панелі. Ось де вона, і яке з чотирьох значень має бути у вашому застосунку.","2026-08-28","\u002Fblog\u002Fwhere-to-find-supabase-api-keys\u002Fcard-800x500.png",{"path":554,"title":1282,"description":1283,"published":1284,"category":799,"image":1285,"draft":803},"New row violates row-level security policy в Supabase. Що далі?","\"New row violates row-level security policy\" означає, що Supabase відмовив у записі. Швидке виправлення знову відкриває таблицю для всіх.","2026-08-27","\u002Fblog\u002Fnew-row-violates-row-level-security-policy\u002Fcard-800x500.png",{"path":1287,"title":1288,"description":1289,"published":1290,"category":799,"image":1291,"draft":803},"\u002Fblog\u002Fcors-wildcard-security-risk","Чи є вайлдкард CORS ризиком для безпеки? Майже ніколи.","Чи є вайлдкард CORS ризиком для безпеки? Зазвичай це типове налаштування вашого білдера, і воно не видає нічого, що ваш сервер і так віддавав кожному.","2026-08-26","\u002Fblog\u002Fcors-wildcard-security-risk\u002Fcard-800x500.png",{"path":1293,"title":1294,"description":1295,"published":1296,"category":905,"image":1297,"draft":803},"\u002Fblog\u002Fsupabase-branching-is-not-a-backup","Branching у Supabase це не бекап. Він іде тільки вперед.","Чому branching у Supabase це не бекап: гілка стартує без ваших даних, а мердж переносить саму лише схему. Для чого він потрібен і що брати замість нього.","2026-08-25","\u002Fblog\u002Fsupabase-branching-is-not-a-backup\u002Fcard-800x500.png",{"path":1299,"title":1300,"description":1301,"published":1302,"category":905,"image":1303,"draft":803},"\u002Fblog\u002Fhow-to-restore-a-supabase-backup","Як відновити резервну копію Supabase, і що ламається потім","Як відновити резервну копію Supabase з панелі або з файлу дампа, що саме замінює відновлення і чому ваш застосунок може лишитися зламаним після нього.","2026-08-24","\u002Fblog\u002Fhow-to-restore-a-supabase-backup\u002Fcard-800x500.png",{"path":1305,"title":1306,"description":1307,"published":1308,"category":799,"image":1309,"draft":803},"\u002Fblog\u002Fsupabase-storage-bucket-public","Ваш бакет Supabase Storage публічний. Чи це проблема?","Публічний бакет Supabase Storage означає, що той, хто має URL файлу, може його відкрити. Він не означає, що хтось може переглянути список вмісту.","2026-08-23","\u002Fblog\u002Fsupabase-storage-bucket-public\u002Fcard-800x500.png",{"path":1311,"title":1312,"description":1313,"published":1314,"category":799,"image":1315,"draft":803},"\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend","Чи є проблемою ключ API Google у вашому браузері?","Ключ API Google у відкритому коді сторінки наш сканер знаходить найчастіше, і зазвичай усе гаразд. Вирішує це одне безкоштовне налаштування.","2026-08-22","\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":1317,"title":1318,"description":1319,"published":1320,"category":905,"image":1321,"draft":803},"\u002Fblog\u002Fai-agent-deleted-my-database","ШІ-агент видалив мої дані в Supabase. Що можна відновити?","ШІ-агент видалив дані з вашої бази. Те, що ви зможете відновити, вирішилося раніше, а наступні хвилини вирішують, скільки з цього вціліє.","2026-08-21","\u002Fblog\u002Fai-agent-deleted-my-database\u002Fcard-800x500.png",{"path":1323,"title":1324,"description":1325,"published":1326,"category":799,"image":1327,"draft":803},"\u002Fblog\u002Fcan-anyone-read-your-supabase-database","Чи може будь-хто прочитати вашу базу Supabase? 3 680 застосунків","Чи може будь-хто прочитати вашу базу даних Supabase без входу? Ми просканували 30 998 живих застосунків, зроблених ШІ-білдерами, і виміряли, як часто так.","2026-08-18","\u002Fblog\u002Fcan-anyone-read-your-supabase-database\u002Fcard-800x500.png",{"path":1329,"title":1330,"description":1331,"published":1326,"category":799,"image":1332,"draft":803},"\u002Fblog\u002Fsource-maps-exposed-in-production","Відкриті source maps: ваш застосунок публікує свій вихідний код","Відкрита source map дає будь-кому прочитати оригінальний код вашого застосунку, разом із коментарями. Перевірка за 30 секунд, і що важить насправді.","\u002Fblog\u002Fsource-maps-exposed-in-production\u002Fcard-800x500.png",{"path":1334,"title":1335,"description":1336,"published":1337,"category":799,"image":1338,"draft":803},"\u002Fblog\u002Fsupabase-new-api-keys","Supabase publishable key: це той самий anon key?","Supabase замінив anon і service_role публічними та секретними ключами. Який із них має бути у вашому застосунку, а який не має бути ніколи?","2026-08-12","\u002Fblog\u002Fsupabase-new-api-keys\u002Fcard-800x500.png",{"path":1340,"title":1341,"description":1342,"published":1343,"category":905,"image":1344,"draft":803},"\u002Fblog\u002Fdoes-supabase-back-up-my-database","Чи робить Supabase резервні копії моєї бази? Залежить від тарифу.","Чи робить Supabase резервні копії вашої бази даних? На платних тарифах щодня, на безкоштовному жодної. Як дізнатися, що у вас насправді є.","2026-08-11","\u002Fblog\u002Fdoes-supabase-back-up-my-database\u002Fcard-800x500.png",{"path":1346,"title":1347,"description":1348,"published":1349,"category":799,"image":1350,"draft":803},"\u002Fblog\u002Fsupabase-rls-on-but-table-still-public","Supabase Row Level Security увімкнено. Таблиця досі публічна.","Увімкнути Supabase Row Level Security не означає захистити таблицю. Захищають політики, а та, що полагодила застосунок, часто впускає всіх.","2026-08-10","\u002Fblog\u002Fsupabase-rls-on-but-table-still-public\u002Fcard-800x500.png",{"path":1352,"title":1353,"description":1354,"published":1349,"category":905,"image":1355,"draft":803},"\u002Fblog\u002Fversion-history-is-not-a-backup","Історія версій не є резервною копією. Вона не поверне таблицю.","Lovable і Bolt ведуть історію версій вашого коду. Ваша база даних працює як окремий сервіс, тож відкат назад не поверне ваші дані.","\u002Fblog\u002Fversion-history-is-not-a-backup\u002Fcard-800x500.png",{"path":1357,"title":1358,"description":1359,"published":1360,"category":905,"image":1361,"draft":803},"\u002Fblog\u002Fthree-ways-to-back-up-a-supabase-database","Три способи зробити резервну копію бази Supabase, і чого бракує","Панель, pg_dump і керований сервіс. Що насправді зберігає кожен спосіб, що тихо лишає осторонь і який із них переживе втрату акаунта.","2026-08-09","\u002Fblog\u002Fthree-ways-to-back-up-a-supabase-database\u002Fcard-800x500.png",{"path":1363,"title":1364,"description":1365,"published":1360,"category":799,"image":1366,"draft":803},"\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend","Які API-ключі безпечні у браузері, а які ні","Ваш ключ anon у Supabase створений, щоб бути публічним. Ключ service_role для цього не створений, і він ігнорує всі правила, які ви задали. Як їх розрізнити.","\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend\u002Fcard-800x500.png",1794213654340]