mirror of
https://github.com/arcan1s/ffxivbis.git
synced 2025-07-10 04:15:52 +00:00
Compare commits
4 Commits
0.1.0
...
feature/pa
Author | SHA1 | Date | |
---|---|---|---|
4ff985bf81 | |||
2d84459c4d | |||
28dabcb44e | |||
36f0b8151a |
27
README.md
27
README.md
@ -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.
|
||||
|
||||
|
75
migrations/20190916_01_zGTB1-party-id.py
Normal file
75
migrations/20190916_01_zGTB1-party-id.py
Normal 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''')
|
||||
]
|
@ -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
|
@ -1,4 +1,3 @@
|
||||
[ariyala]
|
||||
ariyala_url = https://ffxiv.ariyala.com
|
||||
request_timeout = 1
|
||||
xivapi_url = https://xivapi.com
|
||||
|
11
setup.py
11
setup.py
@ -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'],
|
||||
},
|
||||
|
@ -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:
|
66
src/ffxivbis/api/routes.py
Normal file
66
src/ffxivbis/api/routes.py
Normal 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)
|
||||
|
||||
|
@ -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:
|
@ -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
|
||||
|
@ -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)
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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):
|
@ -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
|
||||
|
@ -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)
|
@ -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):
|
@ -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):
|
@ -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)
|
||||
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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
|
@ -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
|
||||
|
33
src/ffxivbis/application/core.py
Normal file
33
src/ffxivbis/application/core.py
Normal 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)
|
@ -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
|
@ -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:
|
@ -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
|
||||
|
@ -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)
|
26
src/ffxivbis/core/party_aggregator.py
Normal file
26
src/ffxivbis/core/party_aggregator.py
Normal 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)
|
@ -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
|
||||
)
|
@ -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)
|
||||
)
|
@ -6,4 +6,4 @@
|
||||
#
|
||||
# License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause
|
||||
#
|
||||
__version__ = '0.1.0'
|
||||
__version__ = '0.1.1'
|
@ -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)
|
@ -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)
|
||||
|
||||
|
@ -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)
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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:
|
||||
|
Reference in New Issue
Block a user