commit
4c4f18147e
2
main.py
2
main.py
|
@ -8,6 +8,7 @@ from starlette.middleware import Middleware
|
||||||
from starlette.middleware.authentication import AuthenticationMiddleware
|
from starlette.middleware.authentication import AuthenticationMiddleware
|
||||||
from starlette.middleware.sessions import SessionMiddleware
|
from starlette.middleware.sessions import SessionMiddleware
|
||||||
from starlette.routing import Route
|
from starlette.routing import Route
|
||||||
|
from orm import init_tables
|
||||||
|
|
||||||
from auth.authenticate import JWTAuthenticate
|
from auth.authenticate import JWTAuthenticate
|
||||||
from auth.oauth import oauth_login, oauth_authorize
|
from auth.oauth import oauth_login, oauth_authorize
|
||||||
|
@ -30,6 +31,7 @@ middleware = [
|
||||||
|
|
||||||
|
|
||||||
async def start_up():
|
async def start_up():
|
||||||
|
init_tables()
|
||||||
await redis.connect()
|
await redis.connect()
|
||||||
await storages_init()
|
await storages_init()
|
||||||
views_stat_task = asyncio.create_task(ViewedStorage().worker())
|
views_stat_task = asyncio.create_task(ViewedStorage().worker())
|
||||||
|
|
|
@ -7,7 +7,6 @@ import sys
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import bs4
|
import bs4
|
||||||
from base.redis import redis
|
|
||||||
from migration.tables.comments import migrate as migrateComment
|
from migration.tables.comments import migrate as migrateComment
|
||||||
from migration.tables.comments import migrate_2stage as migrateComment_2stage
|
from migration.tables.comments import migrate_2stage as migrateComment_2stage
|
||||||
from migration.tables.content_items import get_shout_slug
|
from migration.tables.content_items import get_shout_slug
|
||||||
|
@ -17,6 +16,7 @@ from migration.tables.users import migrate as migrateUser
|
||||||
from migration.tables.users import migrate_2stage as migrateUser_2stage
|
from migration.tables.users import migrate_2stage as migrateUser_2stage
|
||||||
from orm.reaction import Reaction
|
from orm.reaction import Reaction
|
||||||
from settings import DB_URL
|
from settings import DB_URL
|
||||||
|
from orm import init_tables
|
||||||
|
|
||||||
# from export import export_email_subscriptions
|
# from export import export_email_subscriptions
|
||||||
from .export import export_mdx, export_slug
|
from .export import export_mdx, export_slug
|
||||||
|
@ -84,6 +84,7 @@ async def shouts_handle(storage, args):
|
||||||
discours_author = 0
|
discours_author = 0
|
||||||
anonymous_author = 0
|
anonymous_author = 0
|
||||||
pub_counter = 0
|
pub_counter = 0
|
||||||
|
ignored = 0
|
||||||
topics_dataset_bodies = []
|
topics_dataset_bodies = []
|
||||||
topics_dataset_tlist = []
|
topics_dataset_tlist = []
|
||||||
for entry in storage["shouts"]["data"]:
|
for entry in storage["shouts"]["data"]:
|
||||||
|
@ -96,40 +97,44 @@ async def shouts_handle(storage, args):
|
||||||
|
|
||||||
# migrate
|
# migrate
|
||||||
shout = await migrateShout(entry, storage)
|
shout = await migrateShout(entry, storage)
|
||||||
storage["shouts"]["by_oid"][entry["_id"]] = shout
|
if shout:
|
||||||
storage["shouts"]["by_slug"][shout["slug"]] = shout
|
storage["shouts"]["by_oid"][entry["_id"]] = shout
|
||||||
# shouts.topics
|
storage["shouts"]["by_slug"][shout["slug"]] = shout
|
||||||
if not shout["topics"]:
|
# shouts.topics
|
||||||
print("[migration] no topics!")
|
if not shout["topics"]:
|
||||||
|
print("[migration] no topics!")
|
||||||
|
|
||||||
# with author
|
# with author
|
||||||
author: str = shout["authors"][0].dict()
|
author: str = shout["authors"][0].dict()
|
||||||
if author["slug"] == "discours":
|
if author["slug"] == "discours":
|
||||||
discours_author += 1
|
discours_author += 1
|
||||||
if author["slug"] == "anonymous":
|
if author["slug"] == "anonymous":
|
||||||
anonymous_author += 1
|
anonymous_author += 1
|
||||||
# print('[migration] ' + shout['slug'] + ' with author ' + author)
|
# print('[migration] ' + shout['slug'] + ' with author ' + author)
|
||||||
|
|
||||||
if entry.get("published"):
|
if entry.get("published"):
|
||||||
if "mdx" in args:
|
if "mdx" in args:
|
||||||
export_mdx(shout)
|
export_mdx(shout)
|
||||||
pub_counter += 1
|
pub_counter += 1
|
||||||
|
|
||||||
# print main counter
|
# print main counter
|
||||||
counter += 1
|
counter += 1
|
||||||
line = str(counter + 1) + ": " + shout["slug"] + " @" + author["slug"]
|
line = str(counter + 1) + ": " + shout["slug"] + " @" + author["slug"]
|
||||||
print(line)
|
print(line)
|
||||||
|
|
||||||
b = bs4.BeautifulSoup(shout["body"], "html.parser")
|
b = bs4.BeautifulSoup(shout["body"], "html.parser")
|
||||||
texts = [shout["title"].lower().replace(r"[^а-яА-Яa-zA-Z]", "")]
|
texts = [shout["title"].lower().replace(r"[^а-яА-Яa-zA-Z]", "")]
|
||||||
texts = texts + b.findAll(text=True)
|
texts = texts + b.findAll(text=True)
|
||||||
topics_dataset_bodies.append(" ".join([x.strip().lower() for x in texts]))
|
topics_dataset_bodies.append(" ".join([x.strip().lower() for x in texts]))
|
||||||
topics_dataset_tlist.append(shout["topics"])
|
topics_dataset_tlist.append(shout["topics"])
|
||||||
|
else:
|
||||||
|
ignored += 1
|
||||||
|
|
||||||
# np.savetxt('topics_dataset.csv', (topics_dataset_bodies, topics_dataset_tlist), delimiter=',
|
# np.savetxt('topics_dataset.csv', (topics_dataset_bodies, topics_dataset_tlist), delimiter=',
|
||||||
# ', fmt='%s')
|
# ', fmt='%s')
|
||||||
|
|
||||||
print("[migration] " + str(counter) + " content items were migrated")
|
print("[migration] " + str(counter) + " content items were migrated")
|
||||||
|
print("[migration] " + str(ignored) + " content items were ignored")
|
||||||
print("[migration] " + str(pub_counter) + " have been published")
|
print("[migration] " + str(pub_counter) + " have been published")
|
||||||
print("[migration] " + str(discours_author) + " authored by @discours")
|
print("[migration] " + str(discours_author) + " authored by @discours")
|
||||||
print("[migration] " + str(anonymous_author) + " authored by @anonymous")
|
print("[migration] " + str(anonymous_author) + " authored by @anonymous")
|
||||||
|
@ -182,8 +187,6 @@ async def all_handle(storage, args):
|
||||||
await users_handle(storage)
|
await users_handle(storage)
|
||||||
await topics_handle(storage)
|
await topics_handle(storage)
|
||||||
print("[migration] users and topics are migrated")
|
print("[migration] users and topics are migrated")
|
||||||
await redis.connect()
|
|
||||||
print("[migration] redis connected")
|
|
||||||
await shouts_handle(storage, args)
|
await shouts_handle(storage, args)
|
||||||
print("[migration] migrating comments")
|
print("[migration] migrating comments")
|
||||||
await comments_handle(storage)
|
await comments_handle(storage)
|
||||||
|
@ -314,6 +317,7 @@ async def main():
|
||||||
cmd = sys.argv[1]
|
cmd = sys.argv[1]
|
||||||
if type(cmd) == str:
|
if type(cmd) == str:
|
||||||
print("[migration] command: " + cmd)
|
print("[migration] command: " + cmd)
|
||||||
|
init_tables()
|
||||||
await handle_auto()
|
await handle_auto()
|
||||||
else:
|
else:
|
||||||
print("[migration] usage: python server.py migrate")
|
print("[migration] usage: python server.py migrate")
|
||||||
|
|
|
@ -3,10 +3,8 @@ import json
|
||||||
from dateutil.parser import parse as date_parse
|
from dateutil.parser import parse as date_parse
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
from transliterate import translit
|
from transliterate import translit
|
||||||
|
|
||||||
from base.orm import local_session
|
from base.orm import local_session
|
||||||
from migration.extract import prepare_html_body
|
from migration.extract import prepare_html_body
|
||||||
from orm.community import Community
|
|
||||||
from orm.reaction import Reaction, ReactionKind
|
from orm.reaction import Reaction, ReactionKind
|
||||||
from orm.shout import Shout, ShoutTopic, ShoutReactionsFollower
|
from orm.shout import Shout, ShoutTopic, ShoutReactionsFollower
|
||||||
from orm.user import User
|
from orm.user import User
|
||||||
|
@ -103,12 +101,8 @@ async def migrate(entry, storage):
|
||||||
r = {
|
r = {
|
||||||
"layout": type2layout[entry["type"]],
|
"layout": type2layout[entry["type"]],
|
||||||
"title": entry["title"],
|
"title": entry["title"],
|
||||||
"community": Community.default_community.id,
|
|
||||||
"authors": [],
|
"authors": [],
|
||||||
"topics": set([]),
|
"topics": set([])
|
||||||
# 'rating': 0,
|
|
||||||
# 'ratings': [],
|
|
||||||
"createdAt": [],
|
|
||||||
}
|
}
|
||||||
topics_by_oid = storage["topics"]["by_oid"]
|
topics_by_oid = storage["topics"]["by_oid"]
|
||||||
users_by_oid = storage["users"]["by_oid"]
|
users_by_oid = storage["users"]["by_oid"]
|
||||||
|
@ -177,20 +171,24 @@ async def migrate(entry, storage):
|
||||||
# add author as TopicFollower
|
# add author as TopicFollower
|
||||||
with local_session() as session:
|
with local_session() as session:
|
||||||
for tpc in r['topics']:
|
for tpc in r['topics']:
|
||||||
tf = session.query(
|
try:
|
||||||
TopicFollower
|
tf = session.query(
|
||||||
).where(
|
TopicFollower
|
||||||
TopicFollower.follower == userslug
|
).where(
|
||||||
).filter(
|
TopicFollower.follower == userslug
|
||||||
TopicFollower.topic == tpc
|
).filter(
|
||||||
).first()
|
TopicFollower.topic == tpc
|
||||||
if not tf:
|
).first()
|
||||||
tf = TopicFollower.create(
|
if not tf:
|
||||||
topic=tpc,
|
tf = TopicFollower.create(
|
||||||
follower=userslug,
|
topic=tpc,
|
||||||
auto=True
|
follower=userslug,
|
||||||
)
|
auto=True
|
||||||
session.add(tf)
|
)
|
||||||
|
session.add(tf)
|
||||||
|
except IntegrityError:
|
||||||
|
print('[migration.shout] skipped by topic ' + tpc)
|
||||||
|
return
|
||||||
|
|
||||||
entry["topics"] = r["topics"]
|
entry["topics"] = r["topics"]
|
||||||
entry["cover"] = r["cover"]
|
entry["cover"] = r["cover"]
|
||||||
|
@ -205,7 +203,6 @@ async def migrate(entry, storage):
|
||||||
user = None
|
user = None
|
||||||
del shout_dict["topics"]
|
del shout_dict["topics"]
|
||||||
with local_session() as session:
|
with local_session() as session:
|
||||||
# c = session.query(Community).all().pop()
|
|
||||||
if not user and userslug:
|
if not user and userslug:
|
||||||
user = session.query(User).filter(User.slug == userslug).first()
|
user = session.query(User).filter(User.slug == userslug).first()
|
||||||
if not user and userdata:
|
if not user and userdata:
|
||||||
|
|
|
@ -200,7 +200,6 @@
|
||||||
"ecology": "ecology",
|
"ecology": "ecology",
|
||||||
"economics": "economics",
|
"economics": "economics",
|
||||||
"eda": "food",
|
"eda": "food",
|
||||||
"editing": "editing",
|
|
||||||
"editorial-statements": "editorial-statements",
|
"editorial-statements": "editorial-statements",
|
||||||
"eduard-limonov": "eduard-limonov",
|
"eduard-limonov": "eduard-limonov",
|
||||||
"education": "education",
|
"education": "education",
|
||||||
|
@ -597,7 +596,6 @@
|
||||||
"r-b": "rnb",
|
"r-b": "rnb",
|
||||||
"rasizm": "racism",
|
"rasizm": "racism",
|
||||||
"realizm": "realism",
|
"realizm": "realism",
|
||||||
"redaktura": "editorial",
|
|
||||||
"refleksiya": "reflection",
|
"refleksiya": "reflection",
|
||||||
"reggi": "reggae",
|
"reggi": "reggae",
|
||||||
"religion": "religion",
|
"religion": "religion",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from base.orm import local_session
|
from base.orm import local_session
|
||||||
from migration.extract import extract_md, html2text
|
from migration.extract import extract_md, html2text
|
||||||
from orm import Topic, Community
|
from orm import Topic
|
||||||
|
|
||||||
|
|
||||||
def migrate(entry):
|
def migrate(entry):
|
||||||
|
@ -8,9 +8,7 @@ def migrate(entry):
|
||||||
topic_dict = {
|
topic_dict = {
|
||||||
"slug": entry["slug"],
|
"slug": entry["slug"],
|
||||||
"oid": entry["_id"],
|
"oid": entry["_id"],
|
||||||
"title": entry["title"].replace(" ", " "),
|
"title": entry["title"].replace(" ", " ")
|
||||||
"children": [],
|
|
||||||
"community": Community.default_community.slug,
|
|
||||||
}
|
}
|
||||||
topic_dict["body"] = extract_md(html2text(body_orig), entry["_id"])
|
topic_dict["body"] = extract_md(html2text(body_orig), entry["_id"])
|
||||||
with local_session() as session:
|
with local_session() as session:
|
||||||
|
|
|
@ -36,6 +36,7 @@ def migrate(entry):
|
||||||
)
|
)
|
||||||
bio = BeautifulSoup(entry.get("profile").get("bio") or "", features="lxml").text
|
bio = BeautifulSoup(entry.get("profile").get("bio") or "", features="lxml").text
|
||||||
if bio.startswith('<'):
|
if bio.startswith('<'):
|
||||||
|
print('[migration] bio! ' + bio)
|
||||||
bio = BeautifulSoup(bio, features="lxml").text
|
bio = BeautifulSoup(bio, features="lxml").text
|
||||||
bio = bio.replace('\(', '(').replace('\)', ')')
|
bio = bio.replace('\(', '(').replace('\)', ')')
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,9 @@ from orm.reaction import Reaction
|
||||||
from orm.shout import Shout
|
from orm.shout import Shout
|
||||||
from orm.topic import Topic, TopicFollower
|
from orm.topic import Topic, TopicFollower
|
||||||
from orm.user import User, UserRating
|
from orm.user import User, UserRating
|
||||||
|
from orm.viewed import ViewedEntry
|
||||||
|
|
||||||
|
# NOTE: keep orm module isolated
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"User",
|
"User",
|
||||||
|
@ -19,13 +22,18 @@ __all__ = [
|
||||||
"Notification",
|
"Notification",
|
||||||
"Reaction",
|
"Reaction",
|
||||||
"UserRating"
|
"UserRating"
|
||||||
|
"ViewedEntry"
|
||||||
]
|
]
|
||||||
|
|
||||||
Base.metadata.create_all(engine)
|
|
||||||
Operation.init_table()
|
|
||||||
Resource.init_table()
|
|
||||||
User.init_table()
|
|
||||||
Community.init_table()
|
|
||||||
Role.init_table()
|
|
||||||
|
|
||||||
# NOTE: keep orm module isolated
|
def init_tables():
|
||||||
|
Base.metadata.create_all(engine)
|
||||||
|
Operation.init_table()
|
||||||
|
Resource.init_table()
|
||||||
|
User.init_table()
|
||||||
|
Community.init_table()
|
||||||
|
UserRating.init_table()
|
||||||
|
Shout.init_table()
|
||||||
|
Role.init_table()
|
||||||
|
ViewedEntry.init_table()
|
||||||
|
print("[orm] tables initialized")
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from sqlalchemy import Boolean, Column, String, ForeignKey, DateTime
|
from sqlalchemy import Boolean, Column, String, ForeignKey, DateTime
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
from base.orm import Base
|
from base.orm import Base
|
||||||
|
from orm.user import User
|
||||||
|
|
||||||
|
|
||||||
class CollabAuthor(Base):
|
class CollabAuthor(Base):
|
||||||
|
@ -21,5 +22,6 @@ class Collab(Base):
|
||||||
title = Column(String, nullable=True, comment="Title")
|
title = Column(String, nullable=True, comment="Title")
|
||||||
body = Column(String, nullable=True, comment="Body")
|
body = Column(String, nullable=True, comment="Body")
|
||||||
pic = Column(String, nullable=True, comment="Picture")
|
pic = Column(String, nullable=True, comment="Picture")
|
||||||
|
authors = relationship(lambda: User, secondary=CollabAuthor.__tablename__)
|
||||||
createdAt = Column(DateTime, default=datetime.now, comment="Created At")
|
createdAt = Column(DateTime, default=datetime.now, comment="Created At")
|
||||||
createdBy = Column(ForeignKey("user.id"), comment="Created By")
|
createdBy = Column(ForeignKey("user.id"), comment="Created By")
|
||||||
|
|
|
@ -32,12 +32,14 @@ class Community(Base):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def init_table():
|
def init_table():
|
||||||
with local_session() as session:
|
with local_session() as session:
|
||||||
default = (
|
d = (
|
||||||
session.query(Community).filter(Community.slug == "discours").first()
|
session.query(Community).filter(Community.slug == "discours").first()
|
||||||
)
|
)
|
||||||
if not default:
|
if not d:
|
||||||
default = Community.create(
|
d = Community.create(
|
||||||
name="Дискурс", slug="discours", createdBy="discours"
|
name="Дискурс", slug="discours", createdBy="anonymous"
|
||||||
)
|
)
|
||||||
|
session.add(d)
|
||||||
Community.default_community = default
|
session.commit()
|
||||||
|
Community.default_community = d
|
||||||
|
print('[orm] default community id: %s' % d.id)
|
||||||
|
|
|
@ -50,7 +50,7 @@ class Role(Base):
|
||||||
default = Role.create(
|
default = Role.create(
|
||||||
name="author",
|
name="author",
|
||||||
desc="Role for author",
|
desc="Role for author",
|
||||||
community=Community.default_community.id,
|
community=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
Role.default_role = default
|
Role.default_role = default
|
||||||
|
|
22
orm/shout.py
22
orm/shout.py
|
@ -1,9 +1,9 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, JSON
|
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, String, JSON
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from base.orm import Base
|
from base.orm import Base, local_session
|
||||||
from orm.reaction import Reaction
|
from orm.reaction import Reaction
|
||||||
from orm.topic import Topic
|
from orm.topic import Topic
|
||||||
from orm.user import User
|
from orm.user import User
|
||||||
|
@ -43,7 +43,7 @@ class Shout(Base):
|
||||||
__tablename__ = "shout"
|
__tablename__ = "shout"
|
||||||
|
|
||||||
slug = Column(String, unique=True)
|
slug = Column(String, unique=True)
|
||||||
community = Column(Integer, ForeignKey("community.id"), nullable=False, comment="Community")
|
community = Column(ForeignKey("community.id"), default=1)
|
||||||
lang = Column(String, nullable=False, default='ru', comment="Language")
|
lang = Column(String, nullable=False, default='ru', comment="Language")
|
||||||
body = Column(String, nullable=False, comment="Body")
|
body = Column(String, nullable=False, comment="Body")
|
||||||
title = Column(String, nullable=True)
|
title = Column(String, nullable=True)
|
||||||
|
@ -56,7 +56,6 @@ class Shout(Base):
|
||||||
reactions = relationship(lambda: Reaction)
|
reactions = relationship(lambda: Reaction)
|
||||||
visibility = Column(String, nullable=True) # owner authors community public
|
visibility = Column(String, nullable=True) # owner authors community public
|
||||||
versionOf = Column(ForeignKey("shout.slug"), nullable=True)
|
versionOf = Column(ForeignKey("shout.slug"), nullable=True)
|
||||||
lang = Column(String, default='ru')
|
|
||||||
oid = Column(String, nullable=True)
|
oid = Column(String, nullable=True)
|
||||||
media = Column(JSON, nullable=True)
|
media = Column(JSON, nullable=True)
|
||||||
|
|
||||||
|
@ -64,3 +63,18 @@ class Shout(Base):
|
||||||
updatedAt = Column(DateTime, nullable=True, comment="Updated at")
|
updatedAt = Column(DateTime, nullable=True, comment="Updated at")
|
||||||
publishedAt = Column(DateTime, nullable=True)
|
publishedAt = Column(DateTime, nullable=True)
|
||||||
deletedAt = Column(DateTime, nullable=True)
|
deletedAt = Column(DateTime, nullable=True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def init_table():
|
||||||
|
with local_session() as session:
|
||||||
|
s = session.query(Shout).first()
|
||||||
|
if not s:
|
||||||
|
entry = {
|
||||||
|
"slug": "genesis-block",
|
||||||
|
"body": "",
|
||||||
|
"title": "Ничего",
|
||||||
|
"lang": "ru"
|
||||||
|
}
|
||||||
|
s = Shout.create(**entry)
|
||||||
|
session.add(s)
|
||||||
|
session.commit()
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from sqlalchemy import JSON as JSONType
|
|
||||||
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, String
|
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, String
|
||||||
|
|
||||||
from base.orm import Base
|
from base.orm import Base
|
||||||
|
@ -25,10 +24,7 @@ class Topic(Base):
|
||||||
title = Column(String, nullable=False, comment="Title")
|
title = Column(String, nullable=False, comment="Title")
|
||||||
body = Column(String, nullable=True, comment="Body")
|
body = Column(String, nullable=True, comment="Body")
|
||||||
pic = Column(String, nullable=True, comment="Picture")
|
pic = Column(String, nullable=True, comment="Picture")
|
||||||
children = Column(
|
|
||||||
JSONType, nullable=True, default=[], comment="list of children topics"
|
|
||||||
)
|
|
||||||
community = Column(
|
community = Column(
|
||||||
ForeignKey("community.slug"), nullable=False, comment="Community"
|
ForeignKey("community.id"), default=1, comment="Community"
|
||||||
)
|
)
|
||||||
oid = Column(String, nullable=True, comment="Old ID")
|
oid = Column(String, nullable=True, comment="Old ID")
|
||||||
|
|
|
@ -25,6 +25,10 @@ class UserRating(Base):
|
||||||
user = Column(ForeignKey("user.slug"), primary_key=True)
|
user = Column(ForeignKey("user.slug"), primary_key=True)
|
||||||
value = Column(Integer)
|
value = Column(Integer)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def init_table():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class UserRole(Base):
|
class UserRole(Base):
|
||||||
__tablename__ = "user_role"
|
__tablename__ = "user_role"
|
||||||
|
@ -48,6 +52,7 @@ class AuthorFollower(Base):
|
||||||
|
|
||||||
class User(Base):
|
class User(Base):
|
||||||
__tablename__ = "user"
|
__tablename__ = "user"
|
||||||
|
default_user = None
|
||||||
|
|
||||||
email = Column(String, unique=True, nullable=False, comment="Email")
|
email = Column(String, unique=True, nullable=False, comment="Email")
|
||||||
username = Column(String, nullable=False, comment="Login")
|
username = Column(String, nullable=False, comment="Login")
|
||||||
|
|
|
@ -1,13 +1,24 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from sqlalchemy import Column, DateTime, ForeignKey
|
from sqlalchemy import Column, DateTime, ForeignKey, Integer
|
||||||
from base.orm import Base
|
from base.orm import Base, local_session
|
||||||
|
|
||||||
|
|
||||||
class ViewedEntry(Base):
|
class ViewedEntry(Base):
|
||||||
__tablename__ = "viewed"
|
__tablename__ = "viewed"
|
||||||
|
|
||||||
viewer = Column(ForeignKey("user.slug"), default='anonymous')
|
viewer = Column(ForeignKey("user.slug"), default='anonymous')
|
||||||
shout = Column(ForeignKey("shout.slug"))
|
shout = Column(ForeignKey("shout.slug"), default="genesis-block")
|
||||||
|
amount = Column(Integer, default=1)
|
||||||
createdAt = Column(
|
createdAt = Column(
|
||||||
DateTime, nullable=False, default=datetime.now, comment="Created at"
|
DateTime, nullable=False, default=datetime.now, comment="Created at"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def init_table():
|
||||||
|
with local_session() as session:
|
||||||
|
entry = {
|
||||||
|
"amount": 0
|
||||||
|
}
|
||||||
|
viewed = ViewedEntry.create(**entry)
|
||||||
|
session.add(viewed)
|
||||||
|
session.commit()
|
||||||
|
|
|
@ -25,3 +25,4 @@ DateTime~=4.7
|
||||||
asyncio~=3.4.3
|
asyncio~=3.4.3
|
||||||
python-dateutil~=2.8.2
|
python-dateutil~=2.8.2
|
||||||
beautifulsoup4~=4.11.1
|
beautifulsoup4~=4.11.1
|
||||||
|
lxml
|
||||||
|
|
|
@ -11,7 +11,6 @@ from orm.topic import Topic, TopicFollower
|
||||||
from orm.user import AuthorFollower, Role, User, UserRating, UserRole
|
from orm.user import AuthorFollower, Role, User, UserRating, UserRole
|
||||||
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.zine.authors import AuthorsStorage
|
|
||||||
from services.zine.shoutauthor import ShoutAuthor
|
from services.zine.shoutauthor import ShoutAuthor
|
||||||
|
|
||||||
# from .community import followed_communities
|
# from .community import followed_communities
|
||||||
|
@ -33,7 +32,7 @@ async def get_author_stat(slug):
|
||||||
# TODO: implement author stat
|
# TODO: implement author stat
|
||||||
with local_session() as session:
|
with local_session() as session:
|
||||||
return {
|
return {
|
||||||
"shouts": session.query(ShoutAuthor).where(ShoutAuthor.author == slug).count(),
|
"shouts": session.query(ShoutAuthor).where(ShoutAuthor.user == slug).count(),
|
||||||
"followers": session.query(AuthorFollower).where(AuthorFollower.author == slug).count(),
|
"followers": session.query(AuthorFollower).where(AuthorFollower.author == slug).count(),
|
||||||
"followings": session.query(AuthorFollower).where(AuthorFollower.follower == slug).count(),
|
"followings": session.query(AuthorFollower).where(AuthorFollower.follower == slug).count(),
|
||||||
"rating": session.query(func.sum(UserRating.value)).where(UserRating.user == slug).first(),
|
"rating": session.query(func.sum(UserRating.value)).where(UserRating.user == slug).first(),
|
||||||
|
@ -175,12 +174,22 @@ def author_unfollow(user, slug):
|
||||||
|
|
||||||
@query.field("authorsAll")
|
@query.field("authorsAll")
|
||||||
async def get_authors_all(_, _info):
|
async def get_authors_all(_, _info):
|
||||||
authors = await AuthorsStorage.get_all_authors()
|
with local_session() as session:
|
||||||
for author in authors:
|
authors = session.query(User).join(ShoutAuthor).all()
|
||||||
author.stat = await get_author_stat(author.slug)
|
for author in authors:
|
||||||
|
author.stat = await get_author_stat(author.slug)
|
||||||
return authors
|
return authors
|
||||||
|
|
||||||
|
|
||||||
|
@query.field("getAuthor")
|
||||||
|
async def get_author(_, _info, slug):
|
||||||
|
with local_session() as session:
|
||||||
|
author = session.query(User).join(ShoutAuthor).where(User.slug == slug).first()
|
||||||
|
for author in author:
|
||||||
|
author.stat = await get_author_stat(author.slug)
|
||||||
|
return author
|
||||||
|
|
||||||
|
|
||||||
@query.field("loadAuthorsBy")
|
@query.field("loadAuthorsBy")
|
||||||
async def load_authors_by(_, info, by, limit, offset):
|
async def load_authors_by(_, info, by, limit, offset):
|
||||||
authors = []
|
authors = []
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
|
#!/usr/bin/env python3.10
|
||||||
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 selectinload
|
||||||
from sqlalchemy.sql.expression import or_, desc, asc, select, case
|
from sqlalchemy.sql.expression import desc, asc, select, case
|
||||||
from timeit import default_timer as timer
|
|
||||||
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.shout import Shout, ShoutAuthor
|
from orm.shout import Shout
|
||||||
from orm.reaction import Reaction, ReactionKind
|
from orm.reaction import Reaction, ReactionKind
|
||||||
# from resolvers.community import community_follow, community_unfollow
|
# from resolvers.community import community_follow, community_unfollow
|
||||||
from resolvers.profile import author_follow, author_unfollow
|
from resolvers.profile import author_follow, author_unfollow
|
||||||
|
@ -55,7 +55,7 @@ async def load_shouts_by(_, info, options):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
q = select(Shout).options(
|
q = select(Shout).options(
|
||||||
# TODO add cation
|
# TODO add caption
|
||||||
selectinload(Shout.authors),
|
selectinload(Shout.authors),
|
||||||
selectinload(Shout.topics),
|
selectinload(Shout.topics),
|
||||||
).where(
|
).where(
|
||||||
|
@ -67,10 +67,7 @@ async def load_shouts_by(_, info, options):
|
||||||
user = info.context["request"].user
|
user = info.context["request"].user
|
||||||
q.join(Reaction, Reaction.createdBy == user.slug)
|
q.join(Reaction, Reaction.createdBy == user.slug)
|
||||||
if options.get("filters").get("visibility"):
|
if options.get("filters").get("visibility"):
|
||||||
q = q.filter(or_(
|
q = q.filter(Shout.visibility == options.get("filters").get("visibility"))
|
||||||
Shout.visibility.ilike(f"%{options.get('filters').get('visibility')}%"),
|
|
||||||
Shout.visibility.ilike(f"%{'public'}%"),
|
|
||||||
))
|
|
||||||
if options.get("filters").get("layout"):
|
if options.get("filters").get("layout"):
|
||||||
q = q.filter(Shout.layout == options.get("filters").get("layout"))
|
q = q.filter(Shout.layout == options.get("filters").get("layout"))
|
||||||
if options.get("filters").get("author"):
|
if options.get("filters").get("author"):
|
||||||
|
@ -84,49 +81,45 @@ async def load_shouts_by(_, info, options):
|
||||||
if options.get("filters").get("days"):
|
if options.get("filters").get("days"):
|
||||||
before = datetime.now() - timedelta(days=int(options.get("filter").get("days")) or 30)
|
before = datetime.now() - timedelta(days=int(options.get("filter").get("days")) or 30)
|
||||||
q = q.filter(Shout.createdAt > before)
|
q = q.filter(Shout.createdAt > before)
|
||||||
|
o = options.get("order_by")
|
||||||
if options.get("order_by") == 'comments':
|
if o:
|
||||||
q = q.join(Reaction, Shout.slug == Reaction.shout and Reaction.body.is_not(None)).add_columns(
|
q = q.add_columns(sa.func.count(Reaction.id).label(o))
|
||||||
sa.func.count(Reaction.id).label(options.get("order_by")))
|
if o == 'comments':
|
||||||
if options.get("order_by") == 'reacted':
|
q = q.join(Reaction, Shout.slug == Reaction.shout)
|
||||||
q = q.join(Reaction).add_columns(sa.func.max(Reaction.createdAt).label(options.get("order_by")))
|
q = q.filter(Reaction.body.is_not(None))
|
||||||
if options.get("order_by") == "rating":
|
elif o == 'reacted':
|
||||||
q = q.join(Reaction).add_columns(sa.func.sum(case(
|
q = q.join(
|
||||||
(Reaction.kind == ReactionKind.AGREE, 1),
|
Reaction
|
||||||
(Reaction.kind == ReactionKind.DISAGREE, -1),
|
).add_columns(
|
||||||
(Reaction.kind == ReactionKind.PROOF, 1),
|
sa.func.max(Reaction.createdAt).label(o)
|
||||||
(Reaction.kind == ReactionKind.DISPROOF, -1),
|
)
|
||||||
(Reaction.kind == ReactionKind.ACCEPT, 1),
|
elif o == "rating":
|
||||||
(Reaction.kind == ReactionKind.REJECT, -1),
|
q = q.join(Reaction).add_columns(sa.func.sum(case(
|
||||||
(Reaction.kind == ReactionKind.LIKE, 1),
|
(Reaction.kind == ReactionKind.AGREE, 1),
|
||||||
(Reaction.kind == ReactionKind.DISLIKE, -1),
|
(Reaction.kind == ReactionKind.DISAGREE, -1),
|
||||||
else_=0
|
(Reaction.kind == ReactionKind.PROOF, 1),
|
||||||
)).label(options.get("order_by")))
|
(Reaction.kind == ReactionKind.DISPROOF, -1),
|
||||||
# if order_by == 'views':
|
(Reaction.kind == ReactionKind.ACCEPT, 1),
|
||||||
# TODO dump ackee data to db periodically
|
(Reaction.kind == ReactionKind.REJECT, -1),
|
||||||
|
(Reaction.kind == ReactionKind.LIKE, 1),
|
||||||
order_by = options.get("order_by") if options.get("order_by") else 'createdAt'
|
(Reaction.kind == ReactionKind.DISLIKE, -1),
|
||||||
|
else_=0
|
||||||
order_by_desc = True if options.get('order_by_desc') is None else options.get('order_by_desc')
|
)).label(o))
|
||||||
|
order_by = o
|
||||||
query_order_by = desc(order_by) if order_by_desc else asc(order_by)
|
else:
|
||||||
|
order_by = 'createdAt'
|
||||||
q = q.group_by(Shout.id).order_by(query_order_by).limit(options.get("limit")).offset(
|
query_order_by = desc(order_by) if options.get("order_by_desc") else asc(order_by)
|
||||||
options.get("offset") if options.get("offset") else 0)
|
offset = options.get("offset", 0)
|
||||||
|
limit = options.get("limit", 10)
|
||||||
|
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:
|
||||||
# post query stats and author's captions
|
|
||||||
# start = timer()
|
|
||||||
shouts = list(map(lambda r: r.Shout, session.execute(q)))
|
shouts = list(map(lambda r: r.Shout, session.execute(q)))
|
||||||
for s in shouts:
|
for s in shouts:
|
||||||
s.stat = await ReactedStorage.get_shout_stat(s.slug)
|
s.stat = await ReactedStorage.get_shout_stat(s.slug)
|
||||||
for a in s.authors:
|
for a in s.authors:
|
||||||
a.caption = await ShoutAuthorStorage.get_author_caption(s.slug, a.slug)
|
a.caption = await ShoutAuthorStorage.get_author_caption(s.slug, a.slug)
|
||||||
|
|
||||||
# end = timer()
|
|
||||||
# print(end - start)
|
|
||||||
# print(q)
|
|
||||||
|
|
||||||
return shouts
|
return shouts
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ type AuthorStat {
|
||||||
followers: Int
|
followers: Int
|
||||||
rating: Int
|
rating: Int
|
||||||
commented: Int
|
commented: Int
|
||||||
|
shouts: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -116,8 +117,8 @@ input TopicInput {
|
||||||
title: String
|
title: String
|
||||||
body: String
|
body: String
|
||||||
pic: String
|
pic: String
|
||||||
children: [String]
|
# children: [String]
|
||||||
parents: [String]
|
# parents: [String]
|
||||||
}
|
}
|
||||||
|
|
||||||
input ReactionInput {
|
input ReactionInput {
|
||||||
|
@ -481,7 +482,7 @@ type TopicStat {
|
||||||
shouts: Int!
|
shouts: Int!
|
||||||
followers: Int!
|
followers: Int!
|
||||||
authors: Int!
|
authors: Int!
|
||||||
viewed: Int!
|
viewed: Int
|
||||||
reacted: Int!
|
reacted: Int!
|
||||||
commented: Int
|
commented: Int
|
||||||
rating: Int
|
rating: Int
|
||||||
|
@ -492,8 +493,6 @@ type Topic {
|
||||||
title: String
|
title: String
|
||||||
body: String
|
body: String
|
||||||
pic: String
|
pic: String
|
||||||
parents: [String] # NOTE: topic can have parent topics
|
|
||||||
children: [String] # and children
|
|
||||||
community: Community!
|
community: Community!
|
||||||
stat: TopicStat
|
stat: TopicStat
|
||||||
oid: String
|
oid: String
|
||||||
|
|
60
server.py
60
server.py
|
@ -4,6 +4,50 @@ import uvicorn
|
||||||
|
|
||||||
from settings import PORT
|
from settings import PORT
|
||||||
|
|
||||||
|
log_settings = {
|
||||||
|
'version': 1,
|
||||||
|
'disable_existing_loggers': True,
|
||||||
|
'formatters': {
|
||||||
|
'default': {
|
||||||
|
'()': 'uvicorn.logging.DefaultFormatter',
|
||||||
|
'fmt': '%(levelprefix)s %(message)s',
|
||||||
|
'use_colors': None
|
||||||
|
},
|
||||||
|
'access': {
|
||||||
|
'()': 'uvicorn.logging.AccessFormatter',
|
||||||
|
'fmt': '%(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'handlers': {
|
||||||
|
'default': {
|
||||||
|
'formatter': 'default',
|
||||||
|
'class': 'logging.StreamHandler',
|
||||||
|
'stream': 'ext://sys.stderr'
|
||||||
|
},
|
||||||
|
'access': {
|
||||||
|
'formatter': 'access',
|
||||||
|
'class': 'logging.StreamHandler',
|
||||||
|
'stream': 'ext://sys.stdout'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'loggers': {
|
||||||
|
'uvicorn': {
|
||||||
|
'handlers': ['default'],
|
||||||
|
'level': 'INFO'
|
||||||
|
},
|
||||||
|
'uvicorn.error': {
|
||||||
|
'level': 'INFO',
|
||||||
|
'handlers': ['default'],
|
||||||
|
'propagate': True
|
||||||
|
},
|
||||||
|
'uvicorn.access': {
|
||||||
|
'handlers': ['access'],
|
||||||
|
'level': 'INFO',
|
||||||
|
'propagate': False
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
x = ""
|
x = ""
|
||||||
if len(sys.argv) > 1:
|
if len(sys.argv) > 1:
|
||||||
|
@ -21,11 +65,23 @@ if __name__ == "__main__":
|
||||||
("Access-Control-Allow-Credentials", "true"),
|
("Access-Control-Allow-Credentials", "true"),
|
||||||
]
|
]
|
||||||
uvicorn.run(
|
uvicorn.run(
|
||||||
"main:app", host="localhost", port=8080, headers=headers
|
"main:app",
|
||||||
|
host="localhost",
|
||||||
|
port=8080,
|
||||||
|
headers=headers,
|
||||||
|
# log_config=LOGGING_CONFIG,
|
||||||
|
log_level=None,
|
||||||
|
access_log=True
|
||||||
) # , ssl_keyfile="discours.key", ssl_certfile="discours.crt", reload=True)
|
) # , ssl_keyfile="discours.key", ssl_certfile="discours.crt", reload=True)
|
||||||
elif x == "migrate":
|
elif x == "migrate":
|
||||||
from migration import migrate
|
from migration import migrate
|
||||||
|
|
||||||
migrate()
|
migrate()
|
||||||
else:
|
else:
|
||||||
uvicorn.run("main:app", host="0.0.0.0", port=PORT)
|
uvicorn.run(
|
||||||
|
"main:app",
|
||||||
|
host="0.0.0.0",
|
||||||
|
port=PORT,
|
||||||
|
proxy_headers=True,
|
||||||
|
server_header=True
|
||||||
|
)
|
||||||
|
|
|
@ -3,12 +3,14 @@ from services.auth.roles import RoleStorage
|
||||||
from services.auth.users import UserStorage
|
from services.auth.users import UserStorage
|
||||||
from services.zine.topics import TopicStorage
|
from services.zine.topics import TopicStorage
|
||||||
from services.search import SearchService
|
from services.search import SearchService
|
||||||
|
from services.stat.viewed import ViewedStorage
|
||||||
from base.orm import local_session
|
from base.orm import local_session
|
||||||
|
|
||||||
|
|
||||||
async def storages_init():
|
async def storages_init():
|
||||||
with local_session() as session:
|
with local_session() as session:
|
||||||
print('[main] initialize storages')
|
print('[main] initialize storages')
|
||||||
|
ViewedStorage.init()
|
||||||
ReactedStorage.init(session)
|
ReactedStorage.init(session)
|
||||||
RoleStorage.init(session)
|
RoleStorage.init(session)
|
||||||
UserStorage.init(session)
|
UserStorage.init(session)
|
||||||
|
|
|
@ -181,12 +181,11 @@ class ReactedStorage:
|
||||||
c += len(siblings)
|
c += len(siblings)
|
||||||
await self.recount(siblings)
|
await self.recount(siblings)
|
||||||
|
|
||||||
print("[stat.reacted] %d reactions total" % c)
|
print("[stat.reacted] %d reactions recounted" % c)
|
||||||
print("[stat.reacted] %d shouts" % len(self.modified_shouts))
|
print("[stat.reacted] %d shouts modified" % len(self.modified_shouts))
|
||||||
print("[stat.reacted] %d topics" % len(self.reacted["topics"].values()))
|
print("[stat.reacted] %d topics" % len(self.reacted["topics"].values()))
|
||||||
print("[stat.reacted] %d shouts" % len(self.reacted["shouts"]))
|
|
||||||
print("[stat.reacted] %d authors" % len(self.reacted["authors"].values()))
|
print("[stat.reacted] %d authors" % len(self.reacted["authors"].values()))
|
||||||
print("[stat.reacted] %d reactions replied" % len(self.reacted["reactions"]))
|
print("[stat.reacted] %d replies" % len(self.reacted["reactions"]))
|
||||||
self.modified_shouts = set([])
|
self.modified_shouts = set([])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
@ -2,10 +2,10 @@ import asyncio
|
||||||
|
|
||||||
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 orm.shout import ShoutTopic
|
||||||
from orm.viewed import ViewedEntry
|
from orm.viewed import ViewedEntry
|
||||||
from services.zine.topics import TopicStorage
|
|
||||||
from ssl import create_default_context
|
from ssl import create_default_context
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,42 +42,75 @@ ssl = create_default_context()
|
||||||
|
|
||||||
class ViewedStorage:
|
class ViewedStorage:
|
||||||
lock = asyncio.Lock()
|
lock = asyncio.Lock()
|
||||||
|
by_shouts = {}
|
||||||
by_topics = {}
|
by_topics = {}
|
||||||
period = 5 * 60 # 5 minutes
|
period = 5 * 60 # 5 minutes
|
||||||
client = None
|
client = None
|
||||||
transport = None
|
transport = None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def load_views(session):
|
def init():
|
||||||
|
ViewedStorage.transport = AIOHTTPTransport(url="https://ackee.discours.io/", ssl=ssl)
|
||||||
|
ViewedStorage.client = Client(transport=ViewedStorage.transport, fetch_schema_from_transport=True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def update_views(session):
|
||||||
# TODO: when the struture of payload will be transparent
|
# TODO: when the struture of payload will be transparent
|
||||||
# TODO: perhaps ackee token getting here
|
# TODO: perhaps ackee token getting here
|
||||||
|
|
||||||
self = ViewedStorage()
|
self = ViewedStorage
|
||||||
async with self.lock:
|
async with self.lock:
|
||||||
self.transport = AIOHTTPTransport(url="https://ackee.discours.io/", ssl=ssl)
|
|
||||||
self.client = Client(transport=self.transport, fetch_schema_from_transport=True)
|
|
||||||
domains = await self.client.execute_async(query_ackee_views)
|
domains = await self.client.execute_async(query_ackee_views)
|
||||||
print("[stat.ackee] loaded domains")
|
print("[stat.ackee] loaded domains")
|
||||||
print(domains)
|
print(domains)
|
||||||
print('\n\n# TODO: something here...\n\n')
|
print('\n\n# TODO: something here...\n\n')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def get_shout(shout_slug):
|
||||||
|
self = ViewedStorage
|
||||||
|
async with self.lock:
|
||||||
|
r = self.by_shouts.get(shout_slug)
|
||||||
|
if not r:
|
||||||
|
with local_session() as session:
|
||||||
|
shout_views = 0
|
||||||
|
shout_views_q = select(func.sum(ViewedEntry.amount)).where(
|
||||||
|
ViewedEntry.shout == shout_slug
|
||||||
|
)
|
||||||
|
shout_views = session.execute(shout_views_q)
|
||||||
|
self.by_shouts[shout_slug] = shout_views
|
||||||
|
return shout_views
|
||||||
|
else:
|
||||||
|
return r
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def get_topic(topic_slug):
|
||||||
|
self = ViewedStorage
|
||||||
|
topic_views = 0
|
||||||
|
async with self.lock:
|
||||||
|
topic_views_by_shouts = self.by_topics.get(topic_slug) or {}
|
||||||
|
for shout in topic_views_by_shouts:
|
||||||
|
topic_views += shout
|
||||||
|
return topic_views
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def increment(shout_slug, amount=1, viewer='anonymous'):
|
async def increment(shout_slug, amount=1, viewer='anonymous'):
|
||||||
self = ViewedStorage
|
self = ViewedStorage
|
||||||
async with self.lock:
|
async with self.lock:
|
||||||
with local_session() as session:
|
with local_session() as session:
|
||||||
viewed = ViewedEntry.create({
|
viewed = ViewedEntry.create(**{
|
||||||
"viewer": viewer,
|
"viewer": viewer,
|
||||||
"shout": shout_slug
|
"shout": shout_slug,
|
||||||
|
"amount": amount
|
||||||
})
|
})
|
||||||
session.add(viewed)
|
session.add(viewed)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
self.by_shouts[shout_slug] = self.by_shouts.get(shout_slug, 0) + amount
|
||||||
shout_topics = await TopicStorage.get_topics_by_slugs([shout_slug, ])
|
topics = session.query(ShoutTopic).where(ShoutTopic.shout == shout_slug).all()
|
||||||
for t in shout_topics:
|
for t in topics:
|
||||||
self.by_topics[t] = self.by_topics.get(t) or {}
|
tpc = t.topic
|
||||||
self.by_topics[t][shout_slug] = self.by_topics[t].get(shout_slug) or 0
|
if not self.by_topics.get(tpc):
|
||||||
self.by_topics[t][shout_slug] += amount
|
self.by_topics[tpc] = {}
|
||||||
|
self.by_topics[tpc][shout_slug] = self.by_shouts[shout_slug]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def worker():
|
async def worker():
|
||||||
|
@ -85,8 +118,8 @@ class ViewedStorage:
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
with local_session() as session:
|
with local_session() as session:
|
||||||
await self.load_views(session)
|
await self.update_views(session)
|
||||||
|
print("[stat.viewed] next renew in %d minutes" % (self.period / 60))
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
print("[stat.viewed] : %s" % (err))
|
print("[stat.viewed] %s" % (err))
|
||||||
print("[stat.viewed] renew period: %d minutes" % (self.period / 60))
|
|
||||||
await asyncio.sleep(self.period)
|
await asyncio.sleep(self.period)
|
||||||
|
|
|
@ -1,127 +0,0 @@
|
||||||
import asyncio
|
|
||||||
import json
|
|
||||||
|
|
||||||
from gql import Client, gql
|
|
||||||
from gql.transport.aiohttp import AIOHTTPTransport
|
|
||||||
|
|
||||||
from base.redis import redis
|
|
||||||
from services.zine.topics import TopicStorage
|
|
||||||
from ssl import create_default_context
|
|
||||||
|
|
||||||
|
|
||||||
query_ackee_views = gql(
|
|
||||||
"""
|
|
||||||
query getDomainsFacts {
|
|
||||||
domains {
|
|
||||||
statistics {
|
|
||||||
views {
|
|
||||||
id
|
|
||||||
count
|
|
||||||
}
|
|
||||||
pages {
|
|
||||||
id
|
|
||||||
count
|
|
||||||
created
|
|
||||||
}
|
|
||||||
}
|
|
||||||
facts {
|
|
||||||
activeVisitors
|
|
||||||
# averageViews
|
|
||||||
# averageDuration
|
|
||||||
viewsToday
|
|
||||||
viewsMonth
|
|
||||||
viewsYear
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
ssl = create_default_context()
|
|
||||||
|
|
||||||
|
|
||||||
class ViewStat:
|
|
||||||
lock = asyncio.Lock()
|
|
||||||
by_slugs = {}
|
|
||||||
by_topics = {}
|
|
||||||
period = 5 * 60 # 5 minutes
|
|
||||||
transport = AIOHTTPTransport(url="https://ackee.discours.io/", ssl=ssl)
|
|
||||||
client = Client(transport=transport, fetch_schema_from_transport=True)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def load_views():
|
|
||||||
# TODO: when the struture of paylod will be transparent
|
|
||||||
# TODO: perhaps ackee token getting here
|
|
||||||
|
|
||||||
self = ViewStat
|
|
||||||
async with self.lock:
|
|
||||||
self.by_topics = await redis.execute("GET", "views_by_topics")
|
|
||||||
if self.by_topics:
|
|
||||||
self.by_topics = dict(json.loads(self.by_topics))
|
|
||||||
else:
|
|
||||||
self.by_topics = {}
|
|
||||||
self.by_slugs = await redis.execute("GET", "views_by_shouts")
|
|
||||||
if self.by_slugs:
|
|
||||||
self.by_slugs = dict(json.loads(self.by_slugs))
|
|
||||||
else:
|
|
||||||
self.by_slugs = {}
|
|
||||||
domains = await self.client.execute_async(query_ackee_views)
|
|
||||||
print("[stat.ackee] loaded domains")
|
|
||||||
print(domains)
|
|
||||||
|
|
||||||
print('\n\n# TODO: something here...\n\n')
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def get_shout(shout_slug):
|
|
||||||
self = ViewStat
|
|
||||||
async with self.lock:
|
|
||||||
return self.by_slugs.get(shout_slug) or 0
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def get_topic(topic_slug):
|
|
||||||
self = ViewStat
|
|
||||||
async with self.lock:
|
|
||||||
shouts = self.by_topics.get(topic_slug) or {}
|
|
||||||
topic_views = 0
|
|
||||||
for v in shouts.values():
|
|
||||||
topic_views += v
|
|
||||||
return topic_views
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def increment(shout_slug, amount=1):
|
|
||||||
self = ViewStat
|
|
||||||
async with self.lock:
|
|
||||||
self.by_slugs[shout_slug] = self.by_slugs.get(shout_slug) or 0
|
|
||||||
self.by_slugs[shout_slug] += amount
|
|
||||||
await redis.execute(
|
|
||||||
"SET",
|
|
||||||
f"views_by_shouts/{shout_slug}",
|
|
||||||
str(self.by_slugs[shout_slug])
|
|
||||||
)
|
|
||||||
shout_topics = await TopicStorage.get_topics_by_slugs([shout_slug, ])
|
|
||||||
for t in shout_topics:
|
|
||||||
self.by_topics[t] = self.by_topics.get(t) or {}
|
|
||||||
self.by_topics[t][shout_slug] = self.by_topics[t].get(shout_slug) or 0
|
|
||||||
self.by_topics[t][shout_slug] += amount
|
|
||||||
await redis.execute(
|
|
||||||
"SET",
|
|
||||||
f"views_by_topics/{t}/{shout_slug}",
|
|
||||||
str(self.by_topics[t][shout_slug])
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def reset():
|
|
||||||
self = ViewStat
|
|
||||||
self.by_topics = {}
|
|
||||||
self.by_slugs = {}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def worker():
|
|
||||||
self = ViewStat
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
await self.load_views()
|
|
||||||
except Exception as err:
|
|
||||||
print("[stat.ackee] : %s" % (err))
|
|
||||||
print("[stat.ackee] renew period: %d minutes" % (ViewStat.period / 60))
|
|
||||||
await asyncio.sleep(self.period)
|
|
|
@ -1,12 +0,0 @@
|
||||||
from base.orm import local_session
|
|
||||||
from orm.user import User
|
|
||||||
from orm.shout import ShoutAuthor
|
|
||||||
|
|
||||||
|
|
||||||
class AuthorsStorage:
|
|
||||||
@staticmethod
|
|
||||||
async def get_all_authors():
|
|
||||||
with local_session() as session:
|
|
||||||
query = session.query(User).join(ShoutAuthor)
|
|
||||||
result = query.all()
|
|
||||||
return result
|
|
|
@ -12,19 +12,20 @@ class TopicStorage:
|
||||||
topics = session.query(Topic)
|
topics = session.query(Topic)
|
||||||
self.topics = dict([(topic.slug, topic) for topic in topics])
|
self.topics = dict([(topic.slug, topic) for topic in topics])
|
||||||
for tpc in self.topics.values():
|
for tpc in self.topics.values():
|
||||||
self.load_parents(tpc)
|
# self.load_parents(tpc)
|
||||||
|
pass
|
||||||
|
|
||||||
print("[zine.topics] %d precached" % len(self.topics.keys()))
|
print("[zine.topics] %d precached" % len(self.topics.keys()))
|
||||||
|
|
||||||
@staticmethod
|
# @staticmethod
|
||||||
def load_parents(topic):
|
# def load_parents(topic):
|
||||||
self = TopicStorage
|
# self = TopicStorage
|
||||||
parents = []
|
# parents = []
|
||||||
for parent in self.topics.values():
|
# for parent in self.topics.values():
|
||||||
if topic.slug in parent.children:
|
# if topic.slug in parent.children:
|
||||||
parents.append(parent.slug)
|
# parents.append(parent.slug)
|
||||||
topic.parents = parents
|
# topic.parents = parents
|
||||||
return topic
|
# return topic
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def get_topics_all():
|
async def get_topics_all():
|
||||||
|
@ -64,4 +65,4 @@ class TopicStorage:
|
||||||
self = TopicStorage
|
self = TopicStorage
|
||||||
async with self.lock:
|
async with self.lock:
|
||||||
self.topics[topic.slug] = topic
|
self.topics[topic.slug] = topic
|
||||||
self.load_parents(topic)
|
# self.load_parents(topic)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user