From 4ff985bf81a4c7934e6b20444a17244f109ec68c Mon Sep 17 00:00:00 2001 From: Evgeniy Alekseev Date: Wed, 16 Oct 2019 02:48:55 +0300 Subject: [PATCH] some party impl --- migrations/20190916_01_zGTB1-party-id.py | 75 ++++++++++++++++++++ package/ini/ffxivbis.ini | 2 +- src/ffxivbis/api/auth.py | 15 ++-- src/ffxivbis/api/routes.py | 44 ++++++------ src/ffxivbis/api/views/api/login.py | 5 +- src/ffxivbis/api/views/common/login_base.py | 4 +- src/ffxivbis/api/web.py | 12 ++-- src/ffxivbis/application/core.py | 14 +--- src/ffxivbis/core/database.py | 27 ++++---- src/ffxivbis/core/party.py | 23 +++--- src/ffxivbis/core/party_aggregator.py | 26 +++++++ src/ffxivbis/core/postgres.py | 75 +++++++++++--------- src/ffxivbis/core/sqlite.py | 77 ++++++++++++--------- 13 files changed, 262 insertions(+), 137 deletions(-) create mode 100644 migrations/20190916_01_zGTB1-party-id.py create mode 100644 src/ffxivbis/core/party_aggregator.py diff --git a/migrations/20190916_01_zGTB1-party-id.py b/migrations/20190916_01_zGTB1-party-id.py new file mode 100644 index 0000000..b9efddd --- /dev/null +++ b/migrations/20190916_01_zGTB1-party-id.py @@ -0,0 +1,75 @@ +''' +party id +''' + +import random +import string + +from yoyo import step + +__depends__ = {'20190830_01_sYYZL-init-tables', '20190910_01_tgBmx-users-table'} +party_id = ''.join(random.sample(string.ascii_letters, 16)) + +steps = [ + step('''create table players2 ( + party_id text not null, + player_id integer primary key, + created integer not null, + nick text not null, + job text not null, + bis_link text, + priority integer not null default 1 + )'''), + # not safe for injections, but sqlite and psycopg have different placeholders for parameters + step('''insert into players2 select '%s' as party_id, players.* from players''' % (party_id,)), + step('''drop index if exists players_nick_job_idx'''), + step('''create unique index players_nick_job_idx on players2(party_id, nick, job)'''), + + step('''create table loot2 ( + loot_id integer primary key, + player_id integer not null, + created integer not null, + piece text not null, + is_tome integer not null, + foreign key (player_id) references players2(player_id) on delete cascade + )'''), + step('''insert into loot2 select * from loot'''), + step('''drop index if exists loot_owner_idx'''), + step('''create index loot_owner_idx on loot(player_id)'''), + + step('''create table bis2 ( + player_id integer not null, + created integer not null, + piece text not null, + is_tome integer not null, + foreign key (player_id) references players2(player_id) on delete cascade + )'''), + step('''insert into bis2 select * from bis'''), + step('''drop index if exists bis_piece_player_id_idx'''), + step('''create unique index bis_piece_player_id_idx on bis2(player_id, piece)'''), + + step('''create table users2 ( + party_id text not null, + user_id integer primary key, + username text not null, + password text not null, + permission text not null, + foreign key (party_id) references players2(party_id) on delete cascade + )'''), + # not safe for injections, but sqlite and psycopg have different placeholders for parameters + step('''insert into users2 select '%s' as party_id, users.* from users''' % (party_id,)), + step('''drop index if exists users_username_idx'''), + step('''create unique index users_username_idx on users2(party_id, username)'''), + + step('''drop table users'''), + step('''alter table users2 rename to users'''), + + step('''drop table loot'''), + step('''alter table loot2 rename to loot'''), + + step('''drop table bis'''), + step('''alter table bis2 rename to bis'''), + + step('''drop table players'''), + step('''alter table players2 rename to players''') +] diff --git a/package/ini/ffxivbis.ini b/package/ini/ffxivbis.ini index 7bdb2f4..81e5da4 100644 --- a/package/ini/ffxivbis.ini +++ b/package/ini/ffxivbis.ini @@ -7,4 +7,4 @@ priority = is_required loot_count_bis loot_priority loot_count loot_count_total [web] host = 0.0.0.0 port = 8000 -templates = templates +templates = /home/arcanis/Documents/github/ffxivbis/templates \ No newline at end of file diff --git a/src/ffxivbis/api/auth.py b/src/ffxivbis/api/auth.py index efff86e..b9d47b6 100644 --- a/src/ffxivbis/api/auth.py +++ b/src/ffxivbis/api/auth.py @@ -8,7 +8,7 @@ # from aiohttp.web import middleware, Request, Response from aiohttp_security import AbstractAuthorizationPolicy, check_permission -from typing import Callable, Optional +from typing import Callable, Optional, Tuple from ffxivbis.core.database import Database @@ -18,12 +18,19 @@ class AuthorizationPolicy(AbstractAuthorizationPolicy): def __init__(self, database: Database) -> None: self.database = database + def split_identity(self, identity: str) -> Tuple[str, str]: + # identity is party_id + username + party_id, username = identity.split('+') + return party_id, username + async def authorized_userid(self, identity: str) -> Optional[str]: - user = await self.database.get_user(identity) - return identity if user is not None else None + party_id, username = self.split_identity(identity) + user = await self.database.get_user(party_id, username) + return username if user is not None else None async def permits(self, identity: str, permission: str, context: str = None) -> bool: - user = await self.database.get_user(identity) + party_id, username = self.split_identity(identity) + user = await self.database.get_user(party_id, username) if user is None: return False if user.username != identity: diff --git a/src/ffxivbis/api/routes.py b/src/ffxivbis/api/routes.py index a288912..a45581a 100644 --- a/src/ffxivbis/api/routes.py +++ b/src/ffxivbis/api/routes.py @@ -25,21 +25,21 @@ from .views.html.users import UsersHtmlView def setup_routes(app: Application) -> None: # api routes - app.router.add_delete('/admin/api/v1/login/{username}', LoginView) - app.router.add_post('/api/v1/login', LoginView) - app.router.add_post('/api/v1/logout', LogoutView) - app.router.add_put('/admin/api/v1/login', LoginView) + app.router.add_delete('/admin/api/v1/{party_id}/login/{username}', LoginView) + app.router.add_post('/api/v1/{party_id}/login', LoginView) + app.router.add_post('/api/v1/{party_id}/logout', LogoutView) + app.router.add_put('/admin/api/v1/{party_id}/login', LoginView) - app.router.add_get('/api/v1/party', PlayerView) - app.router.add_post('/api/v1/party', PlayerView) + app.router.add_get('/api/v1/party/{party_id}', PlayerView) + app.router.add_post('/api/v1/party/{party_id}', PlayerView) - app.router.add_get('/api/v1/party/bis', BiSView) - app.router.add_post('/api/v1/party/bis', BiSView) - app.router.add_put('/api/v1/party/bis', BiSView) + app.router.add_get('/api/v1/party/{party_id}/bis', BiSView) + app.router.add_post('/api/v1/party/{party_id}/bis', BiSView) + app.router.add_put('/api/v1/party/{party_id}/bis', BiSView) - app.router.add_get('/api/v1/party/loot', LootView) - app.router.add_post('/api/v1/party/loot', LootView) - app.router.add_put('/api/v1/party/loot', LootView) + app.router.add_get('/api/v1/party/{party_id}/loot', LootView) + app.router.add_post('/api/v1/party/{party_id}/loot', LootView) + app.router.add_put('/api/v1/party/{party_id}/loot', LootView) # html routes app.router.add_get('/', IndexHtmlView) @@ -48,19 +48,19 @@ def setup_routes(app: Application) -> None: app.router.add_get('/api-docs', ApiHtmlView) app.router.add_get('/api-docs/swagger.json', ApiDocVIew) - app.router.add_get('/party', PlayerHtmlView) - app.router.add_post('/party', PlayerHtmlView) + app.router.add_get('/party/{party_id}', PlayerHtmlView) + app.router.add_post('/party/{party_id}', PlayerHtmlView) - app.router.add_get('/bis', BiSHtmlView) - app.router.add_post('/bis', BiSHtmlView) + app.router.add_get('/bis/{party_id}', BiSHtmlView) + app.router.add_post('/bis/{party_id}', BiSHtmlView) - app.router.add_get('/loot', LootHtmlView) - app.router.add_post('/loot', LootHtmlView) + app.router.add_get('/loot/{party_id}', LootHtmlView) + app.router.add_post('/loot/{party_id}', LootHtmlView) - app.router.add_get('/suggest', LootSuggestHtmlView) - app.router.add_post('/suggest', LootSuggestHtmlView) + app.router.add_get('/suggest/{party_id}', LootSuggestHtmlView) + app.router.add_post('/suggest/{party_id}', LootSuggestHtmlView) - app.router.add_get('/admin/users', UsersHtmlView) - app.router.add_post('/admin/users', UsersHtmlView) + app.router.add_get('/admin/users/{party_id}', UsersHtmlView) + app.router.add_post('/admin/users/{party_id}', UsersHtmlView) diff --git a/src/ffxivbis/api/views/api/login.py b/src/ffxivbis/api/views/api/login.py index 29194aa..040a847 100644 --- a/src/ffxivbis/api/views/api/login.py +++ b/src/ffxivbis/api/views/api/login.py @@ -126,12 +126,13 @@ class LoginView(LoginBaseView, OpenApi): except Exception: data = dict(await self.request.post()) - required = ['username', 'password'] + required = ['username', 'password', 'party_id'] if any(param not in data for param in required): return wrap_invalid_param(required, data) try: - await self.create_user(data['username'], data['password'], data.get('permission', 'get')) + await self.create_user(data['party_id'], data['username'], + data['password'], data.get('permission', 'get')) except Exception as e: self.request.app.logger.exception('cannot create user') return wrap_exception(e, data) diff --git a/src/ffxivbis/api/views/common/login_base.py b/src/ffxivbis/api/views/common/login_base.py index dd0132f..7e18bcd 100644 --- a/src/ffxivbis/api/views/common/login_base.py +++ b/src/ffxivbis/api/views/common/login_base.py @@ -21,8 +21,8 @@ class LoginBaseView(View): return False return md5_crypt.verify(password, user.password) - async def create_user(self, username: str, password: str, permission: str) -> None: - await self.request.app['database'].insert_user(User(username, password, permission), False) + async def create_user(self, party_id: str, username: str, password: str, permission: str) -> None: + await self.request.app['database'].insert_user(party_id, User(username, password, permission), False) async def login(self, username: str, password: str) -> None: if await self.check_credentials(username, password): diff --git a/src/ffxivbis/api/web.py b/src/ffxivbis/api/web.py index 65743c6..5faa1d9 100644 --- a/src/ffxivbis/api/web.py +++ b/src/ffxivbis/api/web.py @@ -16,8 +16,7 @@ from aiohttp_security import CookiesIdentityPolicy from ffxivbis.core.config import Configuration from ffxivbis.core.database import Database -from ffxivbis.core.loot_selector import LootSelector -from ffxivbis.core.party import Party +from ffxivbis.core.party_aggregator import PartyAggregator from .auth import AuthorizationPolicy, authorize_factory from .routes import setup_routes @@ -35,7 +34,7 @@ def run_server(app: web.Application) -> None: port=app['config'].getint('web', 'port'), handle_signals=False) -def setup_service(config: Configuration, database: Database, loot: LootSelector, party: Party) -> web.Application: +def setup_service(config: Configuration, database: Database, aggregator: PartyAggregator) -> web.Application: app = web.Application(logger=logging.getLogger('http')) app.on_shutdown.append(on_shutdown) @@ -62,10 +61,7 @@ def setup_service(config: Configuration, database: Database, loot: LootSelector, app.logger.info('setup database') app['database'] = database - app.logger.info('setup loot selector') - app['loot'] = loot - - app.logger.info('setup party worker') - app['party'] = party + app.logger.info('setup aggregator') + app['aggregator'] = aggregator return app diff --git a/src/ffxivbis/application/core.py b/src/ffxivbis/application/core.py index 9190e74..460f54f 100644 --- a/src/ffxivbis/application/core.py +++ b/src/ffxivbis/application/core.py @@ -12,9 +12,7 @@ import logging from ffxivbis.api.web import run_server, setup_service from ffxivbis.core.config import Configuration from ffxivbis.core.database import Database -from ffxivbis.core.loot_selector import LootSelector -from ffxivbis.core.party import Party -from ffxivbis.models.user import User +from ffxivbis.core.party_aggregator import PartyAggregator class Application: @@ -29,13 +27,7 @@ class Application: database = loop.run_until_complete(Database.get(self.config)) database.migration() - party = loop.run_until_complete(Party.get(database)) + aggregator = PartyAggregator(self.config, database) - admin = User(self.config.get('auth', 'root_username'), self.config.get('auth', 'root_password'), 'admin') - loop.run_until_complete(database.insert_user(admin, True)) - - priority = self.config.get('settings', 'priority').split() - loot_selector = LootSelector(party, priority) - - web = setup_service(self.config, database, loot_selector, party) + web = setup_service(self.config, database, aggregator) run_server(web) \ No newline at end of file diff --git a/src/ffxivbis/core/database.py b/src/ffxivbis/core/database.py index 3fc0511..2c39a0f 100644 --- a/src/ffxivbis/core/database.py +++ b/src/ffxivbis/core/database.py @@ -59,40 +59,43 @@ class Database: async def init(self) -> None: pass - async def delete_piece(self, player_id: PlayerId, piece: Union[Piece, Upgrade]) -> None: + async def delete_piece(self, party_id: str, player_id: PlayerId, piece: Union[Piece, Upgrade]) -> None: raise NotImplementedError - async def delete_piece_bis(self, player_id: PlayerId, piece: Piece) -> None: + async def delete_piece_bis(self, party_id: str, player_id: PlayerId, piece: Piece) -> None: raise NotImplementedError - async def delete_player(self, player_id: PlayerId) -> None: + async def delete_player(self, party_id: str, player_id: PlayerId) -> None: raise NotImplementedError - async def delete_user(self, username: str) -> None: + async def delete_user(self, party_id: str, username: str) -> None: raise NotImplementedError - async def get_party(self) -> List[Player]: + async def get_party(self, party_id: str) -> List[Player]: raise NotImplementedError - async def get_player(self, player_id: PlayerId) -> Optional[int]: + async def get_player(self, party_id: str, player_id: PlayerId) -> Optional[int]: raise NotImplementedError - async def get_user(self, username: str) -> Optional[User]: + async def get_players(self, party_id: str) -> List[int]: raise NotImplementedError - async def get_users(self) -> List[User]: + async def get_user(self, party_id: str, username: str) -> Optional[User]: raise NotImplementedError - async def insert_piece(self, player_id: PlayerId, piece: Union[Piece, Upgrade]) -> None: + async def get_users(self, party_id: str) -> List[User]: raise NotImplementedError - async def insert_piece_bis(self, player_id: PlayerId, piece: Piece) -> None: + async def insert_piece(self, party_id: str, player_id: PlayerId, piece: Union[Piece, Upgrade]) -> None: raise NotImplementedError - async def insert_player(self, player: Player) -> None: + async def insert_piece_bis(self, party_id: str, player_id: PlayerId, piece: Piece) -> None: raise NotImplementedError - async def insert_user(self, user: User, hashed_password: bool) -> None: + async def insert_player(self, party_id: str, player: Player) -> None: + raise NotImplementedError + + async def insert_user(self, party_id: str, user: User, hashed_password: bool) -> None: raise NotImplementedError def migration(self) -> None: diff --git a/src/ffxivbis/core/party.py b/src/ffxivbis/core/party.py index 48d770b..4a5933c 100644 --- a/src/ffxivbis/core/party.py +++ b/src/ffxivbis/core/party.py @@ -20,8 +20,9 @@ from .database import Database class Party: - def __init__(self, database: Database) -> None: + def __init__(self, party_id: str, database: Database) -> None: self.lock = Lock() + self.party_id = party_id self.players: Dict[PlayerId, Player] = {} self.database = database @@ -31,9 +32,9 @@ class Party: return list(self.players.values()) @classmethod - async def get(cls: Type[Party], database: Database) -> Party: - obj = Party(database) - players = await database.get_party() + async def get(cls: Type[Party], party_id: str, database: Database) -> Party: + obj = cls(party_id, database) + players = await database.get_party(party_id) for player in players: obj.players[player.player_id] = player return obj @@ -42,28 +43,28 @@ class Party: with self.lock: player = self.players[player_id] player.link = link - await self.database.insert_player(player) + await self.database.insert_player(self.party_id, player) async def remove_player(self, player_id: PlayerId) -> Optional[Player]: - await self.database.delete_player(player_id) + await self.database.delete_player(self.party_id, player_id) with self.lock: player = self.players.pop(player_id, None) return player async def set_player(self, player: Player) -> PlayerId: player_id = player.player_id - await self.database.insert_player(player) + await self.database.insert_player(self.party_id, player) with self.lock: self.players[player_id] = player return player_id async def set_item(self, player_id: PlayerId, piece: Union[Piece, Upgrade]) -> None: - await self.database.insert_piece(player_id, piece) + await self.database.insert_piece(self.party_id, player_id, piece) with self.lock: self.players[player_id].loot.append(piece) async def remove_item(self, player_id: PlayerId, piece: Union[Piece, Upgrade]) -> None: - await self.database.delete_piece(player_id, piece) + await self.database.delete_piece(self.party_id, player_id, piece) with self.lock: try: self.players[player_id].loot.remove(piece) @@ -71,11 +72,11 @@ class Party: pass async def set_item_bis(self, player_id: PlayerId, piece: Piece) -> None: - await self.database.insert_piece_bis(player_id, piece) + await self.database.insert_piece_bis(self.party_id, player_id, piece) with self.lock: self.players[player_id].bis.set_item(piece) async def remove_item_bis(self, player_id: PlayerId, piece: Piece) -> None: - await self.database.delete_piece_bis(player_id, piece) + await self.database.delete_piece_bis(self.party_id, player_id, piece) with self.lock: self.players[player_id].bis.remove_item(piece) diff --git a/src/ffxivbis/core/party_aggregator.py b/src/ffxivbis/core/party_aggregator.py new file mode 100644 index 0000000..7c05d9c --- /dev/null +++ b/src/ffxivbis/core/party_aggregator.py @@ -0,0 +1,26 @@ +# +# Copyright (c) 2019 Evgeniy Alekseev. +# +# This file is part of ffxivbis +# (see https://github.com/arcan1s/ffxivbis). +# +# License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause +# +from .config import Configuration +from .database import Database +from .loot_selector import LootSelector +from .party import Party + + +class PartyAggregator: + + def __init__(self, config: Configuration, database: Database) -> None: + self.config = config + self.database = database + + async def get_party(self, party_id: str) -> Party: + return await Party.get(party_id, self.database) + + async def get_loot_selector(self, party: Party) -> LootSelector: + priority = self.config.get('settings', 'priority').split() + return LootSelector(party, priority) \ No newline at end of file diff --git a/src/ffxivbis/core/postgres.py b/src/ffxivbis/core/postgres.py index 079be64..94e2fc4 100644 --- a/src/ffxivbis/core/postgres.py +++ b/src/ffxivbis/core/postgres.py @@ -42,8 +42,8 @@ class PostgresDatabase(Database): self.pool = await asyncpg.create_pool(host=self.host, port=self.port, username=self.username, password=self.password, database=self.database) - async def delete_piece(self, player_id: PlayerId, piece: Union[Piece, Upgrade]) -> None: - player = await self.get_player(player_id) + async def delete_piece(self, party_id: str, player_id: PlayerId, piece: Union[Piece, Upgrade]) -> None: + player = await self.get_player(party_id, player_id) if player is None: return @@ -57,8 +57,8 @@ class PostgresDatabase(Database): player, piece.name, getattr(piece, 'is_tome', True) ) - async def delete_piece_bis(self, player_id: PlayerId, piece: Piece) -> None: - player = await self.get_player(player_id) + async def delete_piece_bis(self, party_id: str, player_id: PlayerId, piece: Piece) -> None: + player = await self.get_player(party_id, player_id) if player is None: return @@ -67,24 +67,29 @@ class PostgresDatabase(Database): '''delete from bis where player_id = $1 and piece = $2''', player, piece.name) - async def delete_player(self, player_id: PlayerId) -> None: + async def delete_player(self, party_id: str, player_id: PlayerId) -> None: async with self.pool.acquire() as conn: - await conn.execute('''delete from players where nick = $1 and job = $2''', - player_id.nick, player_id.job.name) + await conn.execute('''delete from players where nick = $1 and job = $2 and party_id = $3''', + player_id.nick, player_id.job.name, party_id) - async def delete_user(self, username: str) -> None: + async def delete_user(self, party_id: str, username: str) -> None: async with self.pool.acquire() as conn: - await conn.execute('''delete from users where username = $1''', username) + await conn.execute('''delete from users where username = $1 and party_id = $2''', + (username, party_id)) + + async def get_party(self, party_id: str) -> List[Player]: + players = await self.get_players(party_id) + if not players: + return [] - async def get_party(self) -> List[Player]: async with self.pool.acquire() as conn: - rows = await conn.fetch('''select * from bis''') + rows = await conn.fetch('''select * from bis where player_id in $1''', players) bis_pieces = [Loot(row['player_id'], Piece.get(row)) for row in rows] - rows = await conn.fetch('''select * from loot''') + rows = await conn.fetch('''select * from loot where player_id in $1''', players) loot_pieces = [Loot(row['player_id'], Piece.get(row)) for row in rows] - rows = await conn.fetch('''select * from players''') + rows = await conn.fetch('''select * from players where party_id = $1''', party_id) party = { row['player_id']: Player(Job[row['job']], row['nick'], BiS(), [], row['bis_link'], row['priority']) for row in rows @@ -92,24 +97,30 @@ class PostgresDatabase(Database): return self.set_loot(party, bis_pieces, loot_pieces) - async def get_player(self, player_id: PlayerId) -> Optional[int]: + async def get_player(self, party_id: str, player_id: PlayerId) -> Optional[int]: async with self.pool.acquire() as conn: - player = await conn.fetchrow('''select player_id from players where nick = $1 and job = $2''', - player_id.nick, player_id.job.name) + player = await conn.fetchrow('''select player_id from players where nick = $1 and job = $2 and party_id = $3''', + player_id.nick, player_id.job.name, party_id) return player['player_id'] if player is not None else None - async def get_user(self, username: str) -> Optional[User]: + async def get_players(self, party_id: str) -> List[int]: async with self.pool.acquire() as conn: - user = await conn.fetchrow('''select * from users where username = $1''', username) + players = await conn.fetch('''select player_id from players where party_id = $1''', (party_id,)) + return [player['player_id'] for player in players] + + async def get_user(self, party_id: str, username: str) -> Optional[User]: + async with self.pool.acquire() as conn: + user = await conn.fetchrow('''select * from users where username = $1 and party_id = $2''', + username, party_id) return User(user['username'], user['password'], user['permission']) if user is not None else None - async def get_users(self) -> List[User]: + async def get_users(self, party_id: str) -> List[User]: async with self.pool.acquire() as conn: - users = await conn.fetch('''select * from users''') + users = await conn.fetch('''select * from users where party_id = $1''', party_id) return [User(user['username'], user['password'], user['permission']) for user in users] - async def insert_piece(self, player_id: PlayerId, piece: Union[Piece, Upgrade]) -> None: - player = await self.get_player(player_id) + async def insert_piece(self, party_id: str, player_id: PlayerId, piece: Union[Piece, Upgrade]) -> None: + player = await self.get_player(party_id, player_id) if player is None: return @@ -122,8 +133,8 @@ class PostgresDatabase(Database): Database.now(), piece.name, getattr(piece, 'is_tome', True), player ) - async def insert_piece_bis(self, player_id: PlayerId, piece: Piece) -> None: - player = await self.get_player(player_id) + async def insert_piece_bis(self, party_id: str, player_id: PlayerId, piece: Piece) -> None: + player = await self.get_player(party_id, player_id) if player is None: return @@ -138,27 +149,27 @@ class PostgresDatabase(Database): Database.now(), piece.name, piece.is_tome, player ) - async def insert_player(self, player: Player) -> None: + async def insert_player(self, party_id: str, player: Player) -> None: async with self.pool.acquire() as conn: await conn.execute( '''insert into players - (created, nick, job, bis_link, priority) + (party_id, created, nick, job, bis_link, priority) values - ($1, $2, $3, $4, $5) + ($1, $2, $3, $4, $5, $6) on conflict on constraint players_nick_job_idx do update set created = $1, bis_link = $4, priority = $5''', - Database.now(), player.nick, player.job.name, player.link, player.priority + Database.now(), player.nick, player.job.name, player.link, player.priority, party_id ) - async def insert_user(self, user: User, hashed_password: bool) -> None: + async def insert_user(self, party_id: str, user: User, hashed_password: bool) -> None: password = user.password if hashed_password else md5_crypt.hash(user.password) async with self.pool.acquire() as conn: await conn.execute( '''insert into users - (username, password, permission) + (party_id, username, password, permission) values - ($1, $2, $3) + ($1, $2, $3, $4) on conflict on constraint users_username_idx do update set password = $2, permission = $3''', - user.username, password, user.permission + party_id, user.username, password, user.permission ) \ No newline at end of file diff --git a/src/ffxivbis/core/sqlite.py b/src/ffxivbis/core/sqlite.py index fe1ce98..43f79c6 100644 --- a/src/ffxivbis/core/sqlite.py +++ b/src/ffxivbis/core/sqlite.py @@ -31,8 +31,8 @@ class SQLiteDatabase(Database): def connection(self) -> str: return f'sqlite:///{self.database_path}' - async def delete_piece(self, player_id: PlayerId, piece: Union[Piece, Upgrade]) -> None: - player = await self.get_player(player_id) + async def delete_piece(self, party_id: str, player_id: PlayerId, piece: Union[Piece, Upgrade]) -> None: + player = await self.get_player(party_id, player_id) if player is None: return @@ -45,8 +45,8 @@ class SQLiteDatabase(Database): )''', (player, piece.name, getattr(piece, 'is_tome', True))) - async def delete_piece_bis(self, player_id: PlayerId, piece: Piece) -> None: - player = await self.get_player(player_id) + async def delete_piece_bis(self, party_id: str, player_id: PlayerId, piece: Piece) -> None: + player = await self.get_player(party_id, player_id) if player is None: return @@ -55,26 +55,32 @@ class SQLiteDatabase(Database): '''delete from bis where player_id = ? and piece = ?''', (player, piece.name)) - async def delete_player(self, player_id: PlayerId) -> None: + async def delete_player(self, party_id: str, player_id: PlayerId) -> None: async with SQLiteHelper(self.database_path) as cursor: - await cursor.execute('''delete from players where nick = ? and job = ?''', - (player_id.nick, player_id.job.name)) + await cursor.execute('''delete from players where nick = ? and job = ? and party_id = ?''', + (player_id.nick, player_id.job.name, party_id)) - async def delete_user(self, username: str) -> None: + async def delete_user(self, party_id: str, username: str) -> None: async with SQLiteHelper(self.database_path) as cursor: - await cursor.execute('''delete from users where username = ?''', (username,)) + await cursor.execute('''delete from users where username = ? and party_id = ?''', + (username, party_id)) + + async def get_party(self, party_id: str) -> List[Player]: + players = await self.get_players(party_id) + if not players: + return [] + placeholder = ', '.join(['?'] * len(players)) - async def get_party(self) -> List[Player]: async with SQLiteHelper(self.database_path) as cursor: - await cursor.execute('''select * from bis''') + await cursor.execute('''select * from bis where player_id in ({})'''.format(placeholder), players) rows = await cursor.fetchall() bis_pieces = [Loot(row['player_id'], Piece.get(row)) for row in rows] - await cursor.execute('''select * from loot''') + await cursor.execute('''select * from loot where player_id in ({})'''.format(placeholder), players) rows = await cursor.fetchall() loot_pieces = [Loot(row['player_id'], Piece.get(row)) for row in rows] - await cursor.execute('''select * from players''') + await cursor.execute('''select * from players where party_id = ?''', (party_id,)) rows = await cursor.fetchall() party = { row['player_id']: Player(Job[row['job']], row['nick'], BiS(), [], row['bis_link'], row['priority']) @@ -83,27 +89,34 @@ class SQLiteDatabase(Database): return self.set_loot(party, bis_pieces, loot_pieces) - async def get_player(self, player_id: PlayerId) -> Optional[int]: + async def get_player(self, party_id: str, player_id: PlayerId) -> Optional[int]: async with SQLiteHelper(self.database_path) as cursor: - await cursor.execute('''select player_id from players where nick = ? and job = ?''', - (player_id.nick, player_id.job.name)) + await cursor.execute('''select player_id from players where nick = ? and job = ? and party_id = ?''', + (player_id.nick, player_id.job.name, party_id)) player = await cursor.fetchone() return player['player_id'] if player is not None else None - async def get_user(self, username: str) -> Optional[User]: + async def get_players(self, party_id: str) -> List[int]: async with SQLiteHelper(self.database_path) as cursor: - await cursor.execute('''select * from users where username = ?''', (username,)) + await cursor.execute('''select player_id from players where party_id = ?''', (party_id,)) + players = await cursor.fetchall() + return [player['player_id'] for player in players] + + async def get_user(self, party_id: str, username: str) -> Optional[User]: + async with SQLiteHelper(self.database_path) as cursor: + await cursor.execute('''select * from users where username = ? and party_id = ?''', + (username, party_id)) user = await cursor.fetchone() return User(user['username'], user['password'], user['permission']) if user is not None else None - async def get_users(self) -> List[User]: + async def get_users(self, party_id: str) -> List[User]: async with SQLiteHelper(self.database_path) as cursor: - await cursor.execute('''select * from users''') + await cursor.execute('''select * from users where party_id = ?''', (party_id,)) users = await cursor.fetchall() return [User(user['username'], user['password'], user['permission']) for user in users] - async def insert_piece(self, player_id: PlayerId, piece: Union[Piece, Upgrade]) -> None: - player = await self.get_player(player_id) + async def insert_piece(self, party_id: str, player_id: PlayerId, piece: Union[Piece, Upgrade]) -> None: + player = await self.get_player(party_id, player_id) if player is None: return @@ -116,8 +129,8 @@ class SQLiteDatabase(Database): (Database.now(), piece.name, getattr(piece, 'is_tome', True), player) ) - async def insert_piece_bis(self, player_id: PlayerId, piece: Piece) -> None: - player = await self.get_player(player_id) + async def insert_piece_bis(self, party_id: str, player_id: PlayerId, piece: Piece) -> None: + player = await self.get_player(party_id, player_id) if player is None: return @@ -130,23 +143,23 @@ class SQLiteDatabase(Database): (Database.now(), piece.name, piece.is_tome, player) ) - async def insert_player(self, player: Player) -> None: + async def insert_player(self, party_id: str, player: Player) -> None: async with SQLiteHelper(self.database_path) as cursor: await cursor.execute( '''replace into players - (created, nick, job, bis_link, priority) + (party_id, created, nick, job, bis_link, priority) values - (?, ?, ?, ?, ?)''', - (Database.now(), player.nick, player.job.name, player.link, player.priority) + (?, ?, ?, ?, ?, ?)''', + (party_id, Database.now(), player.nick, player.job.name, player.link, player.priority) ) - async def insert_user(self, user: User, hashed_password: bool) -> None: + async def insert_user(self, party_id: str, user: User, hashed_password: bool) -> None: password = user.password if hashed_password else md5_crypt.hash(user.password) async with SQLiteHelper(self.database_path) as cursor: await cursor.execute( '''replace into users - (username, password, permission) + (party_id, username, password, permission) values - (?, ?, ?)''', - (user.username, password, user.permission) + (?, ?, ?, ?)''', + (party_id, user.username, password, user.permission) ) \ No newline at end of file