mirror of
				https://github.com/arcan1s/ffxivbis.git
				synced 2025-10-31 05:33:41 +00:00 
			
		
		
		
	postgres demo support
This commit is contained in:
		
							
								
								
									
										13
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								README.md
									
									
									
									
									
								
							| @ -109,7 +109,7 @@ Service which allows to manage savage loot distribution easy. | |||||||
|  |  | ||||||
|     * `include`: path to include configuration directory, string, optional. |     * `include`: path to include configuration directory, string, optional. | ||||||
|     * `logging`: path to logging configuration, see `logging.ini` for reference, string, optional. |     * `logging`: path to logging configuration, see `logging.ini` for reference, string, optional. | ||||||
|     * `database`: database provide name, string, required. Allowed values: `sqlite`. |     * `database`: database provide name, string, required. Allowed values: `sqlite`, `postgres`. | ||||||
|     * `priority`: methods of `Player` class which will be called to sort players for loot priority, space separated list of strings, required. |     * `priority`: methods of `Player` class which will be called to sort players for loot priority, space separated list of strings, required. | ||||||
|      |      | ||||||
| * `ariyala` section | * `ariyala` section | ||||||
| @ -129,6 +129,17 @@ Service which allows to manage savage loot distribution easy. | |||||||
|     * `root_username`: username of administrator, string, required. |     * `root_username`: username of administrator, string, required. | ||||||
|     * `root_password`: md5 hashed password of administrator, string, required. |     * `root_password`: md5 hashed password of administrator, string, required. | ||||||
|      |      | ||||||
|  | * `postgres` section | ||||||
|  |  | ||||||
|  |     Database settings for `postgres` provider. | ||||||
|  |      | ||||||
|  |     * `database`: database name, string, required. | ||||||
|  |     * `host`: database host, string, required. | ||||||
|  |     * `password`: database password, string, required. | ||||||
|  |     * `port`: database port, int, required. | ||||||
|  |     * `username`: database username, string, required. | ||||||
|  |     * `migrations_path`: path to database migrations, string, required. | ||||||
|  |      | ||||||
| * `sqlite` section | * `sqlite` section | ||||||
|  |  | ||||||
|     Database settings for `sqlite` provider. |     Database settings for `sqlite` provider. | ||||||
|  | |||||||
							
								
								
									
										5
									
								
								TODO.md
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								TODO.md
									
									
									
									
									
								
							| @ -1,2 +1,3 @@ | |||||||
| [ ] postgres support | * [ ] items improvements | ||||||
| [ ] pretty UI | * [ ] multiple parties support | ||||||
|  | * [ ] pretty UI | ||||||
							
								
								
									
										3
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								setup.py
									
									
									
									
									
								
							| @ -28,7 +28,6 @@ setup( | |||||||
|         'aiohttp', |         'aiohttp', | ||||||
|         'aiohttp_jinja2', |         'aiohttp_jinja2', | ||||||
|         'aiohttp_security', |         'aiohttp_security', | ||||||
|         'aiosqlite', |  | ||||||
|         'Jinja2', |         'Jinja2', | ||||||
|         'passlib', |         'passlib', | ||||||
|         'requests', |         'requests', | ||||||
| @ -44,6 +43,8 @@ setup( | |||||||
|     include_package_data=True, |     include_package_data=True, | ||||||
|  |  | ||||||
|     extras_require={ |     extras_require={ | ||||||
|  |         'Postgresql': ['aiopg'], | ||||||
|  |         'SQLite':  ['aiosqlite'], | ||||||
|         'test': ['coverage', 'pytest'], |         'test': ['coverage', 'pytest'], | ||||||
|     }, |     }, | ||||||
| ) | ) | ||||||
|  | |||||||
| @ -26,7 +26,7 @@ class Application: | |||||||
|     def run(self) -> None: |     def run(self) -> None: | ||||||
|         loop = asyncio.get_event_loop() |         loop = asyncio.get_event_loop() | ||||||
|  |  | ||||||
|         database = Database.get(self.config) |         database = loop.run_until_complete(Database.get(self.config)) | ||||||
|         database.migration() |         database.migration() | ||||||
|  |  | ||||||
|         party = loop.run_until_complete(Party.get(database)) |         party = loop.run_until_complete(Party.get(database)) | ||||||
|  | |||||||
| @ -35,22 +35,30 @@ class Database: | |||||||
|         return int(datetime.datetime.now().timestamp()) |         return int(datetime.datetime.now().timestamp()) | ||||||
|  |  | ||||||
|     @classmethod |     @classmethod | ||||||
|     def get(cls: Type[Database], config: Configuration) -> Database: |     async def get(cls: Type[Database], config: Configuration) -> Database: | ||||||
|         database_type = config.get('settings', 'database') |         database_type = config.get('settings', 'database') | ||||||
|         database_settings = config.get_section(database_type) |         database_settings = config.get_section(database_type) | ||||||
|  |  | ||||||
|         if database_type == 'sqlite': |         if database_type == 'sqlite': | ||||||
|             from .sqlite import SQLiteDatabase |             from .sqlite import SQLiteDatabase | ||||||
|             obj: Type[Database] = SQLiteDatabase |             obj: Type[Database] = SQLiteDatabase | ||||||
|  |         elif database_type == 'postgres': | ||||||
|  |             from .postgres import PostgresDatabase | ||||||
|  |             obj = PostgresDatabase | ||||||
|         else: |         else: | ||||||
|             raise InvalidDatabase(database_type) |             raise InvalidDatabase(database_type) | ||||||
|  |  | ||||||
|         return obj(**database_settings) |         database = obj(**database_settings) | ||||||
|  |         await database.init() | ||||||
|  |         return database | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def connection(self) -> str: |     def connection(self) -> str: | ||||||
|         raise NotImplementedError |         raise NotImplementedError | ||||||
|  |  | ||||||
|  |     async def init(self) -> None: | ||||||
|  |         pass | ||||||
|  |  | ||||||
|     async def delete_piece(self, player_id: PlayerId, piece: Union[Piece, Upgrade]) -> None: |     async def delete_piece(self, player_id: PlayerId, piece: Union[Piece, Upgrade]) -> None: | ||||||
|         raise NotImplementedError |         raise NotImplementedError | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										166
									
								
								src/service/core/postgres.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								src/service/core/postgres.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,166 @@ | |||||||
|  | # | ||||||
|  | # 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 | ||||||
|  | # | ||||||
|  | import asyncpg | ||||||
|  |  | ||||||
|  | from passlib.hash import md5_crypt | ||||||
|  | from psycopg2.extras import DictCursor | ||||||
|  | from typing import List, Optional, Union | ||||||
|  |  | ||||||
|  | from service.models.bis import BiS | ||||||
|  | from service.models.job import Job | ||||||
|  | from service.models.loot import Loot | ||||||
|  | from service.models.piece import Piece | ||||||
|  | from service.models.player import Player, PlayerId | ||||||
|  | from service.models.upgrade import Upgrade | ||||||
|  | from service.models.user import User | ||||||
|  |  | ||||||
|  | from .database import Database | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PostgresDatabase(Database): | ||||||
|  |  | ||||||
|  |     def __init__(self, host: str, port: int, username: str, password: str, database: str, migrations_path: str) -> None: | ||||||
|  |         Database.__init__(self, migrations_path) | ||||||
|  |         self.host = host | ||||||
|  |         self.port = int(port) | ||||||
|  |         self.username = username | ||||||
|  |         self.password = password | ||||||
|  |         self.database = database | ||||||
|  |         self.pool: asyncpg.pool.Pool = None  # type: ignore | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def connection(self) -> str: | ||||||
|  |         return 'postgresql://{username}:{password}@{host}:{port}/{database}'.format( | ||||||
|  |             username=self.username, password=self.password, host=self.host, port=self.port, database=self.database | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     async def init(self) -> None: | ||||||
|  |         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) | ||||||
|  |         if player is None: | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         async with self.pool.acquire() as conn: | ||||||
|  |             await conn.execute( | ||||||
|  |                 '''delete from loot | ||||||
|  |                 where loot_id in ( | ||||||
|  |                     select loot_id from loot  | ||||||
|  |                     where player_id = $1 and piece = $2 and is_tome = $3 order by created desc limit 1 | ||||||
|  |                 )''', | ||||||
|  |                 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) | ||||||
|  |         if player is None: | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         async with self.pool.acquire() as conn: | ||||||
|  |             await conn.execute( | ||||||
|  |                 '''delete from bis where player_id = $1 and piece = $2''', | ||||||
|  |                 player, piece.name) | ||||||
|  |  | ||||||
|  |     async def delete_player(self, 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) | ||||||
|  |  | ||||||
|  |     async def delete_user(self, username: str) -> None: | ||||||
|  |         async with self.pool.acquire() as conn: | ||||||
|  |             await conn.execute('''delete from users where username = $1''', username) | ||||||
|  |  | ||||||
|  |     async def get_party(self) -> List[Player]: | ||||||
|  |         async with self.pool.acquire() as conn: | ||||||
|  |             rows = await conn.fetch('''select * from bis''') | ||||||
|  |             bis_pieces = [Loot(row['player_id'], Piece.get(row)) for row in rows] | ||||||
|  |  | ||||||
|  |             rows = await conn.fetch('''select * from loot''') | ||||||
|  |             loot_pieces = [Loot(row['player_id'], Piece.get(row)) for row in rows] | ||||||
|  |  | ||||||
|  |             rows = await conn.fetch('''select * from players''') | ||||||
|  |             party = { | ||||||
|  |                 row['player_id']: Player(Job[row['job']], row['nick'], BiS(), [], row['bis_link'], row['priority']) | ||||||
|  |                 for row in rows | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |         return self.set_loot(party, bis_pieces, loot_pieces) | ||||||
|  |  | ||||||
|  |     async def get_player(self, 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) | ||||||
|  |             return player['player_id'] if player is not None else None | ||||||
|  |  | ||||||
|  |     async def get_user(self, username: str) -> Optional[User]: | ||||||
|  |         async with self.pool.acquire() as conn: | ||||||
|  |             user = await conn.fetchrow('''select * from users where username = $1''', username) | ||||||
|  |             return User(user['username'], user['password'], user['permission']) if user is not None else None | ||||||
|  |  | ||||||
|  |     async def get_users(self) -> List[User]: | ||||||
|  |         async with self.pool.acquire() as conn: | ||||||
|  |             users = await conn.fetch('''select * from users''') | ||||||
|  |             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) | ||||||
|  |         if player is None: | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         async with self.pool.acquire() as conn: | ||||||
|  |             await conn.execute( | ||||||
|  |                 '''insert into loot | ||||||
|  |                 (created, piece, is_tome, player_id) | ||||||
|  |                 values | ||||||
|  |                 ($1, $2, $3, $4)''', | ||||||
|  |                 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) | ||||||
|  |         if player is None: | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         async with self.pool.acquire() as conn: | ||||||
|  |             await conn.execute( | ||||||
|  |                 '''insert into bis | ||||||
|  |                 (created, piece, is_tome, player_id) | ||||||
|  |                 values | ||||||
|  |                 ($1, $2, $3, $4) | ||||||
|  |                 on conflict on constraint bis_piece_player_id_idx do update set | ||||||
|  |                 created = $1, is_tome = $3''', | ||||||
|  |                 Database.now(), piece.name, piece.is_tome, player | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |     async def insert_player(self, player: Player) -> None: | ||||||
|  |         async with self.pool.acquire() as conn: | ||||||
|  |             await conn.execute( | ||||||
|  |                 '''insert into players | ||||||
|  |                 (created, nick, job, bis_link, priority) | ||||||
|  |                 values | ||||||
|  |                 ($1, $2, $3, $4, $5) | ||||||
|  |                 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 | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |     async def insert_user(self, 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) | ||||||
|  |                 values | ||||||
|  |                 ($1, $2, $3) | ||||||
|  |                 on conflict on constraint users_username_idx do update set | ||||||
|  |                 password = $2, permission = $3''', | ||||||
|  |                 user.username, password, user.permission | ||||||
|  |             ) | ||||||
		Reference in New Issue
	
	Block a user