Repository: lnbits/lnbits

Issue:
Tip Jar extension broken
Tip Jars are broken on v0.9.1

Setup:
- venv python based setup (python 3.9.13)
- LNBits v0.9.1
- Backend: PostgreSQL
- LND-Rest connection

Steps to reproduce: 
- add extension "Tip Jar"
- choose wallet
- do not choose onchain
- test tip jar, fill out donator and amount, hit submit
- error occurrs: `'NoneType' object has no attribute 'mempool_endpoint'`

debug log:
```
"POST /tipjar/api/v1/tips HTTP/1.1" 200
ERROR | lnbits.app:basic_error:182 | handled error
ERROR | lnbits.app:basic_error:183 | ERROR:
Traceback (most recent call last):
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/starlette/middleware/errors.py", line 162, in __call__
    await self.app(scope, receive, _send)
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/starlette/middleware/gzip.py", line 24, in __call__
    await responder(scope, receive, send)
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/starlette/middleware/gzip.py", line 43, in __call__
    await self.app(scope, receive, self.send_with_gzip)
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/starlette/middleware/cors.py", line 84, in __call__
    await self.app(scope, receive, send)
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/starlette/exceptions.py", line 93, in __call__
    raise exc
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/starlette/exceptions.py", line 82, in __call__
    await self.app(scope, receive, sender)
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/fastapi/middleware/asyncexitstack.py", line 21, in __call__
    raise e
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/fastapi/middleware/asyncexitstack.py", line 18, in __call__
    await self.app(scope, receive, send)
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/starlette/routing.py", line 670, in __call__
    await route.handle(scope, receive, send)
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/starlette/routing.py", line 266, in handle
    await self.app(scope, receive, send)
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/starlette/routing.py", line 65, in app
    response = await func(request)
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/fastapi/routing.py", line 227, in app
    raw_response = await run_endpoint_function(
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/fastapi/routing.py", line 160, in run_endpoint_function
    return await dependant.call(**values)
  File "/home/lnbits/lnbits-legend/./lnbits/extensions/satspay/views.py", line 43, in display
    "mempool_endpoint": onchainwallet_config.mempool_endpoint,
AttributeError: 'NoneType' object has no attribute 'mempool_endpoint'
```


Discussion:
Fixed in #824.
Unfortunately, moving from v0.9.1 to HEAD has not resolved the issue.

Furthermore tip jar deletion also throws an error: 
```
"GET /tipjar/api/v1/tips HTTP/1.1" 200
ERROR | lnbits.app:basic_error:182 | handled error
ERROR | lnbits.app:basic_error:183 | ERROR:
Traceback (most recent call last):
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1276, in _execute_context
    self.dialect.do_execute(
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/sqlalchemy/engine/default.py", line 608, in do_execute
    cursor.execute(statement, parameters)
psycopg2.errors.ForeignKeyViolation: update or delete on table "tipjars" violates foreign key constraint "tips_tipjar_fkey" on table "tips"
DETAIL:  Key (id)=(2) is still referenced from table "tips".


The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/starlette/middleware/errors.py", line 162, in __call__
    await self.app(scope, receive, _send)
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/starlette/middleware/gzip.py", line 24, in __call__
    await responder(scope, receive, send)
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/starlette/middleware/gzip.py", line 43, in __call__
    await self.app(scope, receive, self.send_with_gzip)
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/starlette/middleware/cors.py", line 92, in __call__
    await self.simple_response(scope, receive, send, request_headers=headers)
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/starlette/middleware/cors.py", line 147, in simple_response
    await self.app(scope, receive, send)
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/starlette/exceptions.py", line 93, in __call__
    raise exc
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/starlette/exceptions.py", line 82, in __call__
    await self.app(scope, receive, sender)
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/fastapi/middleware/asyncexitstack.py", line 21, in __call__
    raise e
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/fastapi/middleware/asyncexitstack.py", line 18, in __call__
    await self.app(scope, receive, send)
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/starlette/routing.py", line 670, in __call__
    await route.handle(scope, receive, send)
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/starlette/routing.py", line 266, in handle
    await self.app(scope, receive, send)
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/starlette/routing.py", line 65, in app
    response = await func(request)
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/fastapi/routing.py", line 227, in app
    raw_response = await run_endpoint_function(
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/fastapi/routing.py", line 160, in run_endpoint_function
    return await dependant.call(**values)
  File "/home/lnbits/lnbits-legend/./lnbits/extensions/tipjar/views_api.py", line 198, in api_delete_tipjar
    await delete_tipjar(tipjar_id)
  File "/home/lnbits/lnbits-legend/./lnbits/extensions/tipjar/crud.py", line 79, in delete_tipjar
    await db.execute("DELETE FROM tipjar.TipJars WHERE id = ?", (tipjar_id,))
  File "/home/lnbits/lnbits-legend/./lnbits/db.py", line 189, in execute
    return await conn.execute(query, values)
  File "/home/lnbits/lnbits-legend/./lnbits/db.py", line 81, in execute
    return await self.conn.execute(self.rewrite_query(query), values)
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/sqlalchemy_aio/base.py", line 291, in execute
    rp = await self._run_in_thread(
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/sqlalchemy_aio/base.py", line 234, in _run_in_thread
    return await _self._worker.run(_func, args, kwargs)
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/sqlalchemy_aio/asyncio.py", line 70, in run
    return request.response.unwrap()
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/outcome/_impl.py", line 138, in unwrap
    raise captured_error
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1003, in execute
    return self._execute_text(object_, multiparams, params)
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1172, in _execute_text
    ret = self._execute_context(
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1316, in _execute_context
    self._handle_dbapi_exception(
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1510, in _handle_dbapi_exception
    util.raise_(
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/sqlalchemy/util/compat.py", line 182, in raise_
    raise exception
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1276, in _execute_context
    self.dialect.do_execute(
  File "/home/lnbits/lnbits-legend/venv/lib/python3.9/site-packages/sqlalchemy/engine/default.py", line 608, in do_execute
    cursor.execute(statement, parameters)
sqlalchemy.exc.IntegrityError: (psycopg2.errors.ForeignKeyViolation) update or delete on table "tipjars" violates foreign key constraint "tips_tipjar_fkey" on table "tips"
DETAIL:  Key (id)=(2) is still referenced from table "tips".

[SQL: DELETE FROM tipjar.TipJars WHERE id = %s]
[parameters: ('2',)]
(Background on this error at: http://sqlalche.me/e/13/gkpj)

```
Do you still get the error from your first post?
Yes, still there.
I can't reproduce the first issue, i believe it was fixed in #824 !!

As for deleting the tipjar, tips have a `FOREIGN KEY` that references the tipjar, this needs to `ON DELETE CASCADE`
```
CREATE TABLE IF NOT EXISTS tipjar.Tips (
            id TEXT PRIMARY KEY,
            wallet TEXT NOT NULL,
            name TEXT NOT NULL,
            message TEXT NOT NULL,
            sats INT NOT NULL,
            tipjar INT NOT NULL,
            FOREIGN KEY(tipjar) REFERENCES {db.references_schema}TipJars(id) ON DELETE CASCADE
        );
``` 

or we need to delete the all tips that reference the tipjar first, then delete the tipjar (i think!). @callebtc , which way sir?
the problem was, that watchonly migration broke, so the tipjar migration never ran, i fixed this problem with @blckbx by deleting watchonly table and restarting. after that migration ran fine. i think this issue can be closed @blckbx, right?
> the problem was, that watchonly migration broke, so the tipjar migration never ran, i fixed this problem with @blckbx by deleting watchonly table and restarting. after that migration ran fine. i think this issue can be closed @blckbx, right?

the error when deleting still exists! at least with postgres... and from what i'm seeing, the `ON DELETE CASCADE` on SQLite is not that straight forward ! 

Resolving patch:
diff --git a/lnbits/extensions/tipjar/crud.py b/lnbits/extensions/tipjar/crud.py
--- a/lnbits/extensions/tipjar/crud.py
+++ b/lnbits/extensions/tipjar/crud.py
@@ -76,10 +76,10 @@ async def get_tipjars(wallet_id: str) -> Optional[list]:
 
 async def delete_tipjar(tipjar_id: int) -> None:
     """Delete a TipJar and all corresponding Tips"""
-    await db.execute("DELETE FROM tipjar.TipJars WHERE id = ?", (tipjar_id,))
     rows = await db.fetchall("SELECT * FROM tipjar.Tips WHERE tipjar = ?", (tipjar_id,))
     for row in rows:
         await delete_tip(row["id"])
+    await db.execute("DELETE FROM tipjar.TipJars WHERE id = ?", (tipjar_id,))
 
 
 async def get_tip(tip_id: str) -> Optional[Tip]:


Test patch: