compendium-ils 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (496) hide show
  1. compendium_ils-1.0.0/.github/workflows/release.yml +31 -0
  2. compendium_ils-1.0.0/.gitignore +60 -0
  3. compendium_ils-1.0.0/.python-version +1 -0
  4. compendium_ils-1.0.0/LICENSE +21 -0
  5. compendium_ils-1.0.0/PKG-INFO +405 -0
  6. compendium_ils-1.0.0/README.md +355 -0
  7. compendium_ils-1.0.0/alembic.ini +149 -0
  8. compendium_ils-1.0.0/docker/.env.example +105 -0
  9. compendium_ils-1.0.0/docker/Dockerfile +51 -0
  10. compendium_ils-1.0.0/docker/README.md +269 -0
  11. compendium_ils-1.0.0/docker/certs/.gitkeep +0 -0
  12. compendium_ils-1.0.0/docker/compendium/entrypoint.sh +41 -0
  13. compendium_ils-1.0.0/docker/crontab.sample +63 -0
  14. compendium_ils-1.0.0/docker/docker-compose.yml +125 -0
  15. compendium_ils-1.0.0/docker/install-cron.sh +128 -0
  16. compendium_ils-1.0.0/docker/nginx/entrypoint.sh +25 -0
  17. compendium_ils-1.0.0/docker/nginx/nginx.conf +77 -0
  18. compendium_ils-1.0.0/docs/architecture.md +822 -0
  19. compendium_ils-1.0.0/docs/compendium.service.sample +87 -0
  20. compendium_ils-1.0.0/docs/crontab.sample +57 -0
  21. compendium_ils-1.0.0/docs/deployment.md +455 -0
  22. compendium_ils-1.0.0/docs/operation-map.md +44 -0
  23. compendium_ils-1.0.0/docs/schema.md +471 -0
  24. compendium_ils-1.0.0/docs/security_audit_2026-04-19.md +190 -0
  25. compendium_ils-1.0.0/ils_parity.md +322 -0
  26. compendium_ils-1.0.0/pyproject.toml +108 -0
  27. compendium_ils-1.0.0/scripts/install-cron.sh +147 -0
  28. compendium_ils-1.0.0/scripts/render_label_matrix.py +197 -0
  29. compendium_ils-1.0.0/src/compendium/__init__.py +1 -0
  30. compendium_ils-1.0.0/src/compendium/__main__.py +4 -0
  31. compendium_ils-1.0.0/src/compendium/api/__init__.py +0 -0
  32. compendium_ils-1.0.0/src/compendium/api/app.py +244 -0
  33. compendium_ils-1.0.0/src/compendium/api/deps.py +122 -0
  34. compendium_ils-1.0.0/src/compendium/api/routes/__init__.py +0 -0
  35. compendium_ils-1.0.0/src/compendium/api/routes/audit.py +34 -0
  36. compendium_ils-1.0.0/src/compendium/api/routes/auth.py +64 -0
  37. compendium_ils-1.0.0/src/compendium/api/routes/branches.py +43 -0
  38. compendium_ils-1.0.0/src/compendium/api/routes/calendar.py +181 -0
  39. compendium_ils-1.0.0/src/compendium/api/routes/claims.py +160 -0
  40. compendium_ils-1.0.0/src/compendium/api/routes/creators.py +53 -0
  41. compendium_ils-1.0.0/src/compendium/api/routes/curated_lists.py +242 -0
  42. compendium_ils-1.0.0/src/compendium/api/routes/fines.py +167 -0
  43. compendium_ils-1.0.0/src/compendium/api/routes/holds.py +200 -0
  44. compendium_ils-1.0.0/src/compendium/api/routes/households.py +146 -0
  45. compendium_ils-1.0.0/src/compendium/api/routes/imports.py +432 -0
  46. compendium_ils-1.0.0/src/compendium/api/routes/items.py +286 -0
  47. compendium_ils-1.0.0/src/compendium/api/routes/labels.py +177 -0
  48. compendium_ils-1.0.0/src/compendium/api/routes/loans.py +148 -0
  49. compendium_ils-1.0.0/src/compendium/api/routes/me.py +175 -0
  50. compendium_ils-1.0.0/src/compendium/api/routes/notifications.py +65 -0
  51. compendium_ils-1.0.0/src/compendium/api/routes/patron_categories.py +93 -0
  52. compendium_ils-1.0.0/src/compendium/api/routes/patrons.py +186 -0
  53. compendium_ils-1.0.0/src/compendium/api/routes/policies.py +52 -0
  54. compendium_ils-1.0.0/src/compendium/api/routes/reports.py +126 -0
  55. compendium_ils-1.0.0/src/compendium/api/routes/settings.py +272 -0
  56. compendium_ils-1.0.0/src/compendium/api/routes/users.py +163 -0
  57. compendium_ils-1.0.0/src/compendium/api/routes/works.py +220 -0
  58. compendium_ils-1.0.0/src/compendium/api/schemas.py +519 -0
  59. compendium_ils-1.0.0/src/compendium/api/uploads.py +51 -0
  60. compendium_ils-1.0.0/src/compendium/cli/__init__.py +0 -0
  61. compendium_ils-1.0.0/src/compendium/cli/commands/__init__.py +0 -0
  62. compendium_ils-1.0.0/src/compendium/cli/commands/audit.py +40 -0
  63. compendium_ils-1.0.0/src/compendium/cli/commands/backup.py +123 -0
  64. compendium_ils-1.0.0/src/compendium/cli/commands/branch.py +54 -0
  65. compendium_ils-1.0.0/src/compendium/cli/commands/bulk_ops.py +545 -0
  66. compendium_ils-1.0.0/src/compendium/cli/commands/calendar.py +158 -0
  67. compendium_ils-1.0.0/src/compendium/cli/commands/claim.py +134 -0
  68. compendium_ils-1.0.0/src/compendium/cli/commands/creator.py +50 -0
  69. compendium_ils-1.0.0/src/compendium/cli/commands/curated_list.py +280 -0
  70. compendium_ils-1.0.0/src/compendium/cli/commands/db.py +47 -0
  71. compendium_ils-1.0.0/src/compendium/cli/commands/fine.py +147 -0
  72. compendium_ils-1.0.0/src/compendium/cli/commands/hold.py +221 -0
  73. compendium_ils-1.0.0/src/compendium/cli/commands/household.py +175 -0
  74. compendium_ils-1.0.0/src/compendium/cli/commands/item.py +639 -0
  75. compendium_ils-1.0.0/src/compendium/cli/commands/keygen.py +46 -0
  76. compendium_ils-1.0.0/src/compendium/cli/commands/labels.py +447 -0
  77. compendium_ils-1.0.0/src/compendium/cli/commands/loan.py +224 -0
  78. compendium_ils-1.0.0/src/compendium/cli/commands/maintenance.py +538 -0
  79. compendium_ils-1.0.0/src/compendium/cli/commands/metadata.py +133 -0
  80. compendium_ils-1.0.0/src/compendium/cli/commands/patron.py +281 -0
  81. compendium_ils-1.0.0/src/compendium/cli/commands/patron_category.py +98 -0
  82. compendium_ils-1.0.0/src/compendium/cli/commands/policy.py +188 -0
  83. compendium_ils-1.0.0/src/compendium/cli/commands/reports.py +187 -0
  84. compendium_ils-1.0.0/src/compendium/cli/commands/role.py +114 -0
  85. compendium_ils-1.0.0/src/compendium/cli/commands/secrets.py +178 -0
  86. compendium_ils-1.0.0/src/compendium/cli/commands/settings.py +245 -0
  87. compendium_ils-1.0.0/src/compendium/cli/commands/user.py +284 -0
  88. compendium_ils-1.0.0/src/compendium/cli/commands/work.py +455 -0
  89. compendium_ils-1.0.0/src/compendium/cli/io.py +49 -0
  90. compendium_ils-1.0.0/src/compendium/cli/main.py +110 -0
  91. compendium_ils-1.0.0/src/compendium/config/__init__.py +0 -0
  92. compendium_ils-1.0.0/src/compendium/config/seed.py +154 -0
  93. compendium_ils-1.0.0/src/compendium/config/settings.py +120 -0
  94. compendium_ils-1.0.0/src/compendium/db/__init__.py +0 -0
  95. compendium_ils-1.0.0/src/compendium/db/engine.py +105 -0
  96. compendium_ils-1.0.0/src/compendium/db/session.py +50 -0
  97. compendium_ils-1.0.0/src/compendium/domain/__init__.py +0 -0
  98. compendium_ils-1.0.0/src/compendium/domain/enums.py +85 -0
  99. compendium_ils-1.0.0/src/compendium/domain/errors.py +60 -0
  100. compendium_ils-1.0.0/src/compendium/domain/identifiers.py +114 -0
  101. compendium_ils-1.0.0/src/compendium/domain/models.py +555 -0
  102. compendium_ils-1.0.0/src/compendium/domain/types.py +32 -0
  103. compendium_ils-1.0.0/src/compendium/migrations/README +1 -0
  104. compendium_ils-1.0.0/src/compendium/migrations/env.py +68 -0
  105. compendium_ils-1.0.0/src/compendium/migrations/script.py.mako +28 -0
  106. compendium_ils-1.0.0/src/compendium/migrations/versions/11dbd4cede12_add_search_text_and_fts.py +98 -0
  107. compendium_ils-1.0.0/src/compendium/migrations/versions/1b17e2ba445c_consolidate_barcode_settings.py +84 -0
  108. compendium_ils-1.0.0/src/compendium/migrations/versions/443891bfaa50_add_item_note.py +47 -0
  109. compendium_ils-1.0.0/src/compendium/migrations/versions/5b9e539aca65_add_household_model.py +106 -0
  110. compendium_ils-1.0.0/src/compendium/migrations/versions/611abe9ea6e5_add_metadata_cache.py +39 -0
  111. compendium_ils-1.0.0/src/compendium/migrations/versions/6e72f68cf15a_add_auth.py +62 -0
  112. compendium_ils-1.0.0/src/compendium/migrations/versions/a1b2c3d4e5f6_add_app_user_password_changed_at.py +31 -0
  113. compendium_ils-1.0.0/src/compendium/migrations/versions/a7b8c9d0e1f2_add_hold_held_item.py +43 -0
  114. compendium_ils-1.0.0/src/compendium/migrations/versions/a8b9c0d1e2f3_add_branch_location_code.py +30 -0
  115. compendium_ils-1.0.0/src/compendium/migrations/versions/a9b9ea15933f_add_holds_and_loan_policies.py +66 -0
  116. compendium_ils-1.0.0/src/compendium/migrations/versions/b1c2d3e4f5a6_add_curated_list.py +140 -0
  117. compendium_ils-1.0.0/src/compendium/migrations/versions/b2c3d4e5f6a7_add_counters_table.py +44 -0
  118. compendium_ils-1.0.0/src/compendium/migrations/versions/b3c4d5e6f7a8_add_audit_log.py +54 -0
  119. compendium_ils-1.0.0/src/compendium/migrations/versions/b8c9d0e1f2a3_add_patron_categories.py +97 -0
  120. compendium_ils-1.0.0/src/compendium/migrations/versions/c1d2e3f4a5b6_add_branch_classification_scheme.py +33 -0
  121. compendium_ils-1.0.0/src/compendium/migrations/versions/c3d4e5f6a7b8_revamp_identifiers.py +133 -0
  122. compendium_ils-1.0.0/src/compendium/migrations/versions/c4d5e6f7a8b9_add_library_calendar.py +120 -0
  123. compendium_ils-1.0.0/src/compendium/migrations/versions/c9d0e1f2a3b4_add_hold_suspend.py +35 -0
  124. compendium_ils-1.0.0/src/compendium/migrations/versions/cec97d4626cf_initial_schema.py +175 -0
  125. compendium_ils-1.0.0/src/compendium/migrations/versions/d0e1f2a3b4c5_add_site_setting.py +46 -0
  126. compendium_ils-1.0.0/src/compendium/migrations/versions/d4e5f6a7b8c9_add_item_loanable.py +41 -0
  127. compendium_ils-1.0.0/src/compendium/migrations/versions/d7e8f9a0b1c2_add_work_sort_title.py +38 -0
  128. compendium_ils-1.0.0/src/compendium/migrations/versions/e1f2a3b4c5d6_split_admin_roles.py +152 -0
  129. compendium_ils-1.0.0/src/compendium/migrations/versions/e5f6a7b8c9d0_add_fines.py +76 -0
  130. compendium_ils-1.0.0/src/compendium/migrations/versions/e9f0a1b2c3d4_fix_failed_login_id_type.py +59 -0
  131. compendium_ils-1.0.0/src/compendium/migrations/versions/f2a3b4c5d6e7_add_failed_login_table.py +42 -0
  132. compendium_ils-1.0.0/src/compendium/migrations/versions/f6a7b8c9d0e1_add_notifications.py +93 -0
  133. compendium_ils-1.0.0/src/compendium/migrations/versions/fab1c2d3e4f5_patron_account_manage.py +86 -0
  134. compendium_ils-1.0.0/src/compendium/repositories/__init__.py +0 -0
  135. compendium_ils-1.0.0/src/compendium/repositories/base.py +430 -0
  136. compendium_ils-1.0.0/src/compendium/repositories/sql/__init__.py +4 -0
  137. compendium_ils-1.0.0/src/compendium/repositories/sql/audit_log_repository.py +48 -0
  138. compendium_ils-1.0.0/src/compendium/repositories/sql/branch_repository.py +24 -0
  139. compendium_ils-1.0.0/src/compendium/repositories/sql/calendar_repository.py +78 -0
  140. compendium_ils-1.0.0/src/compendium/repositories/sql/counters.py +28 -0
  141. compendium_ils-1.0.0/src/compendium/repositories/sql/creator_repository.py +41 -0
  142. compendium_ils-1.0.0/src/compendium/repositories/sql/curated_list_repository.py +86 -0
  143. compendium_ils-1.0.0/src/compendium/repositories/sql/failed_login_repository.py +57 -0
  144. compendium_ils-1.0.0/src/compendium/repositories/sql/fine_repository.py +122 -0
  145. compendium_ils-1.0.0/src/compendium/repositories/sql/hold_repository.py +237 -0
  146. compendium_ils-1.0.0/src/compendium/repositories/sql/household_repository.py +38 -0
  147. compendium_ils-1.0.0/src/compendium/repositories/sql/item_note_repository.py +30 -0
  148. compendium_ils-1.0.0/src/compendium/repositories/sql/item_repository.py +60 -0
  149. compendium_ils-1.0.0/src/compendium/repositories/sql/loan_policy_repository.py +92 -0
  150. compendium_ils-1.0.0/src/compendium/repositories/sql/loan_repository.py +237 -0
  151. compendium_ils-1.0.0/src/compendium/repositories/sql/media_type_repository.py +16 -0
  152. compendium_ils-1.0.0/src/compendium/repositories/sql/notification_repository.py +104 -0
  153. compendium_ils-1.0.0/src/compendium/repositories/sql/patron_category_repository.py +56 -0
  154. compendium_ils-1.0.0/src/compendium/repositories/sql/patron_repository.py +42 -0
  155. compendium_ils-1.0.0/src/compendium/repositories/sql/role_repository.py +26 -0
  156. compendium_ils-1.0.0/src/compendium/repositories/sql/site_setting_repository.py +47 -0
  157. compendium_ils-1.0.0/src/compendium/repositories/sql/user_repository.py +29 -0
  158. compendium_ils-1.0.0/src/compendium/repositories/sql/work_repository.py +713 -0
  159. compendium_ils-1.0.0/src/compendium/services/__init__.py +0 -0
  160. compendium_ils-1.0.0/src/compendium/services/_normalization.py +71 -0
  161. compendium_ils-1.0.0/src/compendium/services/audit.py +99 -0
  162. compendium_ils-1.0.0/src/compendium/services/auth.py +302 -0
  163. compendium_ils-1.0.0/src/compendium/services/backup.py +563 -0
  164. compendium_ils-1.0.0/src/compendium/services/calendar.py +275 -0
  165. compendium_ils-1.0.0/src/compendium/services/catalog.py +1249 -0
  166. compendium_ils-1.0.0/src/compendium/services/circulation.py +591 -0
  167. compendium_ils-1.0.0/src/compendium/services/covers.py +198 -0
  168. compendium_ils-1.0.0/src/compendium/services/curated_lists.py +267 -0
  169. compendium_ils-1.0.0/src/compendium/services/discovery.py +139 -0
  170. compendium_ils-1.0.0/src/compendium/services/fines.py +432 -0
  171. compendium_ils-1.0.0/src/compendium/services/formatting.py +23 -0
  172. compendium_ils-1.0.0/src/compendium/services/holds.py +391 -0
  173. compendium_ils-1.0.0/src/compendium/services/households.py +147 -0
  174. compendium_ils-1.0.0/src/compendium/services/import_export.py +1621 -0
  175. compendium_ils-1.0.0/src/compendium/services/item_notes.py +100 -0
  176. compendium_ils-1.0.0/src/compendium/services/label_canvas_svg.py +138 -0
  177. compendium_ils-1.0.0/src/compendium/services/labels.py +1142 -0
  178. compendium_ils-1.0.0/src/compendium/services/metadata.py +1244 -0
  179. compendium_ils-1.0.0/src/compendium/services/metadata_cache.py +304 -0
  180. compendium_ils-1.0.0/src/compendium/services/notifications/__init__.py +451 -0
  181. compendium_ils-1.0.0/src/compendium/services/notifications/smtp.py +81 -0
  182. compendium_ils-1.0.0/src/compendium/services/notifications/templates/due_soon/body.txt +11 -0
  183. compendium_ils-1.0.0/src/compendium/services/notifications/templates/due_soon/subject.txt +1 -0
  184. compendium_ils-1.0.0/src/compendium/services/notifications/templates/hold_ready/body.txt +12 -0
  185. compendium_ils-1.0.0/src/compendium/services/notifications/templates/hold_ready/subject.txt +1 -0
  186. compendium_ils-1.0.0/src/compendium/services/notifications/templates/overdue/body.txt +18 -0
  187. compendium_ils-1.0.0/src/compendium/services/notifications/templates/overdue/subject.txt +1 -0
  188. compendium_ils-1.0.0/src/compendium/services/patron_categories.py +116 -0
  189. compendium_ils-1.0.0/src/compendium/services/patrons.py +286 -0
  190. compendium_ils-1.0.0/src/compendium/services/policies.py +155 -0
  191. compendium_ils-1.0.0/src/compendium/services/rate_limit.py +119 -0
  192. compendium_ils-1.0.0/src/compendium/services/reports.py +179 -0
  193. compendium_ils-1.0.0/src/compendium/services/roles.py +127 -0
  194. compendium_ils-1.0.0/src/compendium/services/secrets.py +149 -0
  195. compendium_ils-1.0.0/src/compendium/services/settings_registry.py +984 -0
  196. compendium_ils-1.0.0/src/compendium/services/site_settings.py +269 -0
  197. compendium_ils-1.0.0/src/compendium/web/__init__.py +0 -0
  198. compendium_ils-1.0.0/src/compendium/web/app.py +69 -0
  199. compendium_ils-1.0.0/src/compendium/web/csrf.py +83 -0
  200. compendium_ils-1.0.0/src/compendium/web/deps.py +132 -0
  201. compendium_ils-1.0.0/src/compendium/web/jinja.py +86 -0
  202. compendium_ils-1.0.0/src/compendium/web/nav_pages.py +45 -0
  203. compendium_ils-1.0.0/src/compendium/web/routes/__init__.py +0 -0
  204. compendium_ils-1.0.0/src/compendium/web/routes/admin_circulation.py +154 -0
  205. compendium_ils-1.0.0/src/compendium/web/routes/admin_holds.py +117 -0
  206. compendium_ils-1.0.0/src/compendium/web/routes/admin_settings.py +925 -0
  207. compendium_ils-1.0.0/src/compendium/web/routes/audit.py +72 -0
  208. compendium_ils-1.0.0/src/compendium/web/routes/auth.py +137 -0
  209. compendium_ils-1.0.0/src/compendium/web/routes/branches.py +98 -0
  210. compendium_ils-1.0.0/src/compendium/web/routes/bulk.py +520 -0
  211. compendium_ils-1.0.0/src/compendium/web/routes/catalog.py +717 -0
  212. compendium_ils-1.0.0/src/compendium/web/routes/circ.py +178 -0
  213. compendium_ils-1.0.0/src/compendium/web/routes/covers.py +36 -0
  214. compendium_ils-1.0.0/src/compendium/web/routes/creators.py +127 -0
  215. compendium_ils-1.0.0/src/compendium/web/routes/curated_lists.py +331 -0
  216. compendium_ils-1.0.0/src/compendium/web/routes/fines.py +542 -0
  217. compendium_ils-1.0.0/src/compendium/web/routes/households.py +220 -0
  218. compendium_ils-1.0.0/src/compendium/web/routes/items.py +685 -0
  219. compendium_ils-1.0.0/src/compendium/web/routes/kiosk.py +269 -0
  220. compendium_ils-1.0.0/src/compendium/web/routes/labels.py +628 -0
  221. compendium_ils-1.0.0/src/compendium/web/routes/library_hours.py +191 -0
  222. compendium_ils-1.0.0/src/compendium/web/routes/me.py +338 -0
  223. compendium_ils-1.0.0/src/compendium/web/routes/notifications.py +147 -0
  224. compendium_ils-1.0.0/src/compendium/web/routes/patron_categories.py +143 -0
  225. compendium_ils-1.0.0/src/compendium/web/routes/patrons.py +472 -0
  226. compendium_ils-1.0.0/src/compendium/web/routes/policies.py +191 -0
  227. compendium_ils-1.0.0/src/compendium/web/routes/reports.py +274 -0
  228. compendium_ils-1.0.0/src/compendium/web/routes/roles.py +234 -0
  229. compendium_ils-1.0.0/src/compendium/web/routes/users.py +458 -0
  230. compendium_ils-1.0.0/src/compendium/web/static/chart.min.js +20 -0
  231. compendium_ils-1.0.0/src/compendium/web/static/compendium.css +1313 -0
  232. compendium_ils-1.0.0/src/compendium/web/static/favicon.svg +13 -0
  233. compendium_ils-1.0.0/src/compendium/web/static/fonts/fraunces-italic-latin-ext.woff2 +0 -0
  234. compendium_ils-1.0.0/src/compendium/web/static/fonts/fraunces-italic-latin.woff2 +0 -0
  235. compendium_ils-1.0.0/src/compendium/web/static/fonts/fraunces-normal-latin-ext.woff2 +0 -0
  236. compendium_ils-1.0.0/src/compendium/web/static/fonts/fraunces-normal-latin.woff2 +0 -0
  237. compendium_ils-1.0.0/src/compendium/web/static/htmx.min.js +1 -0
  238. compendium_ils-1.0.0/src/compendium/web/static/icons/bluray.svg +5 -0
  239. compendium_ils-1.0.0/src/compendium/web/static/icons/book.svg +4 -0
  240. compendium_ils-1.0.0/src/compendium/web/static/icons/cd.svg +4 -0
  241. compendium_ils-1.0.0/src/compendium/web/static/icons/dvd.svg +5 -0
  242. compendium_ils-1.0.0/src/compendium/web/static/icons/generic.svg +4 -0
  243. compendium_ils-1.0.0/src/compendium/web/static/icons/vhs.svg +7 -0
  244. compendium_ils-1.0.0/src/compendium/web/static/icons/vinyl.svg +6 -0
  245. compendium_ils-1.0.0/src/compendium/web/static/pico.min.css +4 -0
  246. compendium_ils-1.0.0/src/compendium/web/static/scanner.js +172 -0
  247. compendium_ils-1.0.0/src/compendium/web/static/zxing/zxing-browser.min.js +1 -0
  248. compendium_ils-1.0.0/src/compendium/web/templates/_macros.html +28 -0
  249. compendium_ils-1.0.0/src/compendium/web/templates/_partials/brand_glyph.html +12 -0
  250. compendium_ils-1.0.0/src/compendium/web/templates/_partials/empty_state.html +12 -0
  251. compendium_ils-1.0.0/src/compendium/web/templates/_partials/item_preview.html +64 -0
  252. compendium_ils-1.0.0/src/compendium/web/templates/_partials/media_icon.html +48 -0
  253. compendium_ils-1.0.0/src/compendium/web/templates/_partials/scanner_dialog.html +20 -0
  254. compendium_ils-1.0.0/src/compendium/web/templates/_partials/scanner_scripts.html +2 -0
  255. compendium_ils-1.0.0/src/compendium/web/templates/_partials/settings_sidebar.html +17 -0
  256. compendium_ils-1.0.0/src/compendium/web/templates/_partials/suggest.html +12 -0
  257. compendium_ils-1.0.0/src/compendium/web/templates/_partials/title_candidates.html +22 -0
  258. compendium_ils-1.0.0/src/compendium/web/templates/_partials/work_list.html +56 -0
  259. compendium_ils-1.0.0/src/compendium/web/templates/admin/_import_report.html +63 -0
  260. compendium_ils-1.0.0/src/compendium/web/templates/admin/_import_status_partial.html +26 -0
  261. compendium_ils-1.0.0/src/compendium/web/templates/admin/claims.html +28 -0
  262. compendium_ils-1.0.0/src/compendium/web/templates/admin/closed_dates.html +62 -0
  263. compendium_ils-1.0.0/src/compendium/web/templates/admin/export.html +45 -0
  264. compendium_ils-1.0.0/src/compendium/web/templates/admin/fines.html +80 -0
  265. compendium_ils-1.0.0/src/compendium/web/templates/admin/holds.html +103 -0
  266. compendium_ils-1.0.0/src/compendium/web/templates/admin/import.html +92 -0
  267. compendium_ils-1.0.0/src/compendium/web/templates/admin/import_job.html +10 -0
  268. compendium_ils-1.0.0/src/compendium/web/templates/admin/library_hours.html +45 -0
  269. compendium_ils-1.0.0/src/compendium/web/templates/admin/loans.html +84 -0
  270. compendium_ils-1.0.0/src/compendium/web/templates/admin/patron_categories.html +73 -0
  271. compendium_ils-1.0.0/src/compendium/web/templates/admin/settings.html +252 -0
  272. compendium_ils-1.0.0/src/compendium/web/templates/admin/settings_index.html +29 -0
  273. compendium_ils-1.0.0/src/compendium/web/templates/audit/list.html +60 -0
  274. compendium_ils-1.0.0/src/compendium/web/templates/base.html +393 -0
  275. compendium_ils-1.0.0/src/compendium/web/templates/branches/edit.html +59 -0
  276. compendium_ils-1.0.0/src/compendium/web/templates/branches/list.html +38 -0
  277. compendium_ils-1.0.0/src/compendium/web/templates/catalog/creators.html +78 -0
  278. compendium_ils-1.0.0/src/compendium/web/templates/catalog/detail.html +220 -0
  279. compendium_ils-1.0.0/src/compendium/web/templates/catalog/edit.html +108 -0
  280. compendium_ils-1.0.0/src/compendium/web/templates/catalog/refresh_preview.html +80 -0
  281. compendium_ils-1.0.0/src/compendium/web/templates/catalog/search.html +313 -0
  282. compendium_ils-1.0.0/src/compendium/web/templates/circ/desk.html +72 -0
  283. compendium_ils-1.0.0/src/compendium/web/templates/creators/edit.html +36 -0
  284. compendium_ils-1.0.0/src/compendium/web/templates/curated_lists/detail.html +132 -0
  285. compendium_ils-1.0.0/src/compendium/web/templates/curated_lists/list.html +44 -0
  286. compendium_ils-1.0.0/src/compendium/web/templates/curated_lists/new.html +33 -0
  287. compendium_ils-1.0.0/src/compendium/web/templates/error.html +7 -0
  288. compendium_ils-1.0.0/src/compendium/web/templates/error_no_patron.html +20 -0
  289. compendium_ils-1.0.0/src/compendium/web/templates/fines/declare_lost.html +33 -0
  290. compendium_ils-1.0.0/src/compendium/web/templates/fines/mark_damaged.html +27 -0
  291. compendium_ils-1.0.0/src/compendium/web/templates/fines/not_found.html +6 -0
  292. compendium_ils-1.0.0/src/compendium/web/templates/fines/patron.html +95 -0
  293. compendium_ils-1.0.0/src/compendium/web/templates/fines/verify_returned_confirm.html +26 -0
  294. compendium_ils-1.0.0/src/compendium/web/templates/fines/write_off_claim.html +23 -0
  295. compendium_ils-1.0.0/src/compendium/web/templates/households/detail.html +71 -0
  296. compendium_ils-1.0.0/src/compendium/web/templates/households/list.html +26 -0
  297. compendium_ils-1.0.0/src/compendium/web/templates/households/new.html +21 -0
  298. compendium_ils-1.0.0/src/compendium/web/templates/items/detail.html +200 -0
  299. compendium_ils-1.0.0/src/compendium/web/templates/items/edit.html +43 -0
  300. compendium_ils-1.0.0/src/compendium/web/templates/items/loanable.html +59 -0
  301. compendium_ils-1.0.0/src/compendium/web/templates/items/new.html +106 -0
  302. compendium_ils-1.0.0/src/compendium/web/templates/items/new_manual.html +74 -0
  303. compendium_ils-1.0.0/src/compendium/web/templates/items/withdraw_confirm.html +30 -0
  304. compendium_ils-1.0.0/src/compendium/web/templates/kiosk/landing.html +31 -0
  305. compendium_ils-1.0.0/src/compendium/web/templates/kiosk/session.html +53 -0
  306. compendium_ils-1.0.0/src/compendium/web/templates/kiosk_base.html +26 -0
  307. compendium_ils-1.0.0/src/compendium/web/templates/labels/_label_preview.html +4 -0
  308. compendium_ils-1.0.0/src/compendium/web/templates/labels/_symbology_banner.html +15 -0
  309. compendium_ils-1.0.0/src/compendium/web/templates/labels/index.html +23 -0
  310. compendium_ils-1.0.0/src/compendium/web/templates/labels/items.html +154 -0
  311. compendium_ils-1.0.0/src/compendium/web/templates/labels/patrons.html +138 -0
  312. compendium_ils-1.0.0/src/compendium/web/templates/lists/index.html +35 -0
  313. compendium_ils-1.0.0/src/compendium/web/templates/lists/view.html +55 -0
  314. compendium_ils-1.0.0/src/compendium/web/templates/login.html +35 -0
  315. compendium_ils-1.0.0/src/compendium/web/templates/me/fines.html +34 -0
  316. compendium_ils-1.0.0/src/compendium/web/templates/me/holds.html +78 -0
  317. compendium_ils-1.0.0/src/compendium/web/templates/me/loans.html +49 -0
  318. compendium_ils-1.0.0/src/compendium/web/templates/me/password.html +26 -0
  319. compendium_ils-1.0.0/src/compendium/web/templates/me/preferences.html +27 -0
  320. compendium_ils-1.0.0/src/compendium/web/templates/notifications/list.html +79 -0
  321. compendium_ils-1.0.0/src/compendium/web/templates/patrons/detail.html +225 -0
  322. compendium_ils-1.0.0/src/compendium/web/templates/patrons/list.html +41 -0
  323. compendium_ils-1.0.0/src/compendium/web/templates/patrons/loans.html +68 -0
  324. compendium_ils-1.0.0/src/compendium/web/templates/patrons/new.html +60 -0
  325. compendium_ils-1.0.0/src/compendium/web/templates/policies/list.html +80 -0
  326. compendium_ils-1.0.0/src/compendium/web/templates/policies/new.html +46 -0
  327. compendium_ils-1.0.0/src/compendium/web/templates/reports/checkouts.html +71 -0
  328. compendium_ils-1.0.0/src/compendium/web/templates/reports/dormant.html +56 -0
  329. compendium_ils-1.0.0/src/compendium/web/templates/reports/index.html +29 -0
  330. compendium_ils-1.0.0/src/compendium/web/templates/reports/overdues.html +49 -0
  331. compendium_ils-1.0.0/src/compendium/web/templates/reports/popular.html +83 -0
  332. compendium_ils-1.0.0/src/compendium/web/templates/roles/detail.html +110 -0
  333. compendium_ils-1.0.0/src/compendium/web/templates/roles/list.html +30 -0
  334. compendium_ils-1.0.0/src/compendium/web/templates/roles/new.html +59 -0
  335. compendium_ils-1.0.0/src/compendium/web/templates/users/detail.html +163 -0
  336. compendium_ils-1.0.0/src/compendium/web/templates/users/list.html +42 -0
  337. compendium_ils-1.0.0/src/compendium/web/templates/users/new.html +113 -0
  338. compendium_ils-1.0.0/tests/__init__.py +0 -0
  339. compendium_ils-1.0.0/tests/conftest.py +79 -0
  340. compendium_ils-1.0.0/tests/e2e/__init__.py +0 -0
  341. compendium_ils-1.0.0/tests/e2e/conftest.py +229 -0
  342. compendium_ils-1.0.0/tests/e2e/test_audit_viewer_pagination.py +63 -0
  343. compendium_ils-1.0.0/tests/e2e/test_csp_no_console_errors.py +91 -0
  344. compendium_ils-1.0.0/tests/e2e/test_inline_policy_edit.py +45 -0
  345. compendium_ils-1.0.0/tests/e2e/test_kiosk_session_flow.py +80 -0
  346. compendium_ils-1.0.0/tests/e2e/test_login_csrf_roundtrip.py +76 -0
  347. compendium_ils-1.0.0/tests/e2e/test_place_hold_htmx.py +54 -0
  348. compendium_ils-1.0.0/tests/e2e/test_scanner_mocked.py +114 -0
  349. compendium_ils-1.0.0/tests/e2e/test_theme_toggle_no_fouc.py +51 -0
  350. compendium_ils-1.0.0/tests/helpers.py +148 -0
  351. compendium_ils-1.0.0/tests/integration/__init__.py +0 -0
  352. compendium_ils-1.0.0/tests/integration/test_admin_circulation.py +548 -0
  353. compendium_ils-1.0.0/tests/integration/test_admin_holds.py +503 -0
  354. compendium_ils-1.0.0/tests/integration/test_admin_settings.py +844 -0
  355. compendium_ils-1.0.0/tests/integration/test_admin_settings_secrets.py +404 -0
  356. compendium_ils-1.0.0/tests/integration/test_api_audit.py +160 -0
  357. compendium_ils-1.0.0/tests/integration/test_api_auth.py +189 -0
  358. compendium_ils-1.0.0/tests/integration/test_api_authz.py +323 -0
  359. compendium_ils-1.0.0/tests/integration/test_api_households.py +220 -0
  360. compendium_ils-1.0.0/tests/integration/test_api_item_notes.py +279 -0
  361. compendium_ils-1.0.0/tests/integration/test_api_me.py +323 -0
  362. compendium_ils-1.0.0/tests/integration/test_api_patch.py +464 -0
  363. compendium_ils-1.0.0/tests/integration/test_api_patron_account.py +343 -0
  364. compendium_ils-1.0.0/tests/integration/test_audit_log.py +191 -0
  365. compendium_ils-1.0.0/tests/integration/test_audit_prune.py +162 -0
  366. compendium_ils-1.0.0/tests/integration/test_backup.py +584 -0
  367. compendium_ils-1.0.0/tests/integration/test_book_metadata_source_preference.py +535 -0
  368. compendium_ils-1.0.0/tests/integration/test_calendar.py +130 -0
  369. compendium_ils-1.0.0/tests/integration/test_calendar_api.py +142 -0
  370. compendium_ils-1.0.0/tests/integration/test_calendar_cli.py +94 -0
  371. compendium_ils-1.0.0/tests/integration/test_calendar_web.py +173 -0
  372. compendium_ils-1.0.0/tests/integration/test_catalog.py +645 -0
  373. compendium_ils-1.0.0/tests/integration/test_catalog_refresh_bulk.py +347 -0
  374. compendium_ils-1.0.0/tests/integration/test_circulation.py +264 -0
  375. compendium_ils-1.0.0/tests/integration/test_circulation_calendar.py +207 -0
  376. compendium_ils-1.0.0/tests/integration/test_claims_api.py +288 -0
  377. compendium_ils-1.0.0/tests/integration/test_claims_returned.py +269 -0
  378. compendium_ils-1.0.0/tests/integration/test_claims_web.py +226 -0
  379. compendium_ils-1.0.0/tests/integration/test_classification.py +105 -0
  380. compendium_ils-1.0.0/tests/integration/test_classification_ddc_fallback.py +89 -0
  381. compendium_ils-1.0.0/tests/integration/test_classification_fallback.py +108 -0
  382. compendium_ils-1.0.0/tests/integration/test_cli_commands.py +1765 -0
  383. compendium_ils-1.0.0/tests/integration/test_cli_household.py +96 -0
  384. compendium_ils-1.0.0/tests/integration/test_cli_item_note.py +158 -0
  385. compendium_ils-1.0.0/tests/integration/test_counter_repository.py +35 -0
  386. compendium_ils-1.0.0/tests/integration/test_cover_proxy.py +421 -0
  387. compendium_ils-1.0.0/tests/integration/test_csp_inline_handlers.py +57 -0
  388. compendium_ils-1.0.0/tests/integration/test_csp_nonce.py +125 -0
  389. compendium_ils-1.0.0/tests/integration/test_db_binding.py +174 -0
  390. compendium_ils-1.0.0/tests/integration/test_discovery.py +216 -0
  391. compendium_ils-1.0.0/tests/integration/test_discovery_api.py +182 -0
  392. compendium_ils-1.0.0/tests/integration/test_discovery_web.py +150 -0
  393. compendium_ils-1.0.0/tests/integration/test_engine_sqlite.py +109 -0
  394. compendium_ils-1.0.0/tests/integration/test_fines.py +410 -0
  395. compendium_ils-1.0.0/tests/integration/test_fines_api.py +351 -0
  396. compendium_ils-1.0.0/tests/integration/test_fines_calendar.py +259 -0
  397. compendium_ils-1.0.0/tests/integration/test_fines_circulation.py +326 -0
  398. compendium_ils-1.0.0/tests/integration/test_fines_cli.py +242 -0
  399. compendium_ils-1.0.0/tests/integration/test_fines_web.py +463 -0
  400. compendium_ils-1.0.0/tests/integration/test_hold_suspend.py +341 -0
  401. compendium_ils-1.0.0/tests/integration/test_hold_suspend_api.py +277 -0
  402. compendium_ils-1.0.0/tests/integration/test_hold_suspend_web.py +212 -0
  403. compendium_ils-1.0.0/tests/integration/test_holds.py +402 -0
  404. compendium_ils-1.0.0/tests/integration/test_holds_calendar.py +141 -0
  405. compendium_ils-1.0.0/tests/integration/test_household_model.py +76 -0
  406. compendium_ils-1.0.0/tests/integration/test_household_permission.py +49 -0
  407. compendium_ils-1.0.0/tests/integration/test_import_csv_preserve_barcodes.py +171 -0
  408. compendium_ils-1.0.0/tests/integration/test_import_export_api.py +387 -0
  409. compendium_ils-1.0.0/tests/integration/test_import_export_cli.py +380 -0
  410. compendium_ils-1.0.0/tests/integration/test_import_export_csv.py +232 -0
  411. compendium_ils-1.0.0/tests/integration/test_import_export_lt.py +377 -0
  412. compendium_ils-1.0.0/tests/integration/test_import_export_marc.py +294 -0
  413. compendium_ils-1.0.0/tests/integration/test_import_export_web.py +346 -0
  414. compendium_ils-1.0.0/tests/integration/test_import_metadata_cache.py +235 -0
  415. compendium_ils-1.0.0/tests/integration/test_item_note_autolog.py +218 -0
  416. compendium_ils-1.0.0/tests/integration/test_item_note_model.py +138 -0
  417. compendium_ils-1.0.0/tests/integration/test_kiosk_web.py +347 -0
  418. compendium_ils-1.0.0/tests/integration/test_labels_api.py +292 -0
  419. compendium_ils-1.0.0/tests/integration/test_labels_web.py +505 -0
  420. compendium_ils-1.0.0/tests/integration/test_loanable.py +327 -0
  421. compendium_ils-1.0.0/tests/integration/test_loanable_endpoints.py +348 -0
  422. compendium_ils-1.0.0/tests/integration/test_maintenance_refresh_metadata_cli.py +201 -0
  423. compendium_ils-1.0.0/tests/integration/test_notifications.py +447 -0
  424. compendium_ils-1.0.0/tests/integration/test_notifications_api.py +216 -0
  425. compendium_ils-1.0.0/tests/integration/test_notifications_circulation.py +167 -0
  426. compendium_ils-1.0.0/tests/integration/test_notifications_cli.py +248 -0
  427. compendium_ils-1.0.0/tests/integration/test_notifications_web.py +203 -0
  428. compendium_ils-1.0.0/tests/integration/test_patron_categories.py +266 -0
  429. compendium_ils-1.0.0/tests/integration/test_patron_categories_api.py +213 -0
  430. compendium_ils-1.0.0/tests/integration/test_patron_categories_web.py +146 -0
  431. compendium_ils-1.0.0/tests/integration/test_policy_resolution.py +114 -0
  432. compendium_ils-1.0.0/tests/integration/test_rate_limit_login.py +373 -0
  433. compendium_ils-1.0.0/tests/integration/test_refresh_and_enrich.py +435 -0
  434. compendium_ils-1.0.0/tests/integration/test_refresh_metadata_per_source.py +481 -0
  435. compendium_ils-1.0.0/tests/integration/test_reports.py +258 -0
  436. compendium_ils-1.0.0/tests/integration/test_reports_api.py +257 -0
  437. compendium_ils-1.0.0/tests/integration/test_reports_web.py +241 -0
  438. compendium_ils-1.0.0/tests/integration/test_search.py +250 -0
  439. compendium_ils-1.0.0/tests/integration/test_secrets_cli.py +171 -0
  440. compendium_ils-1.0.0/tests/integration/test_security_headers.py +144 -0
  441. compendium_ils-1.0.0/tests/integration/test_site_settings.py +165 -0
  442. compendium_ils-1.0.0/tests/integration/test_uploads_bounded.py +175 -0
  443. compendium_ils-1.0.0/tests/integration/test_web_curated_lists.py +435 -0
  444. compendium_ils-1.0.0/tests/integration/test_web_households.py +145 -0
  445. compendium_ils-1.0.0/tests/integration/test_web_item_notes.py +244 -0
  446. compendium_ils-1.0.0/tests/integration/test_web_patron_detail_household.py +126 -0
  447. compendium_ils-1.0.0/tests/integration/test_web_ui.py +1886 -0
  448. compendium_ils-1.0.0/tests/integration/test_withdrawn_hidden.py +259 -0
  449. compendium_ils-1.0.0/tests/postgres/__init__.py +0 -0
  450. compendium_ils-1.0.0/tests/postgres/conftest.py +45 -0
  451. compendium_ils-1.0.0/tests/postgres/test_backup_cross_backend.py +210 -0
  452. compendium_ils-1.0.0/tests/postgres/test_pg_smoke.py +182 -0
  453. compendium_ils-1.0.0/tests/unit/__init__.py +0 -0
  454. compendium_ils-1.0.0/tests/unit/services/test_normalization.py +110 -0
  455. compendium_ils-1.0.0/tests/unit/test_audit_service.py +86 -0
  456. compendium_ils-1.0.0/tests/unit/test_auth_service.py +434 -0
  457. compendium_ils-1.0.0/tests/unit/test_backup_safe_ident.py +44 -0
  458. compendium_ils-1.0.0/tests/unit/test_calendar_service.py +269 -0
  459. compendium_ils-1.0.0/tests/unit/test_classification_metadata.py +59 -0
  460. compendium_ils-1.0.0/tests/unit/test_cover_and_mds_lookups.py +123 -0
  461. compendium_ils-1.0.0/tests/unit/test_curated_lists_service.py +536 -0
  462. compendium_ils-1.0.0/tests/unit/test_deactivate.py +173 -0
  463. compendium_ils-1.0.0/tests/unit/test_discovery_service.py +100 -0
  464. compendium_ils-1.0.0/tests/unit/test_goodreads_import.py +312 -0
  465. compendium_ils-1.0.0/tests/unit/test_hold_service.py +258 -0
  466. compendium_ils-1.0.0/tests/unit/test_household_repository.py +100 -0
  467. compendium_ils-1.0.0/tests/unit/test_household_service.py +166 -0
  468. compendium_ils-1.0.0/tests/unit/test_identifiers.py +230 -0
  469. compendium_ils-1.0.0/tests/unit/test_item_note_repository.py +126 -0
  470. compendium_ils-1.0.0/tests/unit/test_item_note_service.py +148 -0
  471. compendium_ils-1.0.0/tests/unit/test_label_canvas_svg.py +276 -0
  472. compendium_ils-1.0.0/tests/unit/test_labels_service.py +1704 -0
  473. compendium_ils-1.0.0/tests/unit/test_loc_ddc_lookup.py +133 -0
  474. compendium_ils-1.0.0/tests/unit/test_loc_lcc_lookup.py +190 -0
  475. compendium_ils-1.0.0/tests/unit/test_lt_import.py +402 -0
  476. compendium_ils-1.0.0/tests/unit/test_marcxml_entity_guard.py +56 -0
  477. compendium_ils-1.0.0/tests/unit/test_metadata_adapters.py +236 -0
  478. compendium_ils-1.0.0/tests/unit/test_metadata_cache.py +349 -0
  479. compendium_ils-1.0.0/tests/unit/test_metadata_google_books.py +326 -0
  480. compendium_ils-1.0.0/tests/unit/test_metadata_source_and_fallback.py +353 -0
  481. compendium_ils-1.0.0/tests/unit/test_patron_service_account.py +160 -0
  482. compendium_ils-1.0.0/tests/unit/test_policy_service.py +79 -0
  483. compendium_ils-1.0.0/tests/unit/test_rate_limit_service.py +58 -0
  484. compendium_ils-1.0.0/tests/unit/test_reports_service.py +182 -0
  485. compendium_ils-1.0.0/tests/unit/test_role_service.py +183 -0
  486. compendium_ils-1.0.0/tests/unit/test_secrets_service.py +137 -0
  487. compendium_ils-1.0.0/tests/unit/test_settings.py +51 -0
  488. compendium_ils-1.0.0/tests/unit/test_settings_migration.py +318 -0
  489. compendium_ils-1.0.0/tests/unit/test_settings_registry.py +415 -0
  490. compendium_ils-1.0.0/tests/unit/test_settings_secret_file.py +57 -0
  491. compendium_ils-1.0.0/tests/unit/test_smtp_sender.py +130 -0
  492. compendium_ils-1.0.0/tests/unit/test_startup_warning.py +78 -0
  493. compendium_ils-1.0.0/tests/unit/test_utc_datetime.py +91 -0
  494. compendium_ils-1.0.0/tests/visual/__init__.py +0 -0
  495. compendium_ils-1.0.0/tests/visual/test_label_matrix.py +112 -0
  496. compendium_ils-1.0.0/uv.lock +1773 -0
@@ -0,0 +1,31 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ name: Build and publish to PyPI
10
+ runs-on: ubuntu-latest
11
+ environment: pypi
12
+ permissions:
13
+ id-token: write # required for OIDC Trusted Publishing
14
+ contents: read
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Python
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: "3.12"
23
+
24
+ - name: Install uv
25
+ uses: astral-sh/setup-uv@v4
26
+
27
+ - name: Build distributions
28
+ run: uv build
29
+
30
+ - name: Publish to PyPI
31
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,60 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+
7
+ # Virtual environments
8
+ .venv/
9
+ venv/
10
+ env/
11
+
12
+ # Distribution / packaging
13
+ build/
14
+ dist/
15
+ *.egg-info/
16
+ *.egg
17
+ wheels/
18
+
19
+ # Tools
20
+ .pytest_cache/
21
+ .ruff_cache/
22
+ .mypy_cache/
23
+ .coverage
24
+ htmlcov/
25
+
26
+ # Environments
27
+ .env
28
+ .env.local
29
+
30
+ # IDE
31
+ .vscode/
32
+ .idea/
33
+ *.swp
34
+ *.swo
35
+
36
+ # OS
37
+ .DS_Store
38
+ Thumbs.db
39
+
40
+ # Project data
41
+ *.db
42
+ *.db-shm
43
+ *.db-wal
44
+ compendium.db
45
+ dev.db
46
+
47
+ # Docker deployment
48
+ docker/.env
49
+ docker/certs/*
50
+ !docker/certs/.gitkeep
51
+
52
+ # Local-only project notes (not published)
53
+ CLAUDE.md
54
+ ils_parity.txt
55
+ docs/claude-deferred.md
56
+ docs/superpowers/
57
+ web_import_test_db
58
+
59
+ # Agent / dev output
60
+ out/
@@ -0,0 +1 @@
1
+ 3.11
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shawn Moore
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,405 @@
1
+ Metadata-Version: 2.4
2
+ Name: compendium-ils
3
+ Version: 1.0.0
4
+ Summary: A library card catalog system for physical items
5
+ Project-URL: Homepage, https://github.com/statyk/compendium
6
+ Project-URL: Repository, https://github.com/statyk/compendium
7
+ Project-URL: Issues, https://github.com/statyk/compendium/issues
8
+ Project-URL: Documentation, https://github.com/statyk/compendium/blob/master/README.md
9
+ Author-email: Shawn <spmoore01@gmail.com>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: books,catalog,circulation,ils,library,lms,marc,opac
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Framework :: FastAPI
15
+ Classifier: Intended Audience :: Other Audience
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: MacOS
18
+ Classifier: Operating System :: POSIX
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Other/Nonlisted Topic
24
+ Requires-Python: >=3.11
25
+ Requires-Dist: alembic>=1.13
26
+ Requires-Dist: bcrypt>=4.0
27
+ Requires-Dist: cryptography>=42.0
28
+ Requires-Dist: defusedxml>=0.7.1
29
+ Requires-Dist: fastapi>=0.111
30
+ Requires-Dist: httpx>=0.27
31
+ Requires-Dist: jinja2>=3.1
32
+ Requires-Dist: pydantic-settings>=2.0
33
+ Requires-Dist: pyjwt>=2.8
34
+ Requires-Dist: pymarc>=5.2
35
+ Requires-Dist: python-barcode>=0.16.1
36
+ Requires-Dist: python-multipart>=0.0.9
37
+ Requires-Dist: reportlab>=4.0
38
+ Requires-Dist: sqlalchemy<3.0,>=2.0
39
+ Requires-Dist: typer>=0.12
40
+ Requires-Dist: uvicorn[standard]>=0.29
41
+ Provides-Extra: dev
42
+ Requires-Dist: pytest>=8.0; extra == 'dev'
43
+ Requires-Dist: ruff>=0.5; extra == 'dev'
44
+ Requires-Dist: testcontainers[postgres]>=4.8; extra == 'dev'
45
+ Provides-Extra: e2e
46
+ Requires-Dist: pytest-playwright>=0.5; extra == 'e2e'
47
+ Provides-Extra: postgres
48
+ Requires-Dist: psycopg[binary]>=3.1; extra == 'postgres'
49
+ Description-Content-Type: text/markdown
50
+
51
+ # Compendium
52
+
53
+ A library card catalog system for physical items — books, vinyl records, DVDs, CDs.
54
+
55
+ WARNING NOTICE CAVEAT EMPTOR:
56
+ This project is 100% vibe coded. Not only did I not write the code, I've barely even looked at it.
57
+ I guided the models (Sonnet and Opus, mostly) on design decisions and the like, but it's pretty much all AI-generated code and documentation.
58
+ This paragraph here is about the only part of the project written by a human.
59
+
60
+
61
+ **Status:** v1.0.0 — core feature-complete. Catalog, circulation, holds, fines, notifications, bulk import/export, web UI, REST API, and CLI are all shipped. See [`docs/architecture.md`](docs/architecture.md) for architecture and design decisions.
62
+
63
+ ## Features
64
+
65
+ - **Catalog** — add items by ISBN / UPC / MusicBrainz ID / TMDb ID / title search (Google Books / Open Library, MusicBrainz, TMDb) or manually for obscure items; search and browse works and copies; faceted discovery (media type, decade, availability)
66
+ - **Circulation** — checkout, checkin, loan renewal with category-aware per-media-type loan policies; lost / damaged / claims-returned states; self-checkout kiosk mode; library hours and holiday calendar so due dates never land on closed days and overdue fines skip closed days
67
+ - **Holds** — patron reservation queue; immediate promotion when a copy is available; suspend/resume; auto-expiry via maintenance command
68
+ - **Fines** — configurable per-policy overdue rates with caps and grace periods; lost/damaged fees; threshold-based checkout/hold blocking; pay/waive workflow; per-patron and bulk overdue assessment
69
+ - **Notifications** — outbox-pattern email delivery (hold-ready, due-soon, overdue) drained by a cron-invoked CLI; admin viewer + retry; per-patron opt-out; configurable retention
70
+ - **Reports** — checkouts/month, popular works, dormant items (weeding list), current overdues; CSV export; Chart.js trendlines
71
+ - **Patrons & cards** — patron categories (Adult/Child/Staff/Teacher seeded), card expiry with maintenance auto-deactivation, optional 1:1 patron↔user link for self-service
72
+ - **Bulk import/export** — round-trippable CSV; MARC21 binary + MARCXML import/export; LibraryThing TSV import; GoodReads CSV import (all with lenient encoding for messy real-world exports)
73
+ - **Backup/restore** — portable JSONL tarballs; backend-agnostic (SQLite ↔ Postgres); doubles as a DB migration path
74
+ - **Labels** — Avery-template item labels (spine / pocket) and patron cards (full / sticker) as PDFs. Spine templates: `avery-5167-spine` (½" narrow face), `avery-5160-spine` (1" medium face, rotated), `avery-5160` (flat wrap-around, centered text), `avery-22805`/`avery-22806` (square classification labels). Live in-page SVG preview updates as you change kind/template/fields — no PDF round-trip needed.
75
+ - **Curated lists** — librarian-editable named collections of Works ("Staff picks", "Summer reads") with slugs, per-work annotations, public/private and featured toggles; featured lists appear as a shelf on the OPAC landing page; public browse at `/ui/lists`
76
+ - **Auth** — five preset roles (ReadOnly, Patron, Librarian, SystemAdmin, Administrator) plus custom roles via the admin UI; JWT for API, cookie-based for web UI
77
+ - **Audit log** — synchronous trail of administrative mutations (Librarian + system tier); queryable via web UI, CLI, or REST
78
+ - **DB-editable settings** — most configuration knobs (library name, fines, kiosk timeout, SMTP, retention, configurable nav shortcuts, etc.) editable from the UI / CLI / API; env vars still win as a break-glass
79
+ - **Web UI** — HTMX + Jinja2 browser interface with catalog search, circulation desk (camera-based barcode scanning), patron self-service, light/dark/auto theme; per-user nav shortcut overrides via browser localStorage
80
+ - **REST API** — FastAPI; consumed by the web UI and available for integrations
81
+ - **CLI** — full librarian + sysadmin workflow without running a server, including stdin/stdout (`-`) for backup, import/export, and labels
82
+
83
+ ## Install
84
+
85
+ ```bash
86
+ # Install the CLI as a standalone tool (recommended for production)
87
+ uv tool install compendium-ils # SQLite only
88
+ uv tool install "compendium-ils[postgres]" # + Postgres support
89
+
90
+ # Or with pip
91
+ pip install compendium-ils
92
+ pip install "compendium-ils[postgres]"
93
+ ```
94
+
95
+ The installed command is **`compendium`**.
96
+
97
+ ## Quick start
98
+
99
+ ### Prerequisites
100
+
101
+ Python 3.11.4 or newer. (Backup restore uses the tarfile `data` filter from
102
+ PEP 706, which was backported to 3.11.4.)
103
+
104
+ **Debian / Ubuntu**
105
+ ```bash
106
+ sudo apt-get update
107
+ sudo apt-get install -y python3 python3-venv python3-dev build-essential
108
+ curl -LsSf https://astral.sh/uv/install.sh | sh
109
+ source $HOME/.local/bin/env
110
+ ```
111
+
112
+ **RedHat / CentOS / Fedora**
113
+ ```bash
114
+ sudo dnf install -y python3 python3-devel gcc
115
+ curl -LsSf https://astral.sh/uv/install.sh | sh
116
+ source $HOME/.local/bin/env
117
+ ```
118
+
119
+ **macOS**
120
+ ```bash
121
+ brew install python uv
122
+ ```
123
+
124
+ ### Run
125
+
126
+ ```bash
127
+ # Install dependencies
128
+ uv sync --extra dev
129
+
130
+ # Initialise the database (creates ./compendium.db with SQLite by default)
131
+ uv run compendium db init
132
+
133
+ # Create an Administrator account
134
+ uv run compendium user add --username admin --role Administrator
135
+
136
+ # Add a book by ISBN (looks up metadata from Google Books when key is set, else Open Library)
137
+ uv run compendium item add --isbn 9780441013593
138
+
139
+ # Add a patron
140
+ uv run compendium patron add --name "Alice Example"
141
+
142
+ # Start the server (web UI at http://localhost:8000/ui/catalog)
143
+ uv run compendium serve
144
+ ```
145
+
146
+ Log in at `http://localhost:8000/ui/login` with the username and password you set above.
147
+
148
+ ## CLI reference
149
+
150
+ Run `compendium --help` for the full command tree, or `compendium <group> --help` for the subcommands of a specific group.
151
+
152
+ **Catalog & cataloging**
153
+ | Group | Common subcommands |
154
+ |---|---|
155
+ | `item` | `add` (--isbn / --upc / --mbid / --tmdb-id / --title), `add-manual`, `show`, `list`, `withdraw`, `set-loanable`; `note add/list/delete`; `declare-lost`, `mark-damaged`, `clear-lost`, `clear-damage` |
156
+ | `work` | `search`, `show`, `new-arrivals`, `recently-returned` |
157
+ | `creator` | `list`, `show`, `merge` |
158
+ | `curated-list` | `create`, `list`, `show`, `edit`, `delete`, `add-work`, `remove-work`, `reorder` |
159
+ | `branch` | `list`, `set` |
160
+ | `import` | `csv`, `marc` (use `-` for stdin) |
161
+ | `export` | `csv`, `marc` (use `-` for stdout) |
162
+
163
+ **Circulation**
164
+ | Group | Common subcommands |
165
+ |---|---|
166
+ | `loan` | `checkout`, `checkin`, `renew`, `active`, `list` (system-wide), `history`, `item-history` |
167
+ | `claim` | `returned` (patron disputes), `verify` (found), `write-off`, `list` |
168
+ | `hold` | `place`, `cancel`, `list`, `queue`, `suspend`, `resume`, `list-suspended` |
169
+ | `fine` | `list`, `pay`, `waive`, `assess`, `assess-overdue` |
170
+
171
+ **Patrons & accounts**
172
+ | Group | Common subcommands |
173
+ |---|---|
174
+ | `patron` | `add`, `list` (`--include-inactive`), `set`, `link-user`, `unlink-user`, `deactivate`, `reactivate` |
175
+ | `patron-category` | `list`, `create`, `update`, `delete` |
176
+ | `user` | `add` (default --role Administrator), `list` (`--include-inactive`), `set-role`, `set-password`, `deactivate`, `reactivate` |
177
+ | `role` | `list`, `create`, `update`, `clone` |
178
+ | `policy` | `list`, `create`, `set` (configures fines, category-aware) |
179
+
180
+ **Reporting & labels**
181
+ | Group | Common subcommands |
182
+ |---|---|
183
+ | `reports` | `checkouts`, `popular`, `dormant`, `overdues` (each supports `--format csv`) |
184
+ | `labels` | `templates`, `spine`, `pocket`, `barcode`, `patron-card`, `patron-sticker` (output `-o -` writes PDF to stdout; use `--show`/`--hide` for field toggles, e.g. `--show barcode` on spine) |
185
+ | `audit` | `list` (filters: `--entity`, `--id`, `--user-id`, `--limit`) |
186
+
187
+ **Operations**
188
+ | Command | Description |
189
+ |---|---|
190
+ | `compendium db init` / `db upgrade` / `db history` | Migrate the database |
191
+ | `compendium serve` | Start the API + web UI server |
192
+ | `compendium backup --output <path-or->` | Write a portable JSONL tarball backup |
193
+ | `compendium restore <path-or->` | Restore from a backup tarball (lenient — auto-migrates) |
194
+ | `compendium settings list/get/set/reset` | Inspect & edit DB-backed site settings |
195
+ | `compendium maintenance ...` | Cron-invoked tasks: `expire-holds`, `resume-expired-suspends`, `assess-overdue-fines`, `queue-due-soon-notices`, `queue-overdue-notices`, `send-queued-notifications`, `prune-notifications`, `prune-audit-log`, `deactivate-expired-patrons`, `prune-cover-cache`, `prune-metadata-cache`, `refresh-metadata` |
196
+ | `compendium metadata cache stats` | Show metadata cache row counts by adapter and TTL status |
197
+ | `compendium metadata cache clear` | Delete all metadata cache rows (audited) |
198
+
199
+ File-argument commands (`backup`, `restore`, `import`, `export`, `labels spine/pocket/barcode/patron-card/patron-sticker`) accept `-` for stdin/stdout. Status messages are routed to stderr in stdio mode so they don't corrupt binary output.
200
+
201
+ ## Web UI
202
+
203
+ Start the server with `compendium serve` and open your browser to `http://localhost:8000/ui/catalog`.
204
+
205
+ "Audience" lists the *minimum* preset that can access each route. Administrator can access everything. SystemAdmin gets the System-tier rows; slimmed Librarian gets librarian-tier rows but not System-tier.
206
+
207
+ | URL | Audience | Description |
208
+ |---|---|---|
209
+ | `/ui/catalog` | Anyone | Search catalog with facets (media type, decade, availability); featured curated lists shelf |
210
+ | `/ui/catalog/{work_id}` | Anyone | Work detail, items, place-hold button |
211
+ | `/ui/lists` | Anyone | Public OPAC browse of public curated lists |
212
+ | `/ui/lists/{slug}` | Anyone | Public curated list detail with annotated works |
213
+ | `/ui/login` | Anyone | Login form |
214
+ | `/ui/me/loans` | Patron | Active loans with inline renew + "I returned this" claim |
215
+ | `/ui/me/holds` | Patron | Active holds with inline cancel + suspend/resume |
216
+ | `/ui/me/fines` | Patron | Outstanding and historical fines |
217
+ | `/ui/me/preferences` | Patron | Notification opt-out |
218
+ | `/ui/me/password` | Patron | Self-service password change |
219
+ | `/ui/circ` | Librarian | Circulation desk — checkout / checkin / renew |
220
+ | `/ui/kiosk` | Librarian | Self-checkout kiosk landing/session (patron card-number-only auth) |
221
+ | `/ui/items/new` | Librarian | Add item by ISBN / UPC / MBID / TMDb ID / title search (with barcode scanner) |
222
+ | `/ui/items/new/manual` | Librarian | Manually add an item not found in external sources |
223
+ | `/ui/items/{barcode}` | Librarian | Item detail, loan history, withdraw, set-loanable, lost/damaged/claims actions |
224
+ | `/ui/patrons` | Librarian | Patron list |
225
+ | `/ui/patrons/new` | Librarian | Create patron (with category + expiry) |
226
+ | `/ui/patrons/{card}` | Librarian | Patron detail with active loans, holds, link/unlink user, deactivate, reactivate |
227
+ | `/ui/patrons/{card}/loans` | Librarian | Patron loan history (active / returned / all) |
228
+ | `/ui/patrons/{card}/fines` | Librarian | Patron fines with pay/waive |
229
+ | `/ui/admin/loans` | Librarian | All active loans (system-wide) with overdue/due-soon filters |
230
+ | `/ui/admin/fines` | Librarian | All outstanding fines (system-wide) with running total |
231
+ | `/ui/admin/holds` | Librarian | All active holds with status/branch/work filters |
232
+ | `/ui/admin/claims` | Librarian | Outstanding claims-returned investigations |
233
+ | `/ui/admin/notifications` | Librarian | Notification log + manual retry |
234
+ | `/ui/admin/import` | Librarian | Bulk CSV / GoodReads CSV / LibraryThing TSV / MARC import |
235
+ | `/ui/admin/export` | Librarian | Bulk CSV/MARC export |
236
+ | `/ui/admin/patron-categories` | Librarian | Manage patron categories |
237
+ | `/ui/curated-lists` | Librarian | Curated list admin CRUD (`curatedlist.manage`) |
238
+ | `/ui/admin/settings/general` | Librarian | Library name, default theme, guest search |
239
+ | `/ui/admin/settings/circulation` | Librarian | Currency, fine thresholds, hold/overdue/due-soon defaults |
240
+ | `/ui/admin/settings/kiosk` | Librarian | Kiosk idle timeout |
241
+ | `/ui/policies` | Librarian | Loan policy list with inline edit |
242
+ | `/ui/policies/new` | Librarian | Create loan policy (per media type + patron category) |
243
+ | `/ui/branches` | Librarian | Branch list with classification scheme |
244
+ | `/ui/branches/{id}/edit` | Librarian | Edit a branch's default classification scheme |
245
+ | `/ui/reports` | Librarian | Reports landing — checkouts, popular, dormant, overdues |
246
+ | `/ui/labels` | Librarian | Generate item-label and patron-card PDFs (Avery templates) |
247
+ | `/ui/audit` | Librarian | Audit log viewer |
248
+ | `/ui/users` | SystemAdmin | User list |
249
+ | `/ui/users/new` | SystemAdmin | Create user |
250
+ | `/ui/users/{username}` | SystemAdmin | User detail — change role, deactivate, reactivate, reset password |
251
+ | `/ui/roles` | SystemAdmin | Role list |
252
+ | `/ui/roles/new` | SystemAdmin | Create custom role |
253
+ | `/ui/roles/{id}` | SystemAdmin | Role detail — edit permissions, clone |
254
+ | `/ui/admin/system/smtp` | SystemAdmin | SMTP host/port/from settings |
255
+ | `/ui/admin/system/secrets` | SystemAdmin | Encrypted secrets (SMTP password, TMDb / Google Books API keys) — requires `COMPENDIUM_SECRET_KEY` |
256
+ | `/ui/admin/system/retention` | SystemAdmin | Notification + audit retention, batch sizes |
257
+
258
+ Guest catalog search is enabled by default (toggle via `/ui/admin/settings/general` or `COMPENDIUM_GUEST_SEARCH_ENABLED=false`).
259
+
260
+ ## REST API endpoints
261
+
262
+ The API is mounted at the root. Authenticate with `POST /auth/login` and pass the result as `Authorization: Bearer <token>`. Interactive docs live at `http://localhost:8000/docs` when the server is running.
263
+
264
+ Below is a high-level inventory grouped by concern; the OpenAPI document is the source of truth for parameters and bodies.
265
+
266
+ | Group | Common endpoints | Min permission |
267
+ |---|---|---|
268
+ | **Auth** | `POST /auth/login` | none |
269
+ | **Catalog** | `GET /works/search`, `/works/new-arrivals`, `/works/recently-returned`; `GET /items/{barcode}`; `POST /items/{barcode}/{withdraw,loanable,lost,damaged,clear-lost,clear-damage}` | guest / `item.view` / `item.delete` / `fine.manage` |
270
+ | **Patrons** | `GET/POST /patrons`, `PATCH /patrons/{card}`, `POST /patrons/{card}/{deactivate,reactivate}`, `POST /patrons/{card}/account`, `GET/POST /patron-categories`, `PATCH/DELETE /patron-categories/{id}` | `patron.manage` / `patron.account.manage` for account endpoints |
271
+ | **Loans** | `POST /loans/checkout`, `/loans/{id}/{checkin,renew}`; `GET /loans` (system-wide), `/loans/patron/{card}`, `/loans/item/{barcode}` | `loan.*` (see below) |
272
+ | **Claims** | `GET /claims`; `POST /claims/{barcode}/{returned,verify,write-off}` | `loan.checkin` |
273
+ | **Holds** | `GET /holds`, `/holds/queue/{work_id}`; `POST/DELETE /holds`, `/holds/{id}`; `POST /holds/{id}/{suspend,resume}` | `hold.*` |
274
+ | **Fines** | `GET /fines`, `GET/POST /patrons/{card}/fines`, `POST /fines/{id}/{pay,waive}`, `POST /patrons/{card}/fines/assess-overdue` | `fine.manage` / `fine.view.self` |
275
+ | **Self-service** | `GET /me/loans`, `/me/holds`; `POST /me/holds`, `/me/holds/{id}/{suspend,resume}`; `DELETE /me/holds/{id}`; `POST /me/loans/{id}/{renew,claim-returned}` | `*.self` permissions |
276
+ | **Notifications** | `GET /notifications`, `POST /notifications/{id}/retry` | `notification.manage` |
277
+ | **Reports** | `GET /reports/{checkouts,popular,dormant,overdues}` | `report.view` |
278
+ | **Bulk import/export** | `POST /import/{csv,goodreads,librarything,marc}` (multipart), `GET /export/{csv,marc}` (streaming) | `catalog.import` / `item.view` |
279
+ | **Labels** | `GET /labels/items`, `/labels/patrons` (PDF) | `labels.generate` |
280
+ | **Policies** | `GET/POST /policies` | `item.view` / `policy.edit` |
281
+ | **Users** | `POST /users`, `POST /users/{username}/{deactivate,reactivate}`, `POST/DELETE /users/{username}/patron` | `user.manage` |
282
+ | **Curated lists** | `GET/POST /curated-lists`, `PATCH/DELETE /curated-lists/{slug}`, `POST/DELETE /curated-lists/{slug}/works` | guest / `curatedlist.manage` |
283
+ | **Settings** | `GET /settings/`, `GET/PATCH/DELETE /settings/{key}` | `patron.manage` (librarian-tier) / `system.manage` (system-tier) |
284
+ | **Calendar** | `GET /library-hours/`, `PATCH /library-hours/{weekday}`; `GET/POST /closed-dates/`, `PATCH/DELETE /closed-dates/{id}` | `calendar.manage` |
285
+ | **Audit** | `GET /audit/` | `audit.view` |
286
+
287
+ The "min permission" column lists the lowest preset role that's allowed. Administrator (wildcard) covers everything. Slimmed Librarian covers librarian-tier endpoints; SystemAdmin covers user/role/system-tier endpoints.
288
+
289
+ ## Configuration
290
+
291
+ Compendium settings come from three layers, in order of precedence:
292
+
293
+ 1. **Environment variable** (`COMPENDIUM_<KEY>`) — break-glass override, wins over everything.
294
+ 2. **`site_setting` table** — DB-backed, editable from `/ui/admin/settings/*`, the CLI (`compendium settings ...`), or the API (`PATCH /settings/{key}`). Changes take effect on the next page render — no restart.
295
+ 3. **Registry default** — fallback hard-coded in `services/settings_registry.py`.
296
+
297
+ Most settings (library name, theme, fine thresholds, hold/overdue defaults, kiosk timeout, SMTP host/port/from, retention) are **DB-editable**. A handful stay env-only because they're either secrets or required before the DB is reachable:
298
+
299
+ | Env var | Why env-only |
300
+ |---|---|
301
+ | `COMPENDIUM_DATABASE_URL` | Bootstrap — needed before any DB read |
302
+ | `COMPENDIUM_JWT_SECRET_KEY` | Secret — **required**; server refuses to start with the built-in default. Generate with `python -c "import secrets; print(secrets.token_urlsafe(48))"`. Set `COMPENDIUM_ALLOW_INSECURE_JWT=1` to bypass for first-run/dev only. |
303
+ | `COMPENDIUM_JWT_ALGORITHM` / `COMPENDIUM_JWT_EXPIRE_MINUTES` | Auth deployment knobs |
304
+ | `COMPENDIUM_SSL_CERTFILE` / `COMPENDIUM_SSL_KEYFILE` | OS-level paths |
305
+ | `COMPENDIUM_SECURE_COOKIES` | Default `true`; set `false` only for plain-HTTP LAN deployments (browsers won't send `Secure` cookies over non-HTTPS, except localhost). |
306
+ | `COMPENDIUM_SECRET_KEY` | Encryption key for secrets stored in the DB (SMTP password, API keys). Optional — set to enable the `/ui/admin/system/secrets` page. Generate with `compendium keygen --secret`. |
307
+ | `COMPENDIUM_TMDB_API_KEY` | TMDb API key. Override — also settable via Admin → System → Secrets when `COMPENDIUM_SECRET_KEY` is configured. |
308
+ | `COMPENDIUM_GOOGLE_BOOKS_API_KEY` | Google Books API key (primary book metadata source when set). Override — also settable via Admin → System → Secrets when `COMPENDIUM_SECRET_KEY` is configured. |
309
+ | `COMPENDIUM_BOOK_METADATA_SOURCE_PREFERENCE` | Primary book metadata source: `googlebooks` (default; uses GB when key is set, else OL) or `openlibrary`. DB-editable at **Admin → System → Metadata Sources**. |
310
+ | `COMPENDIUM_BOOK_METADATA_FALLBACK_ENABLED` | When `true` (default), a miss from the primary book adapter automatically tries the secondary (GB→OL and OL→GB symmetric). Set `false` to disable. DB-editable at **Admin → System → Metadata Sources**. |
311
+ | `COMPENDIUM_METADATA_CACHE_TTL_DAYS` | Positive-hit TTL for the metadata cache in days (default 30). Negative (not-found) entries always expire after 24 hours. DB-editable at **Admin → System → Metadata Sources**. |
312
+ | `COMPENDIUM_SMTP_PASSWORD` | SMTP password. Override — also settable via Admin → System → Secrets when `COMPENDIUM_SECRET_KEY` is configured. |
313
+ | `COMPENDIUM_MAX_UPLOAD_BYTES` | Hard cap on bulk-import upload size (default 100 MB / 104857600). Env-only so a compromised admin token can't raise it. |
314
+ | `COMPENDIUM_LOGIN_MAX_FAILURES` | Max consecutive failed logins before the identity is throttled (default 10). DB-editable at **System → Security**. |
315
+ | `COMPENDIUM_LOGIN_FAILURE_WINDOW_SECONDS` | Sliding window for counting failures in seconds (default 300 = 5 min). DB-editable at **System → Security**. |
316
+
317
+ For everything else, run `compendium settings list` to see the current value, source (env vs db/default), and per-key help text. The web admin UI shows the same with an "⚠ Overridden by env var" indicator on rows where an env var is currently masking the DB value.
318
+
319
+ See [`docs/deployment.md`](docs/deployment.md) for full deployment guidance, or [`docker/README.md`](docker/README.md) for the bundled Docker Compose setup (app + Postgres + nginx with auto-generated self-signed TLS).
320
+
321
+ ### PostgreSQL
322
+
323
+ SQLite is the default and fine for home or classroom use (up to ~10k items). For larger collections or multiple concurrent writers, use PostgreSQL:
324
+
325
+ ```bash
326
+ uv sync --extra postgres
327
+ ```
328
+
329
+ Then set:
330
+
331
+ ```dotenv
332
+ COMPENDIUM_DATABASE_URL=postgresql+psycopg://compendium:<password>@localhost:5432/compendium
333
+ ```
334
+
335
+ and run `compendium db init` to apply migrations. Full setup (creating the role and database, TLS, backups) is in [`docs/deployment.md`](docs/deployment.md#postgresql-setup).
336
+
337
+ ## Scheduled maintenance
338
+
339
+ Several maintenance commands need to run on a cadence — most importantly the
340
+ email outbox drain (`send-queued-notifications`), without which queued
341
+ notifications never go out. Install the bundled crontab via:
342
+
343
+ ```bash
344
+ scripts/install-cron.sh
345
+ ```
346
+
347
+ By default this writes the project path into the crontab and points logs at
348
+ `$HOME/.local/state/compendium/maintenance.log`. Override either with flags:
349
+
350
+ ```bash
351
+ scripts/install-cron.sh --project-dir /opt/compendium --log-file journal
352
+ scripts/install-cron.sh --log-file /var/log/compendium/maintenance.log
353
+ ```
354
+
355
+ `--log-file journal` routes output to the systemd journal (view with
356
+ `journalctl -t compendium-maintenance -f`). For paths the installer can't
357
+ create unprivileged (e.g. `/var/log/...`), it prints the one-time `sudo`
358
+ command and exits without touching the crontab.
359
+
360
+ See [`docs/crontab.sample`](docs/crontab.sample) for the full schedule and
361
+ [`docs/compendium.service.sample`](docs/compendium.service.sample) for
362
+ running the daemon under systemd.
363
+
364
+ ## Running tests
365
+
366
+ ```bash
367
+ uv run pytest -q
368
+ ```
369
+
370
+ Tests are split into `tests/unit/` (no DB, mock repos) and `tests/integration/` (SQLite in-memory).
371
+
372
+ ### Browser tests (E2E)
373
+
374
+ Browser tests run against a real `compendium serve` subprocess in Chromium. They are excluded from the default `pytest` run and require a one-time install:
375
+
376
+ ```bash
377
+ uv sync --extra e2e
378
+ playwright install chromium
379
+ ```
380
+
381
+ Then:
382
+
383
+ ```bash
384
+ uv run pytest -m e2e
385
+ ```
386
+
387
+ Expected wall time: 30–60 seconds. Tests live in `tests/e2e/`. The `test_csp_no_console_errors.py` test is the keystone: it navigates to every major page and asserts no console errors, which catches CSP/HTMX loading regressions that unit tests miss.
388
+
389
+ ## Layout
390
+
391
+ ```
392
+ src/compendium/
393
+ ├── domain/ # models, enums, permissions, errors
394
+ ├── repositories/ # base protocols + SQLAlchemy implementations
395
+ ├── services/ # business logic (catalog, circulation, holds, patrons, policies, auth, audit)
396
+ ├── api/ # FastAPI routes + Pydantic schemas + JWT auth
397
+ ├── web/ # HTMX + Jinja2 web UI + CSRF protection
398
+ ├── cli/ # Typer CLI commands
399
+ ├── config/ # settings, seed data
400
+ └── db/ # engine factory, session lifecycle
401
+ ```
402
+
403
+ ## License
404
+
405
+ MIT (to be finalised before first release).