Merge branch 'main' of github.com:Discours/discours-backend into main
This commit is contained in:
commit
d93f686760
2
Pipfile
2
Pipfile
|
@ -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 = "*"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
aioredis
|
aioredis
|
||||||
ariadne
|
ariadne
|
||||||
pyjwt>=2.0.0
|
pyjwt>=2.0.0
|
||||||
starlette
|
starlette
|
||||||
sqlalchemy
|
sqlalchemy
|
||||||
uvicorn
|
uvicorn
|
||||||
pydantic
|
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
|
||||||
transliterate
|
transliterate
|
||||||
requests
|
requests
|
||||||
bcrypt
|
bcrypt
|
||||||
bs4
|
bs4
|
||||||
websockets
|
websockets
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {}
|
||||||
|
|
|
@ -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 {}
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user