Closed ethan-stone closed 1 year ago
@Lucas-Kohorst Let me know what you think of this. I know it's a lot lol but kind of important to resolve before I go much further.
So I started working on this and here is where I got. There are two things in play for a user.
User
tableConnection
table. Each Connection
has a connectionType
(TELEGRAM for now, but DISCORD, SLACK etc. would be added later) and a connectionUserId
, the id for that user on that platform id, as well as an optional userId
which references a row in the User
table.Here's a diagram to help https://drawsql.app/teams/personal-419/diagrams/inquire
userId
is optional on Connection
because a user won't exist until they sign up but we will have their Connection
info from interaction with the bot to track their free quota. Once they sign up and formerly add their connection info (ex. sign in with telegram) then the userId
will be added to the Connection
.
So the basic flow from first using the bot to signing up is as follows.
First, the bot service needs to create the Connection
without the userId
to track the quota. I don't know too many details about the bot and what events you receive, but off the top of my head, the best place to create the Connection
is when the bot is invited to a chat. If you don't receive an event when the bot is invited to a chat then maybe on every message we have to check if the Connection
already exists and if it doesn't then create it. This isn't ideal because it wouldn't scale as well since we need to query the database/API on every message.
At some point, the user signs up formerly with Inquire. When they formerly add a connection we check if the Connection
already exists and if it does we just update it with the userId
, and if not we create it.
I also thought to have a better separation of concerns, rather than having the bot retrieve the quota usage through the API, checking that they haven't hit the limit, then checking the subscription status, and then querying dust.tt, we move all of this to the API. That way the business logic of how to authenticate/authorize an Inquiry is handled where the database lives, and we have better performance for having direct access to the database.
Going down this route, when the bot receives a message, it doesn't have to authenticate/authorize, it just calls an API route to create an inquiry. The possible 200 status code responses might look like this.
If the inquiry is successful.
{
"success": true,
"data": {
"id": "unique id",
"result": "This is the result from dv3 or whatever model is used",
"connectionType": "TELEGRAM",
"connectionUserId": "telegram user id"
}
}
If the quota is reached
{
"success": false,
"code": "QUOTA_REACHED"
}
and from here a link would be sent to the user.
This might take a good amount of time to refactor but I think it would be worthwhile. This to me is a better architecture and better for scalability. The bot only has to care about communicating with the platform (TELEGRAM, DISCORD, etc.) trying to create an inquiry, and what to do if creating an inquiry fails. All the business logic of how to authenticate/authorize a connection, the status of a user's subscription and payments, and basically anything that pertains to if a user can make an inquiry is abstracted away from the bot. New pricing plans, quota limits etc. can be added without ever having to change the bot.
Sweet this is awesome and I am highly aligned with it. A few clarifying this and questions
VerificationToken
table although it was not related to any other table. Just curious what this is / would be for This will likely be way more than you want to know. But I figured I would lay it out
All Telegram responses for literally any action are all done through an update
object. Here are some example responses to events.
The bot is invited into a group chat
This basically logs info on the group chat, who updated the chat (this is in the ChatMemberUpdated
), who was added (new_chat_member
), removed / left (old_chat_member
).
Update(
my_chat_member=ChatMemberUpdated(
chat=Chat(
api_kwargs={'all_members_are_administrators': True},
id=-<Group ID ex. 677136673>,
title=<Group Name>,
type=<ChatType.GROUP>),
date=datetime.datetime(2022, 12, 19, 1, 33, 40, tzinfo=<UTC>),
from_user=User(
first_name='Lucas',
id=779540407
is_bot=False,
language_code='en',
username='lucaskohorst'
),
new_chat_member=ChatMemberMember(
status=<ChatMemberStatus.MEMBER>,
user=User(
first_name='Inquire',
id=5856151524,
is_bot=True,
username='inquire_ai_bot'
)
),
old_chat_member=ChatMemberLeft(
status=<ChatMemberStatus.LEFT>,
user=User(
first_name='Inquire',
id=5856151524,
is_bot=True,
username='inquire_ai_bot'
)
)
),
update_id=10526409
)
On message received This is any message sent to the bot in a direct message or a group from a user. This update includes the chat type and who's it is, in this case its a private chat b/t me and the bot.
Update(
my_chat_member=ChatMemberUpdated(
chat=Chat(first_name='Lucas', id=779540407, type=<ChatType.PRIVATE>, username='lucaskohorst'),
date=datetime.datetime(2022, 12, 19, 1, 35, 1, tzinfo=<UTC>),
from_user=User(first_name='Lucas', id=779540407, is_bot=False, language_code='en', username='lucaskohorst'),
new_chat_member=ChatMemberMember(status=<ChatMemberStatus.MEMBER>, user=User(first_name='Inquire', id=5856151524, is_bot=True, username='inquire_ai_bot')),
old_chat_member=ChatMemberBanned(status=<ChatMemberStatus.BANNED>, until_date=datetime.datetime(1970, 1, 1, 0, 0, tzinfo=<UTC>),
user=User(first_name='Inquire', id=5856151524, is_bot=True, username='inquire_ai_bot'))),
update_id=10526411
)
On message sent
This one is quite long and there is tons of repeated information (the User
object is in there like 4 times) but generally gives
text=
entities represent a reply to a message. The "Hi Lucas ..." is the initial message and the "/help" is the reply to that message
Update(
message=Message(channel_chat_created=False, chat=Chat(first_name='Lucas', id=779540407, type=<ChatType.PRIVATE>, username='lucaskohorst'), date=datetime.datetime(2022, 12, 19, 1, 37, 42, tzinfo=<UTC>), delete_chat_photo=False, entities=[MessageEntity(length=5, offset=0, type=<MessageEntityType.BOT_COMMAND>)], from_user=User(first_name='Lucas', id=779540407, is_bot=False, language_code='en', username='lucaskohorst'),
group_chat_created=False,
message_id=2104,
reply_to_message=Message(channel_chat_created=False, chat=Chat(first_name='Lucas', id=779540407, type=<ChatType.PRIVATE>, username='lucaskohorst'), date=datetime.datetime(2022, 12, 19, 1, 35, 3, tzinfo=<UTC>),
delete_chat_photo=False,
entities=[MessageEntity(length=4, offset=3, type=<MessageEntityType.TEXT_MENTION>, user=User(first_name='Lucas', id=779540407, is_bot=False, language_code='en', username='lucaskohorst')), MessageEntity(length=5, offset=75, type=<MessageEntityType.BOT_COMMAND>), MessageEntity(length=7, offset=115, type=<MessageEntityType.BOT_COMMAND>), MessageEntity(length=5, offset=167, type=<MessageEntityType.BOT_COMMAND>), MessageEntity(length=11, offset=211, type=<MessageEntityType.URL>)],
from_user=User(first_name='Inquire', id=5856151524, is_bot=True, username='inquire_ai_bot'),
group_chat_created=False,
message_id=2103,
supergroup_chat_created=False,
text='Hi Lucas! Welcome to Inquire. Get started by using the following commands:\n\n/chat, chat with Inquire about anything\n/search, chat with Inquire with the power of Google\n/draw, draw pictures using StableDiffusion\n\ninquire.run'), supergroup_chat_created=False, text='/help'), update_id=10526413
)
With all of that. Yes it is totally possible to update Connection
on a message to the bot no matter where the message is coming from.
As mentioned moving everything up to the API level will benefit us in a few ways
I am totally in favor of what you suggested. On my side I was going to focus on integrating dust.tt apps into the python telegram client. But now it seems like I should simultaneously work on
ah also just thought of with this, the axiom logging at the bot level isn't really needed and should just be incorporated to the API level
@ethan-stone this issue can be closed right?
Yup this can be closed
Generally this will be what the tg auth decorator will look like
basically consists of
So I think for this I only need routes for
GET
subscription status andGET/PUT
on the limits for a user