Skip to content
Draft
12 changes: 2 additions & 10 deletions docs/source/dbschema.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,15 @@ This is the schema of the database used to store information about the spots and
.. mermaid::

erDiagram
"pending_post" {
user_id BIGINT
u_message_id BIGINT
g_message_id BIGINT PK
admin_group_id BIGINT PK
message_date TIMESTAMP
}

"admin_votes" {
admin_id BIGINT PK
g_message_id BIGINT PK
admin_group_id BIGINT PK
is_upvote BOOLEAN
credit_username VARCHAR
message_date TIMESTAMP
}

pending_post ||--o{ admin_votes : receives

"published_post" {
channel_id BIGINT PK
c_message_id BIGINT PK
Expand Down
89 changes: 85 additions & 4 deletions src/spotted/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,98 @@
"""Modules used in this bot"""

from telegram.ext import Application
import signal

from spotted.data import Config, init_db
from telegram.error import TelegramError
from telegram.ext import Application, CallbackContext

from spotted.data import Config, PendingPost, init_db
from spotted.debug import logger
from spotted.handlers import add_commands, add_handlers, add_jobs
from spotted.handlers.job_handlers import clean_pending


async def _drain_notify(context: CallbackContext):
"""Notifies admins that the bot is shutting down and starts the drain check loop"""
admin_group_id = Config.post_get("admin_group_id")
n_pending = len(PendingPost.get_all(admin_group_id=admin_group_id))

if n_pending == 0:
logger.info("No pending posts, shutting down immediately")
context.application.stop_running()
return

timeout = Config.debug_get("drain_timeout")
await context.bot.send_message(
chat_id=admin_group_id,
text=(
f"Il bot si sta spegnendo. Ci sono {n_pending} spot in sospeso.\n"
f"Approvateli o rifiutateli entro {timeout // 60} minuti."
),
)

context.application.job_queue.run_repeating(
_drain_check, interval=5, first=5, data={"timeout": timeout, "elapsed": 0}
)


async def _drain_check(context: CallbackContext):
"""Checks if drain is complete (no pending posts or timeout reached)"""
admin_group_id = Config.post_get("admin_group_id")
n_pending = len(PendingPost.get_all(admin_group_id=admin_group_id))
data = context.job.data
data["elapsed"] += 5

if n_pending == 0:
logger.info("Drain complete: no pending posts remaining")
context.job.schedule_removal()
context.application.stop_running()
elif data["elapsed"] >= data["timeout"]:
logger.warning("Drain timeout reached with %d pending posts", n_pending)
pending_posts = PendingPost.get_all(admin_group_id=admin_group_id)
await clean_pending(
context.bot,
admin_group_id,
pending_posts,
"Il bot è stato riavviato e il tuo spot in sospeso è andato perso.\n"
"Per favore, invia nuovamente il tuo spot con /spot",
)
await context.bot.send_message(
chat_id=admin_group_id,
text=f"Timeout raggiunto. {n_pending} spot in sospeso sono stati eliminati. Il bot si spegne.",
)
context.job.schedule_removal()
context.application.stop_running()


async def _post_stop(application: Application):
"""Called after the application stops. Sends a final message to admins."""
try:
admin_group_id = Config.post_get("admin_group_id")
await application.bot.send_message(chat_id=admin_group_id, text="Bot spento.")
except TelegramError:
logger.error("Failed to send shutdown message to admin group")


def run_bot():
"""Init the database, add the handlers and start the bot"""

init_db()
application = Application.builder().token(Config.settings_get("token")).post_init(add_commands).build()
application = (
Application.builder().token(Config.settings_get("token")).post_init(add_commands).post_stop(_post_stop).build()
)
add_handlers(application)
add_jobs(application)

application.run_polling()
def _handle_signal(sig, _frame):
"""Custom signal handler that starts the drain process instead of immediately stopping"""
if PendingPost.is_draining():
logger.info("Received signal %s while already draining, ignoring", signal.Signals(sig).name)
return
logger.info("Received signal %s, starting graceful drain", signal.Signals(sig).name)
PendingPost.start_drain()
application.job_queue.run_once(_drain_notify, when=0)

signal.signal(signal.SIGTERM, _handle_signal)
signal.signal(signal.SIGINT, _handle_signal)

application.run_polling(stop_signals=())
4 changes: 2 additions & 2 deletions src/spotted/config/db/post_db_del.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ DROP TABLE IF EXISTS credited_users
-----
DROP TABLE IF EXISTS admin_votes
-----
DROP TABLE IF EXISTS pending_post
-----
DROP TABLE IF EXISTS published_post
-----
DROP TABLE IF EXISTS banned_users
Expand All @@ -14,3 +12,5 @@ DROP TABLE IF EXISTS spot_report
DROP TABLE IF EXISTS user_report
-----
DROP TABLE IF EXISTS user_follow
-----
DROP TABLE IF EXISTS pending_post
18 changes: 4 additions & 14 deletions src/spotted/config/db/post_db_init.sql
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/*Used to instantiate the database the first time*/
CREATE TABLE IF NOT EXISTS pending_post
CREATE TABLE IF NOT EXISTS admin_votes
(
user_id BIGINT NOT NULL,
u_message_id BIGINT NOT NULL,
admin_id BIGINT NOT NULL,
g_message_id BIGINT NOT NULL,
admin_group_id BIGINT NOT NULL,
is_upvote boolean NOT NULL,
credit_username VARCHAR(255) DEFAULT NULL,
message_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (admin_group_id, g_message_id)
PRIMARY KEY (admin_id, g_message_id, admin_group_id)
);
-----
CREATE TABLE IF NOT EXISTS published_post
Expand All @@ -18,16 +18,6 @@ CREATE TABLE IF NOT EXISTS published_post
PRIMARY KEY (channel_id, c_message_id)
);
-----
CREATE TABLE IF NOT EXISTS admin_votes
(
admin_id BIGINT NOT NULL,
g_message_id BIGINT NOT NULL,
admin_group_id BIGINT NOT NULL,
is_upvote boolean NOT NULL,
PRIMARY KEY (admin_id, g_message_id, admin_group_id),
FOREIGN KEY (g_message_id, admin_group_id) REFERENCES pending_post (g_message_id, admin_group_id) ON DELETE CASCADE ON UPDATE CASCADE
);
-----
CREATE TABLE IF NOT EXISTS credited_users
(
user_id BIGINT NOT NULL,
Expand Down
1 change: 1 addition & 0 deletions src/spotted/config/yaml/settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ debug:
db_file: "spotted.sqlite3"
crypto_key: ""
zip_backup: false
drain_timeout: 300
post:
community_group_id: -1
channel_id: -2
Expand Down
1 change: 1 addition & 0 deletions src/spotted/config/yaml/settings.yaml.types
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ debug:
log_error_file: str
crypto_key: str
zip_backup: bool
drain_timeout: int
post:
community_group_id: int
channel_id: int
Expand Down
Loading