stats refactored

This commit is contained in:
tonyrewin 2022-09-19 16:50:43 +03:00
parent dffdff2869
commit 4536370c79
24 changed files with 355 additions and 410 deletions

View File

@ -16,7 +16,7 @@ class RedisCache:
async def disconnect(self): async def disconnect(self):
if self._instance is None: if self._instance is None:
return return
self._instance.close() await self._instance.close()
# await self._instance.wait_closed() # deprecated # await self._instance.wait_closed() # deprecated
self._instance = None self._instance = None

View File

@ -81,6 +81,7 @@ async def shouts_handle(storage, args):
"""migrating content items one by one""" """migrating content items one by one"""
counter = 0 counter = 0
discours_author = 0 discours_author = 0
anonymous_author = 0
pub_counter = 0 pub_counter = 0
topics_dataset_bodies = [] topics_dataset_bodies = []
topics_dataset_tlist = [] topics_dataset_tlist = []
@ -104,6 +105,8 @@ async def shouts_handle(storage, args):
author: str = shout["authors"][0].dict() author: str = shout["authors"][0].dict()
if author["slug"] == "discours": if author["slug"] == "discours":
discours_author += 1 discours_author += 1
if author["slug"] == "anonymous":
anonymous_author += 1
# print('[migration] ' + shout['slug'] + ' with author ' + author) # print('[migration] ' + shout['slug'] + ' with author ' + author)
if entry.get("published"): if entry.get("published"):
@ -128,6 +131,7 @@ async def shouts_handle(storage, args):
print("[migration] " + str(counter) + " content items were migrated") print("[migration] " + str(counter) + " content items were migrated")
print("[migration] " + str(pub_counter) + " have been published") print("[migration] " + str(pub_counter) + " have been published")
print("[migration] " + str(discours_author) + " authored by @discours") print("[migration] " + str(discours_author) + " authored by @discours")
print("[migration] " + str(anonymous_author) + " authored by @anonymous")
async def comments_handle(storage): async def comments_handle(storage):

View File

@ -1 +1 @@
__all__ = (["users", "tags", "content_items", "comments"],) __all__ = (["users", "topics", "content_items", "comments"],)

View File

@ -8,10 +8,11 @@ from base.orm import local_session
from migration.extract import prepare_html_body from migration.extract import prepare_html_body
from orm.community import Community from orm.community import Community
from orm.reaction import Reaction, ReactionKind from orm.reaction import Reaction, ReactionKind
from orm.shout import Shout, ShoutTopic, User, ShoutReactionsFollower from orm.shout import Shout, ShoutTopic, ShoutReactionsFollower
from orm.user import User
from orm.topic import TopicFollower from orm.topic import TopicFollower
from services.stat.reacted import ReactedStorage from services.stat.reacted import ReactedStorage
from services.stat.viewed import ViewedByDay from services.stat.viewed import ViewedStorage
from services.zine.topics import TopicStorage from services.zine.topics import TopicStorage
OLD_DATE = "2016-03-05 22:22:00.350000" OLD_DATE = "2016-03-05 22:22:00.350000"
@ -137,8 +138,7 @@ async def migrate(entry, storage):
if userdata: if userdata:
userslug = userdata.get('slug') userslug = userdata.get('slug')
else: else:
userslug = "discours" # bad old id slug is used here to change later userslug = "anonymous" # bad old id slug was found
print('DISCOURS AUTHORED: ' + oid)
r["authors"] = [userslug, ] r["authors"] = [userslug, ]
# slug # slug
@ -336,7 +336,7 @@ async def migrate(entry, storage):
raise Exception("[migration] content_item.ratings error: \n%r" % content_rating) raise Exception("[migration] content_item.ratings error: \n%r" % content_rating)
# shout views # shout views
ViewedByDay.create(shout=shout_dict["slug"], value=entry.get("views", 1)) ViewedStorage.increment(shout_dict["slug"], amount=entry.get("views", 1))
# del shout_dict['ratings'] # del shout_dict['ratings']
shout_dict["oid"] = entry.get("_id") shout_dict["oid"] = entry.get("_id")
storage["shouts"]["by_oid"][entry["_id"]] = shout_dict storage["shouts"]["by_oid"][entry["_id"]] = shout_dict

View File

@ -1,12 +1,8 @@
{ {
"207": "207",
"90-e": "90s",
"1990-e": "90s", "1990-e": "90s",
"2000-e": "2000s", "2000-e": "2000s",
"90-e": "90s",
"207": "207",
"kartochki-rubinshteyna": "rubinstein-cards",
"Georgia": "georgia",
"Japan": "japan",
"Sweden": "sweden",
"abstraktsiya": "abstract", "abstraktsiya": "abstract",
"absurdism": "absurdism", "absurdism": "absurdism",
"acclimatization": "acclimatisation", "acclimatization": "acclimatisation",
@ -14,8 +10,8 @@
"adolf-gitler": "adolf-hitler", "adolf-gitler": "adolf-hitler",
"afrika": "africa", "afrika": "africa",
"agata-kristi": "agatha-christie", "agata-kristi": "agatha-christie",
"agressiya": "agression",
"agressivnoe-povedenie": "agression", "agressivnoe-povedenie": "agression",
"agressiya": "agression",
"aktsii": "actions", "aktsii": "actions",
"aktsionizm": "actionism", "aktsionizm": "actionism",
"alber-kamyu": "albert-kamus", "alber-kamyu": "albert-kamus",
@ -40,6 +36,7 @@
"andrey-tarkovskiy": "andrey-tarkovsky", "andrey-tarkovskiy": "andrey-tarkovsky",
"angliyskie-istorii": "english-stories", "angliyskie-istorii": "english-stories",
"angliyskiy-yazyk": "english-langugae", "angliyskiy-yazyk": "english-langugae",
"ango": "ango",
"animation": "animation", "animation": "animation",
"animatsiya": "animation", "animatsiya": "animation",
"anime": "anime", "anime": "anime",
@ -57,12 +54,13 @@
"aristotel": "aristotle", "aristotel": "aristotle",
"arktika": "arctic", "arktika": "arctic",
"armiya": "army", "armiya": "army",
"armiya-1": "army",
"art": "art", "art": "art",
"art-is": "art-is", "art-is": "art-is",
"artists": "artists", "artists": "artists",
"ateizm": "atheism", "ateizm": "atheism",
"audiopoeziya": "audio-poetry",
"audio-poetry": "audio-poetry", "audio-poetry": "audio-poetry",
"audiopoeziya": "audio-poetry",
"audiospektakl": "audio-spectacles", "audiospektakl": "audio-spectacles",
"auktsyon": "auktsyon", "auktsyon": "auktsyon",
"avangard": "avantgarde", "avangard": "avantgarde",
@ -74,6 +72,7 @@
"bannye-chteniya": "sauna-reading", "bannye-chteniya": "sauna-reading",
"bardsongs": "bardsongs", "bardsongs": "bardsongs",
"bdsm": "bdsm", "bdsm": "bdsm",
"beecake": "beecake",
"belarus": "belarus", "belarus": "belarus",
"belgiya": "belgium", "belgiya": "belgium",
"bertold-breht": "berttold-brecht", "bertold-breht": "berttold-brecht",
@ -85,6 +84,7 @@
"biznes": "business", "biznes": "business",
"blizhniy-vostok": "middle-east", "blizhniy-vostok": "middle-east",
"blizost": "closeness", "blizost": "closeness",
"blocked-in-russia": "blocked-in-russia",
"blokada": "blockade", "blokada": "blockade",
"bob-dilan": "bob-dylan", "bob-dilan": "bob-dylan",
"bog": "god", "bog": "god",
@ -152,30 +152,40 @@
"demonstrations": "demonstrations", "demonstrations": "demonstrations",
"depression": "depression", "depression": "depression",
"derevnya": "village", "derevnya": "village",
"derrida": "derrida",
"design": "design", "design": "design",
"detskie-doma": "orphanages", "detskie-doma": "orphanages",
"detstvo": "childhood", "detstvo": "childhood",
"devyanostye": "90s",
"dialog": "dialogue",
"digital": "digital", "digital": "digital",
"digital-art": "digital-art", "digital-art": "digital-art",
"dinozavry": "dinosaurs",
"directing": "directing", "directing": "directing",
"diskurs": "discours", "diskurs": "discours",
"diskurs-1": "discourse", "diskurs-1": "discourse",
"diskurs-analiz": "discourse-analytics",
"dissidenty": "dissidents", "dissidenty": "dissidents",
"diy": "diy", "diy": "diy",
"dmitriy-donskoy": "dmitriy-donskoy", "dmitriy-donskoy": "dmitriy-donskoy",
"dmitriy-prigov": "dmitriy-prigov", "dmitriy-prigov": "dmitriy-prigov",
"dnevnik-1": "dairy",
"dnevniki": "dairies", "dnevniki": "dairies",
"documentary": "documentary", "documentary": "documentary",
"dokumentalnaya-poeziya": "documentary-poetry",
"dokumenty": "doсuments", "dokumenty": "doсuments",
"domashnee-nasilie": "home-terror", "domashnee-nasilie": "home-terror",
"donald-tramp": "donald-trump", "donald-tramp": "donald-trump",
"donbass": "donbass", "donbass": "donbass",
"donbass-diary": "donbass-diary",
"donorstvo": "donation", "donorstvo": "donation",
"dozhd": "rain",
"drama": "drama", "drama": "drama",
"dramaturgy": "dramaturgy", "dramaturgy": "dramaturgy",
"drawing": "drawing", "drawing": "drawing",
"drevo-zhizni": "tree-of-life", "drevo-zhizni": "tree-of-life",
"drugs": "drugs", "drugs": "drugs",
"duh": "spirit",
"dzhaz": "jazz", "dzhaz": "jazz",
"dzhek-keruak": "jack-keruak", "dzhek-keruak": "jack-keruak",
"dzhim-morrison": "jim-morrison", "dzhim-morrison": "jim-morrison",
@ -194,6 +204,7 @@
"ekspressionizm": "expressionism", "ekspressionizm": "expressionism",
"ekstremizm": "extremism", "ekstremizm": "extremism",
"ekzistentsializm-1": "existentialism", "ekzistentsializm-1": "existentialism",
"ekzistentsiya": "existence",
"elections": "elections", "elections": "elections",
"electronic": "electronics", "electronic": "electronics",
"electronics": "electronics", "electronics": "electronics",
@ -248,10 +259,12 @@
"futuristy": "futurists", "futuristy": "futurists",
"futurizm": "futurism", "futurizm": "futurism",
"galereya": "gallery", "galereya": "gallery",
"galereya-anna-nova": "gallery-anna-nova",
"gdr": "gdr", "gdr": "gdr",
"gender": "gender", "gender": "gender",
"gendernyy-diskurs": "gender", "gendernyy-diskurs": "gender",
"gennadiy-aygi": "gennadiy-aygi", "gennadiy-aygi": "gennadiy-aygi",
"Georgia": "georgia",
"gerhard-rihter": "gerhard-rihter", "gerhard-rihter": "gerhard-rihter",
"germaniya": "germany", "germaniya": "germany",
"germenevtika": "hermeneutics", "germenevtika": "hermeneutics",
@ -268,8 +281,11 @@
"gravyura": "engraving", "gravyura": "engraving",
"grazhdanskaya-oborona": "grazhdanskaya-oborona", "grazhdanskaya-oborona": "grazhdanskaya-oborona",
"gretsiya": "greece", "gretsiya": "greece",
"griby": "mushrooms",
"gruziya-2": "georgia",
"gulag": "gulag", "gulag": "gulag",
"han-batyy": "khan-batyy", "han-batyy": "khan-batyy",
"hayku": "haiku",
"health": "health", "health": "health",
"himiya": "chemistry", "himiya": "chemistry",
"hip-hop": "hip-hop", "hip-hop": "hip-hop",
@ -286,6 +302,7 @@
"idm": "idm", "idm": "idm",
"igil": "isis", "igil": "isis",
"igor-pomerantsev": "igor-pomerantsev", "igor-pomerantsev": "igor-pomerantsev",
"igra": "game",
"igra-prestolov": "game-of-throne", "igra-prestolov": "game-of-throne",
"igry": "games", "igry": "games",
"iisus-hristos": "jesus-christ", "iisus-hristos": "jesus-christ",
@ -312,6 +329,7 @@
"iskusstvennyy-intellekt": "artificial-intelligence", "iskusstvennyy-intellekt": "artificial-intelligence",
"islam": "islam", "islam": "islam",
"istoriya-moskvy": "moscow-history", "istoriya-moskvy": "moscow-history",
"istoriya-nauki": "history-of-sceince",
"istoriya-teatra": "theatre-history", "istoriya-teatra": "theatre-history",
"italiya": "italy", "italiya": "italy",
"italyanskiy-yazyk": "italian-language", "italyanskiy-yazyk": "italian-language",
@ -322,6 +340,7 @@
"ivan-krylov": "ivan-krylov", "ivan-krylov": "ivan-krylov",
"izobreteniya": "inventions", "izobreteniya": "inventions",
"izrail-1": "israel", "izrail-1": "israel",
"Japan": "japan",
"jazz": "jazz", "jazz": "jazz",
"john-lennon": "john-lennon", "john-lennon": "john-lennon",
"journalism": "journalism", "journalism": "journalism",
@ -329,12 +348,15 @@
"k-pop": "k-pop", "k-pop": "k-pop",
"kalligrafiya": "calligraphy", "kalligrafiya": "calligraphy",
"karikatura": "caricatures", "karikatura": "caricatures",
"kartochki-rubinshteyna": "rubinstein-cards",
"katrin-nenasheva": "katrin-nenasheva", "katrin-nenasheva": "katrin-nenasheva",
"kavarga": "kavarga",
"kavkaz": "caucasus", "kavkaz": "caucasus",
"kazan": "kazan", "kazan": "kazan",
"kiberbezopasnost": "cybersecurity", "kiberbezopasnost": "cybersecurity",
"kinoklub": "cinema-club", "kinoklub": "cinema-club",
"kirill-serebrennikov": "kirill-serebrennikov", "kirill-serebrennikov": "kirill-serebrennikov",
"kladbische": "cemetery",
"klassika": "classic", "klassika": "classic",
"kollektivnoe-bessoznatelnoe": "сollective-unconscious", "kollektivnoe-bessoznatelnoe": "сollective-unconscious",
"komediya": "comedy", "komediya": "comedy",
@ -342,12 +364,14 @@
"kommunizm": "communism", "kommunizm": "communism",
"kommuny": "communes", "kommuny": "communes",
"kompyuternye-igry": "computer-games", "kompyuternye-igry": "computer-games",
"konets-vesny": "end-of-spring",
"konservatizm": "conservatism", "konservatizm": "conservatism",
"kontrkultura": "counter-culture", "kontrkultura": "counter-culture",
"kontseptualizm": "conceptualism", "kontseptualizm": "conceptualism",
"korotkometrazhka": "cinema-shorts", "korotkometrazhka": "cinema-shorts",
"kosmos": "cosmos", "kosmos": "cosmos",
"kraudfanding": "crowdfunding", "kraudfanding": "crowdfunding",
"kriptovalyuty": "cryptocurrencies",
"krizis": "crisis", "krizis": "crisis",
"krov": "blood", "krov": "blood",
"krym": "crimea", "krym": "crimea",
@ -373,12 +397,15 @@
"lirika": "lirics", "lirika": "lirics",
"literary-studies": "literary-studies", "literary-studies": "literary-studies",
"literature": "literature", "literature": "literature",
"literaturnyykaver": "literature-cover",
"lo-fi": "lo-fi", "lo-fi": "lo-fi",
"lomonosov": "lomonosov",
"love": "love", "love": "love",
"luzha-goluboy-krovi": "luzha-goluboy-krovi", "luzha-goluboy-krovi": "luzha-goluboy-krovi",
"lyudvig-vitgenshteyn": "ludwig-wittgenstein", "lyudvig-vitgenshteyn": "ludwig-wittgenstein",
"lzhedmitriy": "false-dmitry", "lzhedmitriy": "false-dmitry",
"lzhenauka": "pseudoscience", "lzhenauka": "pseudoscience",
"magiya": "magic",
"maks-veber": "max-weber", "maks-veber": "max-weber",
"manifests": "manifests", "manifests": "manifests",
"manipulyatsii-soznaniem": "mind-manipulation", "manipulyatsii-soznaniem": "mind-manipulation",
@ -388,13 +415,12 @@
"marsel-dyushan": "marchel-duchamp", "marsel-dyushan": "marchel-duchamp",
"martin-haydegger": "martin-hidegger", "martin-haydegger": "martin-hidegger",
"matematika": "maths", "matematika": "maths",
"vladimir-mayakovskiy": "vladimir-mayakovsky",
"mayakovskiy": "vladimir-mayakovsky", "mayakovskiy": "vladimir-mayakovsky",
"ekzistentsiya": "existence",
"media": "media", "media": "media",
"medicine": "medicine", "medicine": "medicine",
"memuary": "memoirs", "memuary": "memoirs",
"menedzhment": "management", "menedzhment": "management",
"menty": "police",
"merab-mamardashvili": "merab-mamardashvili", "merab-mamardashvili": "merab-mamardashvili",
"mest": "revenge", "mest": "revenge",
"metamodernizm": "metamodern", "metamodernizm": "metamodern",
@ -417,6 +443,7 @@
"moda": "fashion", "moda": "fashion",
"modernizm": "modernism", "modernizm": "modernism",
"mokyumentari": "mockumentary", "mokyumentari": "mockumentary",
"molodezh": "youth",
"moloko-plus": "moloko-plus", "moloko-plus": "moloko-plus",
"money": "money", "money": "money",
"monologs": "monologues", "monologs": "monologues",
@ -436,6 +463,7 @@
"muzhchiny": "man", "muzhchiny": "man",
"myshlenie": "thinking", "myshlenie": "thinking",
"nagornyy-karabah": "nagorno-karabakh", "nagornyy-karabah": "nagorno-karabakh",
"nasilie-1": "violence",
"natsionalizm": "nationalism", "natsionalizm": "nationalism",
"natsionalnaya-ideya": "national-idea", "natsionalnaya-ideya": "national-idea",
"natsizm": "nazism", "natsizm": "nazism",
@ -500,9 +528,12 @@
"poetry": "poetry", "poetry": "poetry",
"poetry-of-squares": "poetry-of-squares", "poetry-of-squares": "poetry-of-squares",
"poetry-slam": "poetry-slam", "poetry-slam": "poetry-slam",
"pokoy": "peace",
"police": "police", "police": "police",
"politics": "politics", "politics": "politics",
"politzaklyuchennye": "political-prisoners",
"polsha": "poland", "polsha": "poland",
"pomosch": "help",
"pop-art": "pop-art", "pop-art": "pop-art",
"pop-culture": "pop-culture", "pop-culture": "pop-culture",
"pornografiya": "pornography", "pornografiya": "pornography",
@ -543,8 +574,10 @@
"pskov": "pskov", "pskov": "pskov",
"psychiatry": "psychiatry", "psychiatry": "psychiatry",
"psychology": "psychology", "psychology": "psychology",
"ptitsy": "birds",
"punk": "punk", "punk": "punk",
"r-b": "rnb", "r-b": "rnb",
"rasizm": "racism",
"realizm": "realism", "realizm": "realism",
"redaktura": "editorial", "redaktura": "editorial",
"refleksiya": "reflection", "refleksiya": "reflection",
@ -555,6 +588,7 @@
"renovatsiya": "renovation", "renovatsiya": "renovation",
"rep": "rap", "rep": "rap",
"reportage": "reportage", "reportage": "reportage",
"reportazh-1": "reportage",
"repressions": "repressions", "repressions": "repressions",
"research": "research", "research": "research",
"retroveyv": "retrowave", "retroveyv": "retrowave",
@ -570,13 +604,16 @@
"ronald-reygan": "ronald-reygan", "ronald-reygan": "ronald-reygan",
"roskomnadzor": "roskomnadzor", "roskomnadzor": "roskomnadzor",
"rossiyskoe-kino": "russian-cinema", "rossiyskoe-kino": "russian-cinema",
"rouling": "rowling",
"rozhava": "rojava", "rozhava": "rojava",
"rpts": "rpts", "rpts": "rpts",
"rus-na-grani-sryva": "rus-na-grani-sryva", "rus-na-grani-sryva": "rus-na-grani-sryva",
"russia": "russia", "russia": "russia",
"russian-language": "russian-language", "russian-language": "russian-language",
"russian-literature": "russian-literature", "russian-literature": "russian-literature",
"russkaya-toska": "russian-toska",
"russkiy-mir": "russkiy-mir", "russkiy-mir": "russkiy-mir",
"salo": "lard",
"salvador-dali": "salvador-dali", "salvador-dali": "salvador-dali",
"samoidentifikatsiya": "self-identity", "samoidentifikatsiya": "self-identity",
"samoopredelenie": "self-definition", "samoopredelenie": "self-definition",
@ -591,6 +628,7 @@
"second-world-war": "second-world-war", "second-world-war": "second-world-war",
"sekond-hend": "second-hand", "sekond-hend": "second-hand",
"seksprosvet": "sex-education", "seksprosvet": "sex-education",
"seksualnoe-nasilie": "sexual-violence",
"sekty": "sects", "sekty": "sects",
"semiotics": "semiotics", "semiotics": "semiotics",
"serbiya": "serbia", "serbiya": "serbia",
@ -606,6 +644,7 @@
"siriya": "siria", "siriya": "siria",
"skulptura": "sculpture", "skulptura": "sculpture",
"slavoy-zhizhek": "slavoj-zizek", "slavoy-zhizhek": "slavoj-zizek",
"smert-1": "death",
"smysl": "meaning", "smysl": "meaning",
"sny": "dreams", "sny": "dreams",
"sobytiya": "events", "sobytiya": "events",
@ -637,10 +676,12 @@
"strah": "fear", "strah": "fear",
"street-art": "street-art", "street-art": "street-art",
"stsenarii": "scenarios", "stsenarii": "scenarios",
"sud": "court",
"summary": "summary", "summary": "summary",
"supergeroi": "superheroes", "supergeroi": "superheroes",
"svetlana-aleksievich": "svetlana-aleksievich", "svetlana-aleksievich": "svetlana-aleksievich",
"svobodu-ivanu-golunovu": "free-ivan-golunov", "svobodu-ivanu-golunovu": "free-ivan-golunov",
"Sweden": "sweden",
"syurrealizm": "surrealism", "syurrealizm": "surrealism",
"tales": "tales", "tales": "tales",
"tanets": "dance", "tanets": "dance",
@ -679,6 +720,7 @@
"tvorchestvo": "creativity", "tvorchestvo": "creativity",
"ugnetennyy-zhilischnyy-klass": "oppressed-housing-class", "ugnetennyy-zhilischnyy-klass": "oppressed-housing-class",
"uilyam-shekspir": "william-shakespeare", "uilyam-shekspir": "william-shakespeare",
"ukraina-2": "ukraine",
"ukraine": "ukraine", "ukraine": "ukraine",
"university": "university", "university": "university",
"urban-studies": "urban-studies", "urban-studies": "urban-studies",
@ -686,6 +728,7 @@
"usa": "usa", "usa": "usa",
"ussr": "ussr", "ussr": "ussr",
"utopiya": "utopia", "utopiya": "utopia",
"utrata": "loss",
"valter-benyamin": "valter-benyamin", "valter-benyamin": "valter-benyamin",
"varlam-shalamov": "varlam-shalamov", "varlam-shalamov": "varlam-shalamov",
"vasiliy-ii-temnyy": "basil-ii-temnyy", "vasiliy-ii-temnyy": "basil-ii-temnyy",
@ -714,6 +757,7 @@
"visual-culture": "visual-culture", "visual-culture": "visual-culture",
"vizualnaya-poeziya": "visual-poetry", "vizualnaya-poeziya": "visual-poetry",
"vladimir-lenin": "vladimir-lenin", "vladimir-lenin": "vladimir-lenin",
"vladimir-mayakovskiy": "vladimir-mayakovsky",
"vladimir-nabokov": "vladimir-nabokov", "vladimir-nabokov": "vladimir-nabokov",
"vladimir-putin": "vladimir-putin", "vladimir-putin": "vladimir-putin",
"vladimir-sorokin": "vladimir-sorokin", "vladimir-sorokin": "vladimir-sorokin",
@ -723,6 +767,7 @@
"vong-karvay": "wong-karwai", "vong-karvay": "wong-karwai",
"vospominaniya": "memories", "vospominaniya": "memories",
"vostok": "east", "vostok": "east",
"voyna-na-ukraine": "war-in-ukraine",
"vremya": "time", "vremya": "time",
"vudi-allen": "woody-allen", "vudi-allen": "woody-allen",
"vynuzhdennye-otnosheniya": "forced-relationship", "vynuzhdennye-otnosheniya": "forced-relationship",
@ -736,65 +781,21 @@
"yan-vermeer": "yan-vermeer", "yan-vermeer": "yan-vermeer",
"yanka-dyagileva": "yanka-dyagileva", "yanka-dyagileva": "yanka-dyagileva",
"yaponskaya-literatura": "japan-literature", "yaponskaya-literatura": "japan-literature",
"yazychestvo": "paganism",
"youth": "youth", "youth": "youth",
"yozef-rot": "yozef-rot", "yozef-rot": "yozef-rot",
"yurgen-habermas": "jorgen-habermas", "yurgen-habermas": "jorgen-habermas",
"za-liniey-mannergeyma": "behind-mannerheim-line", "za-liniey-mannergeyma": "behind-mannerheim-line",
"zabota": "care",
"zahar-prilepin": "zahar-prilepin", "zahar-prilepin": "zahar-prilepin",
"zakonodatelstvo": "laws", "zakonodatelstvo": "laws",
"zakony-mira": "world-laws", "zakony-mira": "world-laws",
"zametki": "notes", "zametki": "notes",
"zhelanie": "wish", "zhelanie": "wish",
"konets-vesny": "end-of-spring",
"zhivotnye": "animals", "zhivotnye": "animals",
"zhoze-saramago": "jose-saramago", "zhoze-saramago": "jose-saramago",
"zigmund-freyd": "sigmund-freud", "zigmund-freyd": "sigmund-freud",
"zolotaya-orda": "golden-horde", "zolotaya-orda": "golden-horde",
"zombi": "zombie", "zombi": "zombie",
"zombi-simpsony": "zombie-simpsons", "zombi-simpsony": "zombie-simpsons"
"rouling": "rowling",
"diskurs-analiz": "discourse-analytics",
"menty": "police",
"ptitsy": "birds",
"salo": "lard",
"rasizm": "racism",
"griby": "mushrooms",
"politzaklyuchennye": "political-prisoners",
"molodezh": "youth",
"blocked-in-russia": "blocked-in-russia",
"kavarga": "kavarga",
"galereya-anna-nova": "gallery-anna-nova",
"derrida": "derrida",
"dinozavry": "dinosaurs",
"beecake": "beecake",
"literaturnyykaver": "literature-cover",
"dialog": "dialogue",
"dozhd": "rain",
"pomosch": "help",
"igra": "game",
"reportazh-1": "reportage",
"armiya-1": "army",
"ukraina-2": "ukraine",
"nasilie-1": "violence",
"smert-1": "death",
"dnevnik-1": "dairy",
"voyna-na-ukraine": "war-in-ukraine",
"zabota": "care",
"ango": "ango",
"hayku": "haiku",
"utrata": "loss",
"pokoy": "peace",
"kladbische": "cemetery",
"lomonosov": "lomonosov",
"istoriya-nauki": "history-of-sceince",
"sud": "court",
"russkaya-toska": "russian-toska",
"duh": "spirit",
"devyanostye": "90s",
"seksualnoe-nasilie": "sexual-violence",
"gruziya-2": "georgia",
"dokumentalnaya-poeziya": "documentary-poetry",
"kriptovalyuty": "cryptocurrencies",
"magiya": "magic",
"yazychestvo": "paganism"
} }

View File

@ -6,6 +6,7 @@ from orm.reaction import Reaction
from orm.shout import Shout from orm.shout import Shout
from orm.topic import Topic, TopicFollower from orm.topic import Topic, TopicFollower
from orm.user import User, UserRating from orm.user import User, UserRating
from orm.viewed import ViewedByDay
__all__ = [ __all__ = [
"User", "User",
@ -19,6 +20,7 @@ __all__ = [
"Notification", "Notification",
"Reaction", "Reaction",
"UserRating", "UserRating",
"ViewedByDay"
] ]
Base.metadata.create_all(engine) Base.metadata.create_all(engine)
@ -27,3 +29,5 @@ Resource.init_table()
User.init_table() User.init_table()
Community.init_table() Community.init_table()
Role.init_table() Role.init_table()
# NOTE: keep orm module isolated

View File

@ -2,10 +2,25 @@ from datetime import datetime
from sqlalchemy import Column, String, ForeignKey, DateTime from sqlalchemy import Column, String, ForeignKey, DateTime
from sqlalchemy import Enum from sqlalchemy import Enum
from enum import Enum as Enumeration
from base.orm import Base from base.orm import Base
from services.stat.reacted import ReactedStorage, ReactionKind
from services.stat.viewed import ViewedStorage
class ReactionKind(Enumeration):
AGREE = 1 # +1
DISAGREE = 2 # -1
PROOF = 3 # +1
DISPROOF = 4 # -1
ASK = 5 # +0 bookmark
PROPOSE = 6 # +0
QUOTE = 7 # +0 bookmark
COMMENT = 8 # +0
ACCEPT = 9 # +1
REJECT = 0 # -1
LIKE = 11 # +1
DISLIKE = 12 # -1
# TYPE = <reaction index> # rating diff
class Reaction(Base): class Reaction(Base):
@ -26,12 +41,3 @@ class Reaction(Base):
range = Column(String, nullable=True, comment="Range in format <start index>:<end>") range = Column(String, nullable=True, comment="Range in format <start index>:<end>")
kind = Column(Enum(ReactionKind), nullable=False, comment="Reaction kind") kind = Column(Enum(ReactionKind), nullable=False, comment="Reaction kind")
oid = Column(String, nullable=True, comment="Old ID") oid = Column(String, nullable=True, comment="Old ID")
@property
async def stat(self):
return {
"viewed": await ViewedStorage.get_reaction(self.id),
"reacted": len(await ReactedStorage.get_reaction(self.id)),
"rating": await ReactedStorage.get_reaction_rating(self.id),
"commented": len(await ReactedStorage.get_reaction_comments(self.id)),
}

View File

@ -5,10 +5,16 @@ from sqlalchemy.orm import relationship
from base.orm import Base from base.orm import Base
from orm.reaction import Reaction from orm.reaction import Reaction
from orm.topic import Topic, ShoutTopic from orm.topic import Topic
from orm.user import User from orm.user import User
from services.stat.reacted import ReactedStorage
from services.stat.viewed import ViewedStorage
class ShoutTopic(Base):
__tablename__ = "shout_topic"
id = None # type: ignore
shout = Column(ForeignKey("shout.slug"), primary_key=True)
topic = Column(ForeignKey("topic.slug"), primary_key=True)
class ShoutReactionsFollower(Base): class ShoutReactionsFollower(Base):
@ -62,17 +68,9 @@ class Shout(Base):
createdAt = Column(DateTime, nullable=False, default=datetime.now, comment="Created at") createdAt = Column(DateTime, nullable=False, default=datetime.now, comment="Created at")
updatedAt = Column(DateTime, nullable=True, comment="Updated at") updatedAt = Column(DateTime, nullable=True, comment="Updated at")
publishedAt = Column(DateTime, nullable=True) publishedAt = Column(DateTime, nullable=True)
deletedAt = Column(DateTime, nullable=True)
versionOf = Column(ForeignKey("shout.slug"), nullable=True) versionOf = Column(ForeignKey("shout.slug"), nullable=True)
draft = Column(Boolean, default=False) draft = Column(Boolean, default=False)
lang = Column(String, default='ru') lang = Column(String, default='ru')
oid = Column(String, nullable=True) oid = Column(String, nullable=True)
@property
async def stat(self):
return {
"viewed": await ViewedStorage.get_shout(self.slug),
"reacted": len(await ReactedStorage.get_shout(self.slug)),
"commented": len(await ReactedStorage.get_comments(self.slug)),
"rating": await ReactedStorage.get_rating(self.slug),
}

View File

@ -5,14 +5,6 @@ from sqlalchemy import Column, Boolean, String, ForeignKey, DateTime, JSON as JS
from base.orm import Base from base.orm import Base
class ShoutTopic(Base):
__tablename__ = "shout_topic"
id = None # type: ignore
shout = Column(ForeignKey("shout.slug"), primary_key=True)
topic = Column(ForeignKey("topic.slug"), primary_key=True)
class TopicFollower(Base): class TopicFollower(Base):
__tablename__ = "topic_followers" __tablename__ = "topic_followers"

View File

@ -82,17 +82,25 @@ class User(Base):
@staticmethod @staticmethod
def init_table(): def init_table():
with local_session() as session: with local_session() as session:
default = session.query(User).filter(User.slug == "discours").first() default = session.query(User).filter(User.slug == "anonymous").first()
if not default: if not default:
default = User.create( defaul_dict = {
id=0, "email": "noreply@discours.io",
email="welcome@discours.io", "username": "noreply@discours.io",
username="welcome@discours.io", "name": "Аноним",
name="Дискурс", "slug": "anonymous",
slug="discours", }
userpic="https://discours.io/images/logo-mini.svg", default = User.create(**defaul_dict)
) session.add(default)
discours_dict = {
"email": "welcome@discours.io",
"username": "welcome@discours.io",
"name": "Дискурс",
"slug": "discours",
}
discours = User.create(**discours_dict)
session.add(discours)
session.commit()
User.default_user = default User.default_user = default
async def get_permission(self): async def get_permission(self):

12
orm/viewed.py Normal file
View File

@ -0,0 +1,12 @@
from datetime import datetime
from sqlalchemy import Column, DateTime, ForeignKey, Integer
from base.orm import Base
class ViewedByDay(Base):
__tablename__ = "viewed_by_day"
id = None
shout = Column(ForeignKey("shout.slug"), primary_key=True)
day = Column(DateTime, primary_key=True, default=datetime.now)
value = Column(Integer)

View File

@ -5,6 +5,7 @@ from resolvers.auth import (
register, register,
confirm_email, confirm_email,
auth_send_link, auth_send_link,
get_current_user,
) )
from resolvers.collab import remove_author, invite_author from resolvers.collab import remove_author, invite_author
from resolvers.community import ( from resolvers.community import (
@ -18,7 +19,6 @@ from resolvers.community import (
from resolvers.editor import create_shout, delete_shout, update_shout from resolvers.editor import create_shout, delete_shout, update_shout
from resolvers.profile import ( from resolvers.profile import (
get_users_by_slugs, get_users_by_slugs,
get_current_user,
get_user_reacted_shouts, get_user_reacted_shouts,
get_user_roles, get_user_roles,
get_top_authors, get_top_authors,
@ -44,7 +44,7 @@ from resolvers.zine import (
get_shout_by_slug, get_shout_by_slug,
follow, follow,
unfollow, unfollow,
view_shout, increment_view,
top_month, top_month,
top_overall, top_overall,
recent_published, recent_published,
@ -65,8 +65,8 @@ __all__ = [
"confirm_email", "confirm_email",
"auth_send_link", "auth_send_link",
"sign_out", "sign_out",
# profile
"get_current_user", "get_current_user",
# profile
"get_users_by_slugs", "get_users_by_slugs",
"get_user_roles", "get_user_roles",
"get_top_authors", "get_top_authors",
@ -80,7 +80,7 @@ __all__ = [
"top_month", "top_month",
"top_overall", "top_overall",
"top_viewed", "top_viewed",
"view_shout", "increment_view",
"get_shout_by_slug", "get_shout_by_slug",
# editor # editor
"create_shout", "create_shout",

View File

@ -1,9 +1,10 @@
from urllib.parse import quote_plus from urllib.parse import quote_plus
from datetime import datetime
from auth.tokenstorage import TokenStorage
from graphql.type import GraphQLResolveInfo from graphql.type import GraphQLResolveInfo
from transliterate import translit from transliterate import translit
from auth.tokenstorage import TokenStorage
from auth.authenticate import login_required from auth.authenticate import login_required
from auth.email import send_auth_email from auth.email import send_auth_email
from auth.identity import Identity, Password from auth.identity import Identity, Password
@ -20,6 +21,22 @@ from resolvers.profile import get_user_info
from settings import SESSION_TOKEN_HEADER from settings import SESSION_TOKEN_HEADER
@mutation.field("refreshSession")
@login_required
async def get_current_user(_, info):
user = info.context["request"].user
user.lastSeen = datetime.now()
with local_session() as session:
session.add(user)
session.commit()
token = await TokenStorage.create_session(user)
return {
"token": token,
"user": user,
"info": await get_user_info(user.slug),
}
@mutation.field("confirmEmail") @mutation.field("confirmEmail")
async def confirm_email(*_, confirm_token): async def confirm_email(*_, confirm_token):
"""confirm owning email address""" """confirm owning email address"""

View File

@ -99,3 +99,6 @@ async def get_my_collections(_, info):
session.query(Collection).when(Collection.createdBy == user_id).all() session.query(Collection).when(Collection.createdBy == user_id).all()
) )
return collections return collections
# TODO: get shouts list by collection

View File

@ -13,7 +13,7 @@ from services.zine.shoutscache import prepare_shouts
@query.field("shoutsForFeed") @query.field("shoutsForFeed")
@login_required @login_required
def get_user_feed(_, info, offset, limit) -> List[Shout]: async def get_user_feed(_, info, offset, limit) -> List[Shout]:
user = info.context["request"].user user = info.context["request"].user
shouts = [] shouts = []
with local_session() as session: with local_session() as session:

View File

@ -1,23 +1,39 @@
from datetime import datetime
from typing import List from typing import List
from sqlalchemy import and_, desc from sqlalchemy import and_, desc
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
from auth.authenticate import login_required from auth.authenticate import login_required
from auth.tokenstorage import TokenStorage
from base.orm import local_session from base.orm import local_session
from base.resolvers import mutation, query from base.resolvers import mutation, query
from orm.reaction import Reaction from orm.reaction import Reaction
from orm.shout import Shout from orm.shout import Shout
from orm.topic import Topic, TopicFollower from orm.topic import Topic, TopicFollower
from orm.user import User, UserRole, Role, UserRating, AuthorFollower from orm.user import User, UserRole, Role, UserRating, AuthorFollower
from resolvers.community import get_followed_communities from .community import get_followed_communities
from resolvers.inbox import get_unread_counter from .inbox import get_unread_counter
from resolvers.reactions import get_shout_reactions from .reactions import get_shout_reactions
from .topics import get_topic_stat
from services.auth.users import UserStorage from services.auth.users import UserStorage
async def get_user_info(slug):
return {
"unread": await get_unread_counter(slug), # unread inbox messages counter
"topics": [t.slug for t in get_followed_topics(0, slug)], # followed topics slugs
"authors": [a.slug for a in get_followed_authors(0, slug)], # followed authors slugs
"reactions": [r.shout for r in get_shout_reactions(0, slug)], # followed reacted shouts slugs
"communities": [c.slug for c in get_followed_communities(0, slug)], # followed communities slugs
}
async def get_author_stat(slug):
# TODO: implement author stat
return {
}
@query.field("userReactedShouts") @query.field("userReactedShouts")
async def get_user_reacted_shouts(_, _info, slug, offset, limit) -> List[Shout]: async def get_user_reacted_shouts(_, _info, slug, offset, limit) -> List[Shout]:
user = await UserStorage.get_user_by_slug(slug) user = await UserStorage.get_user_by_slug(slug)
@ -38,20 +54,22 @@ async def get_user_reacted_shouts(_, _info, slug, offset, limit) -> List[Shout]:
@query.field("userFollowedTopics") @query.field("userFollowedTopics")
@login_required @login_required
def get_followed_topics(_, slug) -> List[Topic]: async def get_followed_topics(_, slug) -> List[Topic]:
rows = [] topics = []
with local_session() as session: with local_session() as session:
rows = ( topics = (
session.query(Topic) session.query(Topic)
.join(TopicFollower) .join(TopicFollower)
.where(TopicFollower.follower == slug) .where(TopicFollower.follower == slug)
.all() .all()
) )
return rows for topic in topics:
topic.stat = await get_topic_stat(topic.slug)
return topics
@query.field("userFollowedAuthors") @query.field("userFollowedAuthors")
def get_followed_authors(_, slug) -> List[User]: async def get_followed_authors(_, slug) -> List[User]:
authors = [] authors = []
with local_session() as session: with local_session() as session:
authors = ( authors = (
@ -60,6 +78,8 @@ def get_followed_authors(_, slug) -> List[User]:
.where(AuthorFollower.follower == slug) .where(AuthorFollower.follower == slug)
.all() .all()
) )
for author in authors:
author.stat = await get_author_stat(author.slug)
return authors return authors
@ -75,33 +95,6 @@ async def user_followers(_, slug) -> List[User]:
return users return users
# for mutation.field("refreshSession")
async def get_user_info(slug):
return {
"unread": await get_unread_counter(slug),
"topics": [t.slug for t in get_followed_topics(0, slug)],
"authors": [a.slug for a in get_followed_authors(0, slug)],
"reactions": [r.shout for r in get_shout_reactions(0, slug)],
"communities": [c.slug for c in get_followed_communities(0, slug)],
}
@mutation.field("refreshSession")
@login_required
async def get_current_user(_, info):
user = info.context["request"].user
user.lastSeen = datetime.now()
with local_session() as session:
session.add(user)
session.commit()
token = await TokenStorage.create_session(user)
return {
"token": token,
"user": user,
"info": await get_user_info(user.slug),
}
@query.field("getUsersBySlugs") @query.field("getUsersBySlugs")
async def get_users_by_slugs(_, _info, slugs): async def get_users_by_slugs(_, _info, slugs):
with local_session() as session: with local_session() as session:

View File

@ -10,6 +10,16 @@ from orm.shout import ShoutReactionsFollower
from orm.user import User from orm.user import User
from services.auth.users import UserStorage from services.auth.users import UserStorage
from services.stat.reacted import ReactedStorage from services.stat.reacted import ReactedStorage
from services.stat.viewed import ViewedStorage
async def get_reaction_stat(reaction_id):
return {
"viewed": await ViewedStorage.get_reaction(reaction_id),
"reacted": len(await ReactedStorage.get_reaction(reaction_id)),
"rating": await ReactedStorage.get_reaction_rating(reaction_id),
"commented": len(await ReactedStorage.get_reaction_comments(reaction_id)),
}
def reactions_follow(user, slug, auto=False): def reactions_follow(user, slug, auto=False):
@ -61,6 +71,8 @@ async def create_reaction(_, info, inp):
except Exception as e: except Exception as e:
print(f"[resolvers.reactions] error on reactions autofollowing: {e}") print(f"[resolvers.reactions] error on reactions autofollowing: {e}")
reaction.stat = await get_reaction_stat(reaction.id)
return {"reaction": reaction} return {"reaction": reaction}
@ -86,6 +98,8 @@ async def update_reaction(_, info, inp):
reaction.range = inp.get("range") reaction.range = inp.get("range")
session.commit() session.commit()
reaction.stat = await get_reaction_stat(reaction.id)
return {"reaction": reaction} return {"reaction": reaction}
@ -118,6 +132,7 @@ async def get_shout_reactions(_, info, slug, offset, limit):
.all() .all()
) )
for r in reactions: for r in reactions:
r.stat = await get_reaction_stat(r.id)
r.createdBy = await UserStorage.get_user(r.createdBy or "discours") r.createdBy = await UserStorage.get_user(r.createdBy or "discours")
return reactions return reactions
@ -137,6 +152,7 @@ async def get_reactions_for_shouts(_, info, shouts, offset, limit):
.all() .all()
) )
for r in reactions: for r in reactions:
r.stat = await get_reaction_stat(r.id)
r.createdBy = await UserStorage.get_user(r.createdBy or "discours") r.createdBy = await UserStorage.get_user(r.createdBy or "discours")
return reactions return reactions
@ -152,5 +168,6 @@ async def get_reactions_by_author(_, info, slug, limit=50, offset=0):
.offset(offset) .offset(offset)
) )
for r in reactions: for r in reactions:
r.stat = await get_reaction_stat(r.id)
r.createdBy = await UserStorage.get_user(r.createdBy or "discours") r.createdBy = await UserStorage.get_user(r.createdBy or "discours")
return reactions return reactions

View File

@ -6,16 +6,30 @@ from auth.authenticate import login_required
from base.orm import local_session from base.orm import local_session
from base.resolvers import mutation, query from base.resolvers import mutation, query
from orm.topic import Topic, TopicFollower from orm.topic import Topic, TopicFollower
from services.stat.topicstat import TopicStat
from services.zine.shoutscache import ShoutsCache from services.zine.shoutscache import ShoutsCache
from services.zine.topics import TopicStorage from services.zine.topics import TopicStorage
from services.stat.reacted import ReactedStorage
from services.stat.topicstat import TopicStat
from services.stat.viewed import ViewedStorage
async def get_topic_stat(slug):
return {
"shouts": len(TopicStat.shouts_by_topic.get(slug, [])),
"authors": len(TopicStat.authors_by_topic.get(slug, [])),
"followers": len(TopicStat.followers_by_topic.get(slug, [])),
"viewed": await ViewedStorage.get_topic(slug),
"reacted": len(await ReactedStorage.get_topic(slug)),
"commented": len(await ReactedStorage.get_topic_comments(slug)),
"rating": await ReactedStorage.get_topic_rating(slug)
}
@query.field("topicsAll") @query.field("topicsAll")
async def topics_all(_, _info): async def topics_all(_, _info):
topics = await TopicStorage.get_topics_all() topics = await TopicStorage.get_topics_all()
for topic in topics: for topic in topics:
topic.stat = await TopicStat.get_stat(topic.slug) topic.stat = await get_topic_stat(topic.slug)
return topics return topics
@ -23,18 +37,19 @@ async def topics_all(_, _info):
async def topics_by_community(_, info, community): async def topics_by_community(_, info, community):
topics = await TopicStorage.get_topics_by_community(community) topics = await TopicStorage.get_topics_by_community(community)
for topic in topics: for topic in topics:
topic.stat = await TopicStat.get_stat(topic.slug) topic.stat = await get_topic_stat(topic.slug)
return topics return topics
@query.field("topicsByAuthor") @query.field("topicsByAuthor")
async def topics_by_author(_, _info, author): async def topics_by_author(_, _info, author):
topics = ShoutsCache.by_author.get(author) shouts = ShoutsCache.by_author.get(author)
author_topics = set() author_topics = set()
for tpc in topics: for s in shouts:
tpc = await TopicStorage.topics[tpc.slug] for tpc in s.topics:
tpc.stat = await TopicStat.get_stat(tpc.slug) tpc = await TopicStorage.topics[tpc.slug]
author_topics.add(tpc) tpc.stat = await get_topic_stat(tpc.slug)
author_topics.add(tpc)
return list(author_topics) return list(author_topics)
@ -91,11 +106,8 @@ async def topics_random(_, info, amount=12):
topics = await TopicStorage.get_topics_all() topics = await TopicStorage.get_topics_all()
normalized_topics = [] normalized_topics = []
for topic in topics: for topic in topics:
topic_stat = await TopicStat.get_stat(topic.slug) topic.stat = await get_topic_stat(topic.slug)
# FIXME: expects topicstat fix if topic.stat["shouts"] > 2:
# #if topic_stat["shouts"] > 2: normalized_topics.append(topic)
# normalized_topics.append(topic)
topic.stat = topic_stat
normalized_topics.append(topic)
sample_length = min(len(normalized_topics), amount) sample_length = min(len(normalized_topics), amount)
return random.sample(normalized_topics, sample_length) return random.sample(normalized_topics, sample_length)

View File

@ -64,12 +64,6 @@ async def recent_reacted(_, _info, offset, limit):
return ShoutsCache.recent_reacted[offset : offset + limit] return ShoutsCache.recent_reacted[offset : offset + limit]
@mutation.field("viewShout")
async def view_shout(_, _info, slug):
await ViewedStorage.increment(slug)
return {"error": ""}
@query.field("getShoutBySlug") @query.field("getShoutBySlug")
async def get_shout_by_slug(_, info, slug): async def get_shout_by_slug(_, info, slug):
all_fields = [ all_fields = [

View File

@ -157,8 +157,6 @@ type Mutation {
createShout(input: ShoutInput!): Result! createShout(input: ShoutInput!): Result!
updateShout(input: ShoutInput!): Result! updateShout(input: ShoutInput!): Result!
deleteShout(slug: String!): Result! deleteShout(slug: String!): Result!
viewShout(slug: String!): Result!
viewReaction(reaction_id: Int!): Result!
# user profile # user profile
rateUser(slug: String!, value: Int!): Result! rateUser(slug: String!, value: Int!): Result!

View File

@ -1,29 +1,7 @@
import asyncio import asyncio
from datetime import datetime from base.orm import local_session
from enum import Enum as Enumeration from orm.reaction import ReactionKind, Reaction
from services.zine.topics import TopicStorage
from sqlalchemy import Column, DateTime, ForeignKey, Boolean
from sqlalchemy.orm.attributes import flag_modified
from sqlalchemy.types import Enum as ColumnEnum
from base.orm import Base, local_session
from orm.topic import ShoutTopic
class ReactionKind(Enumeration):
AGREE = 1 # +1
DISAGREE = 2 # -1
PROOF = 3 # +1
DISPROOF = 4 # -1
ASK = 5 # +0 bookmark
PROPOSE = 6 # +0
QUOTE = 7 # +0 bookmark
COMMENT = 8 # +0
ACCEPT = 9 # +1
REJECT = 0 # -1
LIKE = 11 # +1
DISLIKE = 12 # -1
# TYPE = <reaction index> # rating diff
def kind_to_rate(kind) -> int: def kind_to_rate(kind) -> int:
@ -45,18 +23,6 @@ def kind_to_rate(kind) -> int:
return 0 return 0
class ReactedByDay(Base):
__tablename__ = "reacted_by_day"
id = None # type: ignore
reaction = Column(ForeignKey("reaction.id"), primary_key=True)
shout = Column(ForeignKey("shout.slug"), primary_key=True)
replyTo = Column(ForeignKey("reaction.id"), nullable=True)
kind = Column(ColumnEnum(ReactionKind), nullable=False, comment="Reaction kind")
day = Column(DateTime, primary_key=True, default=datetime.now)
comment = Column(Boolean, default=False)
class ReactedStorage: class ReactedStorage:
reacted = {"shouts": {}, "topics": {}, "reactions": {}} reacted = {"shouts": {}, "topics": {}, "reactions": {}}
rating = {"shouts": {}, "topics": {}, "reactions": {}} rating = {"shouts": {}, "topics": {}, "reactions": {}}
@ -64,6 +30,7 @@ class ReactedStorage:
to_flush = [] to_flush = []
period = 30 * 60 # sec period = 30 * 60 # sec
lock = asyncio.Lock() lock = asyncio.Lock()
modified_shouts = set([])
@staticmethod @staticmethod
async def get_shout(shout_slug): async def get_shout(shout_slug):
@ -82,7 +49,7 @@ class ReactedStorage:
self = ReactedStorage self = ReactedStorage
async with self.lock: async with self.lock:
return list( return list(
filter(lambda r: r.comment, self.reacted["shouts"].get(shout_slug, {})) filter(lambda r: bool(r.body), self.reacted["shouts"].get(shout_slug, {}))
) )
@staticmethod @staticmethod
@ -90,7 +57,7 @@ class ReactedStorage:
self = ReactedStorage self = ReactedStorage
async with self.lock: async with self.lock:
return list( return list(
filter(lambda r: r.comment, self.reacted["topics"].get(topic_slug, [])) filter(lambda r: bool(r.body), self.reacted["topics"].get(topic_slug, []))
) )
@staticmethod @staticmethod
@ -99,7 +66,7 @@ class ReactedStorage:
async with self.lock: async with self.lock:
return list( return list(
filter( filter(
lambda r: r.comment, self.reacted["reactions"].get(reaction_id, {}) lambda r: bool(r.body), self.reacted["reactions"].get(reaction_id, {})
) )
) )
@ -138,118 +105,62 @@ class ReactedStorage:
@staticmethod @staticmethod
async def react(reaction): async def react(reaction):
ReactedStorage.modified_shouts.add(reaction.shout)
@staticmethod
async def recount(reactions):
self = ReactedStorage self = ReactedStorage
for r in reactions:
async with self.lock: # renew shout counters
reactions = {} self.reacted["shouts"][r.shout] = self.reacted["shouts"].get(r.shout, [])
self.reacted["shouts"][r.shout].append(r)
# iterate sibling reactions # renew topics counters
reactions = self.reacted["shouts"].get(reaction.shout, {}) shout_topics = await TopicStorage.get_topics_by_slugs([r.shout, ])
for r in reactions.values(): for t in shout_topics:
reaction = ReactedByDay.create({ self.reacted["topics"][t] = self.reacted["topics"].get(t, [])
"day": datetime.now().replace( self.reacted["topics"][t].append(r)
hour=0, minute=0, second=0, microsecond=0 self.rating["topics"][t] = \
), self.rating["topics"].get(t, 0) + kind_to_rate(r.kind)
"reaction": r.id, if r.replyTo:
"kind": r.kind, # renew reaction counters
"shout": r.shout, self.reacted["reactions"][r.replyTo] = \
"comment": bool(r.body), self.reacted["reactions"].get(r.replyTo, [])
"replyTo": r.replyTo self.reacted["reactions"][r.replyTo].append(r)
}) self.rating["reactions"][r.replyTo] = \
# renew sorted by shouts store self.rating["reactions"].get(r.replyTo, 0) + kind_to_rate(r.kind)
self.reacted["shouts"][reaction.shout] = self.reacted["shouts"].get(reaction.shout, []) else:
self.reacted["shouts"][reaction.shout].append(reaction) # renew shout rating
if reaction.replyTo: self.rating["shouts"][r.shout] = \
self.reacted["reaction"][reaction.replyTo] = self.reacted[ self.rating["shouts"].get(r.shout, 0) + kind_to_rate(r.kind)
"reactions"
].get(reaction.shout, [])
self.reacted["reaction"][reaction.replyTo].append(reaction)
self.rating["reactions"][reaction.replyTo] = self.rating[
"reactions"
].get(reaction.replyTo, 0) + kind_to_rate(reaction.kind)
else:
# rate only by root reactions on shout
self.rating["shouts"][reaction.replyTo] = self.rating["shouts"].get(
reaction.shout, 0
) + kind_to_rate(reaction.kind)
flag_modified(reaction, "value")
@staticmethod @staticmethod
def init(session): def init(session):
self = ReactedStorage self = ReactedStorage
all_reactions = session.query(ReactedByDay).all() all_reactions = session.query(Reaction).all()
print("[stat.reacted] %d reactions total" % len(all_reactions)) self.modified_shouts = set([r.shout for r in all_reactions])
for reaction in all_reactions: print("[stat.reacted] %d shouts with reactions updates" % len(self.modified_shouts))
shout = reaction.shout
topics = (
session.query(ShoutTopic.topic).where(ShoutTopic.shout == shout).all()
)
kind = reaction.kind
self.reacted["shouts"][shout] = self.reacted["shouts"].get(shout, [])
self.reacted["shouts"][shout].append(reaction)
self.rating["shouts"][shout] = self.rating["shouts"].get(
shout, 0
) + kind_to_rate(kind)
for t in topics:
self.reacted["topics"][t] = self.reacted["topics"].get(t, [])
self.reacted["topics"][t].append(reaction)
self.rating["topics"][t] = self.rating["topics"].get(
t, 0
) + kind_to_rate(
kind
) # rating
if reaction.replyTo:
self.reacted["reactions"][reaction.replyTo] = self.reacted[
"reactions"
].get(reaction.replyTo, [])
self.reacted["reactions"][reaction.replyTo].append(reaction)
self.rating["reactions"][reaction.replyTo] = self.rating[
"reactions"
].get(reaction.replyTo, 0) + kind_to_rate(reaction.kind)
ttt = self.reacted["topics"].values()
print("[stat.reacted] %d topics reacted" % len(ttt))
print("[stat.reacted] %d shouts reacted" % len(self.reacted["shouts"]))
print("[stat.reacted] %d reactions reacted" % len(self.reacted["reactions"]))
@staticmethod @staticmethod
async def flush_changes(session): async def recount_changed(session):
self = ReactedStorage self = ReactedStorage
async with self.lock: async with self.lock:
for slug in dict(self.reacted["shouts"]).keys(): print('[stat.reacted] recounting...')
topics = ( for slug in list(self.modified_shouts):
session.query(ShoutTopic.topic) siblings = session.query(Reaction).where(Reaction.shout == slug).all()
.where(ShoutTopic.shout == slug) await self.recount(siblings)
.all()
) print("[stat.reacted] %d shouts with reactions updates" % len(self.modified_shouts))
reactions = self.reacted["shouts"].get(slug, []) print("[stat.reacted] %d topics reacted" % len(self.reacted["topics"].values()))
# print('[stat.reacted] shout {' + str(slug) + "}: " + str(len(reactions))) print("[stat.reacted] %d shouts reacted" % len(self.reacted["shouts"]))
for ts in list(topics): print("[stat.reacted] %d reactions reacted" % len(self.reacted["reactions"]))
tslug = ts[0] self.modified_shouts = set([])
topic_reactions = self.reacted["topics"].get(tslug, [])
topic_reactions += reactions
# print('[stat.reacted] topic {' + str(tslug) + "}: " + str(len(topic_reactions)))
reactions += list(self.reacted["reactions"].values())
for reaction in reactions:
if getattr(reaction, "modified", False):
session.add(reaction)
flag_modified(reaction, "value")
reaction.modified = False
# print('flushing')
for reaction in self.to_flush:
session.add(reaction)
self.to_flush.clear()
session.commit()
@staticmethod @staticmethod
async def worker(): async def worker():
while True: while True:
try: try:
with local_session() as session: with local_session() as session:
await ReactedStorage().flush_changes(session) await ReactedStorage.recount_changed(session)
print("[stat.reacted] periodical flush")
except Exception as err: except Exception as err:
print("[stat.reacted] errror: %s" % (err)) print("[stat.reacted] recount error %s" % (err))
await asyncio.sleep(ReactedStorage.period) await asyncio.sleep(ReactedStorage.period)

View File

@ -1,17 +1,15 @@
import asyncio import asyncio
from base.orm import local_session from base.orm import local_session
from orm.shout import Shout from orm.shout import Shout, ShoutTopic
from orm.topic import ShoutTopic, TopicFollower from orm.topic import TopicFollower
from services.stat.reacted import ReactedStorage
from services.stat.viewed import ViewedStorage
from services.zine.shoutauthor import ShoutAuthorStorage from services.zine.shoutauthor import ShoutAuthorStorage
class TopicStat: class TopicStat:
shouts_by_topic = {} shouts_by_topic = {} # Shout object stored
authors_by_topic = {} authors_by_topic = {} # User
followers_by_topic = {} followers_by_topic = {} # User
lock = asyncio.Lock() lock = asyncio.Lock()
period = 30 * 60 # sec period = 30 * 60 # sec
@ -19,38 +17,33 @@ class TopicStat:
async def load_stat(session): async def load_stat(session):
self = TopicStat self = TopicStat
shout_topics = session.query(ShoutTopic).all() shout_topics = session.query(ShoutTopic).all()
print("[stat.topics] shout topics amount", len(shout_topics)) print("[stat.topics] shouts linked %d times" % len(shout_topics))
for shout_topic in shout_topics: for shout_topic in shout_topics:
tpc = shout_topic.topic
# shouts by topics # shouts by topics
topic = shout_topic.topic shout = session.query(Shout).where(Shout.slug == shout_topic.shout).first()
shout = shout_topic.shout self.shouts_by_topic[tpc] = self.shouts_by_topic.get(tpc, [])
sss = set(self.shouts_by_topic.get(topic, [])) if shout not in self.shouts_by_topic[tpc]:
shout = session.query(Shout).where(Shout.slug == shout).first() self.shouts_by_topic[tpc].append(shout)
sss.union(
[
shout,
]
)
self.shouts_by_topic[topic] = list(sss)
# authors by topics # authors by topics
authors = await ShoutAuthorStorage.get_authors(shout) authors = await ShoutAuthorStorage.get_authors(shout.slug)
aaa = set(self.authors_by_topic.get(topic, [])) self.authors_by_topic[tpc] = self.authors_by_topic.get(tpc, [])
aaa.union(authors) for a in authors:
self.authors_by_topic[topic] = list(aaa) if a not in self.authors_by_topic[tpc]:
self.authors_by_topic[tpc].append(a)
print("[stat.topics] authors sorted") print("[stat.topics] shouts indexed by %d topics" % len(self.shouts_by_topic.keys()))
print("[stat.topics] shouts sorted") print("[stat.topics] authors indexed by %d topics" % len(self.authors_by_topic.keys()))
self.followers_by_topic = {} self.followers_by_topic = {}
followings = session.query(TopicFollower) followings = session.query(TopicFollower).all()
for flw in followings: for flw in followings:
topic = flw.topic topic = flw.topic
user = flw.follower user = flw.follower
if topic not in self.followers_by_topic: self.followers_by_topic[topic] = self.followers_by_topic.get(topic, [])
self.followers_by_topic[topic] = [] if user not in self.followers_by_topic[topic]:
self.followers_by_topic[topic].append(user) self.followers_by_topic[topic].append(user)
print("[stat.topics] followers sorted") print("[stat.topics] followers sorted")
@staticmethod @staticmethod
@ -59,23 +52,6 @@ class TopicStat:
async with self.lock: async with self.lock:
return self.shouts_by_topic.get(topic, []) return self.shouts_by_topic.get(topic, [])
@staticmethod
async def get_stat(topic):
self = TopicStat
async with self.lock:
shouts = self.shouts_by_topic.get(topic, [])
followers = self.followers_by_topic.get(topic, [])
authors = self.authors_by_topic.get(topic, [])
return {
"shouts": len(shouts),
"authors": len(authors),
"followers": len(followers),
"viewed": await ViewedStorage.get_topic(topic),
"reacted": len(await ReactedStorage.get_topic(topic)),
"commented": len(await ReactedStorage.get_topic_comments(topic)),
"rating": await ReactedStorage.get_topic_rating(topic),
}
@staticmethod @staticmethod
async def worker(): async def worker():
self = TopicStat self = TopicStat
@ -84,7 +60,6 @@ class TopicStat:
with local_session() as session: with local_session() as session:
async with self.lock: async with self.lock:
await self.load_stat(session) await self.load_stat(session)
print("[stat.topics] periodical update")
except Exception as err: except Exception as err:
print("[stat.topics] errror: %s" % (err)) raise Exception(err)
await asyncio.sleep(self.period) await asyncio.sleep(self.period)

View File

@ -1,20 +1,12 @@
import asyncio import asyncio
from datetime import datetime from datetime import datetime
from sqlalchemy import Column, DateTime, ForeignKey, Integer from base.orm import local_session
from sqlalchemy.orm.attributes import flag_modified from sqlalchemy.orm.attributes import flag_modified
from base.orm import Base, local_session from orm.shout import ShoutTopic
from orm.topic import ShoutTopic from orm.viewed import ViewedByDay
class ViewedByDay(Base):
__tablename__ = "viewed_by_day"
id = None
shout = Column(ForeignKey("shout.slug"), primary_key=True)
day = Column(DateTime, primary_key=True, default=datetime.now)
value = Column(Integer)
class ViewedStorage: class ViewedStorage:
@ -47,7 +39,7 @@ class ViewedStorage:
if this_day_view.day < view.day: if this_day_view.day < view.day:
self.this_day_views[shout] = view self.this_day_views[shout] = view
print("[stat.viewed] %d shouts viewed" % len(views)) print("[stat.viewed] %d shouts viewed" % len(self.viewed['shouts']))
@staticmethod @staticmethod
async def get_shout(shout_slug): async def get_shout(shout_slug):
@ -68,7 +60,7 @@ class ViewedStorage:
return self.viewed["reactions"].get(reaction_id, 0) return self.viewed["reactions"].get(reaction_id, 0)
@staticmethod @staticmethod
async def increment(shout_slug): async def increment(shout_slug, amount=1):
self = ViewedStorage self = ViewedStorage
async with self.lock: async with self.lock:
this_day_view = self.this_day_views.get(shout_slug) this_day_view = self.this_day_views.get(shout_slug)
@ -79,11 +71,9 @@ class ViewedStorage:
this_day_view = ViewedByDay.create(shout=shout_slug, value=1) this_day_view = ViewedByDay.create(shout=shout_slug, value=1)
self.this_day_views[shout_slug] = this_day_view self.this_day_views[shout_slug] = this_day_view
else: else:
this_day_view.value = this_day_view.value + 1 this_day_view.value = this_day_view.value + amount
this_day_view.modified = True this_day_view.modified = True
self.viewed["shouts"][shout_slug] = ( self.viewed["shouts"][shout_slug] = (self.viewed["shouts"].get(shout_slug, 0) + amount)
self.viewed["shouts"].get(shout_slug, 0) + 1
)
with local_session() as session: with local_session() as session:
topics = ( topics = (
session.query(ShoutTopic.topic) session.query(ShoutTopic.topic)
@ -91,7 +81,7 @@ class ViewedStorage:
.all() .all()
) )
for t in topics: for t in topics:
self.viewed["topics"][t] = self.viewed["topics"].get(t, 0) + 1 self.viewed["topics"][t] = self.viewed["topics"].get(t, 0) + amount
flag_modified(this_day_view, "value") flag_modified(this_day_view, "value")
@staticmethod @staticmethod

View File

@ -7,13 +7,23 @@ from sqlalchemy.orm import selectinload
from base.orm import local_session from base.orm import local_session
from orm.reaction import Reaction from orm.reaction import Reaction
from orm.shout import Shout, ShoutAuthor, ShoutTopic from orm.shout import Shout, ShoutAuthor, ShoutTopic
from services.stat.viewed import ViewedByDay from services.stat.viewed import ViewedByDay, ViewedStorage
from services.stat.reacted import ReactedStorage
async def get_shout_stat(slug):
return {
"viewed": await ViewedStorage.get_shout(slug),
"reacted": len(await ReactedStorage.get_shout(slug)),
"commented": len(await ReactedStorage.get_comments(slug)),
"rating": await ReactedStorage.get_rating(slug),
}
async def prepare_shouts(session, stmt): async def prepare_shouts(session, stmt):
shouts = [] shouts = []
for s in list(map(lambda r: r.Shout, session.execute(stmt))): for s in list(map(lambda r: r.Shout, session.execute(stmt))):
s.stats = await s.stat s.stat = await get_shout_stat(s.slug)
shouts.append(s) shouts.append(s)
return shouts return shouts
@ -41,10 +51,14 @@ class ShoutsCache:
session, session,
( (
select(Shout) select(Shout)
.options(selectinload(Shout.authors), selectinload(Shout.topics)) .options(
selectinload(Shout.authors),
selectinload(Shout.topics)
)
.where(bool(Shout.publishedAt)) .where(bool(Shout.publishedAt))
.filter(not bool(Shout.deletedAt))
.group_by(Shout.slug) .group_by(Shout.slug)
.order_by(desc("publishedAt")) .order_by(desc(Shout.publishedAt))
.limit(ShoutsCache.limit) .limit(ShoutsCache.limit)
), ),
) )
@ -59,10 +73,13 @@ class ShoutsCache:
session, session,
( (
select(Shout) select(Shout)
.options(selectinload(Shout.authors), selectinload(Shout.topics)) .options(
.where(and_(bool(Shout.publishedAt), bool(Reaction.deletedAt))) selectinload(Shout.authors),
selectinload(Shout.topics)
)
.filter(not bool(Shout.deletedAt))
.group_by(Shout.slug) .group_by(Shout.slug)
.order_by(desc("createdAt")) .order_by(desc(Shout.createdAt))
.limit(ShoutsCache.limit) .limit(ShoutsCache.limit)
), ),
) )
@ -73,22 +90,24 @@ class ShoutsCache:
@staticmethod @staticmethod
async def prepare_recent_reacted(): async def prepare_recent_reacted():
with local_session() as session: with local_session() as session:
reactions = session.query(Reaction).order_by(Reaction.createdAt).limit(ShoutsCache.limit)
reacted_slugs = set([])
for r in reactions:
reacted_slugs.add(r.shout)
shouts = await prepare_shouts( shouts = await prepare_shouts(
session, session,
( (
select( select(Shout)
Shout, func.max(Reaction.createdAt).label("reactionCreatedAt")
)
.options( .options(
selectinload(Shout.authors), selectinload(Shout.authors),
selectinload(Shout.topics), selectinload(Shout.topics),
) )
.join(Reaction, Reaction.shout == Shout.slug) .where(and_(bool(Shout.publishedAt), Shout.slug.in_(list(reacted_slugs))))
.where(and_(bool(Shout.publishedAt), bool(Reaction.deletedAt))) .filter(not bool(Shout.deletedAt))
.group_by(Shout.slug) .group_by(Shout.slug)
.order_by(desc("reactionCreatedAt")) .order_by(Shout.publishedAt)
.limit(ShoutsCache.limit) .limit(ShoutsCache.limit)
), )
) )
async with ShoutsCache.lock: async with ShoutsCache.lock:
ShoutsCache.recent_reacted = shouts ShoutsCache.recent_reacted = shouts
@ -114,7 +133,7 @@ class ShoutsCache:
.limit(ShoutsCache.limit) .limit(ShoutsCache.limit)
), ),
) )
shouts.sort(key=lambda s: s.stats["rating"], reverse=True) shouts.sort(key=lambda s: s.stat["rating"], reverse=True)
async with ShoutsCache.lock: async with ShoutsCache.lock:
print("[zine.cache] %d top shouts " % len(shouts)) print("[zine.cache] %d top shouts " % len(shouts))
ShoutsCache.top_overall = shouts ShoutsCache.top_overall = shouts
@ -135,7 +154,7 @@ class ShoutsCache:
.limit(ShoutsCache.limit) .limit(ShoutsCache.limit)
), ),
) )
shouts.sort(key=lambda s: s.stats["rating"], reverse=True) shouts.sort(key=lambda s: s.stat["rating"], reverse=True)
async with ShoutsCache.lock: async with ShoutsCache.lock:
print("[zine.cache] %d top month shouts " % len(shouts)) print("[zine.cache] %d top month shouts " % len(shouts))
ShoutsCache.top_month = shouts ShoutsCache.top_month = shouts
@ -156,7 +175,7 @@ class ShoutsCache:
.limit(ShoutsCache.limit) .limit(ShoutsCache.limit)
), ),
) )
shouts.sort(key=lambda s: s.stats["commented"], reverse=True) shouts.sort(key=lambda s: s.stat["commented"], reverse=True)
async with ShoutsCache.lock: async with ShoutsCache.lock:
print("[zine.cache] %d top commented shouts " % len(shouts)) print("[zine.cache] %d top commented shouts " % len(shouts))
ShoutsCache.top_viewed = shouts ShoutsCache.top_viewed = shouts
@ -177,7 +196,7 @@ class ShoutsCache:
.limit(ShoutsCache.limit) .limit(ShoutsCache.limit)
), ),
) )
shouts.sort(key=lambda s: s.stats["viewed"], reverse=True) shouts.sort(key=lambda s: s.stat["viewed"], reverse=True)
async with ShoutsCache.lock: async with ShoutsCache.lock:
print("[zine.cache] %d top viewed shouts " % len(shouts)) print("[zine.cache] %d top viewed shouts " % len(shouts))
ShoutsCache.top_viewed = shouts ShoutsCache.top_viewed = shouts
@ -186,14 +205,10 @@ class ShoutsCache:
async def prepare_by_author(): async def prepare_by_author():
shouts_by_author = {} shouts_by_author = {}
with local_session() as session: with local_session() as session:
for a in session.query(ShoutAuthor).all(): for a in session.query(ShoutAuthor).all():
shout = session.query(Shout).filter(Shout.slug == a.shout).first() shout = session.query(Shout).filter(Shout.slug == a.shout).first()
shout.stat = await get_shout_stat(shout.slug)
if not shouts_by_author.get(a.user): shouts_by_author[a.user] = shouts_by_author.get(a.user, [])
shouts_by_author[a.user] = []
if shout not in shouts_by_author[a.user]: if shout not in shouts_by_author[a.user]:
shouts_by_author[a.user].append(shout) shouts_by_author[a.user].append(shout)
async with ShoutsCache.lock: async with ShoutsCache.lock:
@ -204,17 +219,12 @@ class ShoutsCache:
async def prepare_by_topic(): async def prepare_by_topic():
shouts_by_topic = {} shouts_by_topic = {}
with local_session() as session: with local_session() as session:
for a in session.query(ShoutTopic).all():
for t in session.query(ShoutTopic).all(): shout = session.query(Shout).filter(Shout.slug == a.shout).first()
shout.stat = await get_shout_stat(shout.slug)
shout = session.query(Shout).filter(Shout.slug == t.shout).first() shouts_by_topic[a.topic] = shouts_by_topic.get(a.topic, [])
if shout not in shouts_by_topic[a.topic]:
if not shouts_by_topic.get(t.topic): shouts_by_topic[a.topic].append(shout)
shouts_by_topic[t.topic] = []
if shout not in shouts_by_topic[t.topic]:
shouts_by_topic[t.topic].append(shout)
async with ShoutsCache.lock: async with ShoutsCache.lock:
print("[zine.cache] indexed by %d topics " % len(shouts_by_topic.keys())) print("[zine.cache] indexed by %d topics " % len(shouts_by_topic.keys()))
ShoutsCache.by_topic = shouts_by_topic ShoutsCache.by_topic = shouts_by_topic