This commit is contained in:
parent
bd5f910f8c
commit
c00361b2ec
|
@ -1,3 +1,9 @@
|
|||
[0.3.0]
|
||||
- Shout.featured_at timestamp of the frontpage featuring event
|
||||
- added proposal accepting logics
|
||||
- schema modulized
|
||||
-
|
||||
|
||||
[0.2.22]
|
||||
- added precommit hook
|
||||
- fmt
|
||||
|
|
5
main.py
5
main.py
|
@ -13,16 +13,17 @@ from sentry_sdk.integrations.starlette import StarletteIntegration
|
|||
from starlette.applications import Starlette
|
||||
from starlette.routing import Route
|
||||
|
||||
from resolvers.webhook import WebhookEndpoint
|
||||
from services.rediscache import redis
|
||||
from services.schema import resolvers
|
||||
from services.search import search_service
|
||||
from services.viewed import ViewedStorage
|
||||
from services.webhook import WebhookEndpoint
|
||||
from settings import DEV_SERVER_PID_FILE_NAME, MODE, SENTRY_DSN
|
||||
|
||||
|
||||
import_module('resolvers')
|
||||
schema = make_executable_schema(load_schema_from_path('schemas/core.graphql'), resolvers) # type: ignore
|
||||
|
||||
schema = make_executable_schema(load_schema_from_path('schema/'), resolvers)
|
||||
|
||||
|
||||
async def start_up():
|
||||
|
|
|
@ -51,7 +51,7 @@ class ShoutCommunity(Base):
|
|||
class ShoutVisibility(Enumeration):
|
||||
AUTHORS = 'AUTHORS'
|
||||
COMMUNITY = 'COMMUNITY'
|
||||
PUBLIC = 'PUBLIC'
|
||||
FEATURED = 'FEATURED'
|
||||
|
||||
|
||||
class Shout(Base):
|
||||
|
@ -60,6 +60,7 @@ class Shout(Base):
|
|||
created_at = Column(Integer, nullable=False, default=lambda: int(time.time()))
|
||||
updated_at = Column(Integer, nullable=True)
|
||||
published_at = Column(Integer, nullable=True)
|
||||
featured_at = Column(Integer, nullable=True)
|
||||
deleted_at = Column(Integer, nullable=True)
|
||||
|
||||
created_by = Column(ForeignKey('author.id'), nullable=False)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "discoursio-core"
|
||||
version = "0.2.22"
|
||||
version = "0.3.0"
|
||||
description = "core module for discours.io"
|
||||
authors = ["discoursio devteam"]
|
||||
license = "MIT"
|
||||
|
@ -26,14 +26,17 @@ setuptools = "^69.0.2"
|
|||
pyright = "^1.1.341"
|
||||
pytest = "^7.4.2"
|
||||
black = { version = "^23.12.0", python = ">=3.12" }
|
||||
ruff = { version = "^0.1.8", python = ">=3.12" }
|
||||
ruff = { version = "^0.1.15", python = ">=3.12" }
|
||||
isort = "^5.13.2"
|
||||
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
version = {attr = "core.__version__"}
|
||||
readme = {file = "README.md"}
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 120
|
||||
extend-select = [
|
||||
|
|
|
@ -82,35 +82,6 @@ async def update_profile(_, info, profile):
|
|||
return {'error': None, 'author': author}
|
||||
|
||||
|
||||
# for mutation.field("follow")
|
||||
def author_follow(follower_id, slug):
|
||||
try:
|
||||
with local_session() as session:
|
||||
author = session.query(Author).where(Author.slug == slug).one()
|
||||
af = AuthorFollower(follower=follower_id, author=author.id)
|
||||
session.add(af)
|
||||
session.commit()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
# for mutation.field("unfollow")
|
||||
def author_unfollow(follower_id, slug):
|
||||
with local_session() as session:
|
||||
flw = (
|
||||
session.query(AuthorFollower)
|
||||
.join(Author, Author.id == AuthorFollower.author)
|
||||
.filter(and_(AuthorFollower.follower == follower_id, Author.slug == slug))
|
||||
.first()
|
||||
)
|
||||
if flw:
|
||||
session.delete(flw)
|
||||
session.commit()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
# TODO: caching query
|
||||
@query.field('get_authors_all')
|
||||
async def get_authors_all(_, _info):
|
||||
|
|
|
@ -4,11 +4,14 @@ from sqlalchemy import and_, select
|
|||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from orm.author import Author
|
||||
from orm.reaction import Reaction, ReactionKind
|
||||
from orm.shout import Shout, ShoutAuthor, ShoutTopic, ShoutVisibility
|
||||
from orm.topic import Topic
|
||||
from resolvers.reaction import reactions_follow, reactions_unfollow
|
||||
from resolvers.follower import reactions_follow, reactions_unfollow
|
||||
from resolvers.rater import is_negative, is_positive
|
||||
from services.auth import login_required
|
||||
from services.db import local_session
|
||||
from services.diff import apply_diff, get_diff
|
||||
from services.notify import notify_shout
|
||||
from services.schema import mutation, query
|
||||
from services.search import search_service
|
||||
|
@ -187,7 +190,8 @@ async def update_shout( # noqa: C901
|
|||
|
||||
if not publish:
|
||||
await notify_shout(shout_dict, 'update')
|
||||
if shout.visibility is ShoutVisibility.COMMUNITY.value or shout.visibility is ShoutVisibility.PUBLIC.value:
|
||||
else:
|
||||
await notify_shout(shout_dict, 'published')
|
||||
# search service indexing
|
||||
search_service.index(shout)
|
||||
|
||||
|
@ -217,3 +221,30 @@ async def delete_shout(_, info, shout_id):
|
|||
session.commit()
|
||||
await notify_shout(shout_dict, 'delete')
|
||||
return {}
|
||||
|
||||
|
||||
def handle_proposing(session, r, shout):
|
||||
if is_positive(r.kind):
|
||||
# Proposal accepting logic
|
||||
replied_reaction = session.query(Reaction).filter(Reaction.id == r.reply_to).first()
|
||||
if replied_reaction and replied_reaction.kind is ReactionKind.PROPOSE.value and replied_reaction.quote:
|
||||
# patch all the proposals' quotes
|
||||
proposals = session.query(Reaction).filter(and_(Reaction.shout == r.shout, Reaction.kind == ReactionKind.PROPOSE.value)).all()
|
||||
for proposal in proposals:
|
||||
if proposal.quote:
|
||||
proposal_diff = get_diff(shout.body, proposal.quote)
|
||||
proposal_dict = proposal.dict()
|
||||
proposal_dict['quote'] = apply_diff(replied_reaction.quote, proposal_diff)
|
||||
Reaction.update(proposal, proposal_dict)
|
||||
session.add(proposal)
|
||||
|
||||
# patch shout's body
|
||||
shout_dict = shout.dict()
|
||||
shout_dict['body'] = replied_reaction.quote
|
||||
Shout.update(shout, shout_dict)
|
||||
session.add(shout)
|
||||
session.commit()
|
||||
|
||||
if is_negative(r.kind):
|
||||
# TODO: rejection logic
|
||||
pass
|
||||
|
|
|
@ -2,15 +2,14 @@ import logging
|
|||
from typing import List
|
||||
|
||||
from sqlalchemy.orm import aliased
|
||||
from sqlalchemy.sql import and_
|
||||
|
||||
from orm.author import Author, AuthorFollower
|
||||
from orm.community import Community
|
||||
from orm.reaction import Reaction
|
||||
from orm.shout import Shout
|
||||
from orm.shout import Shout, ShoutReactionsFollower
|
||||
from orm.topic import Topic, TopicFollower
|
||||
from resolvers.author import author_follow, author_unfollow
|
||||
from resolvers.community import community_follow, community_unfollow
|
||||
from resolvers.reaction import reactions_follow, reactions_unfollow
|
||||
from resolvers.topic import topic_follow, topic_unfollow
|
||||
from services.auth import login_required
|
||||
from services.db import local_session
|
||||
|
@ -140,3 +139,83 @@ def get_shout_followers(_, _info, slug: str = '', shout_id: int | None = None) -
|
|||
followers.append(r.created_by)
|
||||
|
||||
return followers
|
||||
|
||||
|
||||
|
||||
def reactions_follow(author_id, shout_id, auto=False):
|
||||
try:
|
||||
with local_session() as session:
|
||||
shout = session.query(Shout).where(Shout.id == shout_id).one()
|
||||
|
||||
following = (
|
||||
session.query(ShoutReactionsFollower)
|
||||
.where(
|
||||
and_(
|
||||
ShoutReactionsFollower.follower == author_id,
|
||||
ShoutReactionsFollower.shout == shout.id,
|
||||
)
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if not following:
|
||||
following = ShoutReactionsFollower(follower=author_id, shout=shout.id, auto=auto)
|
||||
session.add(following)
|
||||
session.commit()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def reactions_unfollow(author_id, shout_id: int):
|
||||
try:
|
||||
with local_session() as session:
|
||||
shout = session.query(Shout).where(Shout.id == shout_id).one()
|
||||
|
||||
following = (
|
||||
session.query(ShoutReactionsFollower)
|
||||
.where(
|
||||
and_(
|
||||
ShoutReactionsFollower.follower == author_id,
|
||||
ShoutReactionsFollower.shout == shout.id,
|
||||
)
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if following:
|
||||
session.delete(following)
|
||||
session.commit()
|
||||
return True
|
||||
except Exception as ex:
|
||||
logger.debug(ex)
|
||||
return False
|
||||
|
||||
|
||||
# for mutation.field("follow")
|
||||
def author_follow(follower_id, slug):
|
||||
try:
|
||||
with local_session() as session:
|
||||
author = session.query(Author).where(Author.slug == slug).one()
|
||||
af = AuthorFollower(follower=follower_id, author=author.id)
|
||||
session.add(af)
|
||||
session.commit()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
# for mutation.field("unfollow")
|
||||
def author_unfollow(follower_id, slug):
|
||||
with local_session() as session:
|
||||
flw = (
|
||||
session.query(AuthorFollower)
|
||||
.join(Author, Author.id == AuthorFollower.author)
|
||||
.filter(and_(AuthorFollower.follower == follower_id, Author.slug == slug))
|
||||
.first()
|
||||
)
|
||||
if flw:
|
||||
session.delete(flw)
|
||||
session.commit()
|
||||
return True
|
||||
return False
|
||||
|
|
28
resolvers/rater.py
Normal file
28
resolvers/rater.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
|
||||
from orm.reaction import ReactionKind
|
||||
|
||||
|
||||
RATING_REACTIONS = [
|
||||
ReactionKind.LIKE.value,
|
||||
ReactionKind.ACCEPT.value,
|
||||
ReactionKind.AGREE.value,
|
||||
ReactionKind.DISLIKE.value,
|
||||
ReactionKind.REJECT.value,
|
||||
ReactionKind.DISAGREE.value]
|
||||
|
||||
|
||||
|
||||
def is_negative(x):
|
||||
return x in [
|
||||
ReactionKind.ACCEPT.value,
|
||||
ReactionKind.LIKE.value,
|
||||
ReactionKind.PROOF.value,
|
||||
]
|
||||
|
||||
|
||||
def is_positive(x):
|
||||
return x in [
|
||||
ReactionKind.ACCEPT.value,
|
||||
ReactionKind.LIKE.value,
|
||||
ReactionKind.PROOF.value,
|
||||
]
|
|
@ -8,7 +8,10 @@ from sqlalchemy.sql import union
|
|||
|
||||
from orm.author import Author
|
||||
from orm.reaction import Reaction, ReactionKind
|
||||
from orm.shout import Shout, ShoutReactionsFollower, ShoutVisibility
|
||||
from orm.shout import Shout, ShoutVisibility
|
||||
from resolvers.editor import handle_proposing
|
||||
from resolvers.follower import reactions_follow
|
||||
from resolvers.rater import RATING_REACTIONS, is_negative, is_positive
|
||||
from services.auth import add_user_role, login_required
|
||||
from services.db import local_session
|
||||
from services.notify import notify_reaction
|
||||
|
@ -37,120 +40,52 @@ def add_stat_columns(q, aliased_reaction):
|
|||
return q
|
||||
|
||||
|
||||
def reactions_follow(author_id, shout_id, auto=False):
|
||||
try:
|
||||
with local_session() as session:
|
||||
shout = session.query(Shout).where(Shout.id == shout_id).one()
|
||||
|
||||
following = (
|
||||
session.query(ShoutReactionsFollower)
|
||||
.where(
|
||||
and_(
|
||||
ShoutReactionsFollower.follower == author_id,
|
||||
ShoutReactionsFollower.shout == shout.id,
|
||||
)
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if not following:
|
||||
following = ShoutReactionsFollower(follower=author_id, shout=shout.id, auto=auto)
|
||||
session.add(following)
|
||||
session.commit()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def reactions_unfollow(author_id, shout_id: int):
|
||||
try:
|
||||
with local_session() as session:
|
||||
shout = session.query(Shout).where(Shout.id == shout_id).one()
|
||||
|
||||
following = (
|
||||
session.query(ShoutReactionsFollower)
|
||||
.where(
|
||||
and_(
|
||||
ShoutReactionsFollower.follower == author_id,
|
||||
ShoutReactionsFollower.shout == shout.id,
|
||||
)
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if following:
|
||||
session.delete(following)
|
||||
session.commit()
|
||||
return True
|
||||
except Exception as ex:
|
||||
logger.debug(ex)
|
||||
return False
|
||||
|
||||
|
||||
def is_published_author(session, author_id):
|
||||
"""checks if author has at least one publication"""
|
||||
def is_featured_author(session, author_id):
|
||||
"""checks if author has at least one featured publication"""
|
||||
return (
|
||||
session.query(Shout)
|
||||
.where(Shout.authors.any(id=author_id))
|
||||
.filter(and_(Shout.published_at.is_not(None), Shout.deleted_at.is_(None)))
|
||||
.filter(and_(Shout.featured_at.is_not(None), Shout.deleted_at.is_(None)))
|
||||
.count()
|
||||
> 0
|
||||
)
|
||||
|
||||
|
||||
def is_negative(x):
|
||||
return x in [
|
||||
ReactionKind.ACCEPT.value,
|
||||
ReactionKind.LIKE.value,
|
||||
ReactionKind.PROOF.value,
|
||||
]
|
||||
|
||||
|
||||
def is_positive(x):
|
||||
return x in [
|
||||
ReactionKind.ACCEPT.value,
|
||||
ReactionKind.LIKE.value,
|
||||
ReactionKind.PROOF.value,
|
||||
]
|
||||
|
||||
|
||||
def check_to_publish(session, approver_id, reaction):
|
||||
def check_to_feature(session, approver_id, reaction):
|
||||
"""set shout to public if publicated approvers amount > 4"""
|
||||
if not reaction.reply_to and is_positive(reaction.kind):
|
||||
if is_published_author(session, approver_id):
|
||||
if is_featured_author(session, approver_id):
|
||||
approvers = []
|
||||
approvers.append(approver_id)
|
||||
# now count how many approvers are voted already
|
||||
approvers_reactions = session.query(Reaction).where(Reaction.shout == reaction.shout).all()
|
||||
approvers = [
|
||||
approver_id,
|
||||
]
|
||||
for ar in approvers_reactions:
|
||||
a = ar.created_by
|
||||
if is_published_author(session, a):
|
||||
approvers.append(a)
|
||||
reacted_readers = session.query(Reaction).where(Reaction.shout == reaction.shout).all()
|
||||
for reacted_reader in reacted_readers:
|
||||
if is_featured_author(session, reacted_reader.id):
|
||||
approvers.append(reacted_reader.id)
|
||||
if len(approvers) > 4:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def check_to_hide(session, reaction):
|
||||
"""hides any shout if 20% of reactions are negative"""
|
||||
def check_to_unfeature(session, rejecter_id, reaction):
|
||||
"""unfeature any shout if 20% of reactions are negative"""
|
||||
if not reaction.reply_to and is_negative(reaction.kind):
|
||||
# if is_published_author(author_id):
|
||||
approvers_reactions = session.query(Reaction).where(Reaction.shout == reaction.shout).all()
|
||||
if is_featured_author(session, rejecter_id):
|
||||
reactions = session.query(Reaction).where(and_(Reaction.shout == reaction.shout, Reaction.kind.in_(RATING_REACTIONS))).all()
|
||||
rejects = 0
|
||||
for r in approvers_reactions:
|
||||
for r in reactions:
|
||||
approver = session.query(Author).filter(Author.id == r.created_by).first()
|
||||
if is_featured_author(session, approver):
|
||||
if is_negative(r.kind):
|
||||
rejects += 1
|
||||
if len(approvers_reactions) / rejects < 5:
|
||||
if len(reactions) / rejects < 5:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
async def set_published(session, shout_id, approver_id):
|
||||
async def set_featured(session, shout_id):
|
||||
s = session.query(Shout).where(Shout.id == shout_id).first()
|
||||
s.published_at = int(time.time())
|
||||
s.published_by = approver_id
|
||||
Shout.update(s, {'visibility': ShoutVisibility.PUBLIC.value})
|
||||
s.featured_at = int(time.time())
|
||||
Shout.update(s, {'visibility': ShoutVisibility.FEATURED.value})
|
||||
author = session.query(Author).filter(Author.id == s.created_by).first()
|
||||
if author:
|
||||
await add_user_role(str(author.user))
|
||||
|
@ -158,7 +93,7 @@ async def set_published(session, shout_id, approver_id):
|
|||
session.commit()
|
||||
|
||||
|
||||
def set_hidden(session, shout_id):
|
||||
def set_unfeatured(session, shout_id):
|
||||
s = session.query(Shout).where(Shout.id == shout_id).first()
|
||||
Shout.update(s, {'visibility': ShoutVisibility.COMMUNITY.value})
|
||||
session.add(s)
|
||||
|
@ -171,38 +106,24 @@ async def _create_reaction(session, shout, author, reaction):
|
|||
session.commit()
|
||||
rdict = r.dict()
|
||||
|
||||
# Proposal accepting logic
|
||||
if rdict.get('reply_to'):
|
||||
if r.kind in ['LIKE', 'APPROVE'] and author.id in shout.authors:
|
||||
replied_reaction = session.query(Reaction).filter(Reaction.id == r.reply_to).first()
|
||||
if replied_reaction:
|
||||
if replied_reaction.kind is ReactionKind.PROPOSE.value:
|
||||
if replied_reaction.range:
|
||||
old_body = shout.body
|
||||
start, end = replied_reaction.range.split(':')
|
||||
start = int(start)
|
||||
end = int(end)
|
||||
new_body = old_body[:start] + replied_reaction.body + old_body[end:]
|
||||
shout_dict = shout.dict()
|
||||
shout_dict['body'] = new_body
|
||||
Shout.update(shout, shout_dict)
|
||||
session.add(shout)
|
||||
session.commit()
|
||||
# collaborative editing
|
||||
if rdict.get('reply_to') and r.kind in RATING_REACTIONS and author.id in shout.authors:
|
||||
handle_proposing(session, r, shout)
|
||||
|
||||
# Self-regulation mechanics
|
||||
if check_to_hide(session, r):
|
||||
set_hidden(session, shout.id)
|
||||
elif check_to_publish(session, author.id, r):
|
||||
await set_published(session, shout.id, author.id)
|
||||
# self-regultaion mechanics
|
||||
if check_to_unfeature(session, author.id, r):
|
||||
set_unfeatured(session, shout.id)
|
||||
elif check_to_feature(session, author.id, r):
|
||||
await set_featured(session, shout.id)
|
||||
|
||||
# Reactions auto-following
|
||||
# reactions auto-following
|
||||
reactions_follow(author.id, reaction['shout'], True)
|
||||
|
||||
rdict['shout'] = shout.dict()
|
||||
rdict['created_by'] = author.dict()
|
||||
rdict['stat'] = {'commented': 0, 'reacted': 0, 'rating': 0}
|
||||
|
||||
# Notifications call
|
||||
# notifications call
|
||||
await notify_reaction(rdict, 'create')
|
||||
|
||||
return rdict
|
||||
|
@ -220,14 +141,14 @@ async def create_reaction(_, info, reaction):
|
|||
|
||||
try:
|
||||
with local_session() as session:
|
||||
shout = session.query(Shout).filter(Shout.id == shout_id).one()
|
||||
shout = session.query(Shout).filter(Shout.id == shout_id).first()
|
||||
author = session.query(Author).filter(Author.user == user_id).first()
|
||||
if shout and author:
|
||||
reaction['created_by'] = author.id
|
||||
kind = reaction.get('kind')
|
||||
shout_id = shout.id
|
||||
|
||||
if not kind and reaction.get('body'):
|
||||
if not kind and isinstance(reaction.get('body'), str):
|
||||
kind = ReactionKind.COMMENT.value
|
||||
|
||||
if not kind:
|
||||
|
|
|
@ -26,9 +26,9 @@ def apply_filters(q, filters, author_id=None):
|
|||
if filters.get('reacted') and author_id:
|
||||
q.join(Reaction, Reaction.created_by == author_id)
|
||||
|
||||
by_published = filters.get('published')
|
||||
if by_published:
|
||||
q = q.filter(Shout.visibility == ShoutVisibility.PUBLIC.value)
|
||||
by_featured = filters.get('featured')
|
||||
if by_featured:
|
||||
q = q.filter(Shout.visibility == ShoutVisibility.FEATURED.value)
|
||||
by_layouts = filters.get('layouts')
|
||||
if by_layouts:
|
||||
q = q.filter(Shout.layout.in_(by_layouts))
|
||||
|
@ -114,7 +114,7 @@ async def load_shouts_by(_, _info, options):
|
|||
filters: {
|
||||
layouts: ['audio', 'video', ..],
|
||||
reacted: True,
|
||||
published: True, // filter published-only
|
||||
featured: True, // filter featured-only
|
||||
author: 'discours',
|
||||
topic: 'culture',
|
||||
after: 1234567 // unixtime
|
||||
|
@ -143,13 +143,14 @@ async def load_shouts_by(_, _info, options):
|
|||
q = add_stat_columns(q, aliased_reaction)
|
||||
|
||||
# filters
|
||||
q = apply_filters(q, options.get('filters', {}))
|
||||
filters = options.get('filters', {})
|
||||
q = apply_filters(q, filters)
|
||||
|
||||
# group
|
||||
q = q.group_by(Shout.id)
|
||||
|
||||
# order
|
||||
order_by = options.get('order_by', Shout.published_at)
|
||||
order_by = options.get('order_by', Shout.featured_at if filters.get('featured') else Shout.published_at)
|
||||
query_order_by = desc(order_by) if options.get('order_by_desc', True) else asc(order_by)
|
||||
q = q.order_by(nulls_last(query_order_by))
|
||||
|
||||
|
@ -274,9 +275,10 @@ async def load_shouts_feed(_, info, options):
|
|||
|
||||
aliased_reaction = aliased(Reaction)
|
||||
q = add_stat_columns(q, aliased_reaction)
|
||||
q = apply_filters(q, options.get('filters', {}), reader.id)
|
||||
filters = options.get('filters', {})
|
||||
q = apply_filters(q, filters, reader.id)
|
||||
|
||||
order_by = options.get('order_by', Shout.published_at)
|
||||
order_by = options.get('order_by', Shout.featured_at if filters.get('featured') else Shout.published_at)
|
||||
|
||||
query_order_by = desc(order_by) if options.get('order_by_desc', True) else asc(order_by)
|
||||
offset = options.get('offset', 0)
|
||||
|
@ -464,7 +466,7 @@ async def load_shouts_random_topic(_, info, limit: int = 10):
|
|||
.filter(
|
||||
and_(
|
||||
Shout.deleted_at.is_(None),
|
||||
Shout.visibility == ShoutVisibility.PUBLIC.value,
|
||||
Shout.visibility == ShoutVisibility.FEATURED.value,
|
||||
Shout.topics.any(slug=topic.slug),
|
||||
)
|
||||
)
|
||||
|
|
46
schema/enum.graphql
Normal file
46
schema/enum.graphql
Normal file
|
@ -0,0 +1,46 @@
|
|||
enum ShoutVisibility {
|
||||
AUTHORS
|
||||
COMMUNITY
|
||||
FEATURED
|
||||
}
|
||||
|
||||
enum ReactionStatus {
|
||||
NEW
|
||||
UPDATED
|
||||
CHANGED
|
||||
EXPLAINED
|
||||
DELETED
|
||||
}
|
||||
|
||||
enum ReactionKind {
|
||||
|
||||
# collabs
|
||||
AGREE
|
||||
DISAGREE
|
||||
ASK
|
||||
PROPOSE
|
||||
PROOF
|
||||
DISPROOF
|
||||
ACCEPT
|
||||
REJECT
|
||||
|
||||
# public feed
|
||||
QUOTE
|
||||
COMMENT
|
||||
LIKE
|
||||
DISLIKE
|
||||
|
||||
}
|
||||
|
||||
enum FollowingEntity {
|
||||
TOPIC
|
||||
AUTHOR
|
||||
COMMUNITY
|
||||
REACTIONS
|
||||
}
|
||||
|
||||
enum InviteStatus {
|
||||
PENDING
|
||||
ACCEPTED
|
||||
REJECTED
|
||||
}
|
80
schema/input.graphql
Normal file
80
schema/input.graphql
Normal file
|
@ -0,0 +1,80 @@
|
|||
input ShoutInput {
|
||||
slug: String
|
||||
title: String
|
||||
body: String
|
||||
lead: String
|
||||
description: String
|
||||
layout: String
|
||||
media: String
|
||||
authors: [String]
|
||||
topics: [TopicInput]
|
||||
community: Int
|
||||
subtitle: String
|
||||
cover: String
|
||||
}
|
||||
|
||||
input ProfileInput {
|
||||
slug: String
|
||||
name: String
|
||||
pic: String
|
||||
links: [String]
|
||||
bio: String
|
||||
about: String
|
||||
}
|
||||
|
||||
input TopicInput {
|
||||
id: Int
|
||||
slug: String!
|
||||
title: String
|
||||
body: String
|
||||
pic: String
|
||||
}
|
||||
|
||||
input ReactionInput {
|
||||
kind: ReactionKind!
|
||||
shout: Int!
|
||||
quote: String
|
||||
body: String
|
||||
reply_to: Int
|
||||
}
|
||||
|
||||
input AuthorsBy {
|
||||
last_seen: Int
|
||||
created_at: Int
|
||||
slug: String
|
||||
name: String
|
||||
topic: String
|
||||
order: String
|
||||
after: Int
|
||||
stat: String
|
||||
}
|
||||
|
||||
input LoadShoutsFilters {
|
||||
topic: String
|
||||
author: String
|
||||
layouts: [String]
|
||||
featured: Boolean
|
||||
reacted: Boolean
|
||||
after: Int
|
||||
}
|
||||
|
||||
input LoadShoutsOptions {
|
||||
filters: LoadShoutsFilters
|
||||
with_author_captions: Boolean
|
||||
limit: Int!
|
||||
random_limit: Int
|
||||
offset: Int
|
||||
order_by: String
|
||||
order_by_desc: Boolean
|
||||
}
|
||||
|
||||
input ReactionBy {
|
||||
shout: String
|
||||
shouts: [String]
|
||||
search: String
|
||||
comment: Boolean
|
||||
topic: String
|
||||
created_by: Int
|
||||
after: Int
|
||||
sort: String
|
||||
}
|
31
schema/mutation.graphql
Normal file
31
schema/mutation.graphql
Normal file
|
@ -0,0 +1,31 @@
|
|||
type Mutation {
|
||||
# author
|
||||
rate_author(rated_slug: String!, value: Int!): Result!
|
||||
update_profile(profile: ProfileInput!): Result!
|
||||
|
||||
# editor
|
||||
create_shout(inp: ShoutInput!): Result!
|
||||
update_shout(shout_id: Int!, shout_input: ShoutInput, publish: Boolean): Result!
|
||||
delete_shout(shout_id: Int!): Result!
|
||||
|
||||
# follower
|
||||
follow(what: FollowingEntity!, slug: String!): Result!
|
||||
unfollow(what: FollowingEntity!, slug: String!): Result!
|
||||
|
||||
# topic
|
||||
create_topic(input: TopicInput!): Result!
|
||||
update_topic(input: TopicInput!): Result!
|
||||
delete_topic(slug: String!): Result!
|
||||
|
||||
# reaction
|
||||
create_reaction(reaction: ReactionInput!): Result!
|
||||
update_reaction(id: Int!, reaction: ReactionInput!): Result!
|
||||
delete_reaction(reaction_id: Int!): Result!
|
||||
|
||||
# collab
|
||||
create_invite(slug: String, author_id: Int): Result!
|
||||
remove_author(slug: String, author_id: Int): Result!
|
||||
remove_invite(invite_id: Int!): Result!
|
||||
accept_invite(invite_id: Int!): Result!
|
||||
reject_invite(invite_id: Int!): Result!
|
||||
}
|
41
schema/query.graphql
Normal file
41
schema/query.graphql
Normal file
|
@ -0,0 +1,41 @@
|
|||
type Query {
|
||||
# author
|
||||
get_author(slug: String, author_id: Int): Author
|
||||
get_author_id(user: String!): Author
|
||||
get_authors_all: [Author]
|
||||
get_author_followers(slug: String, user: String, author_id: Int): [Author]
|
||||
get_author_followed(slug: String, user: String, author_id: Int): [Author]
|
||||
load_authors_by(by: AuthorsBy!, limit: Int, offset: Int): [Author]
|
||||
|
||||
# community
|
||||
get_community: Community
|
||||
get_communities_all: [Community]
|
||||
|
||||
# editor
|
||||
get_shouts_drafts: [Shout]
|
||||
|
||||
# follower
|
||||
get_my_followed: Result # { authors topics communities }
|
||||
get_shout_followers(slug: String, shout_id: Int): [Author]
|
||||
|
||||
# reaction
|
||||
load_reactions_by(by: ReactionBy!, limit: Int, offset: Int): [Reaction]
|
||||
|
||||
# reader
|
||||
get_shout(slug: String, shout_id: Int): Shout
|
||||
load_shouts_followed(follower_id: Int!, limit: Int, offset: Int): [Shout] # userReactedShouts
|
||||
load_shouts_by(options: LoadShoutsOptions): [Shout]
|
||||
load_shouts_search(text: String!, limit: Int, offset: Int): [SearchResult]
|
||||
load_shouts_feed(options: LoadShoutsOptions): [Shout]
|
||||
load_shouts_unrated(limit: Int, offset: Int): [Shout]
|
||||
load_shouts_random_top(options: LoadShoutsOptions): [Shout]
|
||||
load_shouts_random_topic(limit: Int!): Result! # { topic shouts }
|
||||
load_shouts_drafts: [Shout]
|
||||
|
||||
# topic
|
||||
get_topic(slug: String!): Topic
|
||||
get_topics_all: [Topic]
|
||||
get_topics_random(amount: Int): [Topic]
|
||||
get_topics_by_author(slug: String, user: String, author_id: Int): [Topic]
|
||||
get_topics_by_community(slug: String, community_id: Int): [Topic]
|
||||
}
|
182
schema/type.graphql
Normal file
182
schema/type.graphql
Normal file
|
@ -0,0 +1,182 @@
|
|||
type AuthorFollowings {
|
||||
unread: Int
|
||||
topics: [String]
|
||||
authors: [String]
|
||||
reactions: [Int]
|
||||
communities: [String]
|
||||
}
|
||||
|
||||
type AuthorStat {
|
||||
shouts: Int
|
||||
followings: Int
|
||||
followers: Int
|
||||
rating: Int
|
||||
rating_shouts: Int
|
||||
rating_comments: Int
|
||||
commented: Int
|
||||
viewed: Int
|
||||
}
|
||||
|
||||
type Author {
|
||||
id: Int!
|
||||
user: String! # user.id
|
||||
slug: String! # user.nickname
|
||||
name: String # user.preferred_username
|
||||
pic: String
|
||||
bio: String
|
||||
about: String
|
||||
links: [String]
|
||||
created_at: Int
|
||||
last_seen: Int
|
||||
updated_at: Int
|
||||
deleted_at: Int
|
||||
seo: String
|
||||
# synthetic
|
||||
stat: AuthorStat # ratings inside
|
||||
communities: [Community]
|
||||
}
|
||||
|
||||
type ReactionUpdating {
|
||||
error: String
|
||||
status: ReactionStatus
|
||||
reaction: Reaction
|
||||
}
|
||||
|
||||
type Rating {
|
||||
rater: String!
|
||||
value: Int!
|
||||
}
|
||||
|
||||
type Reaction {
|
||||
id: Int!
|
||||
shout: Shout!
|
||||
created_at: Int!
|
||||
created_by: Author!
|
||||
updated_at: Int
|
||||
deleted_at: Int
|
||||
deleted_by: Author
|
||||
range: String
|
||||
kind: ReactionKind!
|
||||
body: String
|
||||
reply_to: Int
|
||||
stat: Stat
|
||||
oid: String
|
||||
# old_thread: String
|
||||
}
|
||||
|
||||
type Shout {
|
||||
id: Int!
|
||||
slug: String!
|
||||
body: String!
|
||||
lead: String
|
||||
description: String
|
||||
main_topic: String
|
||||
topics: [Topic]
|
||||
created_by: Author!
|
||||
updated_by: Author
|
||||
deleted_by: Author
|
||||
authors: [Author]
|
||||
communities: [Community]
|
||||
title: String!
|
||||
subtitle: String
|
||||
lang: String
|
||||
community: String
|
||||
cover: String
|
||||
cover_caption: String
|
||||
layout: String!
|
||||
visibility: String
|
||||
|
||||
created_at: Int!
|
||||
updated_at: Int
|
||||
published_at: Int
|
||||
featured_at: Int
|
||||
deleted_at: Int
|
||||
|
||||
version_of: Shout # TODO: use version_of somewhere
|
||||
|
||||
media: String
|
||||
stat: Stat
|
||||
score: Float
|
||||
}
|
||||
|
||||
type Stat {
|
||||
viewed: Int
|
||||
reacted: Int
|
||||
rating: Int
|
||||
commented: Int
|
||||
ranking: Int
|
||||
}
|
||||
|
||||
type Community {
|
||||
id: Int!
|
||||
slug: String!
|
||||
name: String!
|
||||
desc: String
|
||||
pic: String!
|
||||
created_at: Int!
|
||||
created_by: Author!
|
||||
}
|
||||
|
||||
type Collection {
|
||||
id: Int!
|
||||
slug: String!
|
||||
title: String!
|
||||
desc: String
|
||||
amount: Int
|
||||
published_at: Int
|
||||
created_at: Int!
|
||||
created_by: Author!
|
||||
}
|
||||
|
||||
type TopicStat {
|
||||
shouts: Int!
|
||||
followers: Int!
|
||||
authors: Int!
|
||||
viewed: Int
|
||||
}
|
||||
|
||||
type Topic {
|
||||
id: Int!
|
||||
slug: String!
|
||||
title: String
|
||||
body: String
|
||||
pic: String
|
||||
stat: TopicStat
|
||||
oid: String
|
||||
}
|
||||
|
||||
# output type
|
||||
|
||||
type Result {
|
||||
error: String
|
||||
slugs: [String]
|
||||
shout: Shout
|
||||
shouts: [Shout]
|
||||
author: Author
|
||||
authors: [Author]
|
||||
reaction: Reaction
|
||||
reactions: [Reaction]
|
||||
topic: Topic
|
||||
topics: [Topic]
|
||||
community: Community
|
||||
communities: [Community]
|
||||
}
|
||||
|
||||
type SearchResult {
|
||||
slug: String!
|
||||
title: String!
|
||||
cover: String
|
||||
main_topic: String
|
||||
created_at: Int
|
||||
authors: [Author]
|
||||
topics: [Topic]
|
||||
score: Float!
|
||||
}
|
||||
|
||||
type Invite {
|
||||
id: Int!
|
||||
inviter_id: Int!
|
||||
author_id: Int!
|
||||
shout_id: Int!
|
||||
status: InviteStatus
|
||||
}
|
|
@ -1,242 +0,0 @@
|
|||
scalar Dict
|
||||
|
||||
type ConfigType {
|
||||
authorizerURL: String!
|
||||
redirectURL: String!
|
||||
clientID: String!
|
||||
extraHeaders: [Header]
|
||||
}
|
||||
|
||||
type User {
|
||||
id: ID!
|
||||
email: String!
|
||||
preferred_username: String!
|
||||
email_verified: Boolean!
|
||||
signup_methods: String!
|
||||
given_name: String
|
||||
family_name: String
|
||||
middle_name: String
|
||||
nickname: String
|
||||
picture: String
|
||||
gender: String
|
||||
birthdate: String
|
||||
phone_number: String
|
||||
phone_number_verified: Boolean
|
||||
roles: [String]
|
||||
created_at: Int!
|
||||
updated_at: Int!
|
||||
is_multi_factor_auth_enabled: Boolean
|
||||
}
|
||||
|
||||
type AuthToken {
|
||||
message: String
|
||||
access_token: String!
|
||||
expires_in: Int!
|
||||
id_token: String!
|
||||
refresh_token: String
|
||||
user: User
|
||||
should_show_email_otp_screen: Boolean
|
||||
should_show_mobile_otp_screen: Boolean
|
||||
}
|
||||
|
||||
type Response {
|
||||
message: String!
|
||||
}
|
||||
|
||||
type Header {
|
||||
key: String!
|
||||
value: String!
|
||||
}
|
||||
|
||||
input HeaderIn {
|
||||
key: String!
|
||||
value: String!
|
||||
}
|
||||
|
||||
input LoginInput {
|
||||
email: String!
|
||||
password: String!
|
||||
roles: [String]
|
||||
scope: [String]
|
||||
state: String
|
||||
}
|
||||
|
||||
input SignupInput {
|
||||
email: String!
|
||||
password: String!
|
||||
confirm_password: String!
|
||||
given_name: String
|
||||
family_name: String
|
||||
middle_name: String
|
||||
nickname: String
|
||||
picture: String
|
||||
gender: String
|
||||
birthdate: String
|
||||
phone_number: String
|
||||
roles: [String]
|
||||
scope: [String]
|
||||
redirect_uri: String
|
||||
is_multi_factor_auth_enabled: Boolean
|
||||
state: String
|
||||
}
|
||||
|
||||
input MagicLinkLoginInput {
|
||||
email: String!
|
||||
roles: [String]
|
||||
scopes: [String]
|
||||
state: String
|
||||
redirect_uri: String
|
||||
}
|
||||
|
||||
input VerifyEmailInput {
|
||||
token: String!
|
||||
state: String
|
||||
}
|
||||
|
||||
input VerifyOtpInput {
|
||||
email: String
|
||||
phone_number: String
|
||||
otp: String!
|
||||
state: String
|
||||
}
|
||||
|
||||
input ResendOtpInput {
|
||||
email: String
|
||||
phone_number: String
|
||||
}
|
||||
|
||||
input GraphqlQueryInput {
|
||||
query: String!
|
||||
variables: Dict
|
||||
headers: [HeaderIn]
|
||||
}
|
||||
|
||||
|
||||
type MetaData {
|
||||
version: String!
|
||||
client_id: String!
|
||||
is_google_login_enabled: Boolean!
|
||||
is_facebook_login_enabled: Boolean!
|
||||
is_github_login_enabled: Boolean!
|
||||
is_linkedin_login_enabled: Boolean!
|
||||
is_apple_login_enabled: Boolean!
|
||||
is_twitter_login_enabled: Boolean!
|
||||
is_microsoft_login_enabled: Boolean!
|
||||
is_email_verification_enabled: Boolean!
|
||||
is_basic_authentication_enabled: Boolean!
|
||||
is_magic_link_login_enabled: Boolean!
|
||||
is_sign_up_enabled: Boolean!
|
||||
is_strong_password_enabled: Boolean!
|
||||
}
|
||||
|
||||
input UpdateProfileInput {
|
||||
old_password: String
|
||||
new_password: String
|
||||
confirm_new_password: String
|
||||
email: String
|
||||
given_name: String
|
||||
family_name: String
|
||||
middle_name: String
|
||||
nickname: String
|
||||
gender: String
|
||||
birthdate: String
|
||||
phone_number: String
|
||||
picture: String
|
||||
is_multi_factor_auth_enabled: Boolean
|
||||
}
|
||||
|
||||
input ForgotPasswordInput {
|
||||
email: String!
|
||||
state: String
|
||||
redirect_uri: String
|
||||
}
|
||||
|
||||
input ResetPasswordInput {
|
||||
token: String!
|
||||
password: String!
|
||||
confirm_password: String!
|
||||
}
|
||||
|
||||
input SessionQueryInput {
|
||||
roles: [String]
|
||||
}
|
||||
|
||||
input IsValidJWTQueryInput {
|
||||
jwt: String!
|
||||
roles: [String]
|
||||
}
|
||||
|
||||
type ValidJWTResponse {
|
||||
valid: String!
|
||||
message: String!
|
||||
}
|
||||
|
||||
enum OAuthProviders {
|
||||
Apple
|
||||
Github
|
||||
Google
|
||||
Facebook
|
||||
LinkedIn
|
||||
}
|
||||
|
||||
enum ResponseTypes {
|
||||
Code
|
||||
Token
|
||||
}
|
||||
|
||||
input AuthorizeInput {
|
||||
response_type: ResponseTypes!
|
||||
use_refresh_token: Boolean
|
||||
response_mode: String
|
||||
}
|
||||
|
||||
type AuthorizeResponse {
|
||||
state: String!
|
||||
code: String
|
||||
error: String
|
||||
error_description: String
|
||||
}
|
||||
|
||||
input RevokeTokenInput {
|
||||
refresh_token: String!
|
||||
}
|
||||
|
||||
input GetTokenInput {
|
||||
code: String
|
||||
grant_type: String
|
||||
refresh_token: String
|
||||
}
|
||||
|
||||
type GetTokenResponse {
|
||||
access_token: String!
|
||||
expires_in: Int!
|
||||
id_token: String!
|
||||
refresh_token: String
|
||||
}
|
||||
|
||||
input ValidateJWTTokenInput {
|
||||
token_type: TokenType!
|
||||
token: String!
|
||||
roles: [String]
|
||||
}
|
||||
|
||||
type ValidateJWTTokenResponse {
|
||||
is_valid: Boolean!
|
||||
claims: Dict
|
||||
}
|
||||
|
||||
input ValidateSessionInput {
|
||||
cookie: String
|
||||
roles: [String]
|
||||
}
|
||||
|
||||
type ValidateSessionResponse {
|
||||
is_valid: Boolean!
|
||||
user: User
|
||||
}
|
||||
|
||||
enum TokenType {
|
||||
access_token
|
||||
id_token
|
||||
refresh_token
|
||||
}
|
|
@ -1,385 +0,0 @@
|
|||
enum ShoutVisibility {
|
||||
AUTHORS
|
||||
COMMUNITY
|
||||
PUBLIC
|
||||
}
|
||||
|
||||
enum ReactionStatus {
|
||||
NEW
|
||||
UPDATED
|
||||
CHANGED
|
||||
EXPLAINED
|
||||
DELETED
|
||||
}
|
||||
|
||||
enum ReactionKind {
|
||||
# collabs
|
||||
AGREE
|
||||
DISAGREE
|
||||
ASK
|
||||
PROPOSE
|
||||
PROOF
|
||||
DISPROOF
|
||||
ACCEPT
|
||||
REJECT
|
||||
# public feed
|
||||
QUOTE
|
||||
COMMENT
|
||||
LIKE
|
||||
DISLIKE
|
||||
}
|
||||
|
||||
enum FollowingEntity {
|
||||
TOPIC
|
||||
AUTHOR
|
||||
COMMUNITY
|
||||
REACTIONS
|
||||
}
|
||||
|
||||
enum InviteStatus {
|
||||
PENDING
|
||||
ACCEPTED
|
||||
REJECTED
|
||||
}
|
||||
|
||||
# Типы
|
||||
|
||||
type AuthorFollowings {
|
||||
unread: Int
|
||||
topics: [String]
|
||||
authors: [String]
|
||||
reactions: [Int]
|
||||
communities: [String]
|
||||
}
|
||||
|
||||
type AuthorStat {
|
||||
shouts: Int
|
||||
followings: Int
|
||||
followers: Int
|
||||
rating: Int
|
||||
rating_shouts: Int
|
||||
rating_comments: Int
|
||||
commented: Int
|
||||
viewed: Int
|
||||
}
|
||||
|
||||
type Author {
|
||||
id: Int!
|
||||
user: String! # user.id
|
||||
slug: String! # user.nickname
|
||||
name: String # user.preferred_username
|
||||
pic: String
|
||||
bio: String
|
||||
about: String
|
||||
links: [String]
|
||||
created_at: Int
|
||||
last_seen: Int
|
||||
updated_at: Int
|
||||
deleted_at: Int
|
||||
seo: String
|
||||
# synthetic
|
||||
stat: AuthorStat # ratings inside
|
||||
communities: [Community]
|
||||
}
|
||||
|
||||
type ReactionUpdating {
|
||||
error: String
|
||||
status: ReactionStatus
|
||||
reaction: Reaction
|
||||
}
|
||||
|
||||
type Rating {
|
||||
rater: String!
|
||||
value: Int!
|
||||
}
|
||||
|
||||
type Reaction {
|
||||
id: Int!
|
||||
shout: Shout!
|
||||
created_at: Int!
|
||||
created_by: Author!
|
||||
updated_at: Int
|
||||
deleted_at: Int
|
||||
deleted_by: Author
|
||||
range: String
|
||||
kind: ReactionKind!
|
||||
body: String
|
||||
reply_to: Int
|
||||
stat: Stat
|
||||
oid: String
|
||||
# old_thread: String
|
||||
}
|
||||
|
||||
type Shout {
|
||||
id: Int!
|
||||
slug: String!
|
||||
body: String!
|
||||
lead: String
|
||||
description: String
|
||||
created_at: Int!
|
||||
main_topic: String
|
||||
topics: [Topic]
|
||||
created_by: Author!
|
||||
updated_by: Author
|
||||
deleted_by: Author
|
||||
authors: [Author]
|
||||
communities: [Community]
|
||||
title: String!
|
||||
subtitle: String
|
||||
lang: String
|
||||
community: String
|
||||
cover: String
|
||||
cover_caption: String
|
||||
layout: String!
|
||||
version_of: String
|
||||
visibility: String
|
||||
updated_at: Int
|
||||
deleted_at: Int
|
||||
published_at: Int
|
||||
media: String
|
||||
stat: Stat
|
||||
score: Float
|
||||
}
|
||||
|
||||
type Stat {
|
||||
viewed: Int
|
||||
reacted: Int
|
||||
rating: Int
|
||||
commented: Int
|
||||
ranking: Int
|
||||
}
|
||||
|
||||
type Community {
|
||||
id: Int!
|
||||
slug: String!
|
||||
name: String!
|
||||
desc: String
|
||||
pic: String!
|
||||
created_at: Int!
|
||||
created_by: Author!
|
||||
}
|
||||
|
||||
type Collection {
|
||||
id: Int!
|
||||
slug: String!
|
||||
title: String!
|
||||
desc: String
|
||||
amount: Int
|
||||
published_at: Int
|
||||
created_at: Int!
|
||||
created_by: Author!
|
||||
}
|
||||
|
||||
type TopicStat {
|
||||
shouts: Int!
|
||||
followers: Int!
|
||||
authors: Int!
|
||||
viewed: Int
|
||||
}
|
||||
|
||||
type Topic {
|
||||
id: Int!
|
||||
slug: String!
|
||||
title: String
|
||||
body: String
|
||||
pic: String
|
||||
stat: TopicStat
|
||||
oid: String
|
||||
}
|
||||
|
||||
type Invite {
|
||||
id: Int!
|
||||
inviter_id: Int!
|
||||
author_id: Int!
|
||||
shout_id: Int!
|
||||
status: InviteStatus
|
||||
}
|
||||
|
||||
# Входные типы
|
||||
|
||||
input ShoutInput {
|
||||
slug: String
|
||||
title: String
|
||||
body: String
|
||||
lead: String
|
||||
description: String
|
||||
layout: String
|
||||
media: String
|
||||
authors: [String]
|
||||
topics: [TopicInput]
|
||||
community: Int
|
||||
subtitle: String
|
||||
cover: String
|
||||
}
|
||||
|
||||
input ProfileInput {
|
||||
slug: String
|
||||
name: String
|
||||
pic: String
|
||||
links: [String]
|
||||
bio: String
|
||||
about: String
|
||||
}
|
||||
|
||||
input TopicInput {
|
||||
id: Int
|
||||
slug: String!
|
||||
title: String
|
||||
body: String
|
||||
pic: String
|
||||
}
|
||||
|
||||
input ReactionInput {
|
||||
kind: ReactionKind!
|
||||
shout: Int!
|
||||
quote: String
|
||||
body: String
|
||||
reply_to: Int
|
||||
}
|
||||
|
||||
input AuthorsBy {
|
||||
last_seen: Int
|
||||
created_at: Int
|
||||
slug: String
|
||||
name: String
|
||||
topic: String
|
||||
order: String
|
||||
after: Int
|
||||
stat: String
|
||||
}
|
||||
|
||||
input LoadShoutsFilters {
|
||||
topic: String
|
||||
author: String
|
||||
layouts: [String]
|
||||
published: Boolean
|
||||
after: Int
|
||||
reacted: Boolean
|
||||
}
|
||||
|
||||
input LoadShoutsOptions {
|
||||
filters: LoadShoutsFilters
|
||||
with_author_captions: Boolean
|
||||
limit: Int!
|
||||
random_limit: Int
|
||||
offset: Int
|
||||
order_by: String
|
||||
order_by_desc: Boolean
|
||||
}
|
||||
|
||||
input ReactionBy {
|
||||
shout: String
|
||||
shouts: [String]
|
||||
search: String
|
||||
comment: Boolean
|
||||
topic: String
|
||||
created_by: Int
|
||||
after: Int
|
||||
sort: String
|
||||
}
|
||||
|
||||
# output type
|
||||
|
||||
type Result {
|
||||
error: String
|
||||
slugs: [String]
|
||||
shout: Shout
|
||||
shouts: [Shout]
|
||||
author: Author
|
||||
authors: [Author]
|
||||
reaction: Reaction
|
||||
reactions: [Reaction]
|
||||
topic: Topic
|
||||
topics: [Topic]
|
||||
community: Community
|
||||
communities: [Community]
|
||||
}
|
||||
|
||||
type SearchResult {
|
||||
slug: String!
|
||||
title: String!
|
||||
cover: String
|
||||
main_topic: String
|
||||
created_at: Int
|
||||
authors: [Author]
|
||||
topics: [Topic]
|
||||
score: Float!
|
||||
}
|
||||
|
||||
# Мутации
|
||||
|
||||
type Mutation {
|
||||
# author
|
||||
rate_author(rated_slug: String!, value: Int!): Result!
|
||||
update_profile(profile: ProfileInput!): Result!
|
||||
|
||||
# editor
|
||||
create_shout(inp: ShoutInput!): Result!
|
||||
update_shout(shout_id: Int!, shout_input: ShoutInput, publish: Boolean): Result!
|
||||
delete_shout(shout_id: Int!): Result!
|
||||
|
||||
# follower
|
||||
follow(what: FollowingEntity!, slug: String!): Result!
|
||||
unfollow(what: FollowingEntity!, slug: String!): Result!
|
||||
|
||||
# topic
|
||||
create_topic(input: TopicInput!): Result!
|
||||
update_topic(input: TopicInput!): Result!
|
||||
delete_topic(slug: String!): Result!
|
||||
|
||||
# reaction
|
||||
create_reaction(reaction: ReactionInput!): Result!
|
||||
update_reaction(id: Int!, reaction: ReactionInput!): Result!
|
||||
delete_reaction(reaction_id: Int!): Result!
|
||||
|
||||
# collab
|
||||
create_invite(slug: String, author_id: Int): Result!
|
||||
remove_author(slug: String, author_id: Int): Result!
|
||||
remove_invite(invite_id: Int!): Result!
|
||||
accept_invite(invite_id: Int!): Result!
|
||||
reject_invite(invite_id: Int!): Result!
|
||||
}
|
||||
|
||||
# Запросы
|
||||
|
||||
type Query {
|
||||
# author
|
||||
get_author(slug: String, author_id: Int): Author
|
||||
get_author_id(user: String!): Author
|
||||
get_authors_all: [Author]
|
||||
get_author_followers(slug: String, user: String, author_id: Int): [Author]
|
||||
get_author_followed(slug: String, user: String, author_id: Int): [Author]
|
||||
load_authors_by(by: AuthorsBy!, limit: Int, offset: Int): [Author]
|
||||
|
||||
# community
|
||||
get_community: Community
|
||||
get_communities_all: [Community]
|
||||
|
||||
# editor
|
||||
get_shouts_drafts: [Shout]
|
||||
|
||||
# follower
|
||||
get_my_followed: Result # { authors topics communities }
|
||||
get_shout_followers(slug: String, shout_id: Int): [Author]
|
||||
|
||||
# reaction
|
||||
load_reactions_by(by: ReactionBy!, limit: Int, offset: Int): [Reaction]
|
||||
|
||||
# reader
|
||||
get_shout(slug: String, shout_id: Int): Shout
|
||||
load_shouts_followed(follower_id: Int!, limit: Int, offset: Int): [Shout] # userReactedShouts
|
||||
load_shouts_by(options: LoadShoutsOptions): [Shout]
|
||||
load_shouts_search(text: String!, limit: Int, offset: Int): [SearchResult]
|
||||
load_shouts_feed(options: LoadShoutsOptions): [Shout]
|
||||
load_shouts_unrated(limit: Int, offset: Int): [Shout]
|
||||
load_shouts_random_top(options: LoadShoutsOptions): [Shout]
|
||||
load_shouts_random_topic(limit: Int!): Result! # { topic shouts }
|
||||
load_shouts_drafts: [Shout]
|
||||
|
||||
# topic
|
||||
get_topic(slug: String!): Topic
|
||||
get_topics_all: [Topic]
|
||||
get_topics_random(amount: Int): [Topic]
|
||||
get_topics_by_author(slug: String, user: String, author_id: Int): [Topic]
|
||||
get_topics_by_community(slug: String, community_id: Int): [Topic]
|
||||
}
|
46
services/diff.py
Normal file
46
services/diff.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
import re
|
||||
from difflib import ndiff
|
||||
|
||||
|
||||
def get_diff(original, modified):
|
||||
"""
|
||||
Get the difference between two strings using difflib.
|
||||
|
||||
Parameters:
|
||||
- original: The original string.
|
||||
- modified: The modified string.
|
||||
|
||||
Returns:
|
||||
A list of differences.
|
||||
"""
|
||||
diff = list(ndiff(original.split(), modified.split()))
|
||||
return diff
|
||||
|
||||
def apply_diff(original, diff):
|
||||
"""
|
||||
Apply the difference to the original string.
|
||||
|
||||
Parameters:
|
||||
- original: The original string.
|
||||
- diff: The difference obtained from get_diff function.
|
||||
|
||||
Returns:
|
||||
The modified string.
|
||||
"""
|
||||
result = []
|
||||
pattern = re.compile(r'^(\+|-) ')
|
||||
|
||||
for line in diff:
|
||||
match = pattern.match(line)
|
||||
if match:
|
||||
op = match.group(1)
|
||||
content = line[2:]
|
||||
if op == '+':
|
||||
result.append(content)
|
||||
elif op == '-':
|
||||
# Ignore deleted lines
|
||||
pass
|
||||
else:
|
||||
result.append(line)
|
||||
|
||||
return ' '.join(result)
|
Loading…
Reference in New Issue
Block a user