commit a211a8ab8bb28dc5b858c9bbcd13ce7907c6b2fd Author: Padreug Date: Mon Aug 31 13:36:32 2026 +0200 chore: scaffold payroll extension Repo tooling and LNbits extension metadata only — no runtime code yet, so this lands separately from the feature work per the workspace commit rules (cross-cutting concerns commit first). - MIT licence, .gitignore, README - Makefile + pyproject wired for the standard aio fork lint pipeline (black + ruff + mypy) and pytest - config.json declaring id/version/tile so the extension is installable from the aiolabs catalog once there is something to install Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_018jy52j9GRZ6XKa1Zt21LLj diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d990a5e --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +__pycache__/ +*.py[cod] +*.egg-info/ +.venv/ +node_modules/ +*.sqlite3 +.DS_Store +.pytest_cache/ +.ruff_cache/ +.mypy_cache/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cc60ed4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 aiolabs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..97f8835 --- /dev/null +++ b/Makefile @@ -0,0 +1,32 @@ +all: format check + +format: black ruff + +check: mypy checkblack checkruff + +mypy: + uv run mypy . + +black: + uv run black . + +ruff: + uv run ruff check . --fix + +checkruff: + uv run ruff check . + +checkblack: + uv run black --check . + +test: + PYTHONUNBUFFERED=1 \ + DEBUG=true \ + uv run pytest + +install-pre-commit-hook: + @echo "Installing pre-commit hook to git" + uv run pre-commit install + +pre-commit: + uv run pre-commit run --all-files diff --git a/README.md b/README.md new file mode 100644 index 0000000..f1adf29 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# Payroll + +Scheduled recurring payouts between LNbits wallets, driven by the instance +**super user**. + +Pick a user from the account directory, point the payout at one of their +wallets, set an amount + currency, a start date, how often it pays and how +many times — Payroll then moves the money on schedule, from a source wallet +you control, and keeps a ledger of every attempt. + +## Status + +Early. Built for `aiolabs` LNbits instances; see `docs/` for the data model +and operational notes. + +## Who can use it + +Everything under `/payroll/api/v1/` that reads the account directory or +touches a contract is gated on `check_super_user` — payroll moves money +between accounts the operator does not own, so wallet-level admin keys are +deliberately *not* enough. (`require_admin_key` authorises writes to your +own wallet only; it is not instance-admin. See the auth table in the +workspace `CLAUDE.md`.) + +Add `payroll` to `LNBITS_ADMIN_EXTENSIONS` so the extension is hidden from +ordinary users in the UI as well. + +## Development + +``` +make # black + ruff + mypy +make test # pytest +``` + +The dev LNbits compose mounts `~/dev/shared/extensions/` at `/shared` with +`LNBITS_EXTENSIONS_PATH=/shared`, so this checkout *is* the installed +extension. FakeWallet is enough — no regtest needed for anything except +end-to-end Lightning behaviour. + +## Licence + +MIT diff --git a/config.json b/config.json new file mode 100644 index 0000000..871fa5d --- /dev/null +++ b/config.json @@ -0,0 +1,23 @@ +{ + "id": "payroll", + "version": "0.1.0", + "name": "Payroll", + "repo": "https://git.atitlan.io/aiolabs/payroll", + "short_description": "Scheduled recurring salary payouts between LNbits wallets", + "description": "", + "tile": "/payroll/static/image/payroll.png", + "min_lnbits_version": "1.4.1", + "contributors": [ + { + "name": "padreug", + "uri": "https://git.atitlan.io/padreug", + "role": "Author" + } + ], + "images": [], + "license": "MIT", + "paid_features": "", + "tags": ["Finance"], + "donate": "", + "hidden": false +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d327cc8 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,63 @@ +[project] +name = "lnbits-payroll" +version = "0.0.0" +requires-python = ">=3.10,<3.13" +description = "Recurring salary payouts between LNbits wallets, scheduled by the instance super user." +authors = [{ name = "aiolabs", email = "dev@aiolabs.dev" }] +urls = { Homepage = "https://git.atitlan.io/aiolabs/payroll", Repository = "https://git.atitlan.io/aiolabs/payroll" } +dependencies = [ "lnbits>1" ] + +[dependency-groups] +dev = [ + "black", + "pytest-asyncio", + "pytest", + "mypy", + "pre-commit", + "ruff", +] + +[tool.poetry] +package-mode = false + +[tool.mypy] +plugins = ["pydantic.mypy"] + +[tool.pydantic-mypy] +init_forbid_extra = true +init_typed = true +warn_required_dynamic_aliases = true +warn_untyped_fields = true + +[tool.pytest.ini_options] +log_cli = false +testpaths = [ + "tests" +] + +[tool.black] +line-length = 88 + +[tool.ruff] +line-length = 88 + +[tool.ruff.lint] +select = ["F", "E", "W", "I", "A", "C", "N", "UP", "RUF", "B"] +ignore = ["C901"] + +fixable = ["ALL"] +unfixable = [] + +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[tool.ruff.lint.pep8-naming] +classmethod-decorators = [ + "validator", + "root_validator", +] + +[tool.ruff.lint.flake8-bugbear] +extend-immutable-calls = [ + "fastapi.Depends", + "fastapi.Query", +] diff --git a/static/image/payroll.png b/static/image/payroll.png new file mode 100644 index 0000000..74bf378 Binary files /dev/null and b/static/image/payroll.png differ