From 2489c2e4397eaef8a548b7fa489b8e8c107782f5 Mon Sep 17 00:00:00 2001 From: Davide Paci Date: Mon, 15 Dec 2025 18:27:24 +0100 Subject: [PATCH] `gnrcloudtools` and `gnrsubscriptions` after splitting dependent repos #6 --- instances/gnr_comm/config/instanceconfig.xml | 5 +- .../comm/lib/upgrades/0001_migrate_repomgm.py | 234 ++++++++++++++ packages/comm/localization.xml | 290 ++++++++++++++++++ packages/comm/main.py | 2 +- packages/comm/menu.py | 4 +- packages/comm/resources/oidc.css | 5 - .../tables/_packages/adm/user/user.py | 17 + .../_packages/sys/service/th_service.py | 2 +- 8 files changed, 548 insertions(+), 11 deletions(-) create mode 100644 packages/comm/lib/upgrades/0001_migrate_repomgm.py create mode 100644 packages/comm/localization.xml create mode 100644 packages/comm/resources/tables/_packages/adm/user/user.py diff --git a/instances/gnr_comm/config/instanceconfig.xml b/instances/gnr_comm/config/instanceconfig.xml index c173625..bcb366a 100644 --- a/instances/gnr_comm/config/instanceconfig.xml +++ b/instances/gnr_comm/config/instanceconfig.xml @@ -17,8 +17,9 @@ - - + + + diff --git a/packages/comm/lib/upgrades/0001_migrate_repomgm.py b/packages/comm/lib/upgrades/0001_migrate_repomgm.py new file mode 100644 index 0000000..053d190 --- /dev/null +++ b/packages/comm/lib/upgrades/0001_migrate_repomgm.py @@ -0,0 +1,234 @@ +# encoding: utf-8 +"""Migrazione delle tabelle `repomgm` verso i nuovi package `github` e `sbs`. + +Nota: le tabelle sorgente potrebbero non essere più nel model o avere nomi +prefissati (es. public.repomgm_repository). Le individuiamo direttamente dal DB. +""" + + +def table_exists(db, full_name): + """ + True se la tabella esiste in DB (anche se non è più nel model), + False altrimenti. + """ + res = db.execute("SELECT to_regclass(:tname)", dict(tname=full_name)).fetchone() + return bool(res and res[0]) + + +def find_table(db, preferred_names, fallback_name=None): + """ + Restituisce il primo nome tabella esistente tra quelli preferiti. + Se non trova nulla, prova a cercare per table_name (fallback_name). + """ + for name in preferred_names: + if table_exists(db, name): + return name + + if not fallback_name: + return None + + found = db.execute( + """ + SELECT table_schema || '.' || table_name + FROM information_schema.tables + WHERE table_name=:tname + ORDER BY table_schema + LIMIT 1 + """, + dict(tname=fallback_name), + ).fetchone() + if found: + return found[0] + return None + + +def fetch_rows(db, full_name): + """Legge tutte le righe dalla tabella sorgente come lista di dict.""" + cursor = db.execute(f"SELECT * FROM {full_name}") + cols = [c[0] for c in cursor.description] + return [dict(zip(cols, row)) for row in cursor.fetchall()] + + +def migrate_table(db, source_candidates, dest_path, field_map, pkey_field="id"): + """ + Copia le righe dalla prima tabella trovata in `source_candidates` verso `dest_path`. + + field_map è un dict {colonna_sorgente: colonna_destinazione}. + Si inseriscono solo le righe che non esistono già (match sul pkey_field). + """ + source = find_table( + db, + preferred_names=source_candidates, + fallback_name=source_candidates[-1].split(".")[-1], + ) + if not source: + print( + f"Skip {source_candidates[0]}: tabella inesistente " + f"(cercate varianti: {', '.join(source_candidates)})" + ) + return 0 + + dest_tbl = db.table(dest_path) + rows = fetch_rows(db, source) + inserted = 0 + + for row in rows: + pkey = row.get(pkey_field) + if not pkey: + continue + + # evita duplicati se la riga è già stata migrata + if dest_tbl.readColumns(pkey, columns=pkey_field): + continue + + record = dest_tbl.newrecord( + **{dst: row[src] for src, dst in field_map.items() if src in row} + ) + record[pkey_field] = pkey + dest_tbl.insert(record) + inserted += 1 + + db.commit() + print(f"Migrate {source} -> {dest_path}: inserite {inserted} righe") + return inserted + + +def cleanup_repomgm(db, tables): + """Svuota le tabelle repomgm nell'ordine fornito.""" + for candidates in tables: + source = find_table( + db, + preferred_names=candidates, + fallback_name=candidates[-1].split(".")[-1], + ) + if not source: + continue + db.execute(f"DELETE FROM {source}") + db.commit() + + +def main(db): + migrations = [ + ( + [ + "repomgm.git_organization", + "repomgm.repomgm_git_organization", + "public.repomgm_git_organization", + "public.repomgm.repomgm_git_organization", + ], + "github.organization", + {"id": "id", "code": "code", "description": "description"}, + ), + ( + [ + "repomgm.repository", + "repomgm.repomgm_repository", + "public.repomgm_repository", + "public.repomgm.repomgm_repository", + ], + "github.repository", + { + "id": "id", + "code": "code", + "title": "title", + "logo": "logo", + "description": "description", + "organization_id": "organization_id", + "metadata": "metadata", + }, + ), + ( + [ + "repomgm.frequency", + "repomgm.repomgm_frequency", + "public.repomgm_frequency", + "public.repomgm.repomgm_frequency", + ], + "sbs.frequency", + { + "code": "code", + "description": "description", + "days_for_renewal": "days_for_renewal", + }, + "code", + ), + ( + [ + "repomgm.subscription_plan", + "repomgm.repomgm_subscription_plan", + "public.repomgm_subscription_plan", + "public.repomgm.repomgm_subscription_plan", + ], + "sbs.subscription_plan", + { + "id": "id", + "repository_id": "repository_id", + "description": "description", + "full_description": "full_description", + "price": "price", + "currency": "currency", + "frequency_code": "frequency_code", + "start_date": "start_date", + "end_date": "end_date", + "enable_subscription": "enable_subscription", + }, + ), + ( + [ + "repomgm.subscription", + "repomgm.repomgm_subscription", + "public.repomgm_subscription", + "public.repomgm.repomgm_subscription", + ], + "sbs.subscription", + { + "id": "id", + "user_id": "user_id", + "repository_id": "repository_id", + "subscription_plan_id": "subscription_plan_id", + "role_id": "role_id", + "start_date": "start_date", + "end_date": "end_date", + "permission_level": "permission_level", + }, + ), + ] + + for source_candidates, dest, field_map, *pkey in migrations: + migrate_table(db, source_candidates, dest, field_map, pkey[0] if pkey else "id") + + cleanup_repomgm( + db, + [ + [ + "repomgm.subscription", + "repomgm.repomgm_subscription", + "public.repomgm_subscription", + "public.repomgm.repomgm_subscription", + ], + [ + "repomgm.subscription_plan", + "repomgm.repomgm_subscription_plan", + "public.repomgm_subscription_plan", + "public.repomgm.repomgm_subscription_plan", + ], + [ + "repomgm.repository", + "repomgm.repomgm_repository", + "public.repomgm_repository", + "public.repomgm.repomgm_repository", + ], + [ + "repomgm.git_organization", + "repomgm.repomgm_git_organization", + "public.repomgm_git_organization", + "public.repomgm.repomgm_git_organization", + ], + [ + "repomgm.frequency", + "repomgm.repomgm_frequency", + "public.repomgm_frequency", + "public.repomgm.repomgm_frequency", + ], + ], + ) diff --git a/packages/comm/localization.xml b/packages/comm/localization.xml new file mode 100644 index 0000000..5b060e6 --- /dev/null +++ b/packages/comm/localization.xml @@ -0,0 +1,290 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +<_packages> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<_packages> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/packages/comm/main.py b/packages/comm/main.py index a92f44e..1345abd 100644 --- a/packages/comm/main.py +++ b/packages/comm/main.py @@ -11,7 +11,7 @@ def config_db(self, pkg): pass def required_packages(self): - return ['gnrcore:email'] + return ['gnrcore:email', 'gnrcloudtools:github', 'gnrsubscriptions:sbs'] class Table(GnrDboTable): diff --git a/packages/comm/menu.py b/packages/comm/menu.py index b177986..272af44 100644 --- a/packages/comm/menu.py +++ b/packages/comm/menu.py @@ -36,7 +36,7 @@ def config_community(self,root,**kwargs): root.webpage('!!Messages', filepath='/email/my_messages', table='email.message', titleCounter=True, titleCounter_condition='$dest_user_id=:env_user_id AND $read IS NOT TRUE', menucode='messages') - root.webpage('!![en]Subscriptions', filepath='/repomgm/user_subscriptions') + root.webpage('!![en]Subscriptions', filepath='/sbs/user_subscriptions') root.webpage(u"!![en]Community map", filepath="/comm/community_map") self.publicationsSubMenu(root.branch("!![en]Publications")) #root.thpage(u"!![en]Suggestions", table="comm.suggestion") @@ -49,7 +49,7 @@ def publicationsSubMenu(self, branch): def config(self,root,**kwargs): root.packageBranch(u"!![en]Community", pkg="comm") - root.packageBranch(u"!![en]Repository management", pkg="repomgm") + root.packageBranch(u"!![en]Repository management", pkg="sbs") root.packageBranch(u"!![en]Surveys", pkg="srvy", tags='admin') if self.db.application.getPreference('enable_social', pkg='comm'): root.packageBranch(u"!![en]Social", pkg="social", tags='admin') diff --git a/packages/comm/resources/oidc.css b/packages/comm/resources/oidc.css index dda087d..b6ec888 100644 --- a/packages/comm/resources/oidc.css +++ b/packages/comm/resources/oidc.css @@ -1,9 +1,4 @@ .github_sign_in_button{ background: url(brand_icons/github.svg) center center no-repeat; background-size: contain; -} - -.bitbucket_sign_in_button{ - background: url(brand_icons/bitbucket.svg) center center no-repeat; - background-size: contain; } \ No newline at end of file diff --git a/packages/comm/resources/tables/_packages/adm/user/user.py b/packages/comm/resources/tables/_packages/adm/user/user.py new file mode 100644 index 0000000..2097eda --- /dev/null +++ b/packages/comm/resources/tables/_packages/adm/user/user.py @@ -0,0 +1,17 @@ +# encoding: utf-8 +from subscription_token import generate_token + +class Table(object): + def config_db(self,pkg): + tbl=pkg.table('user') + + tbl.colgroup('repo_token', name_long='!![en]Repository tokens') + tbl.column('repo_subscription_token', size='32', name_long='!![en]Subscription token', name_short='!![en]Token') + tbl.column('repo_token_creation_date', dtype='D', name_long='!![en]Subscription token creation date', name_short='!![en]Token creation') + + + def userSubscriptionToken(self, record, refresh=False): + with self.db.table('adm.user').recordToUpdate(record['user_id']) as user: + if refresh or not user['repo_subscription_token']: + user['repo_subscription_token'] = generate_token(32) #sys external token? + user['repo_token_creation_date'] = self.db.workdate \ No newline at end of file diff --git a/packages/comm/resources/tables/_packages/sys/service/th_service.py b/packages/comm/resources/tables/_packages/sys/service/th_service.py index 4c30b64..8e9f3f8 100644 --- a/packages/comm/resources/tables/_packages/sys/service/th_service.py +++ b/packages/comm/resources/tables/_packages/sys/service/th_service.py @@ -29,7 +29,7 @@ def th_options(self): fields=[dict(value='^.implementation', tag='filteringSelect', lbl='!![en]Implementation', - values="git:Github,bitbucket:Bitbucket", + values="git:Github", hasDownArrow=True)])) @public_method