Merge pull request #46 from Discours/prepare-comments

Prepare comments WIP
This commit is contained in:
Tony 2022-11-24 11:25:23 +03:00 committed by GitHub
commit 1bb13eb1e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 217 additions and 175 deletions

1
.gitignore vendored
View File

@ -147,3 +147,4 @@ dump
.vscode .vscode
*dump.sql *dump.sql
*.csv *.csv
dev-server-status.txt

View File

@ -9,7 +9,7 @@ from starlette.requests import HTTPConnection
from auth.credentials import AuthCredentials, AuthUser from auth.credentials import AuthCredentials, AuthUser
from auth.jwtcodec import JWTCodec from auth.jwtcodec import JWTCodec
from auth.tokenstorage import TokenStorage from auth.tokenstorage import TokenStorage
from base.exceptions import InvalidToken from base.exceptions import ExpiredToken, InvalidToken
from services.auth.users import UserStorage from services.auth.users import UserStorage
from settings import SESSION_TOKEN_HEADER from settings import SESSION_TOKEN_HEADER
@ -28,16 +28,17 @@ class SessionToken:
token is of specified type token is of specified type
""" """
try: try:
print('[auth.authenticate] session token verify')
payload = JWTCodec.decode(token) payload = JWTCodec.decode(token)
except ExpiredSignatureError: except ExpiredSignatureError:
payload = JWTCodec.decode(token, verify_exp=False) payload = JWTCodec.decode(token, verify_exp=False)
if not await cls.get(payload.user_id, token): if not await cls.get(payload.user_id, token):
raise InvalidToken("Session token has expired, please try again") raise ExpiredToken("Token signature has expired, please try again")
except DecodeError as e: except DecodeError as e:
raise InvalidToken("token format error") from e raise InvalidToken("token format error") from e
else: else:
if not await cls.get(payload.user_id, token): if not await cls.get(payload.user_id, token):
raise InvalidToken("Session token has expired, please login again") raise ExpiredToken("Session token has expired, please login again")
return payload return payload
@classmethod @classmethod
@ -58,6 +59,8 @@ class JWTAuthenticate(AuthenticationBackend):
try: try:
payload = await SessionToken.verify(token) payload = await SessionToken.verify(token)
except Exception as exc: except Exception as exc:
print("[auth.authenticate] session token verify error")
print(exc)
return AuthCredentials(scopes=[], error_message=str(exc)), AuthUser( return AuthCredentials(scopes=[], error_message=str(exc)), AuthUser(
user_id=None user_id=None
) )

View File

@ -81,6 +81,7 @@ class Identity:
@staticmethod @staticmethod
async def onetime(token: str) -> User: async def onetime(token: str) -> User:
try: try:
print('[auth.identity] using one time token')
payload = JWTCodec.decode(token) payload = JWTCodec.decode(token)
if not await TokenStorage.exist(f"{payload.user_id}-{token}"): if not await TokenStorage.exist(f"{payload.user_id}-{token}"):
raise InvalidToken("Login token has expired, please login again") raise InvalidToken("Login token has expired, please login again")

View File

@ -8,10 +8,8 @@ from settings import JWT_ALGORITHM, JWT_SECRET_KEY
class JWTCodec: class JWTCodec:
@staticmethod @staticmethod
def encode(user: AuthInput, exp: datetime) -> str: def encode(user: AuthInput, exp: datetime) -> str:
issued = int(datetime.now().timestamp()) expires = int(exp.timestamp() * 1000)
print('[jwtcodec] issued at %r' % issued) issued = int(datetime.now().timestamp() * 1000)
expires = int(exp.timestamp())
print('[jwtcodec] expires at %r' % expires)
payload = { payload = {
"user_id": user.id, "user_id": user.id,
"username": user.email or user.phone, "username": user.email or user.phone,
@ -23,7 +21,7 @@ class JWTCodec:
try: try:
return jwt.encode(payload, JWT_SECRET_KEY, JWT_ALGORITHM) return jwt.encode(payload, JWT_SECRET_KEY, JWT_ALGORITHM)
except Exception as e: except Exception as e:
print('[jwtcodec] JWT encode error %r' % e) print('[auth.jwtcodec] JWT encode error %r' % e)
@staticmethod @staticmethod
def decode(token: str, verify_exp: bool = True) -> TokenPayload: def decode(token: str, verify_exp: bool = True) -> TokenPayload:
@ -39,11 +37,13 @@ class JWTCodec:
issuer="discours" issuer="discours"
) )
r = TokenPayload(**payload) r = TokenPayload(**payload)
print('[jwtcodec] debug payload %r' % r) print('[auth.jwtcodec] debug payload %r' % r)
return r return r
except jwt.InvalidIssuedAtError: except jwt.InvalidIssuedAtError:
print('[auth.jwtcodec] invalid issued at: %r' % r)
raise ExpiredToken('check token issued time') raise ExpiredToken('check token issued time')
except jwt.ExpiredSignatureError: except jwt.ExpiredSignatureError:
print('[auth.jwtcodec] expired signature %r' % r)
raise ExpiredToken('check token lifetime') raise ExpiredToken('check token lifetime')
except jwt.InvalidTokenError: except jwt.InvalidTokenError:
raise InvalidToken('token is not valid') raise InvalidToken('token is not valid')

View File

@ -36,7 +36,9 @@ class TokenStorage:
@staticmethod @staticmethod
async def revoke(token: str) -> bool: async def revoke(token: str) -> bool:
payload = None
try: try:
print("[auth.tokenstorage] revoke token")
payload = JWTCodec.decode(token) payload = JWTCodec.decode(token)
except: # noqa except: # noqa
pass pass

View File

@ -1,5 +1,5 @@
from aioredis import from_url from aioredis import from_url
from asyncio import sleep
from settings import REDIS_URL from settings import REDIS_URL
@ -21,7 +21,12 @@ class RedisCache:
self._instance = None self._instance = None
async def execute(self, command, *args, **kwargs): async def execute(self, command, *args, **kwargs):
return await self._instance.execute_command(command, *args, **kwargs) while not self._instance:
await sleep(1)
try:
await self._instance.execute_command(command, *args, **kwargs)
except Exception:
pass
async def lrange(self, key, start, stop): async def lrange(self, key, start, stop):
return await self._instance.lrange(key, start, stop) return await self._instance.lrange(key, start, stop)

21
main.py
View File

@ -1,6 +1,6 @@
import asyncio import asyncio
from importlib import import_module from importlib import import_module
from os.path import exists
from ariadne import load_schema_from_path, make_executable_schema from ariadne import load_schema_from_path, make_executable_schema
from ariadne.asgi import GraphQL from ariadne.asgi import GraphQL
from starlette.applications import Starlette from starlette.applications import Starlette
@ -21,6 +21,8 @@ from services.stat.topicstat import TopicStat
from services.stat.viewed import ViewedStorage from services.stat.viewed import ViewedStorage
from services.zine.gittask import GitTask from services.zine.gittask import GitTask
from services.zine.shoutauthor import ShoutAuthorStorage from services.zine.shoutauthor import ShoutAuthorStorage
from settings import DEV_SERVER_STATUS_FILE_NAME
import_module("resolvers") import_module("resolvers")
schema = make_executable_schema(load_schema_from_path("schema.graphql"), resolvers) # type: ignore schema = make_executable_schema(load_schema_from_path("schema.graphql"), resolvers) # type: ignore
@ -45,6 +47,15 @@ async def start_up():
git_task = asyncio.create_task(GitTask.git_task_worker()) git_task = asyncio.create_task(GitTask.git_task_worker())
print(git_task) print(git_task)
async def dev_start_up():
if exists(DEV_SERVER_STATUS_FILE_NAME):
return
else:
with open(DEV_SERVER_STATUS_FILE_NAME, 'w', encoding='utf-8') as f:
f.write('running')
await start_up()
async def shutdown(): async def shutdown():
await redis.disconnect() await redis.disconnect()
@ -64,3 +75,11 @@ app = Starlette(
routes=routes, routes=routes,
) )
app.mount("/", GraphQL(schema, debug=True)) app.mount("/", GraphQL(schema, debug=True))
dev_app = app = Starlette(
debug=True,
on_startup=[dev_start_up],
middleware=middleware,
routes=routes,
)
dev_app.mount("/", GraphQL(schema, debug=True))

View File

@ -42,6 +42,7 @@ async def get_current_user(_, info):
async def confirm_email(_, info, token): async def confirm_email(_, info, token):
"""confirm owning email address""" """confirm owning email address"""
try: try:
print('[resolvers.auth] confirm email by token')
payload = JWTCodec.decode(token) payload = JWTCodec.decode(token)
user_id = payload.user_id user_id = payload.user_id
await TokenStorage.get(f"{user_id}-{token}") await TokenStorage.get(f"{user_id}-{token}")
@ -175,7 +176,7 @@ async def login(_, info, email: str, password: str = "", lang: str = "ru"):
} }
except InvalidPassword: except InvalidPassword:
print(f"[auth] {email}: invalid password") print(f"[auth] {email}: invalid password")
raise InvalidPassword("invalid passoword") # contains webserver status raise InvalidPassword("invalid password") # contains webserver status
# return {"error": "invalid password"} # return {"error": "invalid password"}

View File

@ -33,6 +33,7 @@ async def load_messages(chatId: str, limit: int, offset: int):
async def load_chats(_, info, limit: int, offset: int): async def load_chats(_, info, limit: int, offset: int):
""" load :limit chats of current user with :offset """ """ load :limit chats of current user with :offset """
user = info.context["request"].user user = info.context["request"].user
if user:
chats = await redis.execute("GET", f"chats_by_user/{user.slug}") chats = await redis.execute("GET", f"chats_by_user/{user.slug}")
if chats: if chats:
chats = list(json.loads(chats))[offset:offset + limit] chats = list(json.loads(chats))[offset:offset + limit]
@ -45,6 +46,11 @@ async def load_chats(_, info, limit: int, offset: int):
"chats": chats, "chats": chats,
"error": None "error": None
} }
else:
return {
"error": "please login",
"chats": []
}
@query.field("loadMessagesBy") @query.field("loadMessagesBy")

View File

@ -1,6 +1,6 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
import sqlalchemy as sa import sqlalchemy as sa
from sqlalchemy.orm import selectinload from sqlalchemy.orm import joinedload
from sqlalchemy.sql.expression import desc, asc, select, case from sqlalchemy.sql.expression import desc, asc, select, case
from base.orm import local_session from base.orm import local_session
from base.resolvers import query from base.resolvers import query
@ -32,42 +32,13 @@ def apply_filters(q, filters, user=None):
return q return q
def extract_order(o, q):
if o:
q = q.add_columns(sa.func.count(Reaction.id).label(o))
if o == 'comments':
q = q.join(Reaction, Shout.slug == Reaction.shout)
q = q.filter(Reaction.body.is_not(None))
elif o == 'reacted':
q = q.join(
Reaction
).add_columns(
sa.func.max(Reaction.createdAt).label(o)
)
elif o == "rating":
q = q.join(Reaction).add_columns(sa.func.sum(case(
(Reaction.kind == ReactionKind.AGREE, 1),
(Reaction.kind == ReactionKind.DISAGREE, -1),
(Reaction.kind == ReactionKind.PROOF, 1),
(Reaction.kind == ReactionKind.DISPROOF, -1),
(Reaction.kind == ReactionKind.ACCEPT, 1),
(Reaction.kind == ReactionKind.REJECT, -1),
(Reaction.kind == ReactionKind.LIKE, 1),
(Reaction.kind == ReactionKind.DISLIKE, -1),
else_=0
)).label(o))
return o
else:
return 'createdAt'
@query.field("loadShout") @query.field("loadShout")
async def load_shout(_, info, slug): async def load_shout(_, info, slug):
with local_session() as session: with local_session() as session:
shout = session.query(Shout).options( shout = session.query(Shout).options(
# TODO add cation # TODO add cation
selectinload(Shout.authors), joinedload(Shout.authors),
selectinload(Shout.topics), joinedload(Shout.topics),
).filter( ).filter(
Shout.slug == slug Shout.slug == slug
).filter( ).filter(
@ -77,6 +48,12 @@ async def load_shout(_, info, slug):
return shout return shout
def map_result_item(result_item):
shout = result_item[0]
shout.rating = result_item[1]
return shout
@query.field("loadShouts") @query.field("loadShouts")
async def load_shouts_by(_, info, options): async def load_shouts_by(_, info, options):
""" """
@ -100,16 +77,40 @@ async def load_shouts_by(_, info, options):
""" """
q = select(Shout).options( q = select(Shout).options(
# TODO add caption joinedload(Shout.authors),
selectinload(Shout.authors), joinedload(Shout.topics),
selectinload(Shout.topics),
).where( ).where(
Shout.deletedAt.is_(None) Shout.deletedAt.is_(None)
) )
user = info.context["request"].user user = info.context["request"].user
q = apply_filters(q, options.get("filters"), user) q = apply_filters(q, options.get("filters"), user)
q = q.join(Reaction).add_columns(sa.func.sum(case(
(Reaction.kind == ReactionKind.AGREE, 1),
(Reaction.kind == ReactionKind.DISAGREE, -1),
(Reaction.kind == ReactionKind.PROOF, 1),
(Reaction.kind == ReactionKind.DISPROOF, -1),
(Reaction.kind == ReactionKind.ACCEPT, 1),
(Reaction.kind == ReactionKind.REJECT, -1),
(Reaction.kind == ReactionKind.LIKE, 1),
(Reaction.kind == ReactionKind.DISLIKE, -1),
else_=0
)).label('rating'))
order_by = extract_order(options.get("order_by"), q) o = options.get("order_by")
if o:
q = q.add_columns(sa.func.count(Reaction.id).label(o))
if o == 'comments':
q = q.join(Reaction, Shout.slug == Reaction.shout)
q = q.filter(Reaction.body.is_not(None))
elif o == 'reacted':
q = q.join(
Reaction
).add_columns(
sa.func.max(Reaction.createdAt).label(o)
)
order_by = o
else:
order_by = Shout.createdAt
order_by_desc = True if options.get('order_by_desc') is None else options.get('order_by_desc') order_by_desc = True if options.get('order_by_desc') is None else options.get('order_by_desc')
@ -119,10 +120,13 @@ async def load_shouts_by(_, info, options):
q = q.group_by(Shout.id).order_by(query_order_by).limit(limit).offset(offset) q = q.group_by(Shout.id).order_by(query_order_by).limit(limit).offset(offset)
with local_session() as session: with local_session() as session:
shouts = list(map(lambda r: r.Shout, session.execute(q))) shouts = list(map(map_result_item, session.execute(q).unique()))
for s in shouts:
s.stat = await ReactedStorage.get_shout_stat(s.slug) for shout in shouts:
for a in s.authors: shout.stat = await ReactedStorage.get_shout_stat(shout.slug, shout.rating)
a.caption = await ShoutAuthorStorage.get_author_caption(s.slug, a.slug)
del shout.rating
for author in shout.authors:
author.caption = await ShoutAuthorStorage.get_author_caption(shout.slug, author.slug)
return shouts return shouts

View File

@ -1,7 +1,7 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from sqlalchemy import and_, asc, desc, select, text, func from sqlalchemy import and_, asc, desc, select, text, func
from sqlalchemy.orm import selectinload from sqlalchemy.orm import aliased
from auth.authenticate import login_required from auth.authenticate import login_required
from base.orm import local_session from base.orm import local_session
from base.resolvers import mutation, query from base.resolvers import mutation, query
@ -23,12 +23,10 @@ async def get_reaction_stat(reaction_id):
def reactions_follow(user: User, slug: str, auto=False): def reactions_follow(user: User, slug: str, auto=False):
with local_session() as session: with local_session() as session:
following = ( following = (
session.query(ShoutReactionsFollower) session.query(ShoutReactionsFollower).where(and_(
.where(and_(
ShoutReactionsFollower.follower == user.slug, ShoutReactionsFollower.follower == user.slug,
ShoutReactionsFollower.shout == slug ShoutReactionsFollower.shout == slug
)) )).first()
.first()
) )
if not following: if not following:
following = ShoutReactionsFollower.create( following = ShoutReactionsFollower.create(
@ -43,12 +41,10 @@ def reactions_follow(user: User, slug: str, auto=False):
def reactions_unfollow(user, slug): def reactions_unfollow(user, slug):
with local_session() as session: with local_session() as session:
following = ( following = (
session.query(ShoutReactionsFollower) session.query(ShoutReactionsFollower).where(and_(
.where(and_(
ShoutReactionsFollower.follower == user.slug, ShoutReactionsFollower.follower == user.slug,
ShoutReactionsFollower.shout == slug ShoutReactionsFollower.shout == slug
)) )).first()
.first()
) )
if following: if following:
session.delete(following) session.delete(following)
@ -200,43 +196,23 @@ async def delete_reaction(_, info, rid):
return {} return {}
def prepare_reactions(q, by): def map_result_item(result_item):
""" query filters and order """ reaction = result_item[0]
if by.get("shout"): user = result_item[1]
q = q.filter(Shout.slug == by["shout"]) reaction.createdBy = user
elif by.get("shouts"): return reaction
q = q.filter(Shout.slug.in_(by["shouts"]))
if by.get("createdBy"):
q = q.filter(Reaction.createdBy == by.get("createdBy"))
if by.get("topic"):
q = q.filter(Shout.topics.contains(by["topic"]))
if by.get("body"):
if by["body"] is True:
q = q.filter(func.length(Reaction.body) > 0)
else:
q = q.filter(Reaction.body.ilike(f'%{by["body"]}%'))
if by.get("days"):
before = datetime.now() - timedelta(days=int(by["days"]) or 30)
q = q.filter(Reaction.createdAt > before)
order_way = asc if by.get("sort", "").startswith("-") else desc
order_field = by.get("sort") or Reaction.createdAt
q = q.group_by(
Reaction.id
).order_by(
order_way(order_field)
)
return q
@query.field("loadReactionsBy") @query.field("loadReactionsBy")
async def load_reactions_by(_, info, by, limit=50, offset=0): async def load_reactions_by(_, _info, by, limit=50, offset=0):
""" """
:param by: { :param by: {
:shout - filter by slug :shout - filter by slug
:shouts - filer by shouts luglist :shouts - filer by shouts luglist
:createdBy - to filter by author :createdBy - to filter by author
:topic - to filter by topic :topic - to filter by topic
:body - to search by body :search - to search by reactions' body
:comment - true if body.length > 0
:days - a number of days ago :days - a number of days ago
:sort - a fieldname to sort desc by default :sort - a fieldname to sort desc by default
} }
@ -244,33 +220,45 @@ async def load_reactions_by(_, info, by, limit=50, offset=0):
:param offset: int offset in this order :param offset: int offset in this order
:return: Reaction[] :return: Reaction[]
""" """
user = None
try: CreatedByUser = aliased(User)
user = info.context["request"].user
except Exception:
pass
q = select( q = select(
Reaction Reaction, CreatedByUser
).options( ).join(CreatedByUser, Reaction.createdBy == CreatedByUser.slug)
selectinload(Reaction.createdBy),
selectinload(Reaction.shout) if by.get("shout"):
).join( q = q.filter(Reaction.shout == by["shout"])
User, Reaction.createdBy == User.slug elif by.get("shouts"):
).join( q = q.filter(Reaction.shout.in_(by["shouts"]))
Shout, Reaction.shout == Shout.slug if by.get("createdBy"):
).where( q = q.filter(Reaction.createdBy == by.get("createdBy"))
Reaction.deletedAt.is_(None) if by.get("topic"):
q = q.filter(Shout.topics.contains(by["topic"]))
if by.get("comment"):
q = q.filter(func.length(Reaction.body) > 0)
if by.get('search', 0) > 2:
q = q.filter(Reaction.body.ilike(f'%{by["body"]}%'))
if by.get("days"):
after = datetime.now() - timedelta(days=int(by["days"]) or 30)
q = q.filter(Reaction.createdAt > after)
order_way = asc if by.get("sort", "").startswith("-") else desc
order_field = by.get("sort") or Reaction.createdAt
q = q.group_by(
Reaction.id, CreatedByUser.id
).order_by(
order_way(order_field)
) )
q = prepare_reactions(q, by, user)
q = q.where(Reaction.deletedAt.is_(None))
q = q.limit(limit).offset(offset) q = q.limit(limit).offset(offset)
rrr = []
with local_session() as session: with local_session() as session:
# post query stats and author's captions reactions = list(map(map_result_item, session.execute(q)))
for r in list(map(lambda r: r.Reaction, session.execute(q))): for reaction in reactions:
r.stat = await get_reaction_stat(r.id) reaction.stat = await get_reaction_stat(reaction.id)
rrr.append(r)
if by.get("stat"): if by.get("stat"):
rrr.sort(lambda r: r.stat.get(by["stat"]) or r.createdAt) reactions.sort(lambda r: r.stat.get(by["stat"]) or r.createdAt)
return rrr
return reactions

View File

@ -1,14 +1,15 @@
import random import sqlalchemy as sa
from sqlalchemy import and_, select
from sqlalchemy import and_
from auth.authenticate import login_required from auth.authenticate import login_required
from base.orm import local_session from base.orm import local_session
from base.resolvers import mutation, query from base.resolvers import mutation, query
from orm import Shout
from orm.topic import Topic, TopicFollower from orm.topic import Topic, TopicFollower
from services.zine.topics import TopicStorage from services.zine.topics import TopicStorage
from services.stat.reacted import ReactedStorage # from services.stat.reacted import ReactedStorage
from services.stat.topicstat import TopicStat from services.stat.topicstat import TopicStat
# from services.stat.viewed import ViewedStorage # from services.stat.viewed import ViewedStorage
@ -18,9 +19,9 @@ async def get_topic_stat(slug):
"authors": len(TopicStat.authors_by_topic.get(slug, {}).keys()), "authors": len(TopicStat.authors_by_topic.get(slug, {}).keys()),
"followers": len(TopicStat.followers_by_topic.get(slug, {}).keys()), "followers": len(TopicStat.followers_by_topic.get(slug, {}).keys()),
# "viewed": await ViewedStorage.get_topic(slug), # "viewed": await ViewedStorage.get_topic(slug),
"reacted": len(await ReactedStorage.get_topic(slug)), # "reacted": len(await ReactedStorage.get_topic(slug)),
"commented": len(await ReactedStorage.get_topic_comments(slug)), # "commented": len(await ReactedStorage.get_topic_comments(slug)),
"rating": await ReactedStorage.get_topic_rating(slug) # "rating": await ReactedStorage.get_topic_rating(slug)
} }
@ -113,11 +114,8 @@ async def topic_unfollow(user, slug):
@query.field("topicsRandom") @query.field("topicsRandom")
async def topics_random(_, info, amount=12): async def topics_random(_, info, amount=12):
topics = await TopicStorage.get_topics_all() with local_session() as session:
normalized_topics = [] q = select(Topic).join(Shout).group_by(Topic.id).having(sa.func.count(Shout.id) > 2).order_by(
for topic in topics: sa.func.random()).limit(amount)
topic.stat = await get_topic_stat(topic.slug) random_topics = list(map(lambda result_item: result_item.Topic, session.execute(q)))
if topic.stat["shouts"] > 2: return random_topics
normalized_topics.append(topic)
sample_length = min(len(normalized_topics), amount)
return random.sample(normalized_topics, sample_length)

View File

@ -248,13 +248,14 @@ input LoadShoutsOptions {
} }
input ReactionBy { input ReactionBy {
shout: String shout: String # slug
shouts: [String] shouts: [String]
body: String search: String # fts on body
topic: String comment: Boolean
createdBy: String topic: String # topic.slug
days: Int createdBy: String # user.slug
sort: String days: Int # before
sort: String # how to sort, default createdAt
} }
################################### Query ################################### Query
@ -476,9 +477,9 @@ type TopicStat {
followers: Int! followers: Int!
authors: Int! authors: Int!
# viewed: Int # viewed: Int
reacted: Int! # reacted: Int!
commented: Int #commented: Int
rating: Int # rating: Int
} }
type Topic { type Topic {

View File

@ -1,8 +1,8 @@
import sys import sys
import os
import uvicorn import uvicorn
from settings import PORT from settings import PORT, DEV_SERVER_STATUS_FILE_NAME
log_settings = { log_settings = {
'version': 1, 'version': 1,
@ -54,6 +54,9 @@ if __name__ == "__main__":
x = sys.argv[1] x = sys.argv[1]
if x == "dev": if x == "dev":
print("DEV MODE") print("DEV MODE")
if os.path.exists(DEV_SERVER_STATUS_FILE_NAME):
os.remove(DEV_SERVER_STATUS_FILE_NAME)
headers = [ headers = [
("Access-Control-Allow-Methods", "GET, POST, OPTIONS, HEAD"), ("Access-Control-Allow-Methods", "GET, POST, OPTIONS, HEAD"),
("Access-Control-Allow-Origin", "http://localhost:3000"), ("Access-Control-Allow-Origin", "http://localhost:3000"),
@ -65,14 +68,15 @@ if __name__ == "__main__":
("Access-Control-Allow-Credentials", "true"), ("Access-Control-Allow-Credentials", "true"),
] ]
uvicorn.run( uvicorn.run(
"main:app", "main:dev_app",
host="localhost", host="localhost",
port=8080, port=8080,
headers=headers, headers=headers,
# log_config=LOGGING_CONFIG, # log_config=LOGGING_CONFIG,
log_level=None, log_level=None,
access_log=True access_log=False,
) # , ssl_keyfile="discours.key", ssl_certfile="discours.crt", reload=True) reload=True
) # , ssl_keyfile="discours.key", ssl_certfile="discours.crt")
elif x == "migrate": elif x == "migrate":
from migration import migrate from migration import migrate

View File

@ -23,10 +23,12 @@ class UserStorage:
async def get_user(id): async def get_user(id):
with local_session() as session: with local_session() as session:
user = ( user = (
session.query(User) session.query(User).options(
.options(selectinload(User.roles), selectinload(User.ratings)) selectinload(User.roles),
.filter(User.id == id) selectinload(User.ratings)
.one() ).filter(
User.id == id
).one()
) )
return user return user

View File

@ -34,13 +34,15 @@ class ReactedStorage:
modified_shouts = set([]) modified_shouts = set([])
@staticmethod @staticmethod
async def get_shout_stat(slug): async def get_shout_stat(slug, rating):
viewed = int(await ViewedStorage.get_shout(slug))
# print(viewed)
return { return {
# TODO "viewed": viewed,
"viewed": 0, # await ViewedStorage.get_shout(slug),
"reacted": len(await ReactedStorage.get_shout(slug)), "reacted": len(await ReactedStorage.get_shout(slug)),
"commented": len(await ReactedStorage.get_comments(slug)), "commented": len(await ReactedStorage.get_comments(slug)),
"rating": await ReactedStorage.get_rating(slug), # "rating": await ReactedStorage.get_rating(slug),
"rating": rating
} }
@staticmethod @staticmethod

View File

@ -3,7 +3,7 @@ from datetime import timedelta, timezone, datetime
from gql import Client, gql from gql import Client, gql
from gql.transport.aiohttp import AIOHTTPTransport from gql.transport.aiohttp import AIOHTTPTransport
from base.orm import local_session from base.orm import local_session
from sqlalchemy import func, select from sqlalchemy import func
from orm.shout import ShoutTopic from orm.shout import ShoutTopic
from orm.viewed import ViewedEntry from orm.viewed import ViewedEntry
from ssl import create_default_context from ssl import create_default_context
@ -119,12 +119,14 @@ class ViewedStorage:
if not shout_views: if not shout_views:
shout_views = 0 shout_views = 0
with local_session() as session: with local_session() as session:
shout_views_q = select(func.sum(ViewedEntry.amount)).where( try:
shout_views = session.query(func.sum(ViewedEntry.amount)).where(
ViewedEntry.shout == shout_slug ViewedEntry.shout == shout_slug
) ).all()[0][0]
shout_views = session.execute(shout_views_q)
self.by_shouts[shout_slug] = shout_views self.by_shouts[shout_slug] = shout_views
self.update_topics(session, shout_slug) self.update_topics(session, shout_slug)
except Exception as e:
raise e
return shout_views return shout_views

View File

@ -25,3 +25,6 @@ for provider in OAUTH_PROVIDERS:
SHOUTS_REPO = "content" SHOUTS_REPO = "content"
SESSION_TOKEN_HEADER = "Authorization" SESSION_TOKEN_HEADER = "Authorization"
# for local development
DEV_SERVER_STATUS_FILE_NAME = 'dev-server-status.txt'