fmt
All checks were successful
Deploy to core / deploy (push) Successful in 2m0s

This commit is contained in:
2024-02-21 10:27:16 +03:00
parent 4f26812340
commit 7cf702eb98
35 changed files with 1059 additions and 825 deletions

View File

@@ -1,75 +1,44 @@
import time
from importlib import import_module
from sqlalchemy import JSON, Boolean, Column, ForeignKey, Integer, String
from sqlalchemy import event
from services.db import Base
class AuthorRating(Base):
__tablename__ = 'author_rating'
__tablename__ = "author_rating"
id = None # type: ignore
rater = Column(ForeignKey('author.id'), primary_key=True)
author = Column(ForeignKey('author.id'), primary_key=True)
rater = Column(ForeignKey("author.id"), primary_key=True)
author = Column(ForeignKey("author.id"), primary_key=True)
plus = Column(Boolean)
class AuthorFollower(Base):
__tablename__ = 'author_follower'
__tablename__ = "author_follower"
id = None # type: ignore
follower = Column(ForeignKey('author.id'), primary_key=True)
author = Column(ForeignKey('author.id'), primary_key=True)
follower = Column(ForeignKey("author.id"), primary_key=True)
author = Column(ForeignKey("author.id"), primary_key=True)
created_at = Column(Integer, nullable=False, default=lambda: int(time.time()))
auto = Column(Boolean, nullable=False, default=False)
class Author(Base):
__tablename__ = 'author'
__tablename__ = "author"
user = Column(String,
) # unbounded link with authorizer's User type
user = Column(String) # unbounded link with authorizer's User type
name = Column(String, nullable=True, comment='Display name')
name = Column(String, nullable=True, comment="Display name")
slug = Column(String, unique=True, comment="Author's slug")
bio = Column(String, nullable=True, comment='Bio') # status description
about = Column(String, nullable=True, comment='About') # long and formatted
pic = Column(String, nullable=True, comment='Picture')
links = Column(JSON, nullable=True, comment='Links')
bio = Column(String, nullable=True, comment="Bio") # status description
about = Column(String, nullable=True, comment="About") # long and formatted
pic = Column(String, nullable=True, comment="Picture")
links = Column(JSON, nullable=True, comment="Links")
created_at = Column(Integer, nullable=False, default=lambda: int(time.time()))
last_seen = Column(Integer, nullable=False, default=lambda: int(time.time()))
updated_at = Column(Integer, nullable=False, default=lambda: int(time.time()))
deleted_at = Column(Integer, nullable=True, comment='Deleted at')
def get_object(connection, table_name, object_id):
return connection.execute(f"SELECT * FROM {table_name} WHERE id = :object_id", {"object_id": object_id}).fetchone()
def update_app_data(connection, user_id, app_data):
connection.execute("UPDATE authorizer_users SET app_data = :app_data WHERE id = :user_id", {"app_data": app_data, "user_id": user_id})
def update_follows(user, entity_type, entity):
app_data = user.app_data or {}
app_data['follows'] = user.app_data or {"topics": [], "authors": [], "shouts": [], "communities": []}
app_data['follows'][f'{entity_type}s'].append(vars(entity))
return app_data
@event.listens_for(Author, 'after_insert')
@event.listens_for(Author, 'after_update')
def after_author_update(mapper, connection, target):
user_id = target.user
user = get_object(connection, 'authorizer_users', user_id)
if user:
app_data = update_follows(user, 'author', target)
update_app_data(connection, user_id, app_data)
deleted_at = Column(Integer, nullable=True, comment="Deleted at")
@event.listens_for(AuthorFollower, 'after_insert')
@event.listens_for(AuthorFollower, 'after_delete')
def after_author_follower_change(mapper, connection, target):
author_id = target.author
follower_id = target.follower
user = get_object(connection, 'authorizer_users', follower_id)
if user:
app_data = update_follows(user, 'author', get_object(connection, 'author', author_id))
update_app_data(connection, follower_id, app_data)
import_module("orm.author_events")

104
orm/author_events.py Normal file
View File

@@ -0,0 +1,104 @@
from sqlalchemy import event, select
from services.rediscache import redis
from orm.author import Author, AuthorFollower
from orm.topic import Topic, TopicFollower
from orm.shout import Shout, ShoutReactionsFollower
@event.listens_for(Author, "after_insert")
@event.listens_for(Author, "after_update")
async def after_author_update(mapper, connection, target):
redis_key = f"user:{target.user}:author"
await redis.execute("HSET", redis_key, vars(target))
async def update_follows_for_user(connection, user_id, entity_type, entity, is_insert):
redis_key = f"user:{user_id}:follows"
follows = await redis.execute("HGET", redis_key)
if not follows:
follows = {
"topics": [],
"authors": [],
"shouts": [],
"communities": [
{"slug": "discours", "name": "Дискурс", "id": 1, "desc": ""}
],
}
entity_type = "communitie" if entity_type == "community" else entity_type
if is_insert:
follows[f"{entity_type}s"].append(entity)
else:
# Remove the entity from follows
follows[f"{entity_type}s"] = [
e for e in follows[f"{entity_type}s"] if e["id"] != entity.id
]
await redis.execute("HSET", redis_key, vars(follows))
async def handle_author_follower_change(connection, author_id, follower_id, is_insert):
author = connection.execute(select(Author).filter(Author.id == author_id)).first()
follower = connection.execute(
select(Author).filter(Author.id == follower_id)
).first()
if follower and author:
await update_follows_for_user(
connection, follower.user, "author", author, is_insert
)
async def handle_shout_follower_change(connection, shout_id, follower_id, is_insert):
shout = connection.execute(select(Topic).filter(Shout.id == shout_id)).first()
follower = connection.execute(
select(Author).filter(Author.id == follower_id)
).first()
if follower and shout:
await update_follows_for_user(
connection, follower.user, "shout", shout, is_insert
)
async def handle_topic_follower_change(connection, topic_id, follower_id, is_insert):
topic = connection.execute(select(Topic).filter(Topic.id == topic_id)).first()
follower = connection.execute(
select(Author).filter(Author.id == follower_id)
).first()
if follower and topic:
await update_follows_for_user(
connection, follower.user, "topic", topic, is_insert
)
@event.listens_for(TopicFollower, "after_insert")
async def after_topic_follower_insert(mapper, connection, target):
await handle_topic_follower_change(connection, target.topic, target.follower, True)
@event.listens_for(TopicFollower, "after_delete")
async def after_topic_follower_delete(mapper, connection, target):
await handle_topic_follower_change(connection, target.topic, target.follower, False)
@event.listens_for(ShoutReactionsFollower, "after_insert")
async def after_shout_follower_insert(mapper, connection, target):
await handle_shout_follower_change(connection, target.shout, target.follower, True)
@event.listens_for(ShoutReactionsFollower, "after_delete")
async def after_shout_follower_delete(mapper, connection, target):
await handle_shout_follower_change(connection, target.shout, target.follower, False)
@event.listens_for(AuthorFollower, "after_insert")
async def after_author_follower_insert(mapper, connection, target):
await handle_author_follower_change(
connection, target.author, target.follower, True
)
@event.listens_for(AuthorFollower, "after_delete")
async def after_author_follower_delete(mapper, connection, target):
await handle_author_follower_change(
connection, target.author, target.follower, False
)

View File

@@ -6,20 +6,20 @@ from services.db import Base
class ShoutCollection(Base):
__tablename__ = 'shout_collection'
__tablename__ = "shout_collection"
id = None # type: ignore
shout = Column(ForeignKey('shout.id'), primary_key=True)
collection = Column(ForeignKey('collection.id'), primary_key=True)
shout = Column(ForeignKey("shout.id"), primary_key=True)
collection = Column(ForeignKey("collection.id"), primary_key=True)
class Collection(Base):
__tablename__ = 'collection'
__tablename__ = "collection"
slug = Column(String, unique=True)
title = Column(String, nullable=False, comment='Title')
body = Column(String, nullable=True, comment='Body')
pic = Column(String, nullable=True, comment='Picture')
title = Column(String, nullable=False, comment="Title")
body = Column(String, nullable=True, comment="Body")
pic = Column(String, nullable=True, comment="Picture")
created_at = Column(Integer, default=lambda: int(time.time()))
created_by = Column(ForeignKey('author.id'), comment='Created By')
created_by = Column(ForeignKey("author.id"), comment="Created By")
published_at = Column(Integer, default=lambda: int(time.time()))

View File

@@ -8,22 +8,22 @@ from orm.author import Author
class CommunityAuthor(Base):
__tablename__ = 'community_author'
__tablename__ = "community_author"
id = None # type: ignore
author = Column(ForeignKey('author.id'), primary_key=True)
community = Column(ForeignKey('community.id'), primary_key=True)
author = Column(ForeignKey("author.id"), primary_key=True)
community = Column(ForeignKey("community.id"), primary_key=True)
joined_at = Column(Integer, nullable=False, default=lambda: int(time.time()))
role = Column(String, nullable=False)
class Community(Base):
__tablename__ = 'community'
__tablename__ = "community"
name = Column(String, nullable=False)
slug = Column(String, nullable=False, unique=True)
desc = Column(String, nullable=False, default='')
pic = Column(String, nullable=False, default='')
desc = Column(String, nullable=False, default="")
pic = Column(String, nullable=False, default="")
created_at = Column(Integer, nullable=False, default=lambda: int(time.time()))
authors = relationship(Author, secondary='community_author')
authors = relationship(Author, secondary="community_author")

View File

@@ -7,19 +7,19 @@ from services.db import Base
class InviteStatus(Enumeration):
PENDING = 'PENDING'
ACCEPTED = 'ACCEPTED'
REJECTED = 'REJECTED'
PENDING = "PENDING"
ACCEPTED = "ACCEPTED"
REJECTED = "REJECTED"
class Invite(Base):
__tablename__ = 'invite'
__tablename__ = "invite"
inviter_id = Column(ForeignKey('author.id'), primary_key=True)
author_id = Column(ForeignKey('author.id'), primary_key=True)
shout_id = Column(ForeignKey('shout.id'), primary_key=True)
inviter_id = Column(ForeignKey("author.id"), primary_key=True)
author_id = Column(ForeignKey("author.id"), primary_key=True)
shout_id = Column(ForeignKey("shout.id"), primary_key=True)
status = Column(String, default=InviteStatus.PENDING.value)
inviter = relationship('author', foreign_keys=[inviter_id])
author = relationship('author', foreign_keys=[author_id])
shout = relationship('shout')
inviter = relationship("author", foreign_keys=[inviter_id])
author = relationship("author", foreign_keys=[author_id])
shout = relationship("shout")

View File

@@ -10,34 +10,34 @@ class ReactionKind(Enumeration):
# TYPE = <reaction index> # rating diff
# editor mode
AGREE = 'AGREE' # +1
DISAGREE = 'DISAGREE' # -1
ASK = 'ASK' # +0
PROPOSE = 'PROPOSE' # +0
PROOF = 'PROOF' # +1
DISPROOF = 'DISPROOF' # -1
ACCEPT = 'ACCEPT' # +1
REJECT = 'REJECT' # -1
AGREE = "AGREE" # +1
DISAGREE = "DISAGREE" # -1
ASK = "ASK" # +0
PROPOSE = "PROPOSE" # +0
PROOF = "PROOF" # +1
DISPROOF = "DISPROOF" # -1
ACCEPT = "ACCEPT" # +1
REJECT = "REJECT" # -1
# public feed
QUOTE = 'QUOTE' # +0 TODO: use to bookmark in collection
COMMENT = 'COMMENT' # +0
LIKE = 'LIKE' # +1
DISLIKE = 'DISLIKE' # -1
QUOTE = "QUOTE" # +0 TODO: use to bookmark in collection
COMMENT = "COMMENT" # +0
LIKE = "LIKE" # +1
DISLIKE = "DISLIKE" # -1
class Reaction(Base):
__tablename__ = 'reaction'
__tablename__ = "reaction"
body = Column(String, default='', comment='Reaction Body')
body = Column(String, default="", comment="Reaction Body")
created_at = Column(Integer, nullable=False, default=lambda: int(time.time()))
updated_at = Column(Integer, nullable=True, comment='Updated at')
deleted_at = Column(Integer, nullable=True, comment='Deleted at')
deleted_by = Column(ForeignKey('author.id'), nullable=True)
reply_to = Column(ForeignKey('reaction.id'), nullable=True)
quote = Column(String, nullable=True, comment='Original quoted text')
shout = Column(ForeignKey('shout.id'), nullable=False)
created_by = Column(ForeignKey('author.id'), nullable=False)
updated_at = Column(Integer, nullable=True, comment="Updated at")
deleted_at = Column(Integer, nullable=True, comment="Deleted at")
deleted_by = Column(ForeignKey("author.id"), nullable=True)
reply_to = Column(ForeignKey("reaction.id"), nullable=True)
quote = Column(String, nullable=True, comment="Original quoted text")
shout = Column(ForeignKey("shout.id"), nullable=False)
created_by = Column(ForeignKey("author.id"), nullable=False)
kind = Column(String, nullable=False)
oid = Column(String)

View File

@@ -1,56 +1,54 @@
import time
from sqlalchemy import JSON, Boolean, Column, ForeignKey, Integer, String
from sqlalchemy import event
from sqlalchemy.orm import relationship
from services.db import Base
from orm.community import Community
from orm.author import Author
from orm.author import get_object, update_follows, update_app_data
from orm.reaction import Reaction
from orm.topic import Topic
class ShoutTopic(Base):
__tablename__ = 'shout_topic'
__tablename__ = "shout_topic"
id = None # type: ignore
shout = Column(ForeignKey('shout.id'), primary_key=True)
topic = Column(ForeignKey('topic.id'), primary_key=True)
shout = Column(ForeignKey("shout.id"), primary_key=True)
topic = Column(ForeignKey("topic.id"), primary_key=True)
main = Column(Boolean, nullable=True)
class ShoutReactionsFollower(Base):
__tablename__ = 'shout_reactions_followers'
__tablename__ = "shout_reactions_followers"
id = None # type: ignore
follower = Column(ForeignKey('author.id'), primary_key=True)
shout = Column(ForeignKey('shout.id'), primary_key=True)
follower = Column(ForeignKey("author.id"), primary_key=True)
shout = Column(ForeignKey("shout.id"), primary_key=True)
auto = Column(Boolean, nullable=False, default=False)
created_at = Column(Integer, nullable=False, default=lambda: int(time.time()))
deleted_at = Column(Integer, nullable=True)
class ShoutAuthor(Base):
__tablename__ = 'shout_author'
__tablename__ = "shout_author"
id = None # type: ignore
shout = Column(ForeignKey('shout.id'), primary_key=True)
author = Column(ForeignKey('author.id'), primary_key=True)
caption = Column(String, nullable=True, default='')
shout = Column(ForeignKey("shout.id"), primary_key=True)
author = Column(ForeignKey("author.id"), primary_key=True)
caption = Column(String, nullable=True, default="")
class ShoutCommunity(Base):
__tablename__ = 'shout_community'
__tablename__ = "shout_community"
id = None # type: ignore
shout = Column(ForeignKey('shout.id'), primary_key=True)
community = Column(ForeignKey('community.id'), primary_key=True)
shout = Column(ForeignKey("shout.id"), primary_key=True)
community = Column(ForeignKey("community.id"), primary_key=True)
class Shout(Base):
__tablename__ = 'shout'
__tablename__ = "shout"
created_at = Column(Integer, nullable=False, default=lambda: int(time.time()))
updated_at = Column(Integer, nullable=True)
@@ -58,39 +56,28 @@ class Shout(Base):
featured_at = Column(Integer, nullable=True)
deleted_at = Column(Integer, nullable=True)
created_by = Column(ForeignKey('author.id'), nullable=False)
updated_by = Column(ForeignKey('author.id'), nullable=True)
deleted_by = Column(ForeignKey('author.id'), nullable=True)
created_by = Column(ForeignKey("author.id"), nullable=False)
updated_by = Column(ForeignKey("author.id"), nullable=True)
deleted_by = Column(ForeignKey("author.id"), nullable=True)
body = Column(String, nullable=False, comment='Body')
body = Column(String, nullable=False, comment="Body")
slug = Column(String, unique=True)
cover = Column(String, nullable=True, comment='Cover image url')
cover_caption = Column(String, nullable=True, comment='Cover image alt caption')
cover = Column(String, nullable=True, comment="Cover image url")
cover_caption = Column(String, nullable=True, comment="Cover image alt caption")
lead = Column(String, nullable=True)
description = Column(String, nullable=True)
title = Column(String, nullable=False)
subtitle = Column(String, nullable=True)
layout = Column(String, nullable=False, default='article')
layout = Column(String, nullable=False, default="article")
media = Column(JSON, nullable=True)
authors = relationship(Author, secondary='shout_author')
topics = relationship(Topic, secondary='shout_topic')
communities = relationship(Community, secondary='shout_community')
authors = relationship(Author, secondary="shout_author")
topics = relationship(Topic, secondary="shout_topic")
communities = relationship(Community, secondary="shout_community")
reactions = relationship(Reaction)
lang = Column(String, nullable=False, default='ru', comment='Language')
version_of = Column(ForeignKey('shout.id'), nullable=True)
lang = Column(String, nullable=False, default="ru", comment="Language")
version_of = Column(ForeignKey("shout.id"), nullable=True)
oid = Column(String, nullable=True)
seo = Column(String, nullable=True) # JSON
@event.listens_for(ShoutReactionsFollower, 'after_insert')
@event.listens_for(ShoutReactionsFollower, 'after_delete')
def after_topic_follower_change(mapper, connection, target):
shout_id = target.shout
follower_id = target.follower
user = get_object(connection, 'authorizer_users', follower_id)
if user:
app_data = update_follows(user, 'shout', get_object(connection, 'shout', shout_id))
update_app_data(connection, follower_id, app_data)

View File

@@ -1,38 +1,26 @@
import time
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
from sqlalchemy import event
from services.db import Base
from orm.author import get_object, update_follows, update_app_data
class TopicFollower(Base):
__tablename__ = 'topic_followers'
__tablename__ = "topic_followers"
id = None # type: ignore
follower = Column(ForeignKey('author.id'), primary_key=True)
topic = Column(ForeignKey('topic.id'), primary_key=True)
follower = Column(ForeignKey("author.id"), primary_key=True)
topic = Column(ForeignKey("topic.id"), primary_key=True)
created_at = Column(Integer, nullable=False, default=lambda: int(time.time()))
auto = Column(Boolean, nullable=False, default=False)
class Topic(Base):
__tablename__ = 'topic'
__tablename__ = "topic"
slug = Column(String, unique=True)
title = Column(String, nullable=False, comment='Title')
body = Column(String, nullable=True, comment='Body')
pic = Column(String, nullable=True, comment='Picture')
community = Column(ForeignKey('community.id'), default=1)
oid = Column(String, nullable=True, comment='Old ID')
@event.listens_for(TopicFollower, 'after_insert')
@event.listens_for(TopicFollower, 'after_delete')
def after_topic_follower_change(mapper, connection, target):
topic_id = target.topic
follower_id = target.follower
user = get_object(connection, 'authorizer_users', follower_id)
if user:
app_data = update_follows(user, 'topic', get_object(connection, 'topic', topic_id))
update_app_data(connection, follower_id, app_data)
title = Column(String, nullable=False, comment="Title")
body = Column(String, nullable=True, comment="Body")
pic = Column(String, nullable=True, comment="Picture")
community = Column(ForeignKey("community.id"), default=1)
oid = Column(String, nullable=True, comment="Old ID")

View File

@@ -6,7 +6,7 @@ from services.db import Base
class User(Base):
__tablename__ = 'authorizer_users'
__tablename__ = "authorizer_users"
id = Column(String, primary_key=True, unique=True, nullable=False, default=None)
key = Column(String)
@@ -24,7 +24,7 @@ class User(Base):
# preferred_username = Column(String, nullable=False)
picture = Column(String)
revoked_timestamp = Column(Integer)
roles = Column(String, default='author, reader')
signup_methods = Column(String, default='magic_link_login')
roles = Column(String, default="author, reader")
signup_methods = Column(String, default="magic_link_login")
created_at = Column(Integer, default=lambda: int(time.time()))
updated_at = Column(Integer, default=lambda: int(time.time()))