From 49035d35f00ce0b99ef09aba65fc87dffa3fb7e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Mon, 9 Mar 2026 10:10:43 +0400 Subject: [PATCH] =?UTF-8?q?refactor:=20notes-python=20=E2=80=94=20=D0=BE?= =?UTF-8?q?=D0=BF=D0=B8=D1=81=D0=B0=D1=82=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B?= =?UTF-8?q?=D0=B5=20=D0=B8=D0=BC=D0=B5=D0=BD=D0=B0=20=D1=84=D0=B0=D0=B9?= =?UTF-8?q?=D0=BB=D0=BE=D0=B2/=D1=80=D0=B5=D1=81=D1=83=D1=80=D1=81=D0=BE?= =?UTF-8?q?=D0=B2=20+=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82=D0=B0?= =?UTF-8?q?=D1=80=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Python файлы: - handler.py → sql_runner.py (entrypoint: sql_runner.handle) - handler.py → notes_crud.py (entrypoint: notes_crud.handle) - handler.py → notes_list.py (entrypoint: notes_list.handle) TF ресурсы переименованы: - sless_function.notes → sless_function.notes_crud - sless_trigger.notes_http → sless_trigger.notes_crud_http - sless_job.create_table → sless_job.notes_table_init - sless_job.create_index → sless_job.notes_index_init - archive_file.notes → archive_file.notes_crud_zip - archive_file.sql_runner → archive_file.sql_runner_zip - archive_file.notes_list → archive_file.notes_list_zip Добавлены подробные комментарии во все .tf файлы --- notes-python/code/notes-list/handler.py | 22 -- notes-python/code/notes-list/notes_list.py | 31 ++ .../code/notes/{handler.py => notes_crud.py} | 19 +- notes-python/code/sql-runner/handler.py | 26 -- notes-python/code/sql-runner/sql_runner.py | 39 ++ notes-python/dist/notes-list.zip | Bin 746 -> 961 bytes notes-python/dist/notes.zip | Bin 1226 -> 1492 bytes notes-python/dist/sql-runner.zip | Bin 796 -> 1038 bytes notes-python/init.tf | 26 +- notes-python/main.tf | 17 +- notes-python/notes-list.tf | 25 +- notes-python/notes.tf | 37 +- notes-python/outputs.tf | 21 +- notes-python/sql-runner.tf | 24 +- .../terraform.tfstate.1773036471.backup | 369 ++++++++++++++++++ .../terraform.tfstate.1773036482.backup | 365 +++++++++++++++++ .../terraform.tfstate.1773036493.backup | 365 +++++++++++++++++ .../terraform.tfstate.1773036504.backup | 360 +++++++++++++++++ notes-python/variables.tf | 8 +- 19 files changed, 1656 insertions(+), 98 deletions(-) delete mode 100644 notes-python/code/notes-list/handler.py create mode 100644 notes-python/code/notes-list/notes_list.py rename notes-python/code/notes/{handler.py => notes_crud.py} (69%) delete mode 100644 notes-python/code/sql-runner/handler.py create mode 100644 notes-python/code/sql-runner/sql_runner.py create mode 100644 notes-python/terraform.tfstate.1773036471.backup create mode 100644 notes-python/terraform.tfstate.1773036482.backup create mode 100644 notes-python/terraform.tfstate.1773036493.backup create mode 100644 notes-python/terraform.tfstate.1773036504.backup diff --git a/notes-python/code/notes-list/handler.py b/notes-python/code/notes-list/handler.py deleted file mode 100644 index 84a0cf4..0000000 --- a/notes-python/code/notes-list/handler.py +++ /dev/null @@ -1,22 +0,0 @@ -# 2026-03-09 -# handler.py — возвращает все записи из таблицы notes. -# GET/POST /fn/default/notes-list → JSON массив записей, сортировка по created_at DESC. -import os -import psycopg2 -import psycopg2.extras - - -def handle(event): - dsn = os.environ['PG_DSN'] - conn = psycopg2.connect(dsn) - try: - cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) - cur.execute( - "SELECT id, title, body, created_at::text FROM notes ORDER BY created_at DESC" - ) - rows = cur.fetchall() - return [dict(r) for r in rows] - except Exception as e: - return {'error': str(e)} - finally: - conn.close() diff --git a/notes-python/code/notes-list/notes_list.py b/notes-python/code/notes-list/notes_list.py new file mode 100644 index 0000000..af04860 --- /dev/null +++ b/notes-python/code/notes-list/notes_list.py @@ -0,0 +1,31 @@ +# 2026-03-09 +# notes_list.py — чтение всех записей из таблицы notes. +# +# Назначение: отдать полный список заметок одним запросом. +# Принимает GET или POST — тело/query параметры игнорируются. +# Возвращает JSON-массив, сортировка: новые записи первые (ORDER BY created_at DESC). +# +# Пример ответа: +# [ +# {"id": 3, "title": "Hello", "body": "World", "created_at": "2026-03-09 ..."}, +# {"id": 1, "title": "First", "body": "Note", "created_at": "2026-03-08 ..."} +# ] +import os +import psycopg2 +import psycopg2.extras + + +def handle(event): + dsn = os.environ['PG_DSN'] + conn = psycopg2.connect(dsn) + try: + cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + cur.execute( + "SELECT id, title, body, created_at::text FROM notes ORDER BY created_at DESC" + ) + rows = cur.fetchall() + return [dict(r) for r in rows] + except Exception as e: + return {'error': str(e)} + finally: + conn.close() diff --git a/notes-python/code/notes/handler.py b/notes-python/code/notes/notes_crud.py similarity index 69% rename from notes-python/code/notes/handler.py rename to notes-python/code/notes/notes_crud.py index cce026f..23e307a 100644 --- a/notes-python/code/notes/handler.py +++ b/notes-python/code/notes/notes_crud.py @@ -1,10 +1,17 @@ # 2026-03-09 -# handler.py — CRUD роутер для таблицы notes. -# Роутинг по event._path (sub-path URL): -# POST /fn/default/notes/add?title=...&body=... → INSERT -# POST /fn/default/notes/update?id=1&title=... → UPDATE -# POST /fn/default/notes/delete?id=1 → DELETE -# _path и _query добавляет runtime из HTTP запроса (server.py). +# notes_crud.py — CRUD роутер для таблицы notes. +# +# Назначение: единая функция, которая обрабатывает все операции с записями. +# Роутинг осуществляется по sub-path URL (event._path), который runtime +# берёт из входящего HTTP-запроса и добавляет в event автоматически. +# +# Доступные маршруты (все POST): +# /fn/default/notes/add?title=...&body=... → создать запись +# /fn/default/notes/update?id=1&title=...&body=... → обновить запись +# /fn/default/notes/delete?id=1 → удалить запись +# +# Параметры берутся из query string (event._query) или из тела запроса (event). +# event._path и event._query добавляет Python runtime (server.py) автоматически. import os import psycopg2 import psycopg2.extras diff --git a/notes-python/code/sql-runner/handler.py b/notes-python/code/sql-runner/handler.py deleted file mode 100644 index 2b39304..0000000 --- a/notes-python/code/sql-runner/handler.py +++ /dev/null @@ -1,26 +0,0 @@ -# 2026-03-09 -# handler.py — универсальный исполнитель SQL запросов. -# Принимает event.statements — массив SQL строк, выполняет последовательно. -# Используется sless_job для DDL операций (CREATE TABLE, миграции и т.д.) -import os -import psycopg2 - - -def handle(event): - dsn = os.environ['PG_DSN'] - statements = event.get('statements', []) - if not statements: - return {'error': 'no statements provided'} - - conn = psycopg2.connect(dsn) - try: - cur = conn.cursor() - for sql in statements: - cur.execute(sql) - conn.commit() - return {'ok': True, 'executed': len(statements)} - except Exception as e: - conn.rollback() - return {'error': str(e)} - finally: - conn.close() diff --git a/notes-python/code/sql-runner/sql_runner.py b/notes-python/code/sql-runner/sql_runner.py new file mode 100644 index 0000000..d71828e --- /dev/null +++ b/notes-python/code/sql-runner/sql_runner.py @@ -0,0 +1,39 @@ +# 2026-03-09 +# sql_runner.py — универсальный DDL/SQL исполнитель. +# +# Назначение: выполнять произвольные SQL запросы переданные через event. +# Используется ТОЛЬКО через sless_job (init.tf) — HTTP-триггера нет намеренно, +# чтобы никто снаружи не мог выполнить произвольный SQL. +# +# Входящий event: +# { +# "statements": [ +# "CREATE TABLE IF NOT EXISTS ...", +# "CREATE INDEX IF NOT EXISTS ..." +# ] +# } +# +# Все statements выполняются последовательно в одной транзакции. +# Если хотя бы один упал — транзакция откатывается целиком. +import os +import psycopg2 + + +def handle(event): + dsn = os.environ['PG_DSN'] + statements = event.get('statements', []) + if not statements: + return {'error': 'no statements provided'} + + conn = psycopg2.connect(dsn) + try: + cur = conn.cursor() + for sql in statements: + cur.execute(sql) + conn.commit() + return {'ok': True, 'executed': len(statements)} + except Exception as e: + conn.rollback() + return {'error': str(e)} + finally: + conn.close() diff --git a/notes-python/dist/notes-list.zip b/notes-python/dist/notes-list.zip index 1e3b0367130cc3e103e500a5a5cd44a4521569bb..ee6206fdd94c4535871a1002a2d1298405c03361 100644 GIT binary patch delta 788 zcmV+v1MB?i1;Gc98h;G{003@pbY*j2Y-w|JE^v8tlizCNMij>H^%MvB!ZIjonm}m< zxoPaAlr|1_pp+1dDvcX~s&Pl-)FuREmo^laU0C*Z*;mkdW30rn@(Sm?!am6|BPaF- z;tR`j{`|gkK94G}v%T}p*7ok!_IE}FVlG+IiNZuWy&-%`-+uyK(->#C#2jaUQySrn z{sEk0h7XvlBi;h$I0qVIh97W&IsHqM66F{bqXPI2Go0fRGrE3cxqt6!WqGGt~u$jT1HC0&_TS zwol5f)J_X5zJL0&&%{uvXOuqbP)ZZk@e!9;C>*8qH@%`Ujp(%&d4mPcajIJAzm?37 z?dH*z5~UH1FvqE__9!T&u_9oBQ@q2>1x3XvO_ZN&8*??pjM7!cYPRZ)7JUCJ_=0(o z1szX9z0uyUtt>6~RP&~#lT&R`<{A|MIMv?(mu484E`RLWV9HQN%yhy0fkhEFZ7_f1 z!B7Q$;UWrDU@fJ>>x%)7nFpar^eBJ{OquS6kcK&M!>po?!`gGEOxrV*>q%(nN62IF5@7Cg z!ex~S!9~@DL<)=5u5>{+jJ+t@G*nv|@q}45<8uH20RR6`O9u!On_Nhv0ssJe1ONb# z8j~afN+=VXTu7q=004Ug000dD0000000000005+c00000Zf|sDb6;#}b99rF0xbdF SlfeQX3V8qk05Ssr0000H+iBDQ delta 584 zcmX@e{)%;iR6Q3114Bk)UP?}CkzPS%%*6X$xBNtozyB=K{`C-xNtgW(N7npRFP$>4 zyYAKWo>)?JiFb*yQSqiwFUP$R8hf>CCxi&RmA?~i@tNzl%F&Hmlk)uh_TB${a?bmZ z1y^(a#^?VF&$s)iRUmy@#a-4f?SsXCu04-r^D9!H$K3Ld`E;vZuQ9yrVD3g=3af{ z^TYo{Kj`bdP2zuYRkQx={pg#=8$+@h4+V_uqs;s&E3<`!qRttPOKIvWdx)-uu z|M=dApFaQ1gQG+VMQuhg}rVDzC)*O_cdL~O@s;HLEEfAyyi z^E%dF=w|!y=Z~!3#!$cmw$v>F9RFUODNdg9zG=f;r1Tr~`S(lM( R@(gBqt}>u#9MCKV1^}8E1fT!_ diff --git a/notes-python/dist/notes.zip b/notes-python/dist/notes.zip index 54023097c2f594e0e6471c7a3b7309aa54d7a9aa..a282a9076acca02b25087c61a450a68bed03ba1c 100644 GIT binary patch delta 1349 zcmV-L1-kmm3DgUa8h;G{003@pbY*j2V{&z5E^v9wR^4hFM-;x-QygTRUBP;trckIr zgb=GqAa#r^w}BAEW@i)+k$2_U*~G#aB)Lsflww+PTiQpE9Sf_mEx*E)6aNVm;4j&PrbxQ~6^)jd4GJ`Qvj@IZHPK>KG@3-$>B zhoC!vN7%U)ZY2B3fJ>W=p^sfFH2fCxXctCv+EQJ7`;RvGm*+SD( z>#$aN2sVGo1Lf9ep^&Xn_wX-}aiGKor!4zai~g1B8{!ca`6G_-Q14MHe2*izU#+ez zBo!JiJJ<&t5|tzB@uC#K12AcURw*5x5C=FUE_DY#n19YF>pRmSYN42$1AxVPu*i7biyLauXtwAvc3TP6;3d~}Z#)ZGiv}?9e0=>I z=njtX2oH@UZ?k;Ay#hIIGEec_f|b6waa9IFHYA+l2oG@hso0D+c!~{IJ*Mhis@T|c z7MW=LgMT=~KAspSv~_Q+=kC}Tr0HhNWeXxDMezLG7-q3R2aZWhI!AW|k4fE0v6GMj znKI)l@<0~rGDEDi)Or|8pOz6xclG)0X%!)<07L|mZ^lA$X6;Nw`LT5Q7WZSt?HRcI zgXPt7r3w$0tB;cZ2e1vGV>&3ncXuAHm48>ko{t=uj|%x&1gMm&Yn9~(%lAMq2QtwP z_>y~yvzn)hMa8#N-kFJ?NH?)NIg{Zl(mj@LohyGm47fdcAhATau_4sdPRbb*zOrTb zDq{iOm^4A+_$eOR?W%#D@=-X87jII+RUZ&Dm80YgUvz@ZatRkU3A-0{baO6rcyv%p1qJ{B000620swgc002b= H00000T;_3a delta 1052 zcmV+%1mpYE3(5(Q8h;7^003xVZe(m_axQRr%vSAc8%GrVucx@k*t-ksmFq&G0ue&A zO$@GV^w9=F5bK@Uc*uH2o|&~RjKOgrw3JdPeSkiKoJ81Q>b%12E96Pa%&cUs;M--))2ZJGeJb(WK9<_T-$j{Yn{yM)< z0=R)48?pgyQuz(<(Mpg)M@KDZ*= zTQ{>_d)up5Rsdjkr_+U6LTd;oQI^V@L90awJ&;7ExDf=w+8#qgJplmt_~v)mYIVYP zcUgWmK#{}`2!CpPwKm0pNq%p)`LG)<%SSlHMLv{=&Tod>p~<&>scYyTW|$8(LAUBs zjnrFBlp5z3z%wez0S0xguHZ?xy9??{jnoH2>?<__S73f*SoeYoIT$c5fr-g%Acip; zyx5#y2lzztNK`5ngp)!!*O2YiD|(+uP-p-X0j5WUGk^Nb*?rt^c3RGJ^LhoQVyH_s zR#%|jsXzs`SFWA#qmLjFqK3G2S zY&-U;4^GkTWM~)PA|tgIE7OObI!O^2fG6x0ysX*2*=1d zD`mwQ5AX?&Gl|^=xT^iSV5`;LvEMeh2GBPhcz^K2!|h(!0e4mSuqwRjA_BC-Zm-?i zYCQ&me3;4hA?7%e81*AruSuhJ?oW(&?rrZb0~4jgx2cs00030|4>T@2vJY( zBC-Pj09Oi=?*wKgQBUt8vI77BR|)_C3IG5A0000000000q=5hc003xVZe(m_a+7@p WEduHTldJ_E3U&Yh06qi&0000{`2L*$ diff --git a/notes-python/dist/sql-runner.zip b/notes-python/dist/sql-runner.zip index b684fdd65a10ad88487bf655a57212f82318e1ce..d2083fe0bcd5f914057cf83b4b9c8f3374e404ec 100644 GIT binary patch delta 911 zcmV;A191GD295}j8k1H6Jdpwze+>Wt0CRC{UvhPBZe?;VaCvl8-D(?06u#f5I9L}e zfxR^@6e{pVTC1(WxM{76fHB5djnZw_Gs^7B!~}yR(}ZGELusM3G#9<@g(^qY`bVo* z_|7ZzNy-_mEG2Ptq1m1Je$IU7uq9esUHfEZ_2ZS*PrVW)&*FXC)>_%1e|1d%b$%f} z#TXd|7;=ZZ=;MaZG3GN|(ZN>mae)(kw>ctpq;wjH z`){+#_#7iX#gKd4qTY~t0dX~-aMvqq@y%Qn-!PBSM~bC74C5d z87^_@@O{Eq1ZC;tw$K=3jC|QE5x?Rda{MFp%C)cMmALD&=Oti<{yyb4PWq&$Z$2id(|ru(F^xaw3MU=sXA0TO_r*(e_QMlEq(rN?Loap z^#`AA)oAkz+WxvuwMUyf^&JX=U}^b1d z`aKcB=iI}!nDdzwe{nY|{8Fls1|r98;6;ZmV^cE8?5UL;ho<#p&GWp5YSMQHx)H0g z>qDjLNiK~<(>j?XQ2HpcM(_F$zue#0+4lF`@j^!H#q2&&Y1yCe`^&VuS8?E|N!p|f zoT-KUtV-Kf(+gi&Yph=-Uz3;#x}bP>7xUDwQr))I zGWi9+;a4eEx;(e4oH(L-9;#MKHMc}Y(?LS2`nHobas2ooe0rbPEVm@JWmRZ4BYhCZ z$M2|wu}M_97vlXN00960P)i30Gg%>T@B#n;KLr2)lgI)pWdHyG0CHtCZDnqB zb1rmvbWlqH0u%!j00;mG0000XiZfXuZ}0*D06zr)01W^D00000000000HlFb0001U lacp05b#88DaxQRrP)h{{000000ssO4c>n+ae**vj005R>sKx*Q delta 661 zcmeCRliIFPq+2_#L@5`)O5K-u_1n3@%;HqAgq!?~ew32>$-} z_UBtCt-qf+RegG8-nY_|Tb{rBw_DZ1_Ve4PJ9uMW*q)b4lsj1xUi9OUk!EX&cKw5* zjq>xnE_N%6?cCn?wWVoxoa_Gsb0Syl_4sJ&efpoC?HjKZQT_sDONB0d-X0b05pbRJ z$e)`@($xp@B5qtg6MX&Tx!ryubN$wQ7uvqLLwh@u@6A-z0`q?-);b*8vEBLS_G>5d zoTg>1xE-!zu|;IVsr@RNlb$RRjk7(j^=;XfFKRc`Hp_kvoAl+sH>dEz8PBcjnHd=V z{}1qH=lFB