postgres demo support

This commit is contained in:
Evgenii Alekseev 2019-09-11 11:56:27 +03:00
parent 98b20a073a
commit c11103a5ba
6 changed files with 194 additions and 7 deletions

View File

@ -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.

View File

@ -1,2 +1,3 @@
[ ] postgres support * [ ] items improvements
[ ] pretty UI * [ ] multiple parties support
* [ ] pretty UI

View File

@ -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'],
}, },
) )

View File

@ -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))

View File

@ -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

View 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
)