mirror of
https://github.com/arcan1s/ffxivbis.git
synced 2026-10-13 18:12:15 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ff985bf81 | ||
|
|
2d84459c4d | ||
|
|
28dabcb44e |
No files matched your search
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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'],
|
||||
},
|
||||
|
||||
File renamed without changes.
File renamed without changes.
@@ -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:
|
||||
File renamed without changes.
@@ -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:
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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
|
||||
|
||||
File renamed without changes.
+5
-5
@@ -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)
|
||||
+3
-3
@@ -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):
|
||||
+3
-3
@@ -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):
|
||||
+5
-5
@@ -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)
|
||||
|
||||
File renamed without changes.
File renamed without changes.
@@ -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):
|
||||
File renamed without changes.
@@ -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):
|
||||
+6
-6
@@ -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):
|
||||
File renamed without changes.
@@ -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
|
||||
File renamed without changes.
@@ -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
|
||||
|
||||
@@ -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)
|
||||
File renamed without changes.
@@ -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
|
||||
File renamed without changes.
@@ -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:
|
||||
File renamed without changes.
@@ -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)
|
||||
@@ -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)
|
||||
)
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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)
|
||||
+11
-11
@@ -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
|
||||
+3
-3
@@ -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:
|
||||
|
||||
+4
-4
@@ -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
-1
@@ -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:
|
||||
|
||||
+2
-2
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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