From a211a8ab8bb28dc5b858c9bbcd13ce7907c6b2fd Mon Sep 17 00:00:00 2001 From: Padreug Date: Mon, 31 Aug 2026 13:36:32 +0200 Subject: [PATCH] chore: scaffold payroll extension MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .gitignore | 10 +++++++ LICENSE | 21 +++++++++++++ Makefile | 32 ++++++++++++++++++++ README.md | 42 ++++++++++++++++++++++++++ config.json | 23 ++++++++++++++ pyproject.toml | 63 +++++++++++++++++++++++++++++++++++++++ static/image/payroll.png | Bin 0 -> 481 bytes 7 files changed, 191 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 config.json create mode 100644 pyproject.toml create mode 100644 static/image/payroll.png 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 0000000000000000000000000000000000000000..74bf3786a149085658764ad6fcdbe0572fcffb3f GIT binary patch literal 481 zcmeAS@N?(olHy`uVBq!ia0y~yU|0ac983%h3@mFuR536xuJCkm45_&F_U=aBLk0p4 z7p0jOUeP%tbmh#1Mhzv!rA<;&T@ts;{eKBR@tpWR#=`HkYbW=7h7And5M+?RC!>C!&EBjIMJ2sI+~(_+_JDUxRE$u`&DIJI5T&w;LuLC`mVC2(IPkM^WltuL z#D{#_ZF5fr?zLA-I8b-7?qUNYGaHY@1_o|}gaZxIdR8R~5D^fUk$VFJNEXay7JHDp z|L%29<~h^8*`Fz9etsc03v6%$qjid9isWRFQ;}3BFhcCrlV+H{Lyu+7|3mT+bIMK> zoiI|9oUCJ|V+A&z6XdZ2aBG?7{9AZq(E--^EP5<4-)^QOWEy%skEwL?t@eT%-81`^p;^w93vZMh_QnF3<=u}49u{w ct^LZ>r2G3u&ouYD3=9kmp00i_>zopr0F7g$WB>pF literal 0 HcmV?d00001