4 Commits

Author SHA1 Message Date
4ff985bf81 some party impl 2019-10-16 02:48:55 +03:00
2d84459c4d replace requests by aiohttp 2019-09-15 03:27:29 +03:00
28dabcb44e rename service to ffxivbis, add notes about venv 2019-09-15 02:57:53 +03:00
36f0b8151a fix args rename 2019-09-15 01:37:30 +03:00
74 changed files with 531 additions and 381 deletions

View File

@ -9,14 +9,23 @@ This service requires python >= 3.7. For other dependencies see `setup.py`.
In general installation process looks like:
```bash
python setup.py build
python setup.py build install
python setup.py test # if you want to run tests
```
Service can be run from `src` directory by using command:
With virtualenv (make sure that virtualenv package was installed) the process may look like:
```bash
python -m service.application.application
virtualenv -p python3.7 env
source env/bin/activate
python setup.py install
pip install aiosqlite # setup.py does not handle extras
```
Service can be run by using command (if you don't use virtualenv, you have to run it from `src` directory):
```bash
python -m ffxivbis.application.application
```
To see all available options type `--help`.
@ -27,6 +36,17 @@ REST API documentation is available at `http://0.0.0.0:8000/api-docs`. HTML repr
*Note*: host and port depend on configuration settings.
### Authorization
Default admin user is `admin:qwerty`, but it may be changed by generating new hash, e.g.:
```python
from passlib.hash import md5_crypt
md5_crypt.hash('newstrongpassword')
```
and add new password to configuration.
## Configuration
* `settings` section
@ -43,7 +63,6 @@ REST API documentation is available at `http://0.0.0.0:8000/api-docs`. HTML repr
Settings related to ariyala parser.
* `ariyala_url`: ariyala base url, string, required.
* `request_timeout`: xivapi request timeout, float, optional, default 30.
* `xivapi_key`: xivapi developer key, string, optional.
* `xivapi_url`: xivapi base url, string, required.

View File

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

View File

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

View File

@ -1,4 +1,3 @@
[ariyala]
ariyala_url = https://ffxiv.ariyala.com
request_timeout = 1
xivapi_url = https://xivapi.com

View File

@ -5,7 +5,7 @@ from os import path
here = path.abspath(path.dirname(__file__))
metadata = dict()
with open(convert_path('src/service/core/version.py')) as metadata_file:
with open(convert_path('src/ffxivbis/core/version.py')) as metadata_file:
exec(metadata_file.read(), metadata)
@ -22,16 +22,17 @@ setup(
license='BSD',
packages=find_packages(exclude=['contrib', 'docs', 'tests']),
package_dir={'': 'src'},
packages=find_packages(where='src', exclude=['contrib', 'docs', 'test']),
install_requires=[
'aiohttp',
'aiohttp==3.6.0',
'aiohttp_jinja2',
'aiohttp_security',
'apispec',
'iniherit',
'Jinja2',
'passlib',
'requests',
'yoyo_migrations'
],
setup_requires=[
@ -44,7 +45,7 @@ setup(
include_package_data=True,
extras_require={
'Postgresql': ['aiopg'],
'Postgresql': ['asyncpg'],
'SQLite': ['aiosqlite'],
'test': ['coverage', 'pytest'],
},

View File

@ -8,9 +8,9 @@
#
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 service.core.database import Database
from ffxivbis.core.database import Database
class AuthorizationPolicy(AbstractAuthorizationPolicy):
@ -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:

View File

@ -0,0 +1,66 @@
#
# 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 aiohttp.web import Application
from .views.api.bis import BiSView
from .views.api.login import LoginView
from .views.api.logout import LogoutView
from .views.api.loot import LootView
from .views.api.player import PlayerView
from .views.html.api import ApiDocVIew, ApiHtmlView
from .views.html.bis import BiSHtmlView
from .views.html.index import IndexHtmlView
from .views.html.loot import LootHtmlView
from .views.html.loot_suggest import LootSuggestHtmlView
from .views.html.player import PlayerHtmlView
from .views.html.static import StaticHtmlView
from .views.html.users import UsersHtmlView
def setup_routes(app: Application) -> None:
# api routes
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/{party_id}', PlayerView)
app.router.add_post('/api/v1/party/{party_id}', PlayerView)
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/{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)
app.router.add_get('/static/{resource_id}', StaticHtmlView)
app.router.add_get('/api-docs', ApiHtmlView)
app.router.add_get('/api-docs/swagger.json', ApiDocVIew)
app.router.add_get('/party/{party_id}', PlayerHtmlView)
app.router.add_post('/party/{party_id}', PlayerHtmlView)
app.router.add_get('/bis/{party_id}', BiSHtmlView)
app.router.add_post('/bis/{party_id}', BiSHtmlView)
app.router.add_get('/loot/{party_id}', LootHtmlView)
app.router.add_post('/loot/{party_id}', LootHtmlView)
app.router.add_get('/suggest/{party_id}', LootSuggestHtmlView)
app.router.add_post('/suggest/{party_id}', LootSuggestHtmlView)
app.router.add_get('/admin/users/{party_id}', UsersHtmlView)
app.router.add_post('/admin/users/{party_id}', UsersHtmlView)

View File

@ -9,17 +9,17 @@
from aiohttp.web import Application
from apispec import APISpec
from service.core.version import __version__
from service.models.action import Action
from service.models.bis import BiS, BiSLink
from service.models.error import Error
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, PlayerIdWithCounters
from service.models.player_edit import PlayerEdit
from service.models.upgrade import Upgrade
from service.models.user import User
from ffxivbis.core.version import __version__
from ffxivbis.models.action import Action
from ffxivbis.models.bis import BiS, BiSLink
from ffxivbis.models.error import Error
from ffxivbis.models.job import Job
from ffxivbis.models.loot import Loot
from ffxivbis.models.piece import Piece
from ffxivbis.models.player import Player, PlayerId, PlayerIdWithCounters
from ffxivbis.models.player_edit import PlayerEdit
from ffxivbis.models.upgrade import Upgrade
from ffxivbis.models.user import User
def get_spec(app: Application) -> APISpec:

View File

@ -9,12 +9,12 @@
from aiohttp.web import Response
from typing import Any, Dict, List, Optional, Type
from service.models.job import Job
from service.models.piece import Piece
from service.models.player import PlayerId
from ffxivbis.models.job import Job
from ffxivbis.models.piece import Piece
from ffxivbis.models.player import PlayerId
from service.api.utils import wrap_exception, wrap_invalid_param, wrap_json
from service.api.views.common.bis_base import BiSBaseView
from ffxivbis.api.utils import wrap_exception, wrap_invalid_param, wrap_json
from ffxivbis.api.views.common.bis_base import BiSBaseView
from .openapi import OpenApi

View File

@ -9,8 +9,8 @@
from aiohttp.web import Response
from typing import Any, Dict, List, Optional, Type
from service.api.utils import wrap_exception, wrap_invalid_param, wrap_json
from service.api.views.common.login_base import LoginBaseView
from ffxivbis.api.utils import wrap_exception, wrap_invalid_param, wrap_json
from ffxivbis.api.views.common.login_base import LoginBaseView
from .openapi import OpenApi
@ -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)

View File

@ -6,8 +6,8 @@
from aiohttp.web import Response
from typing import Any, Dict, List, Optional, Type
from service.api.utils import wrap_exception, wrap_json
from service.api.views.common.login_base import LoginBaseView
from ffxivbis.api.utils import wrap_exception, wrap_json
from ffxivbis.api.views.common.login_base import LoginBaseView
from .openapi import OpenApi

View File

@ -9,12 +9,12 @@
from aiohttp.web import Response
from typing import Any, Dict, List, Optional, Type
from service.models.job import Job
from service.models.piece import Piece
from service.models.player import PlayerId
from ffxivbis.models.job import Job
from ffxivbis.models.piece import Piece
from ffxivbis.models.player import PlayerId
from service.api.utils import wrap_exception, wrap_invalid_param, wrap_json
from service.api.views.common.loot_base import LootBaseView
from ffxivbis.api.utils import wrap_exception, wrap_invalid_param, wrap_json
from ffxivbis.api.views.common.loot_base import LootBaseView
from .openapi import OpenApi

View File

@ -10,7 +10,7 @@ from __future__ import annotations
from typing import Any, Dict, List, Optional, Type
from service.models.serializable import Serializable
from ffxivbis.models.serializable import Serializable
class OpenApi(Serializable):

View File

@ -9,10 +9,10 @@
from aiohttp.web import Response
from typing import Any, Dict, List, Optional, Type
from service.models.job import Job
from ffxivbis.models.job import Job
from service.api.utils import wrap_exception, wrap_invalid_param, wrap_json
from service.api.views.common.player_base import PlayerBaseView
from ffxivbis.api.utils import wrap_exception, wrap_invalid_param, wrap_json
from ffxivbis.api.views.common.player_base import PlayerBaseView
from .openapi import OpenApi

View File

@ -9,10 +9,10 @@
from aiohttp.web import View
from typing import List, Optional
from service.core.ariyala_parser import AriyalaParser
from service.models.bis import BiS
from service.models.piece import Piece
from service.models.player import PlayerId
from ffxivbis.core.ariyala_parser import AriyalaParser
from ffxivbis.models.bis import BiS
from ffxivbis.models.piece import Piece
from ffxivbis.models.player import PlayerId
class BiSBaseView(View):
@ -38,7 +38,7 @@ class BiSBaseView(View):
async def bis_put(self, player_id: PlayerId, link: str) -> BiS:
parser = AriyalaParser(self.request.app['config'])
items = parser.get(link, player_id.job.name)
items = await parser.get(link, player_id.job.name)
for piece in items:
await self.request.app['party'].set_item_bis(player_id, piece)
await self.request.app['party'].set_bis_link(player_id, link)

View File

@ -10,7 +10,7 @@ from aiohttp.web import HTTPFound, HTTPUnauthorized, View
from aiohttp_security import check_authorized, forget, remember
from passlib.hash import md5_crypt
from service.models.user import User
from ffxivbis.models.user import User
class LoginBaseView(View):
@ -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):

View File

@ -9,9 +9,9 @@
from aiohttp.web import View
from typing import List, Optional, Union
from service.models.piece import Piece
from service.models.player import PlayerId, PlayerIdWithCounters
from service.models.upgrade import Upgrade
from ffxivbis.models.piece import Piece
from ffxivbis.models.player import PlayerId, PlayerIdWithCounters
from ffxivbis.models.upgrade import Upgrade
class LootBaseView(View):

View File

@ -9,10 +9,10 @@
from aiohttp.web import View
from typing import List, Optional
from service.core.ariyala_parser import AriyalaParser
from service.models.bis import BiS
from service.models.job import Job
from service.models.player import Player, PlayerId
from ffxivbis.core.ariyala_parser import AriyalaParser
from ffxivbis.models.bis import BiS
from ffxivbis.models.job import Job
from ffxivbis.models.player import Player, PlayerId
class PlayerBaseView(View):
@ -24,7 +24,7 @@ class PlayerBaseView(View):
if link:
parser = AriyalaParser(self.request.app['config'])
items = parser.get(link, job.name)
items = await parser.get(link, job.name)
for piece in items:
await self.request.app['party'].set_item_bis(player_id, piece)

View File

@ -10,12 +10,12 @@ from aiohttp.web import HTTPFound, Response
from aiohttp_jinja2 import template
from typing import Any, Dict, List
from service.models.piece import Piece
from service.models.player import Player, PlayerId
from ffxivbis.models.piece import Piece
from ffxivbis.models.player import Player, PlayerId
from service.api.utils import wrap_exception, wrap_invalid_param
from service.api.views.common.bis_base import BiSBaseView
from service.api.views.common.player_base import PlayerBaseView
from ffxivbis.api.utils import wrap_exception, wrap_invalid_param
from ffxivbis.api.views.common.bis_base import BiSBaseView
from ffxivbis.api.views.common.player_base import PlayerBaseView
class BiSHtmlView(BiSBaseView, PlayerBaseView):

View File

@ -10,13 +10,13 @@ from aiohttp.web import HTTPFound, Response
from aiohttp_jinja2 import template
from typing import Any, Dict, List
from service.models.piece import Piece
from service.models.player import Player, PlayerId
from service.models.upgrade import Upgrade
from ffxivbis.models.piece import Piece
from ffxivbis.models.player import Player, PlayerId
from ffxivbis.models.upgrade import Upgrade
from service.api.utils import wrap_exception, wrap_invalid_param
from service.api.views.common.loot_base import LootBaseView
from service.api.views.common.player_base import PlayerBaseView
from ffxivbis.api.utils import wrap_exception, wrap_invalid_param
from ffxivbis.api.views.common.loot_base import LootBaseView
from ffxivbis.api.views.common.player_base import PlayerBaseView
class LootHtmlView(LootBaseView, PlayerBaseView):

View File

@ -10,13 +10,13 @@ from aiohttp.web import Response
from aiohttp_jinja2 import template
from typing import Any, Dict, List, Union
from service.models.piece import Piece
from service.models.player import PlayerIdWithCounters
from service.models.upgrade import Upgrade
from ffxivbis.models.piece import Piece
from ffxivbis.models.player import PlayerIdWithCounters
from ffxivbis.models.upgrade import Upgrade
from service.api.utils import wrap_invalid_param
from service.api.views.common.loot_base import LootBaseView
from service.api.views.common.player_base import PlayerBaseView
from ffxivbis.api.utils import wrap_invalid_param
from ffxivbis.api.views.common.loot_base import LootBaseView
from ffxivbis.api.views.common.player_base import PlayerBaseView
class LootSuggestHtmlView(LootBaseView, PlayerBaseView):

View File

@ -10,11 +10,11 @@ from aiohttp.web import HTTPFound, Response
from aiohttp_jinja2 import template
from typing import Any, Dict, List
from service.models.job import Job
from service.models.player import PlayerIdWithCounters
from ffxivbis.models.job import Job
from ffxivbis.models.player import PlayerIdWithCounters
from service.api.utils import wrap_exception, wrap_invalid_param
from service.api.views.common.player_base import PlayerBaseView
from ffxivbis.api.utils import wrap_exception, wrap_invalid_param
from ffxivbis.api.views.common.player_base import PlayerBaseView
class PlayerHtmlView(PlayerBaseView):

View File

@ -10,10 +10,10 @@ from aiohttp.web import HTTPFound, Response
from aiohttp_jinja2 import template
from typing import Any, Dict, List
from service.models.user import User
from ffxivbis.models.user import User
from service.api.utils import wrap_exception, wrap_invalid_param
from service.api.views.common.login_base import LoginBaseView
from ffxivbis.api.utils import wrap_exception, wrap_invalid_param
from ffxivbis.api.views.common.login_base import LoginBaseView
class UsersHtmlView(LoginBaseView):

View File

@ -14,10 +14,9 @@ from aiohttp import web
from aiohttp_security import setup as setup_security
from aiohttp_security import CookiesIdentityPolicy
from service.core.config import Configuration
from service.core.database import Database
from service.core.loot_selector import LootSelector
from service.core.party import Party
from ffxivbis.core.config import Configuration
from ffxivbis.core.database import Database
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

View File

@ -6,7 +6,7 @@
#
# License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
#
from service.core.config import Configuration
from ffxivbis.core.config import Configuration
from .core import Application

View File

@ -0,0 +1,33 @@
#
# 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 asyncio
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.party_aggregator import PartyAggregator
class Application:
def __init__(self, config: Configuration) -> None:
self.config = config
self.logger = logging.getLogger('application')
def run(self) -> None:
loop = asyncio.get_event_loop()
database = loop.run_until_complete(Database.get(self.config))
database.migration()
aggregator = PartyAggregator(self.config, database)
web = setup_service(self.config, database, aggregator)
run_server(web)

View File

@ -7,11 +7,12 @@
# License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
#
import os
import requests
import socket
from aiohttp import ClientSession
from typing import Dict, List, Optional
from service.models.piece import Piece
from ffxivbis.models.piece import Piece
from .config import Configuration
@ -22,7 +23,6 @@ class AriyalaParser:
self.ariyala_url = config.get('ariyala', 'ariyala_url')
self.xivapi_key = config.get('ariyala', 'xivapi_key', fallback=None)
self.xivapi_url = config.get('ariyala', 'xivapi_url')
self.request_timeout = config.getfloat('ariyala', 'request_timeout', fallback=30)
def __remap_key(self, key: str) -> Optional[str]:
if key == 'mainhand':
@ -37,19 +37,20 @@ class AriyalaParser:
return key
return None
def get(self, url: str, job: str) -> List[Piece]:
items = self.get_ids(url, job)
async def get(self, url: str, job: str) -> List[Piece]:
items = await self.get_ids(url, job)
return [
Piece.get({'piece': slot, 'is_tome': self.get_is_tome(item_id)}) # type: ignore
Piece.get({'piece': slot, 'is_tome': await self.get_is_tome(item_id)}) # type: ignore
for slot, item_id in items.items()
]
def get_ids(self, url: str, job: str) -> Dict[str, int]:
async def get_ids(self, url: str, job: str) -> Dict[str, int]:
norm_path = os.path.normpath(url)
set_id = os.path.basename(norm_path)
response = requests.get(f'{self.ariyala_url}/store.app', params={'identifier': set_id})
response.raise_for_status()
data = response.json()
async with ClientSession() as session:
async with session.get(f'{self.ariyala_url}/store.app', params={'identifier': set_id}) as response:
response.raise_for_status()
data = await response.json(content_type='text/html')
# it has job in response but for some reasons job name differs sometimes from one in dictionary,
# e.g. http://ffxiv.ariyala.com/store.app?identifier=1AJB8
@ -67,13 +68,16 @@ class AriyalaParser:
result[key] = value
return result
def get_is_tome(self, item_id: int) -> bool:
async def get_is_tome(self, item_id: int) -> bool:
params = {'columns': 'IsEquippable'}
if self.xivapi_key is not None:
params['private_key'] = self.xivapi_key
response = requests.get(f'{self.xivapi_url}/item/{item_id}', params=params, timeout=self.request_timeout)
response.raise_for_status()
data = response.json()
async with ClientSession() as session:
# for some reasons ipv6 does not work for me
session.connector._family = socket.AF_INET # type: ignore
async with session.get(f'{self.xivapi_url}/item/{item_id}', params=params) as response:
response.raise_for_status()
data = await response.json()
return data['IsEquippable'] == 0 # don't ask

View File

@ -14,11 +14,11 @@ import logging
from yoyo import get_backend, read_migrations
from typing import List, Mapping, Optional, Type, Union
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 ffxivbis.models.loot import Loot
from ffxivbis.models.piece import Piece
from ffxivbis.models.player import Player, PlayerId
from ffxivbis.models.upgrade import Upgrade
from ffxivbis.models.user import User
from .config import Configuration
from .exceptions import InvalidDatabase
@ -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:

View File

@ -8,9 +8,9 @@
#
from typing import Iterable, List, Tuple, Union
from service.models.player import Player, PlayerIdWithCounters
from service.models.piece import Piece
from service.models.upgrade import Upgrade
from ffxivbis.models.player import Player, PlayerIdWithCounters
from ffxivbis.models.piece import Piece
from ffxivbis.models.upgrade import Upgrade
from .party import Party

View File

@ -11,17 +11,18 @@ from __future__ import annotations
from threading import Lock
from typing import Dict, List, Optional, Type, Union
from service.models.piece import Piece
from service.models.player import Player, PlayerId
from service.models.upgrade import Upgrade
from ffxivbis.models.piece import Piece
from ffxivbis.models.player import Player, PlayerId
from ffxivbis.models.upgrade import Upgrade
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)

View File

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

View File

@ -12,13 +12,13 @@ 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 ffxivbis.models.bis import BiS
from ffxivbis.models.job import Job
from ffxivbis.models.loot import Loot
from ffxivbis.models.piece import Piece
from ffxivbis.models.player import Player, PlayerId
from ffxivbis.models.upgrade import Upgrade
from ffxivbis.models.user import User
from .database import Database
@ -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
)

View File

@ -9,13 +9,13 @@
from passlib.hash import md5_crypt
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 ffxivbis.models.bis import BiS
from ffxivbis.models.job import Job
from ffxivbis.models.loot import Loot
from ffxivbis.models.piece import Piece
from ffxivbis.models.player import Player, PlayerId
from ffxivbis.models.upgrade import Upgrade
from ffxivbis.models.user import User
from .database import Database
from .sqlite_helper import SQLiteHelper
@ -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)
)

View File

@ -6,4 +6,4 @@
#
# License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
#
__version__ = '0.1.0'
__version__ = '0.1.1'

View File

@ -11,7 +11,7 @@ from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Dict, List, Mapping, Type, Union
from service.core.exceptions import InvalidDataRow
from ffxivbis.core.exceptions import InvalidDataRow
from .serializable import Serializable
from .upgrade import Upgrade
@ -46,7 +46,9 @@ class Piece(Serializable):
@classmethod
def get(cls: Type[Piece], data: Mapping[str, Any]) -> Union[Piece, Upgrade]:
try:
piece_type = data['piece']
piece_type = data.get('piece') or data.get('name')
if piece_type is None:
raise KeyError
is_tome = data['is_tome'] in ('yes', 'on', '1', 1, True)
except KeyError:
raise InvalidDataRow(data)

View File

@ -1,66 +0,0 @@
#
# 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 aiohttp.web import Application
from .views.api.bis import BiSView
from .views.api.login import LoginView
from .views.api.logout import LogoutView
from .views.api.loot import LootView
from .views.api.player import PlayerView
from .views.html.api import ApiDocVIew, ApiHtmlView
from .views.html.bis import BiSHtmlView
from .views.html.index import IndexHtmlView
from .views.html.loot import LootHtmlView
from .views.html.loot_suggest import LootSuggestHtmlView
from .views.html.player import PlayerHtmlView
from .views.html.static import StaticHtmlView
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_get('/api/v1/party', PlayerView)
app.router.add_post('/api/v1/party', 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/loot', LootView)
app.router.add_post('/api/v1/party/loot', LootView)
app.router.add_put('/api/v1/party/loot', LootView)
# html routes
app.router.add_get('/', IndexHtmlView)
app.router.add_get('/static/{resource_id}', StaticHtmlView)
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('/bis', BiSHtmlView)
app.router.add_post('/bis', BiSHtmlView)
app.router.add_get('/loot', LootHtmlView)
app.router.add_post('/loot', LootHtmlView)
app.router.add_get('/suggest', LootSuggestHtmlView)
app.router.add_post('/suggest', LootSuggestHtmlView)
app.router.add_get('/admin/users', UsersHtmlView)
app.router.add_post('/admin/users', UsersHtmlView)

View File

@ -1,41 +0,0 @@
#
# 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 asyncio
import logging
from service.api.web import run_server, setup_service
from service.core.config import Configuration
from service.core.database import Database
from service.core.loot_selector import LootSelector
from service.core.party import Party
from service.models.user import User
class Application:
def __init__(self, config: Configuration) -> None:
self.config = config
self.logger = logging.getLogger('application')
def run(self) -> None:
loop = asyncio.get_event_loop()
database = loop.run_until_complete(Database.get(self.config))
database.migration()
party = loop.run_until_complete(Party.get(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)
run_server(web)

View File

@ -4,17 +4,17 @@ import tempfile
from typing import Any, List
from service.api.web import setup_service
from service.core.ariyala_parser import AriyalaParser
from service.core.config import Configuration
from service.core.database import Database
from service.core.loot_selector import LootSelector
from service.core.party import Party
from service.core.sqlite import SQLiteDatabase
from service.models.bis import BiS
from service.models.job import Job
from service.models.piece import Head, Piece, Weapon
from service.models.player import Player
from ffxivbis.api.web import setup_service
from ffxivbis.core.ariyala_parser import AriyalaParser
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.sqlite import SQLiteDatabase
from ffxivbis.models.bis import BiS
from ffxivbis.models.job import Job
from ffxivbis.models.piece import Head, Piece, Weapon
from ffxivbis.models.player import Player
@pytest.fixture

View File

@ -1,11 +1,11 @@
from typing import List
from service.core.ariyala_parser import AriyalaParser
from service.models.piece import Piece
from service.models.player import Player
from ffxivbis.core.ariyala_parser import AriyalaParser
from ffxivbis.models.piece import Piece
from ffxivbis.models.player import Player
def test_get(parser: AriyalaParser, player: Player, bis_link: str, bis_set: List[Piece]) -> None:
items = parser.get(bis_link, player.job.name)
async def test_get(parser: AriyalaParser, player: Player, bis_link: str, bis_set: List[Piece]) -> None:
items = await parser.get(bis_link, player.job.name)
assert items == bis_set

View File

@ -1,6 +1,6 @@
from service.models.bis import BiS
from service.models.piece import Piece
from service.models.upgrade import Upgrade
from ffxivbis.models.bis import BiS
from ffxivbis.models.piece import Piece
from ffxivbis.models.upgrade import Upgrade
def test_set_item(bis: BiS, weapon: Piece) -> None:

View File

@ -1,6 +1,6 @@
from service.core.loot_selector import LootSelector
from service.models.piece import Piece
from service.models.player import Player
from ffxivbis.core.loot_selector import LootSelector
from ffxivbis.models.piece import Piece
from ffxivbis.models.player import Player
def test_suggest_by_need(selector: LootSelector, player: Player, player2: Player, head_with_upgrade: Piece) -> None:

View File

@ -1,7 +1,7 @@
from service.core.database import Database
from service.core.party import Party
from service.models.piece import Piece
from service.models.player import Player
from ffxivbis.core.database import Database
from ffxivbis.core.party import Party
from ffxivbis.models.piece import Piece
from ffxivbis.models.player import Player
async def test_set_player(party: Party, player: Player) -> None:

View File

@ -1,4 +1,4 @@
from service.models.piece import Piece
from ffxivbis.models.piece import Piece
def test_parse_head(head_with_upgrade: Piece) -> None:

View File

@ -1,5 +1,5 @@
from service.models.piece import Piece
from service.models.player import Player
from ffxivbis.models.piece import Piece
from ffxivbis.models.player import Player
def test_loot_count(player: Player, head_with_upgrade: Piece, weapon: Piece) -> None:

View File

@ -1,9 +1,9 @@
from typing import Any, List
from service.api.utils import make_json
from service.core.party import Party
from service.models.piece import Piece
from service.models.player import Player
from ffxivbis.api.utils import make_json
from ffxivbis.core.party import Party
from ffxivbis.models.piece import Piece
from ffxivbis.models.player import Player
async def test_bis_get(server: Any, party: Party, player: Player, player2: Player,
@ -35,7 +35,7 @@ async def test_bis_get_with_filter(server: Any, party: Party, player: Player, pl
async def test_bis_post_add(server: Any, player: Player, head_with_upgrade: Piece) -> None:
response = await server.post('/api/v1/party/bis', json={
'action': 'add',
'piece': head_with_upgrade.name,
'name': head_with_upgrade.name,
'is_tome': head_with_upgrade.is_tome,
'job': player.job.name,
'nick': player.nick
@ -47,7 +47,7 @@ async def test_bis_post_add(server: Any, player: Player, head_with_upgrade: Piec
async def test_bis_post_remove(server: Any, player: Player, player2: Player, weapon: Piece) -> None:
response = await server.post('/api/v1/party/bis', json={
'action': 'remove',
'piece': weapon.name,
'name': weapon.name,
'is_tome': weapon.is_tome,
'job': player.job.name,
'nick': player.nick

View File

@ -1,9 +1,9 @@
from typing import Any
from service.api.utils import make_json
from service.core.party import Party
from service.models.piece import Piece
from service.models.player import Player
from ffxivbis.api.utils import make_json
from ffxivbis.core.party import Party
from ffxivbis.models.piece import Piece
from ffxivbis.models.player import Player
async def test_loot_get(server: Any, party: Party, player: Player, player2: Player, weapon: Piece) -> None:
@ -36,7 +36,7 @@ async def test_loot_post_add(server: Any, player: Player, weapon: Piece) -> None
response = await server.post('/api/v1/party/loot', json={
'action': 'add',
'piece': weapon.name,
'name': weapon.name,
'is_tome': weapon.is_tome,
'job': player.job.name,
'nick': player.nick
@ -53,7 +53,7 @@ async def test_loot_post_remove(server: Any, player: Player, head_with_upgrade:
response = await server.post('/api/v1/party/loot', json={
'action': 'remove',
'piece': weapon.name,
'name': weapon.name,
'is_tome': weapon.is_tome,
'job': player.job.name,
'nick': player.nick
@ -65,7 +65,7 @@ async def test_loot_post_remove(server: Any, player: Player, head_with_upgrade:
response = await server.post('/api/v1/party/loot', json={
'action': 'remove',
'piece': weapon.name,
'name': weapon.name,
'is_tome': weapon.is_tome,
'job': player.job.name,
'nick': player.nick
@ -78,7 +78,7 @@ async def test_loot_post_remove(server: Any, player: Player, head_with_upgrade:
async def test_loot_put(server: Any, player: Player, player2: Player, head_with_upgrade: Piece) -> None:
response = await server.put('/api/v1/party/loot', json={
'is_tome': head_with_upgrade.is_tome,
'piece': head_with_upgrade.name
'name': head_with_upgrade.name
})
assert response.status == 200
assert await response.text() == make_json(

View File

@ -1,9 +1,9 @@
from typing import Any, List
from service.api.utils import make_json
from service.core.party import Party
from service.models.piece import Piece
from service.models.player import Player
from ffxivbis.api.utils import make_json
from ffxivbis.core.party import Party
from ffxivbis.models.piece import Piece
from ffxivbis.models.player import Player
async def test_players_get(server: Any, party: Party, player: Player) -> None: