diff --git a/README.md b/README.md index f578ee3..5b2ff7d 100755 --- a/README.md +++ b/README.md @@ -3,53 +3,61 @@

simple, but useful telegram bot to gather all of group members attention! -## Contents +# Contents * [Getting started.](#getting-started) - * [Installation](#installation) * [Requirements](#requirements) - * [Env file](#env-file) + * [Installation](#installation) + * [Logs](#logs) + * [Env files](#env-files) * [Commands](#commands) * [`/in`](#in) * [`/out`](#out) * [`/everyone`](#everyone) -### Getting started -#### Installation +## Getting started + +### Requirements +- `docker-compose` in version `1.25.0` +- `docker` in version `20.10.7` + +### Installation ```bash git clone https://github.com/miloszowi/everyone-mention-telegram-bot.git -pip install -r requirements.txt -python entrypoint.py ``` - -#### Requirements -- `python` with version specified in `runtime.txt` -- `pip` with version `20.0.2` - -#### Env files -First, copy env files for database and app containers +after that, you need to copy env files and fulfill it with correct values ```bash -cp docker/config/app/app.dist.env docker/config/app/app.env -cp docker/config/database/database.dist.env docker/config/app/app.env +cp docker/config/app.dist.env docker/config/app.env +cp docker/config/database.dist.env docker/config/app.env ``` -and then fulfill copied `.env` files with required values - +and finally, you can run the bot by launching docker containers +```bash +docker-compose up -d +``` +(`-d` flag will run containers in detached mode) +### Logs +You can use +```bash +docker/logs +``` +to check container logs +### Env files app.env -- `bot_token` - your telegram bot token from [BotFather](https://telegram.me/BotFather) +- `BOT_TOKEN` - your telegram bot token from [BotFather](https://telegram.me/BotFather) - `MONGODB_DATABASE` - MongoDB database name - `MONGODB_USERNAME` - MongoDB username - `MONGODB_PASSWORD` - MongoDB password - `MONGODB_HOSTNAME` - MongoDB host (default `database` - container name) -- `MONGODB_PORT` - MongoDB port (default `port` - given in docker-compose configuration) +- `MONGODB_PORT` - MongoDB port (default `27017` - given in docker-compose configuration) database.env - `MONGO_INITDB_ROOT_USERNAME` - conf from `app.env` - `MONGO_INITDB_ROOT_PASSWORD` - conf from `app.env` - `MONGO_INITDB_DATABASE` - conf from `app.env` - `MONGODB_DATA_DIR` - directory to store MongoDB documents (inside a container) -- `MONDODB_LOG_DIR` - log file -### Commands -#### `/in` +- `MONDODB_LOG_DIR` - path to logs storage +## Commands +### `/in` Will sign you in for everyone-mentions. ![in command example](docs/in_command.png) @@ -58,7 +66,7 @@ If you have already opted-in before, alternative reply will be displayed. ![in command when someone already opted in example](docs/in_command_already_opted_in.png) -#### `/out` +### `/out` Will sign you off for everyone-mentions. ![out command example](docs/out_command.png) @@ -67,10 +75,10 @@ If you haven't opted-in before, alternative reply will be displayed. ![out command when someone did not opt in example](docs/out_command_did_not_opt_in_before.png) -#### `/everone` +### `/everyone` Will mention everyone that opted-in for everyone-mentions separated by spaces. -If user does not have nickname, it will assign random name from `names` python library to his ID +If user does not have nickname, it will first try to assign his firstname, then random firstname from `names` python library ![everybody command example](docs/everyone_command.png) diff --git a/docker-compose.yml b/docker-compose.yml index 21047b1..3d7b5b8 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,7 @@ services: image: mongo:4.0.8 restart: unless-stopped env_file: - - ./docker/config/database/database.env + - ./docker/config/database.env volumes: - db-data:/data/db ports: @@ -18,7 +18,7 @@ services: build: . command: python app.py env_file: - - ./docker/config/app/app.env + - ./docker/config/app.env volumes: - ./src:/src ports: diff --git a/docker/config/app/app.dist.env b/docker/config/app.dist.env similarity index 100% rename from docker/config/app/app.dist.env rename to docker/config/app.dist.env diff --git a/docker/config/database/database.dist.env b/docker/config/database.dist.env similarity index 100% rename from docker/config/database/database.dist.env rename to docker/config/database.dist.env diff --git a/src/app.py b/src/app.py index 70eea6c..ffefb32 100755 --- a/src/app.py +++ b/src/app.py @@ -1,15 +1,17 @@ -from config.credentials import bot_token -from config.handlers import handlers -from handlers.handlerInterface import HandlerInterface -from telegram.ext.dispatcher import Dispatcher from telegram.ext import Updater +from telegram.ext.dispatcher import Dispatcher + +from config.credentials import BOT_TOKEN +from handler.abstractHandler import AbstractHandler +from handler import (inHandler, mentionHandler, outHandler) + class App: updater: Updater dispatcher: Dispatcher def __init__(self): - self.updater = Updater(bot_token) + self.updater = Updater(BOT_TOKEN) def run(self) -> None: self.registerHandlers() @@ -18,14 +20,13 @@ class App: self.updater.idle() def registerHandlers(self) -> None: - for handler in handlers: - if not isinstance(handler, HandlerInterface): - raise Exception('Invalid list of handlers provided. Handler must implement HandlerInterface') - - self.updater.dispatcher.add_handler(handler.getBotHandler()) + for handler in AbstractHandler.__subclasses__(): + self.updater.dispatcher.add_handler( + handler().getBotHandler() + ) if __name__ == "__main__": app = App() - app.run() \ No newline at end of file + app.run() diff --git a/src/config/contents.py b/src/config/contents.py index d36c2fc..a3345c1 100755 --- a/src/config/contents.py +++ b/src/config/contents.py @@ -1,8 +1,8 @@ import re # These are MarkdownV2 python-telegram-bot specific -opted_in_successfully = re.escape('You have opted-in for everyone-mentions.') +opted_in = re.escape('You have opted-in for everyone-mentions.') opted_in_failed = re.escape('You already opted-in for everyone-mentions.') -opted_off_successfully = re.escape('You have opted-off for everyone-mentions.') +opted_off = re.escape('You have opted-off for everyone-mentions.') opted_off_failed = re.escape('You need to opt-in first before processing this command.') mention_failed = re.escape('There are no users to mention.') diff --git a/src/config/credentials.py b/src/config/credentials.py index 3bcc710..7a1ffaa 100755 --- a/src/config/credentials.py +++ b/src/config/credentials.py @@ -1,9 +1,10 @@ import os + from dotenv import load_dotenv load_dotenv() -bot_token = os.environ['bot_token'] +BOT_TOKEN = os.environ['BOT_TOKEN'] MONGODB_DATABASE=os.environ['MONGODB_DATABASE'] MONGODB_USERNAME=os.environ['MONGODB_USERNAME'] diff --git a/src/config/handlers.py b/src/config/handlers.py deleted file mode 100755 index 0ea3e9c..0000000 --- a/src/config/handlers.py +++ /dev/null @@ -1,9 +0,0 @@ -from handlers.inHandler import InHandler -from handlers.outHandler import OutHandler -from handlers.mentionHandler import MentionHandler - -handlers = [ - InHandler(), - OutHandler(), - MentionHandler() -] diff --git a/src/database/databaseClient.py b/src/database/client.py similarity index 53% rename from src/database/databaseClient.py rename to src/database/client.py index 97ac57e..2295067 100755 --- a/src/database/databaseClient.py +++ b/src/database/client.py @@ -1,10 +1,13 @@ -from pymongo.errors import ServerSelectionTimeoutError -from config.credentials import MONGODB_USERNAME, MONGODB_PASSWORD, MONGODB_DATABASE, MONGODB_HOSTNAME, MONGODB_PORT -from pymongo import MongoClient -from pymongo.database import Database from urllib.parse import quote_plus -class DatabaseClient(): +from config.credentials import (MONGODB_DATABASE, MONGODB_HOSTNAME, + MONGODB_PASSWORD, MONGODB_PORT, + MONGODB_USERNAME) +from pymongo import MongoClient +from pymongo.database import Database + + +class Client(): mongoClient: MongoClient database: Database @@ -17,14 +20,17 @@ class DatabaseClient(): self.mongoClient = MongoClient(uri) self.database = self.mongoClient[MONGODB_DATABASE] - def insert(self, collection: str, data: dict) -> None: + def insertOne(self, collection: str, data: dict) -> None: self.database.get_collection(collection).insert_one(data) - def find(self, collection: str, query: dict) -> dict: - return self.database.get_collection(collection).find(query) - def findOne(self, collection: str, query: dict) -> dict: return self.database.get_collection(collection).find_one(query) - def remove(self, collection: str, data: dict) -> None: - self.database.get_collection(collection).remove(data) \ No newline at end of file + def findMany(self, collection: str, filter: dict) -> dict: + return self.database.get_collection(collection).find(filter) + + def updateOne(self, collection: str, filter: dict, data: dict) -> None: + self.database.get_collection(collection).update_one( + filter, + { "$set" : data } + ) diff --git a/src/entities/chat.py b/src/entities/chat.py deleted file mode 100755 index ea6217e..0000000 --- a/src/entities/chat.py +++ /dev/null @@ -1,28 +0,0 @@ -from __future__ import annotations -from typing import Optional - - -class Chat(): - id: str - - def __init__(self, id: str) -> None: - self.id = id - - def getId(self) -> str: - return self.id - - def toDict(self) -> dict: - return { - '_id': self.id - } - - @staticmethod - def getMongoRoot() -> str: - return 'chat' - - @staticmethod - def fromDocument(document: Optional[dict]) -> Optional[Chat]: - if not document: - return None - - return Chat(document['_id']) \ No newline at end of file diff --git a/src/entities/chatPerson.py b/src/entities/chatPerson.py deleted file mode 100755 index 3a38791..0000000 --- a/src/entities/chatPerson.py +++ /dev/null @@ -1,34 +0,0 @@ -from __future__ import annotations -from typing import Optional - - -class ChatPerson(): - chat_id: str - person_id: str - - def __init__(self, chatId: str, personId: str) -> None: - self.chat_id = chatId - self.person_id = personId - - def getChatId(self) -> str: - return self.chat_id - - def getPersonId(self) -> str: - return self.person_id - - def toDict(self) -> dict: - return { - '_id': f'{self.chat_id}-{self.person_id}', - 'chat_id': self.chat_id, - 'person_id': self.person_id - } - - @staticmethod - def getMongoRoot() -> str: - return 'chat_person' - - @staticmethod - def fromDocument(document: Optional[dict]) -> Optional[ChatPerson]: - if not document: - return None - return ChatPerson(document['chat_id'], document['person_id']) \ No newline at end of file diff --git a/src/entities/person.py b/src/entities/person.py deleted file mode 100755 index 2c1cfdf..0000000 --- a/src/entities/person.py +++ /dev/null @@ -1,44 +0,0 @@ -from __future__ import annotations -from abc import abstractmethod -from typing import Optional -import names - - -class Person(): - id: str - username: str - - def __init__(self, id: str, username: Optional[str] = None) -> None: - self.id = id - - if not username: - self.username = names.get_first_name() - else: - self.username = username - - def getId(self) -> str: - return self.id - - def getUsername(self) -> str: - return self.username - - def toDict(self, withUsername: bool = True) -> dict: - result = { - '_id': self.id - } - - if withUsername: - result['username'] = self.username - - return result - - @staticmethod - def getMongoRoot() -> str: - return 'person' - - @staticmethod - def fromDocument(document: Optional[dict]) -> Optional[Person]: - if not document: - return None - - return Person(document['_id'], document['username']) \ No newline at end of file diff --git a/src/entity/user.py b/src/entity/user.py new file mode 100644 index 0000000..58fd2b3 --- /dev/null +++ b/src/entity/user.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +from typing import Iterable + + +class User(): + collection: str = 'users' + idIndex: str = '_id' + chatsIndex: str = 'chats' + usernameIndex: str = 'username' + + userId: str + username: str + chats: Iterable[str] + + def __init__(self, userId, username, chats) -> None: + self.userId = userId + self.username = username + self.chats = chats + + def getUserId(self) -> str: + return self.userId + + def getUsername(self) -> str: + return self.username + + def getChats(self) -> Iterable[str]: + return self.chats + + def isInChat(self, chatId: str) -> bool: + return chatId in self.getChats() + + def addToChat(self, chatId: str) -> None: + self.chats.append(chatId) + + def removeFromChat(self, chatId: str) -> None: + if chatId in self.getChats(): + self.chats.remove(chatId) + + def toMongoDocument(self) -> dict: + return { + self.usernameIndex: self.getUsername(), + self.chatsIndex: self.getChats() + } + + @staticmethod + def fromMongoDocument(mongoDocument: dict) -> User: + return User( + mongoDocument[User.idIndex], + mongoDocument[User.usernameIndex], + mongoDocument[User.chatsIndex] + ) diff --git a/src/exception/alreadyExistsException.py b/src/exception/alreadyExistsException.py new file mode 100644 index 0000000..d3498b1 --- /dev/null +++ b/src/exception/alreadyExistsException.py @@ -0,0 +1,2 @@ +class AlreadyExistsException(Exception): + pass \ No newline at end of file diff --git a/src/exception/notFoundException.py b/src/exception/notFoundException.py new file mode 100644 index 0000000..11e1ffd --- /dev/null +++ b/src/exception/notFoundException.py @@ -0,0 +1,2 @@ +class NotFoundException(Exception): + pass \ No newline at end of file diff --git a/src/handlers/handlerInterface.py b/src/handler/abstractHandler.py similarity index 76% rename from src/handlers/handlerInterface.py rename to src/handler/abstractHandler.py index 5a4607c..e5ba4c7 100755 --- a/src/handlers/handlerInterface.py +++ b/src/handler/abstractHandler.py @@ -1,10 +1,13 @@ from abc import abstractmethod + from telegram.ext.callbackcontext import CallbackContext from telegram.ext.handler import Handler from telegram.update import Update +from handler.vo.updateData import UpdateData -class HandlerInterface: + +class AbstractHandler: def __init__(self) -> None: pass @@ -14,8 +17,8 @@ class HandlerInterface: @abstractmethod def handle(self, update: Update, context: CallbackContext) -> None: raise Exception('handle method is not implemented') - @abstractmethod - def getCommandName(self) -> str: raise Exception('getCommandName method is not implemented') + def getUpdateData(self, update: Update) -> UpdateData: + return UpdateData.createFromUpdate(update) def reply(self, update: Update, message: str) -> None: update.effective_message.reply_markdown_v2(text=message) diff --git a/src/handler/inHandler.py b/src/handler/inHandler.py new file mode 100755 index 0000000..045e306 --- /dev/null +++ b/src/handler/inHandler.py @@ -0,0 +1,38 @@ +from config.contents import opted_in, opted_in_failed +from exception.notFoundException import NotFoundException +from repository.userRepository import UserRepository +from telegram.ext.callbackcontext import CallbackContext +from telegram.ext.commandhandler import CommandHandler +from telegram.update import Update + +from handler.abstractHandler import AbstractHandler + + +class InHandler(AbstractHandler): + botHandler: CommandHandler + userRepository: UserRepository + + def __init__(self) -> None: + self.botHandler = CommandHandler('in', self.handle) + self.userRepository = UserRepository() + + def handle(self, update: Update, context: CallbackContext) -> None: + updateData = self.getUpdateData(update) + + try: + user = self.userRepository.getById(updateData.getUserId()) + + if user.isInChat(updateData.getChatId()): + self.reply(update, opted_in_failed) + return + + user.addToChat(updateData.getChatId()) + self.userRepository.save(user) + + except NotFoundException: + self.userRepository.saveByUpdateData(updateData) + + self.reply(update, opted_in) + + def getBotHandler(self) -> CommandHandler: + return self.botHandler diff --git a/src/handler/mentionHandler.py b/src/handler/mentionHandler.py new file mode 100755 index 0000000..72079d7 --- /dev/null +++ b/src/handler/mentionHandler.py @@ -0,0 +1,40 @@ +from typing import Iterable + +from config.contents import mention_failed +from entity.user import User +from repository.userRepository import UserRepository +from telegram.ext.callbackcontext import CallbackContext +from telegram.ext.commandhandler import CommandHandler +from telegram.update import Update + +from handler.abstractHandler import AbstractHandler + + +class MentionHandler(AbstractHandler): + botHandler: CommandHandler + userRepository: UserRepository + + def __init__(self) -> None: + self.botHandler = CommandHandler('everyone', self.handle) + self.userRepository = UserRepository() + + def handle(self, update: Update, context: CallbackContext) -> None: + updateData = self.getUpdateData(update) + users = self.userRepository.getAllForChat(updateData.getChatId()) + + if users: + self.reply(update, self.buildMentionMessage(users)) + return + + self.reply(update, mention_failed) + + def getBotHandler(self) -> CommandHandler: + return self.botHandler + + def buildMentionMessage(self, users: Iterable[User]) -> str: + result = '' + + for user in users: + result += f'*[{user.getUsername()}](tg://user?id={user.getUserId()})* ' + + return result diff --git a/src/handler/outHandler.py b/src/handler/outHandler.py new file mode 100755 index 0000000..2648e11 --- /dev/null +++ b/src/handler/outHandler.py @@ -0,0 +1,36 @@ +from config.contents import opted_off, opted_off_failed +from exception.notFoundException import NotFoundException +from repository.userRepository import UserRepository +from telegram.ext.callbackcontext import CallbackContext +from telegram.ext.commandhandler import CommandHandler +from telegram.update import Update + +from handler.abstractHandler import AbstractHandler + + +class OutHandler(AbstractHandler): + botHandler: CommandHandler + userRepository: UserRepository + + def __init__(self) -> None: + self.botHandler = CommandHandler('out', self.handle) + self.userRepository = UserRepository() + + def handle(self, update: Update, context: CallbackContext) -> None: + updateData = self.getUpdateData(update) + + try: + user = self.userRepository.getById(updateData.getUserId()) + if not user.isInChat(updateData.getChatId()): + raise NotFoundException() + except NotFoundException: + self.reply(update, opted_off_failed) + return + + user.removeFromChat(updateData.getChatId()) + self.userRepository.save(user) + + self.reply(update, opted_off) + + def getBotHandler(self) -> CommandHandler: + return self.botHandler diff --git a/src/handler/vo/updateData.py b/src/handler/vo/updateData.py new file mode 100644 index 0000000..7c6737e --- /dev/null +++ b/src/handler/vo/updateData.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +import names +from telegram.update import Update + + +class UpdateData(): + userId: str + chatId: str + username: str + + def __init__(self, userId: str, chatId: str, username: str) -> None: + self.userId = userId + self.chatId = chatId + self.username = username + + def getUserId(self) -> str: + return self.userId + + def getChatId(self) -> str: + return self.chatId + + def getUsername(self) -> str: + return self.username + + @staticmethod + def createFromUpdate(update: Update) -> UpdateData: + userId = str(update.effective_user.id) + chatId = str(update.effective_chat.id) + chatId = "-284685928" + username = update.effective_user.username or update.effective_user.first_name + + if not username: + username = names.get_first_name() + + return UpdateData(userId, chatId, username) diff --git a/src/handlers/inHandler.py b/src/handlers/inHandler.py deleted file mode 100755 index 49f5840..0000000 --- a/src/handlers/inHandler.py +++ /dev/null @@ -1,38 +0,0 @@ -from config.contents import opted_in_successfully, opted_in_failed -from repositories.relationRepository import RelationRepository -from database.databaseClient import DatabaseClient -from handlers.handlerInterface import HandlerInterface -from telegram.ext.callbackcontext import CallbackContext -from telegram.ext.commandhandler import CommandHandler -from telegram.update import Update - -class InHandler(HandlerInterface): - botHandler: CommandHandler - commandName: str = 'in' - - def __init__(self) -> None: - self.botHandler = CommandHandler( - self.getCommandName(), - self.handle - ) - - def handle(self, update: Update, context: CallbackContext) -> None: - personId = update.effective_user.id - chatId = update.effective_chat.id - username = update.effective_user.username - - relationRepository = RelationRepository() - relation = relationRepository.get(chatId, personId) - - if relation: - self.reply(update, opted_in_failed) - return - - relationRepository.save(chatId, personId, username) - self.reply(update, opted_in_successfully) - - def getBotHandler(self) -> CommandHandler: - return self.botHandler - - def getCommandName(self) -> str: - return self.commandName diff --git a/src/handlers/mentionHandler.py b/src/handlers/mentionHandler.py deleted file mode 100755 index 4cad530..0000000 --- a/src/handlers/mentionHandler.py +++ /dev/null @@ -1,45 +0,0 @@ -from typing import Iterable -from config.contents import mention_failed -from entities.person import Person -from handlers.handlerInterface import HandlerInterface -from repositories.relationRepository import RelationRepository -from repositories.personRepository import PersonRepository -from telegram.ext.callbackcontext import CallbackContext -from telegram.ext.commandhandler import CommandHandler -from telegram.update import Update - - -class MentionHandler(HandlerInterface): - botHandler: CommandHandler - commandName: str = 'everyone' - - def __init__(self) -> None: - self.botHandler = CommandHandler( - self.getCommandName(), - self.handle - ) - - def handle(self, update: Update, context: CallbackContext) -> None: - relationRepository = RelationRepository() - persons = relationRepository.getPersonsForChat(update.effective_chat.id) - - if not persons: - self.reply(update, mention_failed) - return - - self.reply(update, self.buildMentionMessage(persons)) - - - def getBotHandler(self) -> CommandHandler: - return self.botHandler - - def getCommandName(self) -> str: - return self.commandName - - def buildMentionMessage(self, persons: Iterable[Person]) -> str: - result = '' - - for person in persons: - result += f'*[{person.getUsername()}](tg://user?id={person.getId()})* ' - - return result diff --git a/src/handlers/outHandler.py b/src/handlers/outHandler.py deleted file mode 100755 index f0597a5..0000000 --- a/src/handlers/outHandler.py +++ /dev/null @@ -1,38 +0,0 @@ -from config.contents import opted_off_successfully, opted_off_failed -from handlers.handlerInterface import HandlerInterface -from telegram.ext.callbackcontext import CallbackContext -from telegram.ext.commandhandler import CommandHandler -from telegram.update import Update - -from repositories.relationRepository import RelationRepository - - -class OutHandler(HandlerInterface): - botHandler: CommandHandler - commandName: str = 'out' - - def __init__(self) -> None: - self.botHandler = CommandHandler( - self.getCommandName(), - self.handle - ) - - def handle(self, update: Update, context: CallbackContext) -> None: - personId = update.effective_user.id - chatId = update.effective_chat.id - - relationRepository = RelationRepository() - relation = relationRepository.get(chatId, personId) - - if not relation: - self.reply(update, opted_off_failed) - return - - relationRepository.remove(relation) - self.reply(update, opted_off_successfully) - - def getBotHandler(self) -> CommandHandler: - return self.botHandler - - def getCommandName(self) -> str: - return self.commandName diff --git a/src/repositories/chatRepository.py b/src/repositories/chatRepository.py deleted file mode 100755 index ec57f0a..0000000 --- a/src/repositories/chatRepository.py +++ /dev/null @@ -1,18 +0,0 @@ -from database.databaseClient import DatabaseClient -from entities.chat import Chat -from typing import Optional - -class ChatRepository: - database: DatabaseClient - - def __init__(self) -> None: - self.database = DatabaseClient() - - def get(self, id: str) -> Optional[Chat]: - chat = Chat(id) - search = self.database.findOne(Chat.getMongoRoot(), chat.toDict()) - - return Chat.fromDocument(search) - - def save(self, chat: Chat) -> None: - self.database.insert(Chat.getMongoRoot(), chat.toDict()) \ No newline at end of file diff --git a/src/repositories/personRepository.py b/src/repositories/personRepository.py deleted file mode 100755 index b3ee6bb..0000000 --- a/src/repositories/personRepository.py +++ /dev/null @@ -1,27 +0,0 @@ -from database.databaseClient import DatabaseClient -from entities.person import Person -from typing import Iterable, Optional - -class PersonRepository: - database: DatabaseClient - - def __init__(self) -> None: - self.database = DatabaseClient() - - def get(self, id: str) -> Optional[Person]: - person = Person(id) - search = self.database.findOne(Person.getMongoRoot(), person.toDict(False)) - - return Person.fromDocument(search) - - def find(self, query: dict) -> Iterable[Person]: - result = [] - search = self.database.find(Person.getMongoRoot(), query) - - for document in search: - result.append(Person.fromDocument(document)) - - return result - - def save(self, person: Person) -> None: - self.database.insert(Person.getMongoRoot(), person.toDict()) \ No newline at end of file diff --git a/src/repositories/relationRepository.py b/src/repositories/relationRepository.py deleted file mode 100755 index 3482544..0000000 --- a/src/repositories/relationRepository.py +++ /dev/null @@ -1,56 +0,0 @@ -from typing import Iterable, Optional -from database.databaseClient import DatabaseClient -from entities.chat import Chat -from entities.chatPerson import ChatPerson -from entities.person import Person -from repositories.personRepository import PersonRepository -from repositories.chatRepository import ChatRepository - - -class RelationRepository(): - client: DatabaseClient - - def __init__(self) -> None: - self.client = DatabaseClient() - - def get(self, chatId: str, personId: str) -> Optional[ChatPerson]: - relation = ChatPerson(chatId, personId) - search = self.client.findOne(ChatPerson.getMongoRoot(), relation.toDict()) - - return ChatPerson.fromDocument(search) - - def save(self, chatId: str, personId: str, username: Optional[str] = None) -> None: - relation = ChatPerson(chatId, personId) - - self.client.insert(ChatPerson.getMongoRoot(), relation.toDict()) - personRepository = PersonRepository() - person = personRepository.get(personId) - - if not person: - person = Person(personId, username) - personRepository.save(person) - - chatRepository = ChatRepository() - chat = chatRepository.get(chatId) - - if not chat: - chat = Chat(chatId) - chatRepository.save(chat) - - def getPersonsForChat(self, chatId: str) -> Iterable[ChatPerson]: - result = [] - relations = self.client.find(ChatPerson.getMongoRoot(), {'chat_id': chatId}) - - search = {} - for relation in relations: - search['_id'] = relation['person_id'] - - if not search: - return result - - personRepository = PersonRepository() - return personRepository.find(search) - - def remove(self, relation: ChatPerson) -> None: - self.client.remove(ChatPerson.getMongoRoot() ,relation.toDict()) - diff --git a/src/repository/userRepository.py b/src/repository/userRepository.py new file mode 100644 index 0000000..2f16f81 --- /dev/null +++ b/src/repository/userRepository.py @@ -0,0 +1,66 @@ +from typing import Iterable, Optional + +from database.client import Client +from entity.user import User +from exception.notFoundException import NotFoundException +from handler.vo.updateData import UpdateData + + +class UserRepository(): + client: Client + + def __init__(self) -> None: + self.client = Client() + + def getById(self, id: str) -> User: + user = self.client.findOne( + User.collection, + { + User.idIndex: id + } + ) + + if not user: + raise NotFoundException(f'Could not find user with "{id}" id') + + return User( + user[User.idIndex], + user[User.usernameIndex], + user[User.chatsIndex] + ) + + def save(self, user: User) -> None: + self.client.updateOne( + User.collection, + { User.idIndex: user.getUserId() }, + user.toMongoDocument() + ) + + def saveByUpdateData(self, data: UpdateData) -> None: + self.client.insertOne( + User.collection, + { + User.idIndex: data.getUserId(), + User.usernameIndex: data.getUsername(), + User.chatsIndex: [data.getChatId()] + } + ) + + def getAllForChat(self, chatId: str) -> Iterable[User]: + result = [] + users = self.client.findMany( + User.collection, + { + User.chatsIndex: { + "$in" : [chatId] + } + } + ) + + for record in users: + result.append(User.fromMongoDocument(record)) + + return result + + +