Merge branch 'main' of github.com:Discours/discours-backend into main

This commit is contained in:
tonyrewin 2022-06-13 20:28:47 +03:00
commit d93f686760
10 changed files with 130 additions and 70 deletions

View File

@ -13,7 +13,7 @@ passlib = "*"
PyJWT = "*" PyJWT = "*"
SQLAlchemy = "*" SQLAlchemy = "*"
itsdangerous = "*" itsdangerous = "*"
httpx = "<0.18.2" httpx = ">=0.23.0"
psycopg2-binary = "*" psycopg2-binary = "*"
Authlib = "*" Authlib = "*"
bson = "*" bson = "*"

View File

@ -1,5 +1,5 @@
from orm.rbac import Operation, Resource, Permission, Role, RoleStorage from orm.rbac import Operation, Resource, Permission, Role, RoleStorage
from orm.community import Community from orm.community import Community, CommunitySubscription
from orm.user import User, UserRating, UserRole, UserStorage from orm.user import User, UserRating, UserRole, UserStorage
from orm.topic import Topic, TopicSubscription, TopicStorage from orm.topic import Topic, TopicSubscription, TopicStorage
from orm.notification import Notification from orm.notification import Notification

View File

@ -3,6 +3,14 @@ from sqlalchemy import Column, Integer, String, ForeignKey, DateTime
from sqlalchemy.orm import relationship, backref from sqlalchemy.orm import relationship, backref
from orm.base import Base, local_session from orm.base import Base, local_session
class CommunitySubscription(Base):
__tablename__ = 'community_subscription'
id = None
subscriber = Column(ForeignKey('user.slug'), primary_key = True)
community = Column(ForeignKey('community.slug'), primary_key = True)
createdAt: str = Column(DateTime, nullable=False, default = datetime.now, comment="Created at")
class Community(Base): class Community(Base):
__tablename__ = 'community' __tablename__ = 'community'

View File

@ -8,7 +8,7 @@ pydantic
passlib passlib
itsdangerous itsdangerous
authlib==0.15.5 authlib==0.15.5
httpx==0.20.0 httpx>=0.23.0
psycopg2-binary psycopg2-binary
bson bson
python-frontmatter python-frontmatter

View File

@ -1,6 +1,6 @@
from resolvers.auth import login, sign_out, is_email_free, register, confirm from resolvers.auth import login, sign_out, is_email_free, register, confirm
from resolvers.zine import create_shout, get_shout_by_slug, \ from resolvers.zine import create_shout, get_shout_by_slug, \
top_month, top_overall, recent_shouts, recent_all, top_viewed, shouts_by_authors, shouts_by_topics, shouts_by_communities, \ top_month, top_overall, recent_published, recent_all, top_viewed, shouts_by_authors, shouts_by_topics, shouts_by_communities, \
shouts_candidates, shouts_reviewed, shouts_subscribed shouts_candidates, shouts_reviewed, shouts_subscribed
from resolvers.profile import get_users_by_slugs, get_current_user from resolvers.profile import get_users_by_slugs, get_current_user
from resolvers.topics import topic_subscribe, topic_unsubscribe, topics_by_author, \ from resolvers.topics import topic_subscribe, topic_unsubscribe, topics_by_author, \
@ -19,7 +19,7 @@ __all__ = [
"get_current_user", "get_current_user",
"get_users_by_slugs", "get_users_by_slugs",
"get_shout_by_slug", "get_shout_by_slug",
"recent_shouts", "recent_published",
"recent_all", "recent_all",
"shouts_by_topics", "shouts_by_topics",
"shouts_by_authors", "shouts_by_authors",

View File

@ -1,10 +1,12 @@
from orm import Community from orm import Community, CommunitySubscription
from orm.base import local_session from orm.base import local_session
from resolvers.base import mutation, query, subscription from resolvers.base import mutation, query, subscription
from auth.authenticate import login_required from auth.authenticate import login_required
import asyncio import asyncio
from datetime import datetime from datetime import datetime
from sqlalchemy import and_
@mutation.field("createCommunity") @mutation.field("createCommunity")
@login_required @login_required
async def create_community(_, info, title, desc): async def create_community(_, info, title, desc):
@ -68,3 +70,28 @@ async def get_communities(_, info):
with local_session() as session: with local_session() as session:
communities = session.query(Community) communities = session.query(Community)
return communities return communities
def community_subscribe(user, slug):
CommunitySubscription.create(
subscriber = user.slug,
community = slug
)
def community_unsubscribe(user, slug):
with local_session() as session:
sub = session.query(CommunitySubscription).\
filter(and_(CommunitySubscription.subscriber == user.slug, CommunitySubscription.community == slug)).\
first()
if not sub:
raise Exception("subscription not exist")
session.delete(sub)
session.commit()
def get_subscribed_communities(user_slug):
with local_session() as session:
rows = session.query(Community.slug).\
join(CommunitySubscription).\
where(CommunitySubscription.subscriber == user_slug).\
all()
slugs = [row.slug for row in rows]
return slugs

View File

@ -4,6 +4,8 @@ from orm.comment import Comment
from orm.base import local_session from orm.base import local_session
from orm.topic import Topic, TopicSubscription from orm.topic import Topic, TopicSubscription
from resolvers.base import mutation, query, subscription from resolvers.base import mutation, query, subscription
from resolvers.topics import topic_subscribe, topic_unsubscribe
from resolvers.community import community_subscribe, community_unsubscribe, get_subscribed_communities
from auth.authenticate import login_required from auth.authenticate import login_required
from inbox_resolvers.inbox import get_total_unread_messages_for_user from inbox_resolvers.inbox import get_total_unread_messages_for_user
@ -30,9 +32,10 @@ def _get_user_subscribed_authors(slug):
async def get_user_info(slug): async def get_user_info(slug):
return { return {
"totalUnreadMessages" : await get_total_unread_messages_for_user(slug), "totalUnreadMessages" : await get_total_unread_messages_for_user(slug),
"userSubscribedTopics" : _get_user_subscribed_topic_slugs(slug), "userSubscribedTopics" : _get_user_subscribed_topic_slugs(slug),
"userSubscribedAuthors": _get_user_subscribed_authors(slug) "userSubscribedAuthors" : _get_user_subscribed_authors(slug),
"userSubscribedCommunities": get_subscribed_communities(slug)
} }
@query.field("getCurrentUser") @query.field("getCurrentUser")
@ -133,30 +136,53 @@ async def rate_user(_, info, slug, value):
return {} return {}
@mutation.field("authorSubscribe")
@login_required
async def author_subscribe(_, info, slug):
user = info.context["request"].user
def author_subscribe(user, slug):
AuthorSubscription.create( AuthorSubscription.create(
subscriber = user.slug, subscriber = user.slug,
author = slug author = slug
) )
return {} def author_unsubscribe(user, slug):
@mutation.field("authorUnsubscribe")
@login_required
async def author_unsubscribe(_, info, slug):
user = info.context["request"].user
with local_session() as session: with local_session() as session:
sub = session.query(AuthorSubscription).\ sub = session.query(AuthorSubscription).\
filter(and_(AuthorSubscription.subscriber == user.slug, AuthorSubscription.author == slug)).\ filter(and_(AuthorSubscription.subscriber == user.slug, AuthorSubscription.author == slug)).\
first() first()
if not sub: if not sub:
return { "error" : "subscription not exist" } raise Exception("subscription not exist")
session.delete(sub) session.delete(sub)
session.commit() session.commit()
@mutation.field("subscribe")
@login_required
async def subscribe(_, info, subscription, slug):
user = info.context["request"].user
try:
if subscription == "AUTHOR":
author_subscribe(user, slug)
elif subscription == "TOPIC":
topic_subscribe(user, slug)
elif subscription == "COMMUNITY":
community_subscribe(user, slug)
except Exception as e:
return {"error" : e}
return {}
@mutation.field("unsubscribe")
@login_required
async def unsubscribe(_, info, subscription, slug):
user = info.context["request"].user
try:
if subscription == "AUTHOR":
author_unsubscribe(user, slug)
elif subscription == "TOPIC":
topic_unsubscribe(user, slug)
elif subscription == "COMMUNITY":
community_unsubscribe(user, slug)
except Exception as e:
return {"error" : e}
return {} return {}

View File

@ -60,29 +60,17 @@ async def update_topic(_, info, input):
return { "topic" : topic } return { "topic" : topic }
@mutation.field("topicSubscribe") def topic_subscribe(user, slug):
@login_required
async def topic_subscribe(_, info, slug):
user = info.context["request"].user
TopicSubscription.create( TopicSubscription.create(
subscriber = user.slug, subscriber = user.slug,
topic = slug) topic = slug)
return {} def topic_unsubscribe(user, slug):
@mutation.field("topicUnsubscribe")
@login_required
async def topic_unsubscribe(_, info, slug):
user = info.context["request"].user
with local_session() as session: with local_session() as session:
sub = session.query(TopicSubscription).\ sub = session.query(TopicSubscription).\
filter(and_(TopicSubscription.subscriber == user.slug, TopicSubscription.topic == slug)).\ filter(and_(TopicSubscription.subscriber == user.slug, TopicSubscription.topic == slug)).\
first() first()
if not sub: if not sub:
return { "error" : "subscription not exist" } raise Exception("subscription not exist")
session.delete(sub) session.delete(sub)
session.commit() session.commit()
return {}

View File

@ -1,5 +1,6 @@
from orm import Shout, ShoutAuthor, ShoutTopic, ShoutRating, ShoutViewByDay, User, Community, Resource,\ from orm import Shout, ShoutAuthor, ShoutTopic, ShoutRating, ShoutViewByDay, User, Community, Resource,\
ShoutRatingStorage, ShoutViewStorage, Comment, CommentRating, Topic ShoutRatingStorage, ShoutViewStorage, Comment, CommentRating, Topic
from orm.community import CommunitySubscription
from orm.base import local_session from orm.base import local_session
from orm.user import UserStorage, AuthorSubscription from orm.user import UserStorage, AuthorSubscription
from orm.topic import TopicSubscription from orm.topic import TopicSubscription
@ -83,7 +84,7 @@ class ShoutsCache:
lock = asyncio.Lock() lock = asyncio.Lock()
@staticmethod @staticmethod
async def prepare_recent_shouts(): async def prepare_recent_published():
with local_session() as session: with local_session() as session:
stmt = select(Shout).\ stmt = select(Shout).\
options(selectinload(Shout.authors), selectinload(Shout.topics)).\ options(selectinload(Shout.authors), selectinload(Shout.topics)).\
@ -96,14 +97,14 @@ class ShoutsCache:
shout.ratings = await ShoutRatingStorage.get_ratings(shout.slug) shout.ratings = await ShoutRatingStorage.get_ratings(shout.slug)
shouts.append(shout) shouts.append(shout)
async with ShoutsCache.lock: async with ShoutsCache.lock:
ShoutsCache.recent_shouts = shouts ShoutsCache.recent_published = shouts
@staticmethod @staticmethod
async def prepare_recent_all(): async def prepare_recent_all():
with local_session() as session: with local_session() as session:
stmt = select(Shout).\ stmt = select(Shout).\
options(selectinload(Shout.authors), selectinload(Shout.topics)).\ options(selectinload(Shout.authors), selectinload(Shout.topics)).\
where(Shout.publishedAt != None).\ order_by(desc("createdAt")).\
limit(ShoutsCache.limit) limit(ShoutsCache.limit)
shouts = [] shouts = []
for row in session.execute(stmt): for row in session.execute(stmt):
@ -198,7 +199,7 @@ class ShoutsCache:
await ShoutsCache.prepare_top_month() await ShoutsCache.prepare_top_month()
await ShoutsCache.prepare_top_overall() await ShoutsCache.prepare_top_overall()
await ShoutsCache.prepare_top_viewed() await ShoutsCache.prepare_top_viewed()
await ShoutsCache.prepare_recent_shouts() await ShoutsCache.prepare_recent_published()
await ShoutsCache.prepare_recent_all() await ShoutsCache.prepare_recent_all()
await ShoutsCache.prepare_recent_commented() await ShoutsCache.prepare_recent_commented()
print("shouts cache update finished") print("shouts cache update finished")
@ -242,9 +243,9 @@ async def top_overall(_, info, page, size):
return ShoutsCache.top_overall[(page - 1) * size : page * size] return ShoutsCache.top_overall[(page - 1) * size : page * size]
@query.field("recentPublished") @query.field("recentPublished")
async def recent_shouts(_, info, page, size): async def recent_published(_, info, page, size):
async with ShoutsCache.lock: async with ShoutsCache.lock:
return ShoutsCache.recent_shouts[(page - 1) * size : page * size] return ShoutsCache.recent_published[(page - 1) * size : page * size]
@query.field("recentAll") @query.field("recentAll")
async def recent_all(_, info, page, size): async def recent_all(_, info, page, size):
@ -446,13 +447,18 @@ async def shouts_subscribed(_, info, page, size):
shouts_by_topic = session.query(Shout).\ shouts_by_topic = session.query(Shout).\
join(ShoutTopic).\ join(ShoutTopic).\
join(TopicSubscription, ShoutTopic.topic == TopicSubscription.topic).\ join(TopicSubscription, ShoutTopic.topic == TopicSubscription.topic).\
where(and_(Shout.publishedAt != None, TopicSubscription.subscriber == user.slug)) where(TopicSubscription.subscriber == user.slug)
shouts_by_author = session.query(Shout).\ shouts_by_author = session.query(Shout).\
join(ShoutAuthor).\ join(ShoutAuthor).\
join(AuthorSubscription, ShoutAuthor.user == AuthorSubscription.author).\ join(AuthorSubscription, ShoutAuthor.user == AuthorSubscription.author).\
where(and_(Shout.publishedAt != None, AuthorSubscription.subscriber == user.slug)) where(AuthorSubscription.subscriber == user.slug)
shouts_by_community = session.query(Shout).\
join(Community).\
join(CommunitySubscription).\
where(CommunitySubscription.subscriber == user.slug)
shouts = shouts_by_topic.union(shouts_by_author).\ shouts = shouts_by_topic.union(shouts_by_author).\
order_by(desc(Shout.publishedAt)).\ union(shouts_by_community).\
order_by(desc(Shout.createdAt)).\
limit(size).\ limit(size).\
offset( (page - 1) * size) offset( (page - 1) * size)

View File

@ -7,9 +7,10 @@ type Result {
} }
type CurrentUserInfo { type CurrentUserInfo {
totalUnreadMessages : Int totalUnreadMessages : Int
userSubscribedTopics : [String]! userSubscribedTopics : [String]!
userSubscribedAuthors: [User]! userSubscribedAuthors : [User]!
userSubscribedCommunities : [String]!
} }
type AuthResult { type AuthResult {
@ -93,6 +94,12 @@ type CommentUpdatedResult {
comment: Comment comment: Comment
} }
enum SubscriptionType {
TOPIC
AUTHOR
COMMUNITY
}
################################### Mutation ################################### Mutation
type Mutation { type Mutation {
@ -118,8 +125,6 @@ type Mutation {
# topics # topics
createTopic(input: TopicInput!): TopicResult! createTopic(input: TopicInput!): TopicResult!
updateTopic(input: TopicInput!): TopicResult! updateTopic(input: TopicInput!): TopicResult!
topicSubscribe(slug: String!): Result!
topicUnsubscribe(slug: String!): Result!
createComment(body: String!, shout: String!, replyTo: Int): CommentResult! createComment(body: String!, shout: String!, replyTo: Int): CommentResult!
updateComment(id: Int!, body: String!): CommentResult! updateComment(id: Int!, body: String!): CommentResult!
@ -130,8 +135,8 @@ type Mutation {
updateCommunity(community: CommunityInput!): Community! updateCommunity(community: CommunityInput!): Community!
deleteCommunity(id: Int!): Result! deleteCommunity(id: Int!): Result!
authorSubscribe(slug: String!): Result! subscribe(subscription : SubscriptionType!, slug: String!): Result!
authorUnsubscribe(slug: String!): Result! unsubscribe(subscription : SubscriptionType!, slug: String!): Result!
} }
################################### Query ################################### Query