├── tests ├── conftest.py └── unit │ ├── __init__.py │ ├── helpers │ ├── __init__.py │ └── test_hashers.py │ └── repositories │ └── __init__.py ├── futuramaapi ├── helpers │ ├── __init__.py │ ├── templates.py │ └── pydantic.py ├── mixins │ └── __init__.py ├── middlewares │ ├── __init__.py │ ├── cors.py │ ├── counter.py │ └── secure.py ├── routers │ ├── rest │ │ ├── __init__.py │ │ ├── crypto │ │ │ ├── __init__.py │ │ │ └── api.py │ │ ├── episodes │ │ │ ├── __init__.py │ │ │ ├── schemas.py │ │ │ └── api.py │ │ ├── randoms │ │ │ ├── __init__.py │ │ │ └── api.py │ │ ├── root │ │ │ ├── __init__.py │ │ │ └── schemas.py │ │ ├── seasons │ │ │ ├── __init__.py │ │ │ ├── schemas.py │ │ │ └── api.py │ │ ├── tokens │ │ │ ├── __init__.py │ │ │ ├── dependencies.py │ │ │ ├── api.py │ │ │ └── schemas.py │ │ ├── users │ │ │ ├── __init__.py │ │ │ └── dependencies.py │ │ ├── callbacks │ │ │ ├── __init__.py │ │ │ └── schemas.py │ │ ├── characters │ │ │ ├── __init__.py │ │ │ └── schemas.py │ │ └── notifications │ │ │ ├── __init__.py │ │ │ ├── api.py │ │ │ └── schemas.py │ ├── services │ │ ├── __init__.py │ │ ├── seasons │ │ │ ├── __init__.py │ │ │ ├── list_seasons.py │ │ │ └── get_season.py │ │ └── _base.py │ ├── graphql │ │ ├── __init__.py │ │ ├── dependencies.py │ │ ├── api.py │ │ ├── context.py │ │ ├── validators.py │ │ ├── mixins.py │ │ └── conversion.py │ ├── exceptions.py │ └── __init__.py ├── repositories │ ├── migrations │ │ ├── __init__.py │ │ ├── versions │ │ │ ├── __init__.py │ │ │ ├── d413d1284339_initial_revision.py │ │ │ ├── c03e060df1b8_add_production_code_to_episode.py │ │ │ ├── 1b86ee33d1ba_add_broadcast_number_to_episode.py │ │ │ ├── 928d4358646c_add_image_field.py │ │ │ ├── d7ce6e6090f5_add_requests_counter.py │ │ │ ├── ca664de1bf44_add_system_message.py │ │ │ ├── 2693764b6723_add_secret_message_model.py │ │ │ ├── 81f374066bbf_add_auth_session.py │ │ │ ├── 4d5b68e5d9df_add_links_model.py │ │ │ └── ee5656c8dc7f_define_user_model.py │ │ ├── README │ │ ├── script.py.mako │ │ └── env.py │ ├── __init__.py │ └── session.py ├── __init__.py ├── web_servers │ ├── __init__.py │ └── hypercorn.py ├── __version__.py ├── utils │ ├── __init__.py │ └── _compat.py ├── apps │ └── __init__.py ├── core │ └── __init__.py └── __main__.py ├── static ├── css │ ├── base.css │ └── cookie-banner.css ├── intro-image.jpg ├── img │ ├── alien │ │ ├── drrr.webp │ │ ├── edna.webp │ │ ├── fawn.webp │ │ ├── fnog.webp │ │ ├── jrrr.webp │ │ ├── kug.webp │ │ ├── lrrr.webp │ │ ├── ndnd.webp │ │ ├── thog.webp │ │ ├── yivo.webp │ │ ├── doingg.webp │ │ ├── elzar.webp │ │ ├── flamo.webp │ │ ├── glurmo.webp │ │ ├── hobsy.webp │ │ ├── moivin.webp │ │ ├── morbo.webp │ │ ├── ornik.webp │ │ ├── sandy.webp │ │ ├── alcazar.webp │ │ ├── borax-kid.webp │ │ ├── garglie.webp │ │ ├── melllvar.webp │ │ ├── zoidfarb.webp │ │ ├── admiral-chu.webp │ │ ├── brett-blob.webp │ │ ├── curly_-joe.webp │ │ ├── feffernoose.webp │ │ ├── h_-g_-blob.webp │ │ ├── harold-zoid.webp │ │ ├── kif-kroker.webp │ │ ├── ladybuggle.webp │ │ ├── rock-alien.webp │ │ ├── slurm-queen.webp │ │ ├── space-pope.webp │ │ ├── grand-midwife.webp │ │ ├── lord-nibbler.webp │ │ ├── malachi_-jr_.webp │ │ ├── malachi_-sr_.webp │ │ ├── norm-zoidberg.webp │ │ ├── singing-wind.webp │ │ ├── female-zoidberg.webp │ │ ├── john-a_-zoidberg.webp │ │ ├── norman-zoidberg.webp │ │ ├── princess-num-num.webp │ │ ├── slurms-mackenzie.webp │ │ ├── malachi_-sr__s-wife.webp │ │ ├── melllvar_s-mother.webp │ │ ├── mr_-and-mrs_-kroker.webp │ │ ├── neutral-president.webp │ │ ├── ethan-_bubblegum_-tate.webp │ │ └── mystic-aldermen-of-the-sun.webp │ ├── human │ │ ├── biff.webp │ │ ├── chaz.webp │ │ ├── chu.webp │ │ ├── gus.webp │ │ ├── gwen.webp │ │ ├── ipji.webp │ │ ├── jim.webp │ │ ├── kirk.webp │ │ ├── koji.webp │ │ ├── lou.webp │ │ ├── minx.webp │ │ ├── mom.webp │ │ ├── nina.webp │ │ ├── sal.webp │ │ ├── sam.webp │ │ ├── sean.webp │ │ ├── walt.webp │ │ ├── albert.webp │ │ ├── benny.webp │ │ ├── butch.webp │ │ ├── candy.webp │ │ ├── dixie.webp │ │ ├── farmer.webp │ │ ├── frydo.webp │ │ ├── igner.webp │ │ ├── jeremy.webp │ │ ├── larry.webp │ │ ├── leroy.webp │ │ ├── mugger.webp │ │ ├── nj_rd.webp │ │ ├── raoul.webp │ │ ├── vernon.webp │ │ ├── vogel.webp │ │ ├── 7__-clerk.webp │ │ ├── armando.webp │ │ ├── dandy-jim.webp │ │ ├── darlene.webp │ │ ├── enos-fry.webp │ │ ├── fishy-joe.webp │ │ ├── inez-wong.webp │ │ ├── leo-wong.webp │ │ ├── marianne.webp │ │ ├── mr_-astor.webp │ │ ├── mrs_-fry.webp │ │ ├── petunia.webp │ │ ├── yancy-fry.webp │ │ ├── you-there.webp │ │ ├── andy-warhol.webp │ │ ├── beck_s-head.webp │ │ ├── bill-mcneal.webp │ │ ├── mildred-fry.webp │ │ ├── mr_-conrad.webp │ │ ├── mr_-panucci.webp │ │ ├── mrs_-astor.webp │ │ ├── mrs_-conrad.webp │ │ ├── tude-guard.webp │ │ ├── adlai-atkins.webp │ │ ├── al-gore_s-head.webp │ │ ├── angus-maczongo.webp │ │ ├── australian-man.webp │ │ ├── barack-obama.webp │ │ ├── barbados-slim.webp │ │ ├── beth-jenkins.webp │ │ ├── butch_s-mother.webp │ │ ├── captain-musky.webp │ │ ├── crack-addict.webp │ │ ├── dwight-conrad.webp │ │ ├── hacking-jack.webp │ │ ├── helmut-spargle.webp │ │ ├── hermes-conrad.webp │ │ ├── human-friend.webp │ │ ├── j_-j_-abrams.webp │ │ ├── jack-johnson.webp │ │ ├── jenny-mcneal.webp │ │ ├── john-jackson.webp │ │ ├── langdon-cobb.webp │ │ ├── lars-fillmore.webp │ │ ├── lauren-cahill.webp │ │ ├── morgan-proctor.webp │ │ ├── ned-farnsworth.webp │ │ ├── number-9-man.webp │ │ ├── philip-j_-fry.webp │ │ ├── randy-munchnik.webp │ │ ├── turanga-leela.webp │ │ ├── yancy-fry_-sr_.webp │ │ ├── zapp-brannigan.webp │ │ ├── abner-doubledeal.webp │ │ ├── amy-wong-kroker.webp │ │ ├── bob-dole_s-head.webp │ │ ├── boobs-vanderbilt.webp │ │ ├── david-farnsworth.webp │ │ ├── elizabeth-bennet.webp │ │ ├── floyd-farnsworth.webp │ │ ├── frida-waterfall.webp │ │ ├── gary-_season-2_.webp │ │ ├── hank-aaron-xxiv.webp │ │ ├── hattie-mcdoogal.webp │ │ ├── head-of-the-aclu.webp │ │ ├── hutch-waterfall.webp │ │ ├── jackie-anderson.webp │ │ ├── joe-_defrostee_.webp │ │ ├── kate-moss_s-head.webp │ │ ├── labarbara-conrad.webp │ │ ├── lucy-liu_s-head.webp │ │ ├── michelle-jenkins.webp │ │ ├── ogden-wernstrom.webp │ │ ├── philip-j_-fry-ii.webp │ │ ├── tex_-connecticut.webp │ │ ├── velma-farnsworth.webp │ │ ├── 21st-century-woman.webp │ │ ├── beastie-boys_-heads.webp │ │ ├── bill-clinton_s-head.webp │ │ ├── bob-barker_s-head.webp │ │ ├── bob-uecker_s-head.webp │ │ ├── butch_s-girlfriend.webp │ │ ├── charles-constantine.webp │ │ ├── chester-z_-arthur.webp │ │ ├── colleen-o_hallahan.webp │ │ ├── conspiracy-nutter.webp │ │ ├── dick-cheney_s-head.webp │ │ ├── dick-clark_s-head.webp │ │ ├── dr_-schlovinowitz.webp │ │ ├── eric-cartman_s-head.webp │ │ ├── free-waterfall-iii.webp │ │ ├── free-waterfall_-jr_.webp │ │ ├── free-waterfall_-sr_.webp │ │ ├── gerald-ford_s-head.webp │ │ ├── hank-aaron_s-head.webp │ │ ├── heidi-klum_s-head.webp │ │ ├── hermes-conrad_s-fan.webp │ │ ├── jimmy-carter_s-head.webp │ │ ├── joan-rivers_-head.webp │ │ ├── joe-_nation-of-joe_.webp │ │ ├── leonardo-da-vinci.webp │ │ ├── old-man-waterfall.webp │ │ ├── orson-welles_-head.webp │ │ ├── rich-little_s-head.webp │ │ ├── rob-reiner_s-head.webp │ │ ├── ron-jeremy_s-head.webp │ │ ├── ron-popeil_s-head.webp │ │ ├── ross-perot_s-head.webp │ │ ├── snoop-dogg_s-head.webp │ │ ├── the-little-prince.webp │ │ ├── traci-lords_-head.webp │ │ ├── andrew-jackson_s-head.webp │ │ ├── antonin-scalia_s-head.webp │ │ ├── billy-crystal_s-head.webp │ │ ├── cindy-crawford_s-head.webp │ │ ├── conan-o_brien_s-head.webp │ │ ├── crack-mansion-butler.webp │ │ ├── cubert-j_-farnsworth.webp │ │ ├── david-duchovny_s-head.webp │ │ ├── david-x_-cohen_s-head.webp │ │ ├── elvis-presley_s-head.webp │ │ ├── george-foreman_s-head.webp │ │ ├── herbert-hoover_s-head.webp │ │ ├── homer-simpson_s-head.webp │ │ ├── hubert-j_-farnsworth.webp │ │ ├── jonathan-frakes_-head.webp │ │ ├── laetitia-casta_s-head.webp │ │ ├── leonard-nimoy_s-head.webp │ │ ├── linda-van-schoonhoven.webp │ │ ├── martha-stewart_s-head.webp │ │ ├── matt-groening_s-head.webp │ │ ├── penn-jillette_s-head.webp │ │ ├── rebecca-romijn_s-head.webp │ │ ├── robert-wagner_s-head.webp │ │ ├── ronald-reagan_s-head.webp │ │ ├── samuel-genital_s-head.webp │ │ ├── scruffy-scruffington.webp │ │ ├── sergio-aragon_s_-head.webp │ │ ├── walter-mondale_s-head.webp │ │ ├── abraham-lincoln_s-head.webp │ │ ├── benjamin-harrison_s-head.webp │ │ ├── buzz-aldrin-_character_.webp │ │ ├── c_-randall-poopenmeyer.webp │ │ ├── charles-de-gaulle_s-head.webp │ │ ├── chester-a_-arthur_s-head.webp │ │ ├── claudia-schiffer_s-head.webp │ │ ├── deforest-kelley_s-head.webp │ │ ├── elizabeth-taylor_s-head.webp │ │ ├── gary-gygax-_character_.webp │ │ ├── george-h_-w_-bush_s-head.webp │ │ ├── george-washington_s-head.webp │ │ ├── grover-cleveland_s-head.webp │ │ ├── harry-s_-truman_s-head.webp │ │ ├── henry-kissinger_s-head.webp │ │ ├── jill-big-breasts_-head.webp │ │ ├── leonardo-dicaprio_s-head.webp │ │ ├── long-dong-silver_s-head.webp │ │ ├── martin-van-buren_s-head.webp │ │ ├── pamela-anderson_s-head.webp │ │ ├── richard-m_-nixon_s-head.webp │ │ ├── thomas-jefferson_s-head.webp │ │ ├── william-shatner_s-head.webp │ │ ├── theodore-roosevelt_s-head.webp │ │ ├── william-howard-taft_s-head.webp │ │ ├── headless-body-of-spiro-agnew.webp │ │ ├── nichelle-nichols-_character_.webp │ │ └── hubert-j_-farnsworth_s-girlfriend.webp │ ├── robot │ │ ├── bev.webp │ │ ├── boxy.webp │ │ ├── fan.webp │ │ ├── izac.webp │ │ ├── lisa.webp │ │ ├── oily.webp │ │ ├── url.webp │ │ ├── andrew.webp │ │ ├── basil.webp │ │ ├── bella.webp │ │ ├── donbot.webp │ │ ├── fanny.webp │ │ ├── fatbot.webp │ │ ├── fender.webp │ │ ├── flexo.webp │ │ ├── frybot.webp │ │ ├── ihawk.webp │ │ ├── liubot.webp │ │ ├── 7__-robot.webp │ │ ├── alphabot.webp │ │ ├── angleyne.webp │ │ ├── betabot.webp │ │ ├── calculon.webp │ │ ├── enemabot.webp │ │ ├── femputer.webp │ │ ├── foreigner.webp │ │ ├── frankie.webp │ │ ├── gammabot.webp │ │ ├── gearshift.webp │ │ ├── judge-723.webp │ │ ├── judge-724.webp │ │ ├── judge-802.webp │ │ ├── keg-robot.webp │ │ ├── leelabot.webp │ │ ├── monique.webp │ │ ├── pickles.webp │ │ ├── roberto.webp │ │ ├── robot-1x.webp │ │ ├── tiny-tim.webp │ │ ├── unit-2013.webp │ │ ├── vladimir.webp │ │ ├── animatronio.webp │ │ ├── crushinator.webp │ │ ├── destructor.webp │ │ ├── hair-robot.webp │ │ ├── hedonismbot.webp │ │ ├── kwanzaabot.webp │ │ ├── lulubelle-7.webp │ │ ├── preacherbot.webp │ │ ├── robot-devil.webp │ │ ├── sinclair-2k.webp │ │ ├── ben-rodr_guez.webp │ │ ├── billionairebot.webp │ │ ├── cartridge-unit.webp │ │ ├── chain-smoker.webp │ │ ├── daisy-mae-128k.webp │ │ ├── dr_-perceptron.webp │ │ ├── emotitron_-jr_.webp │ │ ├── gorgeous-gonks.webp │ │ ├── humorbot-5_0.webp │ │ ├── joey-mousepad.webp │ │ ├── nurse-ratchet.webp │ │ ├── parts-hilton.webp │ │ ├── charlotte-widnar.webp │ │ ├── emperor-nikolai.webp │ │ ├── macaulay-culckon.webp │ │ ├── mad-hatter-robot.webp │ │ ├── the-clearcutter.webp │ │ ├── the-robot-elders.webp │ │ ├── abraham-lincolnbot.webp │ │ ├── countess-de-la-roca.webp │ │ ├── human-_character_.webp │ │ ├── master-of-the-hunt.webp │ │ ├── robot-santa-claus.webp │ │ ├── antonio-calculon_-jr_.webp │ │ ├── comrade-greeting-card.webp │ │ ├── cymbal-banging-monkey.webp │ │ ├── francis-x_-clampazzo.webp │ │ ├── malfunctioning-eddie.webp │ │ ├── bender-bending-rodr_guez.webp │ │ ├── billy-west-_character_.webp │ │ ├── sergeant-feces-processor.webp │ │ ├── john-quincy-adding-machine.webp │ │ └── bender-bending-rodr_guez_s-first-born-son.webp │ ├── mutant │ │ ├── armo.webp │ │ ├── lazar.webp │ │ ├── mandy.webp │ │ ├── moose.webp │ │ ├── sally.webp │ │ ├── dwayne.webp │ │ ├── grotrian.webp │ │ ├── thorias.webp │ │ ├── virginia.webp │ │ ├── vyolet.webp │ │ ├── arachneon.webp │ │ ├── fly-mutant.webp │ │ ├── andy-goldman.webp │ │ ├── el-chupanibre.webp │ │ ├── turanga-munda.webp │ │ ├── munda_s-mother.webp │ │ ├── turanga-morris.webp │ │ └── munda_s-grandmother.webp │ ├── unknown │ │ ├── doug.webp │ │ ├── paco.webp │ │ ├── yuri.webp │ │ ├── ab-bot.webp │ │ ├── jezabel.webp │ │ ├── tarquin.webp │ │ ├── yanos.webp │ │ ├── adam-west.webp │ │ ├── fabricio.webp │ │ ├── robot-fox.webp │ │ ├── big-caboose.webp │ │ ├── craterface.webp │ │ ├── farmer-bot.webp │ │ ├── george-takei.webp │ │ ├── hermes-bot.webp │ │ ├── human-horn.webp │ │ ├── mecha-hermes.webp │ │ ├── mouth-mutant.webp │ │ ├── roberto-v2_0.webp │ │ ├── sam-zoidberg.webp │ │ ├── grunka-lunkas.webp │ │ ├── professor-bot.webp │ │ ├── suspendington.webp │ │ ├── walter-koenig.webp │ │ ├── bender-duplicates.webp │ │ ├── colonel-_mutant_.webp │ │ ├── stephen-hawking.webp │ │ ├── army-of-the-damned.webp │ │ └── 147573952589676412927.webp │ ├── monster │ │ ├── bigfoot.webp │ │ ├── pazuzu.webp │ │ ├── umbriel.webp │ │ ├── mr_-peppy.webp │ │ ├── tritonian-yeti.webp │ │ ├── colonel-_merman_.webp │ │ ├── loch-ness-monster.webp │ │ └── giant-unattractive-monster.webp │ └── head │ │ ├── checkers_-head.webp │ │ └── planet-express-ship.webp └── js │ └── cookie-banner.js ├── favicon.ico ├── docker-entrypoint.sh ├── robots.txt ├── Makefile ├── .github └── workflows │ ├── deploy.yml │ └── test.yml ├── .pre-commit-config.yaml ├── templates ├── auth.html ├── password_change.html └── index.html ├── .env.template ├── .env.test ├── Dockerfile ├── .gitignore ├── alembic.ini └── README.md /tests/conftest.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /futuramaapi/helpers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /futuramaapi/mixins/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/helpers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /futuramaapi/middlewares/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /futuramaapi/routers/rest/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/repositories/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /futuramaapi/routers/services/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /futuramaapi/repositories/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /futuramaapi/routers/services/seasons/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /futuramaapi/repositories/migrations/versions/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/css/base.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin:0; 3 | padding:0; 4 | } 5 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/favicon.ico -------------------------------------------------------------------------------- /futuramaapi/__init__.py: -------------------------------------------------------------------------------- 1 | from .apps import app 2 | 3 | __all__ = [ 4 | "app", 5 | ] 6 | -------------------------------------------------------------------------------- /static/intro-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/intro-image.jpg -------------------------------------------------------------------------------- /futuramaapi/repositories/migrations/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration with an async dbapi. 2 | -------------------------------------------------------------------------------- /futuramaapi/web_servers/__init__.py: -------------------------------------------------------------------------------- 1 | from .hypercorn import run 2 | 3 | __all__ = [ 4 | "run", 5 | ] 6 | -------------------------------------------------------------------------------- /static/img/alien/drrr.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/drrr.webp -------------------------------------------------------------------------------- /static/img/alien/edna.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/edna.webp -------------------------------------------------------------------------------- /static/img/alien/fawn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/fawn.webp -------------------------------------------------------------------------------- /static/img/alien/fnog.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/fnog.webp -------------------------------------------------------------------------------- /static/img/alien/jrrr.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/jrrr.webp -------------------------------------------------------------------------------- /static/img/alien/kug.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/kug.webp -------------------------------------------------------------------------------- /static/img/alien/lrrr.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/lrrr.webp -------------------------------------------------------------------------------- /static/img/alien/ndnd.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/ndnd.webp -------------------------------------------------------------------------------- /static/img/alien/thog.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/thog.webp -------------------------------------------------------------------------------- /static/img/alien/yivo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/yivo.webp -------------------------------------------------------------------------------- /static/img/human/biff.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/biff.webp -------------------------------------------------------------------------------- /static/img/human/chaz.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/chaz.webp -------------------------------------------------------------------------------- /static/img/human/chu.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/chu.webp -------------------------------------------------------------------------------- /static/img/human/gus.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/gus.webp -------------------------------------------------------------------------------- /static/img/human/gwen.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/gwen.webp -------------------------------------------------------------------------------- /static/img/human/ipji.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/ipji.webp -------------------------------------------------------------------------------- /static/img/human/jim.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/jim.webp -------------------------------------------------------------------------------- /static/img/human/kirk.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/kirk.webp -------------------------------------------------------------------------------- /static/img/human/koji.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/koji.webp -------------------------------------------------------------------------------- /static/img/human/lou.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/lou.webp -------------------------------------------------------------------------------- /static/img/human/minx.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/minx.webp -------------------------------------------------------------------------------- /static/img/human/mom.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/mom.webp -------------------------------------------------------------------------------- /static/img/human/nina.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/nina.webp -------------------------------------------------------------------------------- /static/img/human/sal.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/sal.webp -------------------------------------------------------------------------------- /static/img/human/sam.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/sam.webp -------------------------------------------------------------------------------- /static/img/human/sean.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/sean.webp -------------------------------------------------------------------------------- /static/img/human/walt.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/walt.webp -------------------------------------------------------------------------------- /static/img/robot/bev.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/bev.webp -------------------------------------------------------------------------------- /static/img/robot/boxy.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/boxy.webp -------------------------------------------------------------------------------- /static/img/robot/fan.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/fan.webp -------------------------------------------------------------------------------- /static/img/robot/izac.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/izac.webp -------------------------------------------------------------------------------- /static/img/robot/lisa.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/lisa.webp -------------------------------------------------------------------------------- /static/img/robot/oily.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/oily.webp -------------------------------------------------------------------------------- /static/img/robot/url.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/url.webp -------------------------------------------------------------------------------- /futuramaapi/__version__.py: -------------------------------------------------------------------------------- 1 | from futuramaapi.utils import metadata 2 | 3 | __version__ = metadata["version"] 4 | -------------------------------------------------------------------------------- /futuramaapi/routers/graphql/__init__.py: -------------------------------------------------------------------------------- 1 | from .api import router 2 | 3 | __all__ = [ 4 | "router", 5 | ] 6 | -------------------------------------------------------------------------------- /static/img/alien/doingg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/doingg.webp -------------------------------------------------------------------------------- /static/img/alien/elzar.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/elzar.webp -------------------------------------------------------------------------------- /static/img/alien/flamo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/flamo.webp -------------------------------------------------------------------------------- /static/img/alien/glurmo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/glurmo.webp -------------------------------------------------------------------------------- /static/img/alien/hobsy.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/hobsy.webp -------------------------------------------------------------------------------- /static/img/alien/moivin.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/moivin.webp -------------------------------------------------------------------------------- /static/img/alien/morbo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/morbo.webp -------------------------------------------------------------------------------- /static/img/alien/ornik.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/ornik.webp -------------------------------------------------------------------------------- /static/img/alien/sandy.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/sandy.webp -------------------------------------------------------------------------------- /static/img/human/albert.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/albert.webp -------------------------------------------------------------------------------- /static/img/human/benny.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/benny.webp -------------------------------------------------------------------------------- /static/img/human/butch.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/butch.webp -------------------------------------------------------------------------------- /static/img/human/candy.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/candy.webp -------------------------------------------------------------------------------- /static/img/human/dixie.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/dixie.webp -------------------------------------------------------------------------------- /static/img/human/farmer.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/farmer.webp -------------------------------------------------------------------------------- /static/img/human/frydo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/frydo.webp -------------------------------------------------------------------------------- /static/img/human/igner.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/igner.webp -------------------------------------------------------------------------------- /static/img/human/jeremy.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/jeremy.webp -------------------------------------------------------------------------------- /static/img/human/larry.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/larry.webp -------------------------------------------------------------------------------- /static/img/human/leroy.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/leroy.webp -------------------------------------------------------------------------------- /static/img/human/mugger.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/mugger.webp -------------------------------------------------------------------------------- /static/img/human/nj_rd.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/nj_rd.webp -------------------------------------------------------------------------------- /static/img/human/raoul.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/raoul.webp -------------------------------------------------------------------------------- /static/img/human/vernon.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/vernon.webp -------------------------------------------------------------------------------- /static/img/human/vogel.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/vogel.webp -------------------------------------------------------------------------------- /static/img/mutant/armo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/mutant/armo.webp -------------------------------------------------------------------------------- /static/img/mutant/lazar.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/mutant/lazar.webp -------------------------------------------------------------------------------- /static/img/mutant/mandy.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/mutant/mandy.webp -------------------------------------------------------------------------------- /static/img/mutant/moose.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/mutant/moose.webp -------------------------------------------------------------------------------- /static/img/mutant/sally.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/mutant/sally.webp -------------------------------------------------------------------------------- /static/img/robot/andrew.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/andrew.webp -------------------------------------------------------------------------------- /static/img/robot/basil.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/basil.webp -------------------------------------------------------------------------------- /static/img/robot/bella.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/bella.webp -------------------------------------------------------------------------------- /static/img/robot/donbot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/donbot.webp -------------------------------------------------------------------------------- /static/img/robot/fanny.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/fanny.webp -------------------------------------------------------------------------------- /static/img/robot/fatbot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/fatbot.webp -------------------------------------------------------------------------------- /static/img/robot/fender.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/fender.webp -------------------------------------------------------------------------------- /static/img/robot/flexo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/flexo.webp -------------------------------------------------------------------------------- /static/img/robot/frybot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/frybot.webp -------------------------------------------------------------------------------- /static/img/robot/ihawk.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/ihawk.webp -------------------------------------------------------------------------------- /static/img/robot/liubot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/liubot.webp -------------------------------------------------------------------------------- /static/img/unknown/doug.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/doug.webp -------------------------------------------------------------------------------- /static/img/unknown/paco.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/paco.webp -------------------------------------------------------------------------------- /static/img/unknown/yuri.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/yuri.webp -------------------------------------------------------------------------------- /futuramaapi/routers/rest/crypto/__init__.py: -------------------------------------------------------------------------------- 1 | from .api import router 2 | 3 | __all__ = [ 4 | "router", 5 | ] 6 | -------------------------------------------------------------------------------- /futuramaapi/routers/rest/episodes/__init__.py: -------------------------------------------------------------------------------- 1 | from .api import router 2 | 3 | __all__ = [ 4 | "router", 5 | ] 6 | -------------------------------------------------------------------------------- /futuramaapi/routers/rest/randoms/__init__.py: -------------------------------------------------------------------------------- 1 | from .api import router 2 | 3 | __all__ = [ 4 | "router", 5 | ] 6 | -------------------------------------------------------------------------------- /futuramaapi/routers/rest/root/__init__.py: -------------------------------------------------------------------------------- 1 | from .api import router 2 | 3 | __all__ = [ 4 | "router", 5 | ] 6 | -------------------------------------------------------------------------------- /futuramaapi/routers/rest/seasons/__init__.py: -------------------------------------------------------------------------------- 1 | from .api import router 2 | 3 | __all__ = [ 4 | "router", 5 | ] 6 | -------------------------------------------------------------------------------- /futuramaapi/routers/rest/tokens/__init__.py: -------------------------------------------------------------------------------- 1 | from .api import router 2 | 3 | __all__ = [ 4 | "router", 5 | ] 6 | -------------------------------------------------------------------------------- /futuramaapi/routers/rest/users/__init__.py: -------------------------------------------------------------------------------- 1 | from .api import router 2 | 3 | __all__ = [ 4 | "router", 5 | ] 6 | -------------------------------------------------------------------------------- /static/img/alien/alcazar.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/alcazar.webp -------------------------------------------------------------------------------- /static/img/alien/borax-kid.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/borax-kid.webp -------------------------------------------------------------------------------- /static/img/alien/garglie.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/garglie.webp -------------------------------------------------------------------------------- /static/img/alien/melllvar.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/melllvar.webp -------------------------------------------------------------------------------- /static/img/alien/zoidfarb.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/zoidfarb.webp -------------------------------------------------------------------------------- /static/img/human/7__-clerk.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/7__-clerk.webp -------------------------------------------------------------------------------- /static/img/human/armando.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/armando.webp -------------------------------------------------------------------------------- /static/img/human/dandy-jim.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/dandy-jim.webp -------------------------------------------------------------------------------- /static/img/human/darlene.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/darlene.webp -------------------------------------------------------------------------------- /static/img/human/enos-fry.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/enos-fry.webp -------------------------------------------------------------------------------- /static/img/human/fishy-joe.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/fishy-joe.webp -------------------------------------------------------------------------------- /static/img/human/inez-wong.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/inez-wong.webp -------------------------------------------------------------------------------- /static/img/human/leo-wong.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/leo-wong.webp -------------------------------------------------------------------------------- /static/img/human/marianne.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/marianne.webp -------------------------------------------------------------------------------- /static/img/human/mr_-astor.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/mr_-astor.webp -------------------------------------------------------------------------------- /static/img/human/mrs_-fry.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/mrs_-fry.webp -------------------------------------------------------------------------------- /static/img/human/petunia.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/petunia.webp -------------------------------------------------------------------------------- /static/img/human/yancy-fry.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/yancy-fry.webp -------------------------------------------------------------------------------- /static/img/human/you-there.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/you-there.webp -------------------------------------------------------------------------------- /static/img/monster/bigfoot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/monster/bigfoot.webp -------------------------------------------------------------------------------- /static/img/monster/pazuzu.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/monster/pazuzu.webp -------------------------------------------------------------------------------- /static/img/monster/umbriel.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/monster/umbriel.webp -------------------------------------------------------------------------------- /static/img/mutant/dwayne.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/mutant/dwayne.webp -------------------------------------------------------------------------------- /static/img/mutant/grotrian.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/mutant/grotrian.webp -------------------------------------------------------------------------------- /static/img/mutant/thorias.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/mutant/thorias.webp -------------------------------------------------------------------------------- /static/img/mutant/virginia.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/mutant/virginia.webp -------------------------------------------------------------------------------- /static/img/mutant/vyolet.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/mutant/vyolet.webp -------------------------------------------------------------------------------- /static/img/robot/7__-robot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/7__-robot.webp -------------------------------------------------------------------------------- /static/img/robot/alphabot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/alphabot.webp -------------------------------------------------------------------------------- /static/img/robot/angleyne.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/angleyne.webp -------------------------------------------------------------------------------- /static/img/robot/betabot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/betabot.webp -------------------------------------------------------------------------------- /static/img/robot/calculon.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/calculon.webp -------------------------------------------------------------------------------- /static/img/robot/enemabot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/enemabot.webp -------------------------------------------------------------------------------- /static/img/robot/femputer.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/femputer.webp -------------------------------------------------------------------------------- /static/img/robot/foreigner.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/foreigner.webp -------------------------------------------------------------------------------- /static/img/robot/frankie.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/frankie.webp -------------------------------------------------------------------------------- /static/img/robot/gammabot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/gammabot.webp -------------------------------------------------------------------------------- /static/img/robot/gearshift.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/gearshift.webp -------------------------------------------------------------------------------- /static/img/robot/judge-723.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/judge-723.webp -------------------------------------------------------------------------------- /static/img/robot/judge-724.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/judge-724.webp -------------------------------------------------------------------------------- /static/img/robot/judge-802.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/judge-802.webp -------------------------------------------------------------------------------- /static/img/robot/keg-robot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/keg-robot.webp -------------------------------------------------------------------------------- /static/img/robot/leelabot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/leelabot.webp -------------------------------------------------------------------------------- /static/img/robot/monique.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/monique.webp -------------------------------------------------------------------------------- /static/img/robot/pickles.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/pickles.webp -------------------------------------------------------------------------------- /static/img/robot/roberto.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/roberto.webp -------------------------------------------------------------------------------- /static/img/robot/robot-1x.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/robot-1x.webp -------------------------------------------------------------------------------- /static/img/robot/tiny-tim.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/tiny-tim.webp -------------------------------------------------------------------------------- /static/img/robot/unit-2013.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/unit-2013.webp -------------------------------------------------------------------------------- /static/img/robot/vladimir.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/vladimir.webp -------------------------------------------------------------------------------- /static/img/unknown/ab-bot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/ab-bot.webp -------------------------------------------------------------------------------- /static/img/unknown/jezabel.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/jezabel.webp -------------------------------------------------------------------------------- /static/img/unknown/tarquin.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/tarquin.webp -------------------------------------------------------------------------------- /static/img/unknown/yanos.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/yanos.webp -------------------------------------------------------------------------------- /futuramaapi/routers/rest/callbacks/__init__.py: -------------------------------------------------------------------------------- 1 | from .api import router 2 | 3 | __all__ = [ 4 | "router", 5 | ] 6 | -------------------------------------------------------------------------------- /futuramaapi/routers/rest/characters/__init__.py: -------------------------------------------------------------------------------- 1 | from .api import router 2 | 3 | __all__ = [ 4 | "router", 5 | ] 6 | -------------------------------------------------------------------------------- /futuramaapi/routers/rest/notifications/__init__.py: -------------------------------------------------------------------------------- 1 | from .api import router 2 | 3 | __all__ = [ 4 | "router", 5 | ] 6 | -------------------------------------------------------------------------------- /static/img/alien/admiral-chu.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/admiral-chu.webp -------------------------------------------------------------------------------- /static/img/alien/brett-blob.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/brett-blob.webp -------------------------------------------------------------------------------- /static/img/alien/curly_-joe.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/curly_-joe.webp -------------------------------------------------------------------------------- /static/img/alien/feffernoose.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/feffernoose.webp -------------------------------------------------------------------------------- /static/img/alien/h_-g_-blob.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/h_-g_-blob.webp -------------------------------------------------------------------------------- /static/img/alien/harold-zoid.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/harold-zoid.webp -------------------------------------------------------------------------------- /static/img/alien/kif-kroker.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/kif-kroker.webp -------------------------------------------------------------------------------- /static/img/alien/ladybuggle.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/ladybuggle.webp -------------------------------------------------------------------------------- /static/img/alien/rock-alien.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/rock-alien.webp -------------------------------------------------------------------------------- /static/img/alien/slurm-queen.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/slurm-queen.webp -------------------------------------------------------------------------------- /static/img/alien/space-pope.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/space-pope.webp -------------------------------------------------------------------------------- /static/img/human/andy-warhol.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/andy-warhol.webp -------------------------------------------------------------------------------- /static/img/human/beck_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/beck_s-head.webp -------------------------------------------------------------------------------- /static/img/human/bill-mcneal.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/bill-mcneal.webp -------------------------------------------------------------------------------- /static/img/human/mildred-fry.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/mildred-fry.webp -------------------------------------------------------------------------------- /static/img/human/mr_-conrad.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/mr_-conrad.webp -------------------------------------------------------------------------------- /static/img/human/mr_-panucci.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/mr_-panucci.webp -------------------------------------------------------------------------------- /static/img/human/mrs_-astor.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/mrs_-astor.webp -------------------------------------------------------------------------------- /static/img/human/mrs_-conrad.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/mrs_-conrad.webp -------------------------------------------------------------------------------- /static/img/human/tude-guard.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/tude-guard.webp -------------------------------------------------------------------------------- /static/img/monster/mr_-peppy.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/monster/mr_-peppy.webp -------------------------------------------------------------------------------- /static/img/mutant/arachneon.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/mutant/arachneon.webp -------------------------------------------------------------------------------- /static/img/mutant/fly-mutant.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/mutant/fly-mutant.webp -------------------------------------------------------------------------------- /static/img/robot/animatronio.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/animatronio.webp -------------------------------------------------------------------------------- /static/img/robot/crushinator.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/crushinator.webp -------------------------------------------------------------------------------- /static/img/robot/destructor.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/destructor.webp -------------------------------------------------------------------------------- /static/img/robot/hair-robot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/hair-robot.webp -------------------------------------------------------------------------------- /static/img/robot/hedonismbot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/hedonismbot.webp -------------------------------------------------------------------------------- /static/img/robot/kwanzaabot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/kwanzaabot.webp -------------------------------------------------------------------------------- /static/img/robot/lulubelle-7.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/lulubelle-7.webp -------------------------------------------------------------------------------- /static/img/robot/preacherbot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/preacherbot.webp -------------------------------------------------------------------------------- /static/img/robot/robot-devil.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/robot-devil.webp -------------------------------------------------------------------------------- /static/img/robot/sinclair-2k.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/sinclair-2k.webp -------------------------------------------------------------------------------- /static/img/unknown/adam-west.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/adam-west.webp -------------------------------------------------------------------------------- /static/img/unknown/fabricio.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/fabricio.webp -------------------------------------------------------------------------------- /static/img/unknown/robot-fox.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/robot-fox.webp -------------------------------------------------------------------------------- /static/img/alien/grand-midwife.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/grand-midwife.webp -------------------------------------------------------------------------------- /static/img/alien/lord-nibbler.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/lord-nibbler.webp -------------------------------------------------------------------------------- /static/img/alien/malachi_-jr_.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/malachi_-jr_.webp -------------------------------------------------------------------------------- /static/img/alien/malachi_-sr_.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/malachi_-sr_.webp -------------------------------------------------------------------------------- /static/img/alien/norm-zoidberg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/norm-zoidberg.webp -------------------------------------------------------------------------------- /static/img/alien/singing-wind.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/singing-wind.webp -------------------------------------------------------------------------------- /static/img/head/checkers_-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/head/checkers_-head.webp -------------------------------------------------------------------------------- /static/img/human/adlai-atkins.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/adlai-atkins.webp -------------------------------------------------------------------------------- /static/img/human/al-gore_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/al-gore_s-head.webp -------------------------------------------------------------------------------- /static/img/human/angus-maczongo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/angus-maczongo.webp -------------------------------------------------------------------------------- /static/img/human/australian-man.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/australian-man.webp -------------------------------------------------------------------------------- /static/img/human/barack-obama.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/barack-obama.webp -------------------------------------------------------------------------------- /static/img/human/barbados-slim.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/barbados-slim.webp -------------------------------------------------------------------------------- /static/img/human/beth-jenkins.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/beth-jenkins.webp -------------------------------------------------------------------------------- /static/img/human/butch_s-mother.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/butch_s-mother.webp -------------------------------------------------------------------------------- /static/img/human/captain-musky.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/captain-musky.webp -------------------------------------------------------------------------------- /static/img/human/crack-addict.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/crack-addict.webp -------------------------------------------------------------------------------- /static/img/human/dwight-conrad.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/dwight-conrad.webp -------------------------------------------------------------------------------- /static/img/human/hacking-jack.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/hacking-jack.webp -------------------------------------------------------------------------------- /static/img/human/helmut-spargle.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/helmut-spargle.webp -------------------------------------------------------------------------------- /static/img/human/hermes-conrad.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/hermes-conrad.webp -------------------------------------------------------------------------------- /static/img/human/human-friend.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/human-friend.webp -------------------------------------------------------------------------------- /static/img/human/j_-j_-abrams.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/j_-j_-abrams.webp -------------------------------------------------------------------------------- /static/img/human/jack-johnson.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/jack-johnson.webp -------------------------------------------------------------------------------- /static/img/human/jenny-mcneal.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/jenny-mcneal.webp -------------------------------------------------------------------------------- /static/img/human/john-jackson.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/john-jackson.webp -------------------------------------------------------------------------------- /static/img/human/langdon-cobb.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/langdon-cobb.webp -------------------------------------------------------------------------------- /static/img/human/lars-fillmore.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/lars-fillmore.webp -------------------------------------------------------------------------------- /static/img/human/lauren-cahill.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/lauren-cahill.webp -------------------------------------------------------------------------------- /static/img/human/morgan-proctor.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/morgan-proctor.webp -------------------------------------------------------------------------------- /static/img/human/ned-farnsworth.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/ned-farnsworth.webp -------------------------------------------------------------------------------- /static/img/human/number-9-man.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/number-9-man.webp -------------------------------------------------------------------------------- /static/img/human/philip-j_-fry.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/philip-j_-fry.webp -------------------------------------------------------------------------------- /static/img/human/randy-munchnik.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/randy-munchnik.webp -------------------------------------------------------------------------------- /static/img/human/turanga-leela.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/turanga-leela.webp -------------------------------------------------------------------------------- /static/img/human/yancy-fry_-sr_.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/yancy-fry_-sr_.webp -------------------------------------------------------------------------------- /static/img/human/zapp-brannigan.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/zapp-brannigan.webp -------------------------------------------------------------------------------- /static/img/mutant/andy-goldman.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/mutant/andy-goldman.webp -------------------------------------------------------------------------------- /static/img/mutant/el-chupanibre.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/mutant/el-chupanibre.webp -------------------------------------------------------------------------------- /static/img/mutant/turanga-munda.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/mutant/turanga-munda.webp -------------------------------------------------------------------------------- /static/img/robot/ben-rodr_guez.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/ben-rodr_guez.webp -------------------------------------------------------------------------------- /static/img/robot/billionairebot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/billionairebot.webp -------------------------------------------------------------------------------- /static/img/robot/cartridge-unit.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/cartridge-unit.webp -------------------------------------------------------------------------------- /static/img/robot/chain-smoker.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/chain-smoker.webp -------------------------------------------------------------------------------- /static/img/robot/daisy-mae-128k.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/daisy-mae-128k.webp -------------------------------------------------------------------------------- /static/img/robot/dr_-perceptron.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/dr_-perceptron.webp -------------------------------------------------------------------------------- /static/img/robot/emotitron_-jr_.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/emotitron_-jr_.webp -------------------------------------------------------------------------------- /static/img/robot/gorgeous-gonks.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/gorgeous-gonks.webp -------------------------------------------------------------------------------- /static/img/robot/humorbot-5_0.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/humorbot-5_0.webp -------------------------------------------------------------------------------- /static/img/robot/joey-mousepad.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/joey-mousepad.webp -------------------------------------------------------------------------------- /static/img/robot/nurse-ratchet.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/nurse-ratchet.webp -------------------------------------------------------------------------------- /static/img/robot/parts-hilton.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/parts-hilton.webp -------------------------------------------------------------------------------- /static/img/unknown/big-caboose.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/big-caboose.webp -------------------------------------------------------------------------------- /static/img/unknown/craterface.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/craterface.webp -------------------------------------------------------------------------------- /static/img/unknown/farmer-bot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/farmer-bot.webp -------------------------------------------------------------------------------- /static/img/unknown/george-takei.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/george-takei.webp -------------------------------------------------------------------------------- /static/img/unknown/hermes-bot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/hermes-bot.webp -------------------------------------------------------------------------------- /static/img/unknown/human-horn.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/human-horn.webp -------------------------------------------------------------------------------- /static/img/unknown/mecha-hermes.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/mecha-hermes.webp -------------------------------------------------------------------------------- /static/img/unknown/mouth-mutant.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/mouth-mutant.webp -------------------------------------------------------------------------------- /static/img/unknown/roberto-v2_0.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/roberto-v2_0.webp -------------------------------------------------------------------------------- /static/img/unknown/sam-zoidberg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/sam-zoidberg.webp -------------------------------------------------------------------------------- /static/img/alien/female-zoidberg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/female-zoidberg.webp -------------------------------------------------------------------------------- /static/img/alien/john-a_-zoidberg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/john-a_-zoidberg.webp -------------------------------------------------------------------------------- /static/img/alien/norman-zoidberg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/norman-zoidberg.webp -------------------------------------------------------------------------------- /static/img/alien/princess-num-num.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/princess-num-num.webp -------------------------------------------------------------------------------- /static/img/alien/slurms-mackenzie.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/slurms-mackenzie.webp -------------------------------------------------------------------------------- /static/img/human/abner-doubledeal.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/abner-doubledeal.webp -------------------------------------------------------------------------------- /static/img/human/amy-wong-kroker.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/amy-wong-kroker.webp -------------------------------------------------------------------------------- /static/img/human/bob-dole_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/bob-dole_s-head.webp -------------------------------------------------------------------------------- /static/img/human/boobs-vanderbilt.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/boobs-vanderbilt.webp -------------------------------------------------------------------------------- /static/img/human/david-farnsworth.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/david-farnsworth.webp -------------------------------------------------------------------------------- /static/img/human/elizabeth-bennet.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/elizabeth-bennet.webp -------------------------------------------------------------------------------- /static/img/human/floyd-farnsworth.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/floyd-farnsworth.webp -------------------------------------------------------------------------------- /static/img/human/frida-waterfall.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/frida-waterfall.webp -------------------------------------------------------------------------------- /static/img/human/gary-_season-2_.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/gary-_season-2_.webp -------------------------------------------------------------------------------- /static/img/human/hank-aaron-xxiv.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/hank-aaron-xxiv.webp -------------------------------------------------------------------------------- /static/img/human/hattie-mcdoogal.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/hattie-mcdoogal.webp -------------------------------------------------------------------------------- /static/img/human/head-of-the-aclu.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/head-of-the-aclu.webp -------------------------------------------------------------------------------- /static/img/human/hutch-waterfall.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/hutch-waterfall.webp -------------------------------------------------------------------------------- /static/img/human/jackie-anderson.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/jackie-anderson.webp -------------------------------------------------------------------------------- /static/img/human/joe-_defrostee_.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/joe-_defrostee_.webp -------------------------------------------------------------------------------- /static/img/human/kate-moss_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/kate-moss_s-head.webp -------------------------------------------------------------------------------- /static/img/human/labarbara-conrad.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/labarbara-conrad.webp -------------------------------------------------------------------------------- /static/img/human/lucy-liu_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/lucy-liu_s-head.webp -------------------------------------------------------------------------------- /static/img/human/michelle-jenkins.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/michelle-jenkins.webp -------------------------------------------------------------------------------- /static/img/human/ogden-wernstrom.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/ogden-wernstrom.webp -------------------------------------------------------------------------------- /static/img/human/philip-j_-fry-ii.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/philip-j_-fry-ii.webp -------------------------------------------------------------------------------- /static/img/human/tex_-connecticut.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/tex_-connecticut.webp -------------------------------------------------------------------------------- /static/img/human/velma-farnsworth.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/velma-farnsworth.webp -------------------------------------------------------------------------------- /static/img/monster/tritonian-yeti.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/monster/tritonian-yeti.webp -------------------------------------------------------------------------------- /static/img/mutant/munda_s-mother.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/mutant/munda_s-mother.webp -------------------------------------------------------------------------------- /static/img/mutant/turanga-morris.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/mutant/turanga-morris.webp -------------------------------------------------------------------------------- /static/img/robot/charlotte-widnar.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/charlotte-widnar.webp -------------------------------------------------------------------------------- /static/img/robot/emperor-nikolai.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/emperor-nikolai.webp -------------------------------------------------------------------------------- /static/img/robot/macaulay-culckon.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/macaulay-culckon.webp -------------------------------------------------------------------------------- /static/img/robot/mad-hatter-robot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/mad-hatter-robot.webp -------------------------------------------------------------------------------- /static/img/robot/the-clearcutter.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/the-clearcutter.webp -------------------------------------------------------------------------------- /static/img/robot/the-robot-elders.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/the-robot-elders.webp -------------------------------------------------------------------------------- /static/img/unknown/grunka-lunkas.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/grunka-lunkas.webp -------------------------------------------------------------------------------- /static/img/unknown/professor-bot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/professor-bot.webp -------------------------------------------------------------------------------- /static/img/unknown/suspendington.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/suspendington.webp -------------------------------------------------------------------------------- /static/img/unknown/walter-koenig.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/walter-koenig.webp -------------------------------------------------------------------------------- /futuramaapi/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from ._compat import config, metadata 2 | 3 | __all__ = [ 4 | "config", 5 | "metadata", 6 | ] 7 | -------------------------------------------------------------------------------- /static/img/alien/malachi_-sr__s-wife.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/malachi_-sr__s-wife.webp -------------------------------------------------------------------------------- /static/img/alien/melllvar_s-mother.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/melllvar_s-mother.webp -------------------------------------------------------------------------------- /static/img/alien/mr_-and-mrs_-kroker.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/mr_-and-mrs_-kroker.webp -------------------------------------------------------------------------------- /static/img/alien/neutral-president.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/neutral-president.webp -------------------------------------------------------------------------------- /static/img/head/planet-express-ship.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/head/planet-express-ship.webp -------------------------------------------------------------------------------- /static/img/human/21st-century-woman.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/21st-century-woman.webp -------------------------------------------------------------------------------- /static/img/human/beastie-boys_-heads.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/beastie-boys_-heads.webp -------------------------------------------------------------------------------- /static/img/human/bill-clinton_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/bill-clinton_s-head.webp -------------------------------------------------------------------------------- /static/img/human/bob-barker_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/bob-barker_s-head.webp -------------------------------------------------------------------------------- /static/img/human/bob-uecker_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/bob-uecker_s-head.webp -------------------------------------------------------------------------------- /static/img/human/butch_s-girlfriend.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/butch_s-girlfriend.webp -------------------------------------------------------------------------------- /static/img/human/charles-constantine.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/charles-constantine.webp -------------------------------------------------------------------------------- /static/img/human/chester-z_-arthur.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/chester-z_-arthur.webp -------------------------------------------------------------------------------- /static/img/human/colleen-o_hallahan.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/colleen-o_hallahan.webp -------------------------------------------------------------------------------- /static/img/human/conspiracy-nutter.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/conspiracy-nutter.webp -------------------------------------------------------------------------------- /static/img/human/dick-cheney_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/dick-cheney_s-head.webp -------------------------------------------------------------------------------- /static/img/human/dick-clark_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/dick-clark_s-head.webp -------------------------------------------------------------------------------- /static/img/human/dr_-schlovinowitz.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/dr_-schlovinowitz.webp -------------------------------------------------------------------------------- /static/img/human/eric-cartman_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/eric-cartman_s-head.webp -------------------------------------------------------------------------------- /static/img/human/free-waterfall-iii.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/free-waterfall-iii.webp -------------------------------------------------------------------------------- /static/img/human/free-waterfall_-jr_.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/free-waterfall_-jr_.webp -------------------------------------------------------------------------------- /static/img/human/free-waterfall_-sr_.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/free-waterfall_-sr_.webp -------------------------------------------------------------------------------- /static/img/human/gerald-ford_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/gerald-ford_s-head.webp -------------------------------------------------------------------------------- /static/img/human/hank-aaron_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/hank-aaron_s-head.webp -------------------------------------------------------------------------------- /static/img/human/heidi-klum_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/heidi-klum_s-head.webp -------------------------------------------------------------------------------- /static/img/human/hermes-conrad_s-fan.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/hermes-conrad_s-fan.webp -------------------------------------------------------------------------------- /static/img/human/jimmy-carter_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/jimmy-carter_s-head.webp -------------------------------------------------------------------------------- /static/img/human/joan-rivers_-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/joan-rivers_-head.webp -------------------------------------------------------------------------------- /static/img/human/joe-_nation-of-joe_.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/joe-_nation-of-joe_.webp -------------------------------------------------------------------------------- /static/img/human/leonardo-da-vinci.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/leonardo-da-vinci.webp -------------------------------------------------------------------------------- /static/img/human/old-man-waterfall.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/old-man-waterfall.webp -------------------------------------------------------------------------------- /static/img/human/orson-welles_-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/orson-welles_-head.webp -------------------------------------------------------------------------------- /static/img/human/rich-little_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/rich-little_s-head.webp -------------------------------------------------------------------------------- /static/img/human/rob-reiner_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/rob-reiner_s-head.webp -------------------------------------------------------------------------------- /static/img/human/ron-jeremy_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/ron-jeremy_s-head.webp -------------------------------------------------------------------------------- /static/img/human/ron-popeil_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/ron-popeil_s-head.webp -------------------------------------------------------------------------------- /static/img/human/ross-perot_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/ross-perot_s-head.webp -------------------------------------------------------------------------------- /static/img/human/snoop-dogg_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/snoop-dogg_s-head.webp -------------------------------------------------------------------------------- /static/img/human/the-little-prince.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/the-little-prince.webp -------------------------------------------------------------------------------- /static/img/human/traci-lords_-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/traci-lords_-head.webp -------------------------------------------------------------------------------- /static/img/monster/colonel-_merman_.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/monster/colonel-_merman_.webp -------------------------------------------------------------------------------- /static/img/monster/loch-ness-monster.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/monster/loch-ness-monster.webp -------------------------------------------------------------------------------- /static/img/robot/abraham-lincolnbot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/abraham-lincolnbot.webp -------------------------------------------------------------------------------- /static/img/robot/countess-de-la-roca.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/countess-de-la-roca.webp -------------------------------------------------------------------------------- /static/img/robot/human-_character_.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/human-_character_.webp -------------------------------------------------------------------------------- /static/img/robot/master-of-the-hunt.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/master-of-the-hunt.webp -------------------------------------------------------------------------------- /static/img/robot/robot-santa-claus.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/robot-santa-claus.webp -------------------------------------------------------------------------------- /static/img/unknown/bender-duplicates.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/bender-duplicates.webp -------------------------------------------------------------------------------- /static/img/unknown/colonel-_mutant_.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/colonel-_mutant_.webp -------------------------------------------------------------------------------- /static/img/unknown/stephen-hawking.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/stephen-hawking.webp -------------------------------------------------------------------------------- /static/img/human/andrew-jackson_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/andrew-jackson_s-head.webp -------------------------------------------------------------------------------- /static/img/human/antonin-scalia_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/antonin-scalia_s-head.webp -------------------------------------------------------------------------------- /static/img/human/billy-crystal_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/billy-crystal_s-head.webp -------------------------------------------------------------------------------- /static/img/human/cindy-crawford_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/cindy-crawford_s-head.webp -------------------------------------------------------------------------------- /static/img/human/conan-o_brien_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/conan-o_brien_s-head.webp -------------------------------------------------------------------------------- /static/img/human/crack-mansion-butler.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/crack-mansion-butler.webp -------------------------------------------------------------------------------- /static/img/human/cubert-j_-farnsworth.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/cubert-j_-farnsworth.webp -------------------------------------------------------------------------------- /static/img/human/david-duchovny_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/david-duchovny_s-head.webp -------------------------------------------------------------------------------- /static/img/human/david-x_-cohen_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/david-x_-cohen_s-head.webp -------------------------------------------------------------------------------- /static/img/human/elvis-presley_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/elvis-presley_s-head.webp -------------------------------------------------------------------------------- /static/img/human/george-foreman_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/george-foreman_s-head.webp -------------------------------------------------------------------------------- /static/img/human/herbert-hoover_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/herbert-hoover_s-head.webp -------------------------------------------------------------------------------- /static/img/human/homer-simpson_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/homer-simpson_s-head.webp -------------------------------------------------------------------------------- /static/img/human/hubert-j_-farnsworth.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/hubert-j_-farnsworth.webp -------------------------------------------------------------------------------- /static/img/human/jonathan-frakes_-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/jonathan-frakes_-head.webp -------------------------------------------------------------------------------- /static/img/human/laetitia-casta_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/laetitia-casta_s-head.webp -------------------------------------------------------------------------------- /static/img/human/leonard-nimoy_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/leonard-nimoy_s-head.webp -------------------------------------------------------------------------------- /static/img/human/linda-van-schoonhoven.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/linda-van-schoonhoven.webp -------------------------------------------------------------------------------- /static/img/human/martha-stewart_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/martha-stewart_s-head.webp -------------------------------------------------------------------------------- /static/img/human/matt-groening_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/matt-groening_s-head.webp -------------------------------------------------------------------------------- /static/img/human/penn-jillette_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/penn-jillette_s-head.webp -------------------------------------------------------------------------------- /static/img/human/rebecca-romijn_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/rebecca-romijn_s-head.webp -------------------------------------------------------------------------------- /static/img/human/robert-wagner_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/robert-wagner_s-head.webp -------------------------------------------------------------------------------- /static/img/human/ronald-reagan_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/ronald-reagan_s-head.webp -------------------------------------------------------------------------------- /static/img/human/samuel-genital_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/samuel-genital_s-head.webp -------------------------------------------------------------------------------- /static/img/human/scruffy-scruffington.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/scruffy-scruffington.webp -------------------------------------------------------------------------------- /static/img/human/sergio-aragon_s_-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/sergio-aragon_s_-head.webp -------------------------------------------------------------------------------- /static/img/human/walter-mondale_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/walter-mondale_s-head.webp -------------------------------------------------------------------------------- /static/img/mutant/munda_s-grandmother.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/mutant/munda_s-grandmother.webp -------------------------------------------------------------------------------- /static/img/robot/antonio-calculon_-jr_.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/antonio-calculon_-jr_.webp -------------------------------------------------------------------------------- /static/img/robot/comrade-greeting-card.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/comrade-greeting-card.webp -------------------------------------------------------------------------------- /static/img/robot/cymbal-banging-monkey.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/cymbal-banging-monkey.webp -------------------------------------------------------------------------------- /static/img/robot/francis-x_-clampazzo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/francis-x_-clampazzo.webp -------------------------------------------------------------------------------- /static/img/robot/malfunctioning-eddie.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/malfunctioning-eddie.webp -------------------------------------------------------------------------------- /static/img/unknown/army-of-the-damned.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/army-of-the-damned.webp -------------------------------------------------------------------------------- /static/img/alien/ethan-_bubblegum_-tate.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/ethan-_bubblegum_-tate.webp -------------------------------------------------------------------------------- /static/img/human/abraham-lincoln_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/abraham-lincoln_s-head.webp -------------------------------------------------------------------------------- /static/img/human/benjamin-harrison_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/benjamin-harrison_s-head.webp -------------------------------------------------------------------------------- /static/img/human/buzz-aldrin-_character_.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/buzz-aldrin-_character_.webp -------------------------------------------------------------------------------- /static/img/human/c_-randall-poopenmeyer.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/c_-randall-poopenmeyer.webp -------------------------------------------------------------------------------- /static/img/human/charles-de-gaulle_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/charles-de-gaulle_s-head.webp -------------------------------------------------------------------------------- /static/img/human/chester-a_-arthur_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/chester-a_-arthur_s-head.webp -------------------------------------------------------------------------------- /static/img/human/claudia-schiffer_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/claudia-schiffer_s-head.webp -------------------------------------------------------------------------------- /static/img/human/deforest-kelley_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/deforest-kelley_s-head.webp -------------------------------------------------------------------------------- /static/img/human/elizabeth-taylor_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/elizabeth-taylor_s-head.webp -------------------------------------------------------------------------------- /static/img/human/gary-gygax-_character_.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/gary-gygax-_character_.webp -------------------------------------------------------------------------------- /static/img/human/george-h_-w_-bush_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/george-h_-w_-bush_s-head.webp -------------------------------------------------------------------------------- /static/img/human/george-washington_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/george-washington_s-head.webp -------------------------------------------------------------------------------- /static/img/human/grover-cleveland_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/grover-cleveland_s-head.webp -------------------------------------------------------------------------------- /static/img/human/harry-s_-truman_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/harry-s_-truman_s-head.webp -------------------------------------------------------------------------------- /static/img/human/henry-kissinger_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/henry-kissinger_s-head.webp -------------------------------------------------------------------------------- /static/img/human/jill-big-breasts_-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/jill-big-breasts_-head.webp -------------------------------------------------------------------------------- /static/img/human/leonardo-dicaprio_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/leonardo-dicaprio_s-head.webp -------------------------------------------------------------------------------- /static/img/human/long-dong-silver_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/long-dong-silver_s-head.webp -------------------------------------------------------------------------------- /static/img/human/martin-van-buren_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/martin-van-buren_s-head.webp -------------------------------------------------------------------------------- /static/img/human/pamela-anderson_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/pamela-anderson_s-head.webp -------------------------------------------------------------------------------- /static/img/human/richard-m_-nixon_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/richard-m_-nixon_s-head.webp -------------------------------------------------------------------------------- /static/img/human/thomas-jefferson_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/thomas-jefferson_s-head.webp -------------------------------------------------------------------------------- /static/img/human/william-shatner_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/william-shatner_s-head.webp -------------------------------------------------------------------------------- /static/img/robot/bender-bending-rodr_guez.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/bender-bending-rodr_guez.webp -------------------------------------------------------------------------------- /static/img/robot/billy-west-_character_.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/billy-west-_character_.webp -------------------------------------------------------------------------------- /static/img/robot/sergeant-feces-processor.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/sergeant-feces-processor.webp -------------------------------------------------------------------------------- /static/img/unknown/147573952589676412927.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/unknown/147573952589676412927.webp -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Migrations 4 | make migrate 5 | 6 | poetry run python -m futuramaapi -b :"${PORT:-8080}" "$@" 7 | -------------------------------------------------------------------------------- /static/img/alien/mystic-aldermen-of-the-sun.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/alien/mystic-aldermen-of-the-sun.webp -------------------------------------------------------------------------------- /static/img/human/theodore-roosevelt_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/theodore-roosevelt_s-head.webp -------------------------------------------------------------------------------- /static/img/human/william-howard-taft_s-head.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/william-howard-taft_s-head.webp -------------------------------------------------------------------------------- /static/img/robot/john-quincy-adding-machine.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/john-quincy-adding-machine.webp -------------------------------------------------------------------------------- /static/img/human/headless-body-of-spiro-agnew.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/headless-body-of-spiro-agnew.webp -------------------------------------------------------------------------------- /static/img/human/nichelle-nichols-_character_.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/nichelle-nichols-_character_.webp -------------------------------------------------------------------------------- /static/img/monster/giant-unattractive-monster.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/monster/giant-unattractive-monster.webp -------------------------------------------------------------------------------- /static/img/human/hubert-j_-farnsworth_s-girlfriend.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/human/hubert-j_-farnsworth_s-girlfriend.webp -------------------------------------------------------------------------------- /static/img/robot/bender-bending-rodr_guez_s-first-born-son.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koldakov/futuramaapi/HEAD/static/img/robot/bender-bending-rodr_guez_s-first-born-son.webp -------------------------------------------------------------------------------- /futuramaapi/apps/__init__.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | 3 | from .fastapi import futurama_api 4 | 5 | app: FastAPI = futurama_api 6 | 7 | __all__ = [ 8 | "app", 9 | ] 10 | -------------------------------------------------------------------------------- /futuramaapi/core/__init__.py: -------------------------------------------------------------------------------- 1 | from ._settings import email_settings, feature_flags, settings 2 | 3 | __all__ = [ 4 | "email_settings", 5 | "feature_flags", 6 | "settings", 7 | ] 8 | -------------------------------------------------------------------------------- /static/css/cookie-banner.css: -------------------------------------------------------------------------------- 1 | #futurama-api-cookie-banner { 2 | position: fixed; 3 | bottom: 0; 4 | left: 0; 5 | width: 100%; 6 | z-index: 999; 7 | border-radius: 0; 8 | display: none; 9 | } 10 | -------------------------------------------------------------------------------- /futuramaapi/__main__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from .web_servers import run 4 | 5 | 6 | def _run() -> int: 7 | return run(sys.argv[1:]) 8 | 9 | 10 | if __name__ == "__main__": 11 | sys.exit(_run()) 12 | -------------------------------------------------------------------------------- /robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /api/ 3 | Disallow: /s/ 4 | Allow: / 5 | 6 | User-agent: Googlebot 7 | Disallow: /api/ 8 | Disallow: /s/ 9 | Allow: / 10 | 11 | User-agent: Yandex 12 | Disallow: /api/ 13 | Disallow: /s/ 14 | Allow: / 15 | -------------------------------------------------------------------------------- /futuramaapi/routers/graphql/dependencies.py: -------------------------------------------------------------------------------- 1 | from fastapi import Depends 2 | 3 | from .context import Context 4 | 5 | 6 | async def get_context( 7 | context: Context = Depends(Context.from_dependency), # noqa: B008 8 | ) -> Context: 9 | return context 10 | -------------------------------------------------------------------------------- /tests/unit/helpers/test_hashers.py: -------------------------------------------------------------------------------- 1 | from futuramaapi.helpers.hashers import hasher 2 | 3 | 4 | class TestHasher: 5 | def test_verify(self): 6 | password: str = "123" # noqa: S105 7 | decoded: str = hasher.encode(password) 8 | 9 | assert hasher.verify(password, decoded) 10 | -------------------------------------------------------------------------------- /futuramaapi/repositories/__init__.py: -------------------------------------------------------------------------------- 1 | from ._base import ( 2 | INT32, 3 | Base, 4 | FilterStatementKwargs, 5 | ModelAlreadyExistsError, 6 | ModelDoesNotExistError, 7 | ) 8 | 9 | __all__ = [ 10 | "INT32", 11 | "Base", 12 | "FilterStatementKwargs", 13 | "ModelAlreadyExistsError", 14 | "ModelDoesNotExistError", 15 | ] 16 | -------------------------------------------------------------------------------- /futuramaapi/routers/graphql/api.py: -------------------------------------------------------------------------------- 1 | import strawberry 2 | from strawberry.fastapi import GraphQLRouter 3 | 4 | from .dependencies import get_context 5 | from .schemas import Query 6 | 7 | schema = strawberry.Schema(Query) 8 | 9 | router = GraphQLRouter( 10 | schema, 11 | path="/graphql", 12 | context_getter=get_context, 13 | include_in_schema=False, 14 | ) 15 | -------------------------------------------------------------------------------- /futuramaapi/utils/_compat.py: -------------------------------------------------------------------------------- 1 | import tomllib 2 | from importlib.metadata import metadata as _metadata 3 | from typing import Any 4 | 5 | __all__ = [ 6 | "config", 7 | "metadata", 8 | ] 9 | 10 | 11 | def _get_config() -> dict[str, Any]: 12 | with open("pyproject.toml", "rb") as f: 13 | return tomllib.load(f) 14 | 15 | 16 | metadata = _metadata("futuramaapi") 17 | config: dict[str, Any] = _get_config() 18 | -------------------------------------------------------------------------------- /futuramaapi/routers/services/_base.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from collections.abc import Sequence 3 | from typing import Any 4 | 5 | from futuramaapi.helpers.pydantic import BaseModel 6 | 7 | 8 | class BaseService(BaseModel, ABC): 9 | context: dict[str, Any] | None = None 10 | 11 | @abstractmethod 12 | async def __call__(self, *args, **kwargs) -> BaseModel | Sequence[BaseModel] | None: 13 | pass 14 | -------------------------------------------------------------------------------- /futuramaapi/routers/exceptions.py: -------------------------------------------------------------------------------- 1 | from pydantic import Field 2 | 3 | from futuramaapi.helpers.pydantic import BaseModel 4 | 5 | 6 | class ModelNotFoundError(Exception): ... 7 | 8 | 9 | class ModelExistsError(Exception): ... 10 | 11 | 12 | class UpdateArgsNotDefined(Exception): ... 13 | 14 | 15 | class NotFoundResponse(BaseModel): 16 | detail: str = Field("Not Found") 17 | 18 | 19 | class UnauthorizedResponse(BaseModel): 20 | detail: str = Field("Unauthorized") 21 | -------------------------------------------------------------------------------- /futuramaapi/repositories/migrations/versions/d413d1284339_initial_revision.py: -------------------------------------------------------------------------------- 1 | """Initial revision 2 | 3 | Revision ID: d413d1284339 4 | Revises: 5 | Create Date: 2023-11-25 19:46:49.496715 6 | """ 7 | 8 | from collections.abc import Sequence 9 | 10 | revision: str = "d413d1284339" 11 | down_revision: str | None = None 12 | branch_labels: str | Sequence[str] | None = None 13 | depends_on: str | Sequence[str] | None = None 14 | 15 | 16 | def upgrade() -> None: 17 | pass 18 | 19 | 20 | def downgrade() -> None: 21 | pass 22 | -------------------------------------------------------------------------------- /futuramaapi/routers/rest/seasons/schemas.py: -------------------------------------------------------------------------------- 1 | from typing import ClassVar 2 | 3 | from futuramaapi.helpers.pydantic import BaseModel 4 | from futuramaapi.mixins.pydantic import BaseModelDatabaseMixin 5 | from futuramaapi.repositories.models import SeasonModel 6 | from futuramaapi.routers.rest.episodes.schemas import EpisodeBase 7 | 8 | 9 | class Season(BaseModel, BaseModelDatabaseMixin): 10 | model: ClassVar[type[SeasonModel]] = SeasonModel 11 | 12 | class Episode(EpisodeBase): ... 13 | 14 | id: int 15 | episodes: list[Episode] 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL = /bin/bash 2 | PYTHON = python3 3 | 4 | help: # Display this message 5 | @sed -ne '/@sed/!s/# //p' $(MAKEFILE_LIST) 6 | 7 | install-dev: # Install DEV/TEST Environ and dependencies 8 | @echo "Upgrading pip" 9 | @$(PYTHON) -m pip install --upgrade pip 10 | @echo "Installing poetry" 11 | @$(PYTHON) -m pip install poetry 12 | @echo "Installing dependencies" 13 | @$(PYTHON) -m poetry install 14 | 15 | test: # Run tests 16 | @$(PYTHON) -m poetry run $(PYTHON) -m pytest 17 | 18 | migrate: # Migrate 19 | @$(PYTHON) -m poetry run $(PYTHON) -m alembic upgrade head 20 | -------------------------------------------------------------------------------- /futuramaapi/middlewares/cors.py: -------------------------------------------------------------------------------- 1 | from fastapi.middleware.cors import CORSMiddleware as CORSMiddlewareBase 2 | 3 | 4 | class CORSMiddleware(CORSMiddlewareBase): 5 | def is_allowed_origin(self, origin: str) -> bool: 6 | # Starlette restricts to have origin "*" with allow_credentials for ``fastapi.middleware.cors.CORSMiddleware``. 7 | # But for FuturamaAPI it's fine if anyone can access API. 8 | # Not a security issue at all. But if you have any suggestions you are free to create a task here: 9 | # https://github.com/koldakov/futuramaapi/issues. 10 | return True 11 | -------------------------------------------------------------------------------- /futuramaapi/routers/graphql/context.py: -------------------------------------------------------------------------------- 1 | from fastapi import Depends 2 | from sqlalchemy.ext.asyncio.session import AsyncSession 3 | from strawberry.fastapi import BaseContext 4 | 5 | from futuramaapi.repositories.session import get_async_session 6 | 7 | 8 | class Context(BaseContext): 9 | def __init__(self, session: AsyncSession): 10 | self.session: AsyncSession = session 11 | 12 | super().__init__() 13 | 14 | @classmethod 15 | async def from_dependency( 16 | cls, 17 | session: AsyncSession = Depends(get_async_session), # noqa: B008 18 | ): 19 | return cls(session) 20 | -------------------------------------------------------------------------------- /futuramaapi/web_servers/hypercorn.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from typing import TYPE_CHECKING 3 | 4 | from hypercorn.__main__ import main 5 | 6 | if TYPE_CHECKING: 7 | from collections.abc import Sequence 8 | 9 | 10 | class Config: 11 | worker_class = "uvloop" 12 | workers = 2 13 | 14 | 15 | hypercorn_config: Config = Config() 16 | 17 | 18 | def run( 19 | args: list[str] | None, 20 | ) -> int: 21 | argv: Sequence[str] = args if args is not None else sys.argv[1:] 22 | main( 23 | [ 24 | "futuramaapi:app", 25 | "--config=python:futuramaapi.web_servers.hypercorn.hypercorn_config", 26 | *argv, 27 | ] 28 | ) 29 | return 0 30 | -------------------------------------------------------------------------------- /futuramaapi/repositories/migrations/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from typing import Sequence, Union 9 | 10 | from alembic import op 11 | import sqlalchemy as sa 12 | ${imports if imports else ""} 13 | 14 | # revision identifiers, used by Alembic. 15 | revision: str = ${repr(up_revision)} 16 | down_revision: str | None = ${repr(down_revision)} 17 | branch_labels: str | Sequence[str] | None = ${repr(branch_labels)} 18 | depends_on: str | Sequence[str] | None = ${repr(depends_on)} 19 | 20 | 21 | def upgrade() -> None: 22 | ${upgrades if upgrades else "pass"} 23 | 24 | 25 | def downgrade() -> None: 26 | ${downgrades if downgrades else "pass"} 27 | -------------------------------------------------------------------------------- /futuramaapi/middlewares/counter.py: -------------------------------------------------------------------------------- 1 | from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint 2 | from starlette.requests import Request 3 | from starlette.responses import Response 4 | 5 | from futuramaapi.repositories.models import RequestsCounterModel 6 | 7 | 8 | def _get_url(request: Request, /) -> str: 9 | # Quick fix 10 | return request.url.__str__().split("?")[0][:64] 11 | 12 | 13 | class APIRequestsCounter(BaseHTTPMiddleware): 14 | async def dispatch( 15 | self, 16 | request: Request, 17 | call_next: RequestResponseEndpoint, 18 | ) -> Response: 19 | if request.url.path.startswith("/api"): 20 | await RequestsCounterModel.count_url(_get_url(request)) 21 | 22 | return await call_next(request) 23 | -------------------------------------------------------------------------------- /static/js/cookie-banner.js: -------------------------------------------------------------------------------- 1 | var FuturamaAPICookieName = "FuturamaAPI_CookieAccepted"; 2 | var cookieBannerId = "futurama-api-cookie-banner" 3 | 4 | function showCookieBanner(){ 5 | let cookieBanner = document.getElementById(cookieBannerId); 6 | cookieBanner.style.display = "block"; 7 | } 8 | 9 | function hideCookieBanner(){ 10 | localStorage.setItem(FuturamaAPICookieName, true); 11 | 12 | let cookieBanner = document.getElementById(cookieBannerId); 13 | cookieBanner.style.display = "none"; 14 | } 15 | 16 | function initializeCookieBanner(){ 17 | if(localStorage.getItem(FuturamaAPICookieName) === null) { 18 | showCookieBanner(); 19 | } 20 | } 21 | 22 | window.onload = initializeCookieBanner(); 23 | window.futuramaapi_hideCookieBanner = hideCookieBanner; 24 | -------------------------------------------------------------------------------- /futuramaapi/repositories/migrations/versions/c03e060df1b8_add_production_code_to_episode.py: -------------------------------------------------------------------------------- 1 | """Add production code to episode 2 | 3 | Revision ID: c03e060df1b8 4 | Revises: 928d4358646c 5 | Create Date: 2023-12-21 20:12:27.108201 6 | """ 7 | 8 | from collections.abc import Sequence 9 | 10 | import sqlalchemy as sa 11 | from alembic import op 12 | 13 | # revision identifiers, used by Alembic. 14 | revision: str = "c03e060df1b8" 15 | down_revision: str | None = "928d4358646c" 16 | branch_labels: str | Sequence[str] | None = None 17 | depends_on: str | Sequence[str] | None = None 18 | 19 | 20 | def upgrade() -> None: 21 | op.add_column( 22 | "episodes", 23 | sa.Column( 24 | "production_code", 25 | sa.VARCHAR(length=8), 26 | nullable=True, 27 | ), 28 | ) 29 | 30 | 31 | def downgrade() -> None: 32 | op.drop_column( 33 | "episodes", 34 | "production_code", 35 | ) 36 | -------------------------------------------------------------------------------- /futuramaapi/repositories/migrations/versions/1b86ee33d1ba_add_broadcast_number_to_episode.py: -------------------------------------------------------------------------------- 1 | """Add broadcast number to episode 2 | 3 | Revision ID: 1b86ee33d1ba 4 | Revises: c03e060df1b8 5 | Create Date: 2023-12-21 21:57:04.032458 6 | """ 7 | 8 | from collections.abc import Sequence 9 | 10 | import sqlalchemy as sa 11 | from alembic import op 12 | 13 | # revision identifiers, used by Alembic. 14 | revision: str = "1b86ee33d1ba" 15 | down_revision: str | None = "c03e060df1b8" 16 | branch_labels: str | Sequence[str] | None = None 17 | depends_on: str | Sequence[str] | None = None 18 | 19 | 20 | def upgrade() -> None: 21 | op.add_column( 22 | "episodes", 23 | sa.Column( 24 | "broadcast_number", 25 | sa.SmallInteger(), 26 | nullable=True, 27 | ), 28 | ) 29 | 30 | 31 | def downgrade() -> None: 32 | op.drop_column( 33 | "episodes", 34 | "broadcast_number", 35 | ) 36 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to Heroku 2 | 3 | on: 4 | release: 5 | types: 6 | - created 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | env: 12 | HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} 13 | HEROKU_APP_NAME: ${{ secrets.HEROKU_APP_NAME }} 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v3 18 | 19 | - name: Log in to Heroku Container Registry 20 | run: echo "$HEROKU_API_KEY" | /usr/bin/docker login --username=_ --password-stdin registry.heroku.com 21 | 22 | - name: Build Docker image 23 | run: docker build -t registry.heroku.com/$HEROKU_APP_NAME/web . 24 | 25 | - name: Push Docker image to Heroku 26 | run: docker push registry.heroku.com/$HEROKU_APP_NAME/web 27 | 28 | - name: Install Heroku CLI 29 | run: curl https://cli-assets.heroku.com/install.sh | sh 30 | 31 | - name: Release on Heroku 32 | run: heroku container:release web --app $HEROKU_APP_NAME 33 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | fail_fast: true 2 | 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v5.0.0 6 | hooks: 7 | - id: check-added-large-files 8 | args: ["--maxkb=700"] 9 | - id: check-yaml 10 | - id: check-toml 11 | - id: check-json 12 | - id: check-symlinks 13 | 14 | - repo: https://github.com/astral-sh/ruff-pre-commit 15 | rev: v0.9.10 16 | hooks: 17 | - id: ruff 18 | - id: ruff-format 19 | - id: ruff 20 | args: 21 | - --no-fix 22 | stages: 23 | - manual 24 | - id: ruff-format 25 | args: 26 | - --check 27 | stages: 28 | - manual 29 | 30 | - repo: https://github.com/pre-commit/mirrors-mypy 31 | rev: v1.15.0 32 | hooks: 33 | - id: mypy 34 | pass_filenames: false 35 | args: 36 | - --install-types 37 | - --non-interactive 38 | - --check-untyped-defs 39 | - --python-version=3.12 40 | -------------------------------------------------------------------------------- /futuramaapi/routers/services/seasons/list_seasons.py: -------------------------------------------------------------------------------- 1 | from abc import ABC 2 | 3 | from fastapi_pagination import Page 4 | from fastapi_pagination.ext.sqlalchemy import paginate 5 | from sqlalchemy import Select, select 6 | from sqlalchemy.orm import selectinload 7 | 8 | from futuramaapi.repositories.models import SeasonModel 9 | from futuramaapi.repositories.session import session_manager 10 | from futuramaapi.routers.services._base import BaseService 11 | from futuramaapi.routers.services.seasons.get_season import GetSeasonResponse 12 | 13 | 14 | class ListSeasonResponse(GetSeasonResponse): 15 | pass 16 | 17 | 18 | class ListSeasonsService(BaseService, ABC): 19 | @property 20 | def statement(self) -> Select: 21 | return select(SeasonModel).filter().options(selectinload(SeasonModel.episodes)) 22 | 23 | async def __call__(self, *args, **kwargs) -> Page[ListSeasonResponse]: 24 | async with session_manager.session() as session: 25 | return await paginate( 26 | session, 27 | self.statement, 28 | ) 29 | -------------------------------------------------------------------------------- /futuramaapi/routers/rest/characters/schemas.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import ClassVar 3 | 4 | from fastapi_storages import StorageImage 5 | from pydantic import HttpUrl, field_validator 6 | 7 | from futuramaapi.core import settings 8 | from futuramaapi.helpers.pydantic import BaseModel 9 | from futuramaapi.mixins.pydantic import BaseModelDatabaseMixin 10 | from futuramaapi.repositories.models import CharacterModel 11 | 12 | 13 | class Character(BaseModel, BaseModelDatabaseMixin): 14 | model: ClassVar[type[CharacterModel]] = CharacterModel 15 | 16 | id: int 17 | name: str 18 | gender: CharacterModel.CharacterGender 19 | status: CharacterModel.CharacterStatus 20 | species: CharacterModel.CharacterSpecies 21 | created_at: datetime 22 | image: HttpUrl | None = None 23 | 24 | @field_validator("image", mode="before") 25 | @classmethod 26 | def make_url(cls, value: StorageImage | str | None, /) -> HttpUrl | None: 27 | if value is None: 28 | return None 29 | if isinstance(value, StorageImage): 30 | return settings.build_url(path=value._name) 31 | return HttpUrl(value) 32 | -------------------------------------------------------------------------------- /futuramaapi/routers/rest/episodes/schemas.py: -------------------------------------------------------------------------------- 1 | from datetime import date, datetime 2 | from typing import ClassVar 3 | 4 | from pydantic import Field, computed_field 5 | 6 | from futuramaapi.helpers.pydantic import BaseModel 7 | from futuramaapi.mixins.pydantic import BaseModelDatabaseMixin 8 | from futuramaapi.repositories.models import EpisodeModel 9 | 10 | 11 | class EpisodeBase(BaseModel, BaseModelDatabaseMixin): 12 | model: ClassVar[type[EpisodeModel]] = EpisodeModel 13 | 14 | id: int 15 | name: str 16 | broadcast_number: int = Field(alias="number") 17 | production_code: str = Field( 18 | examples=[ 19 | "1ACV01", 20 | ], 21 | ) 22 | 23 | 24 | class Episode(EpisodeBase): 25 | class Season(BaseModel): 26 | id: int 27 | 28 | air_date: date | None 29 | duration: int | None 30 | created_at: datetime 31 | season: Season 32 | 33 | @computed_field( # type: ignore[misc] 34 | examples=[ 35 | "S01E01", 36 | ], 37 | return_type=str, 38 | ) 39 | @property 40 | def broadcast_code(self) -> str: 41 | return f"S{self.season.id:02d}E{self.broadcast_number:02d}" 42 | -------------------------------------------------------------------------------- /templates/auth.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% set active_page = "user_auth" %} 4 | 5 | {% block titile %}Password Reset{% endblock %} | Futurama API 6 | 7 | {% block main_info %}{% endblock %} 8 | 9 | {% block main_content %} 10 |
13 |

Sign In

14 |
18 |
21 | 26 | 33 |
34 |
37 | 42 | 48 |
49 | 54 |
55 |
56 | {% endblock %} 57 | -------------------------------------------------------------------------------- /futuramaapi/repositories/migrations/versions/928d4358646c_add_image_field.py: -------------------------------------------------------------------------------- 1 | """Add image field 2 | 3 | Revision ID: 928d4358646c 4 | Revises: 1353be8a56b8 5 | Create Date: 2023-12-08 20:58:59.382849 6 | """ 7 | 8 | from collections.abc import Sequence 9 | 10 | import sqlalchemy as sa 11 | from alembic import op 12 | from fastapi_storages import FileSystemStorage 13 | from fastapi_storages.integrations.sqlalchemy import ImageType 14 | 15 | from futuramaapi.core import settings 16 | 17 | # revision identifiers, used by Alembic. 18 | revision: str = "928d4358646c" 19 | down_revision: str | None = "1353be8a56b8" 20 | branch_labels: str | Sequence[str] | None = None 21 | depends_on: str | Sequence[str] | None = None 22 | 23 | 24 | def upgrade() -> None: 25 | op.add_column( 26 | "characters", 27 | sa.Column( 28 | "image", 29 | ImageType( 30 | storage=FileSystemStorage( 31 | path=settings.project_root / settings.static, 32 | ), 33 | ), 34 | nullable=True, 35 | ), 36 | ) 37 | 38 | 39 | def downgrade() -> None: 40 | op.drop_column( 41 | "characters", 42 | "image", 43 | ) 44 | -------------------------------------------------------------------------------- /futuramaapi/routers/__init__.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | 3 | from .graphql import router as graphql_router 4 | from .rest.callbacks import router as callbacks_router 5 | from .rest.characters import router as characters_router 6 | from .rest.crypto import router as crypto_router 7 | from .rest.episodes import router as episodes_router 8 | from .rest.notifications import router as notification_router 9 | from .rest.randoms import router as randoms_router 10 | from .rest.root import router as root_router 11 | from .rest.seasons import router as seasons_router 12 | from .rest.tokens import router as tokens_router 13 | from .rest.users import router as users_router 14 | 15 | __all__ = [ 16 | "api_router", 17 | "graphql_router", 18 | "root_router", 19 | ] 20 | 21 | api_router = APIRouter(prefix="/api") 22 | 23 | api_router.include_router(callbacks_router) 24 | api_router.include_router(randoms_router) 25 | api_router.include_router(characters_router) 26 | api_router.include_router(crypto_router) 27 | api_router.include_router(episodes_router) 28 | api_router.include_router(notification_router) 29 | api_router.include_router(seasons_router) 30 | api_router.include_router(tokens_router) 31 | api_router.include_router(users_router) 32 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test pipeline 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - '**' 10 | 11 | jobs: 12 | pre-commit: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | 17 | - name: Set up Python 18 | uses: actions/setup-python@v4 19 | with: 20 | python-version: "3.12" 21 | 22 | - name: Install dependencies 23 | run: | 24 | pip install poetry 25 | poetry install 26 | 27 | - name: Run pre-commit 28 | run: | 29 | poetry run pre-commit run --all-files 30 | 31 | unit-tests: 32 | runs-on: ubuntu-latest 33 | needs: pre-commit 34 | steps: 35 | - uses: actions/checkout@v3 36 | 37 | - name: Set up Python 38 | uses: actions/setup-python@v4 39 | with: 40 | python-version: "3.12" 41 | 42 | - name: Install dependencies 43 | run: | 44 | pip install poetry 45 | poetry install 46 | 47 | - name: Load env file 48 | run: | 49 | grep -vE '^(#|$)' .env.test >> $GITHUB_ENV 50 | 51 | - name: Run unit tests 52 | run: | 53 | poetry run pytest 54 | -------------------------------------------------------------------------------- /.env.template: -------------------------------------------------------------------------------- 1 | ALLOW_ORIGINS=* 2 | DATABASE_URL=postgres+asyncpg://user:password@host/db_name 3 | TRUSTED_HOST=localhost 4 | SECRET_KEY=PRODUCTION-SECRET-KEY 5 | # Optional 6 | G_TAG=G-TAG 7 | POOL_MAX_OVERFLOW=0 8 | POOL_SIZE=10 9 | POOL_TIMEOUT=10 10 | POOL_RECYCLE=1800 11 | 12 | # Email settings 13 | EMAIL_FROM_DEFAULT=default@from.email 14 | EMAIL_HOST_USER=host-user 15 | EMAIL_HOST=email-host 16 | EMAIL_API_KEY=secret-api-key 17 | 18 | # Broker 19 | REDISCLOUD_URL=redis://user:password@host:6379 20 | 21 | # Logging 22 | # Optional, if not set alerts will not be fired. 23 | # To activate do not forget about feature flag ENABLE_SENTRY, which is False by default. 24 | SENTRY_DSN=https://password@host/project 25 | # Can be development, staging or production only 26 | # Default value is production. 27 | SENTRY_ENVIRONMENT=production 28 | 29 | ##### 30 | # Feature Flags 31 | # All Feature Flags are booleans and default values you can see below. 32 | ##### 33 | # Determines if user creating enabled 34 | ACTIVATE_USERS=false 35 | # Enable HTTPS redirect behind the proxy. 36 | # Keep in mind proxy must pass x-forwarded-proto, x-forwarded-port and host in headers. 37 | # TRUSTED_HOST must be valid. 38 | ENABLE_HTTPS_REDIRECT=false 39 | # Enable Sentry Logging 40 | # Requires SENTRY_DSN environment variable to set. 41 | ENABLE_SENTRY=false 42 | SEND_EMAILS=true 43 | COUNT_API_REQUESTS=true 44 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | ALLOW_ORIGINS=* 2 | DATABASE_URL=postgres+asyncpg://user:password@host/db_name 3 | TRUSTED_HOST=localhost 4 | SECRET_KEY=PRODUCTION-SECRET-KEY 5 | # Optional 6 | G_TAG=G-TAG 7 | POOL_MAX_OVERFLOW=0 8 | POOL_SIZE=10 9 | POOL_TIMEOUT=10 10 | POOL_RECYCLE=1800 11 | 12 | # Email settings 13 | EMAIL_FROM_DEFAULT=default@from.email 14 | EMAIL_HOST_USER=host-user 15 | EMAIL_HOST=email-host 16 | EMAIL_API_KEY=secret-api-key 17 | 18 | # Broker 19 | REDISCLOUD_URL=redis://user:password@host:6379 20 | 21 | # Logging 22 | # Optional, if not set alerts will not be fired. 23 | # To activate do not forget about feature flag ENABLE_SENTRY, which is False by default. 24 | SENTRY_DSN=https://password@host/project 25 | # Can be development, staging or production only 26 | # Default value is production. 27 | SENTRY_ENVIRONMENT=production 28 | 29 | ##### 30 | # Feature Flags 31 | # All Feature Flags are booleans and default values you can see below. 32 | ##### 33 | # Determines if user creating enabled 34 | ACTIVATE_USERS=false 35 | # Enable HTTPS redirect behind the proxy. 36 | # Keep in mind proxy must pass x-forwarded-proto, x-forwarded-port and host in headers. 37 | # TRUSTED_HOST must be valid. 38 | ENABLE_HTTPS_REDIRECT=false 39 | # Enable Sentry Logging 40 | # Requires SENTRY_DSN environment variable to set. 41 | ENABLE_SENTRY=false 42 | SEND_EMAILS=true 43 | COUNT_API_REQUESTS=true 44 | -------------------------------------------------------------------------------- /futuramaapi/routers/graphql/validators.py: -------------------------------------------------------------------------------- 1 | from typing import Any, ClassVar 2 | 3 | from graphql import GRAPHQL_MAX_INT 4 | from strawberry import Info 5 | from strawberry.extensions.field_extension import AsyncExtensionResolver, FieldExtension 6 | 7 | 8 | class LimitsRule(FieldExtension): 9 | min_limit: ClassVar[int] = 0 10 | max_limit: ClassVar[int] = 50 11 | min_offset: ClassVar[int] = 0 12 | 13 | def _validate_limit(self, limit: int, /) -> None: 14 | if not self.min_limit <= limit <= self.max_limit: 15 | raise ValueError(f"limit violation. Allowed range is {self.min_limit}-{self.max_limit}, current={limit}.") 16 | 17 | def _validate_offset(self, offset: int, /) -> None: 18 | if not self.min_offset <= offset <= GRAPHQL_MAX_INT: 19 | raise ValueError( 20 | f"offset violation. Allowed range is {self.min_offset}-{GRAPHQL_MAX_INT}, current={offset}." 21 | ) 22 | 23 | def validate_kwargs(self, kwargs: dict, /) -> None: 24 | self._validate_limit(kwargs["limit"]) 25 | self._validate_offset(kwargs["offset"]) 26 | 27 | async def resolve_async( 28 | self, 29 | next_: AsyncExtensionResolver, 30 | source: Any, 31 | info: Info, 32 | **kwargs, 33 | ): 34 | self.validate_kwargs(kwargs) 35 | 36 | return await next_(source, info, **kwargs) 37 | -------------------------------------------------------------------------------- /futuramaapi/helpers/templates.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import TYPE_CHECKING, Any 3 | 4 | from fastapi.templating import Jinja2Templates as _Jinja2Templates 5 | from jinja2 import Environment, pass_context 6 | from starlette.datastructures import URL 7 | 8 | if TYPE_CHECKING: 9 | from fastapi import Request 10 | 11 | TEMPLATES_PATH: Path = Path("templates") 12 | 13 | 14 | class Jinja2Templates(_Jinja2Templates): 15 | def _setup_env_defaults(self, env: Environment) -> None: 16 | @pass_context 17 | def url_for( 18 | context: dict[str, Any], 19 | name: str, 20 | /, 21 | **path_params: Any, 22 | ) -> URL: 23 | request: Request = context["request"] 24 | return request.url_for(name, **path_params) 25 | 26 | env.globals.setdefault("url_for", url_for) 27 | 28 | @pass_context 29 | def relative_path_for( 30 | context: dict[str, Any], 31 | name: str, 32 | /, 33 | **path_params: Any, 34 | ) -> URL: 35 | request: Request = context["request"] 36 | http_url: URL = request.url_for(name, **path_params) 37 | return http_url.replace(netloc="", scheme="") 38 | 39 | env.globals.setdefault("relative_path_for", relative_path_for) 40 | 41 | 42 | templates: Jinja2Templates = Jinja2Templates(TEMPLATES_PATH) 43 | -------------------------------------------------------------------------------- /templates/password_change.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block titile %}Password Reset{% endblock %} | Futurama API 4 | 5 | {% block main_info %}{% endblock %} 6 | 7 | {% block main_content %} 8 |
11 |

Password Reset

12 |

13 | Dear {{ user.name }} {{ user.surname }}, you requested password change. 14 | You have 15 minutes to reset the password. 15 |

16 |
20 | 25 |
28 | 33 | 39 |
40 |
43 | 48 | 54 |
55 | 60 |
61 |
62 | {% endblock %} 63 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12.0-slim-bullseye AS python-base 2 | 3 | # Environ 4 | ENV PYTHONUNBUFFERED=1 \ 5 | PYTHONDONTWRITEBYTECODE=1 \ 6 | PATH="${PATH}:/root/.local/bin" \ 7 | POETRY_HOME=/opt/poetry \ 8 | POETRY_VENV=/opt/poetry-venv \ 9 | POETRY_CACHE_DIR=/opt/.cache 10 | 11 | RUN curl -sSL https://install.python-poetry.org | python3 - 12 | 13 | # Install dependencies 14 | RUN apt-get update 15 | RUN apt-get -y install make 16 | 17 | # Create stage for Poetry installation 18 | FROM python-base AS poetry-base 19 | 20 | # Creating a virtual environment just for poetry and install it with pip 21 | RUN python3 -m venv $POETRY_VENV \ 22 | && $POETRY_VENV/bin/pip install poetry 23 | 24 | # Create a new stage from the base python image 25 | FROM python-base AS futuramaapi-app 26 | 27 | ARG APP_USER=futuramaapi 28 | ARG WORK_DIR=/app 29 | 30 | # Copy Poetry to app image 31 | COPY --from=poetry-base ${POETRY_VENV} ${POETRY_VENV} 32 | 33 | # Add Poetry to PATH 34 | ENV PATH="${PATH}:${POETRY_VENV}/bin" 35 | 36 | WORKDIR ${WORK_DIR} 37 | 38 | # Copy Dependencies 39 | COPY . ${WORK_DIR} 40 | 41 | # Install Dependencies 42 | RUN poetry install --no-interaction --no-cache --without dev --without test 43 | 44 | EXPOSE 8000 45 | 46 | # Add user 47 | RUN groupadd \ 48 | --system ${APP_USER} \ 49 | && useradd --no-log-init --system --gid ${APP_USER} ${APP_USER} 50 | 51 | # Set project user 52 | USER ${APP_USER}:${APP_USER} 53 | 54 | # Main launch command 55 | CMD ["./docker-entrypoint.sh"] 56 | -------------------------------------------------------------------------------- /futuramaapi/repositories/migrations/versions/d7ce6e6090f5_add_requests_counter.py: -------------------------------------------------------------------------------- 1 | """Add requests counter 2 | 3 | Revision ID: d7ce6e6090f5 4 | Revises: 81f374066bbf 5 | Create Date: 2025-05-09 19:51:59.241837 6 | 7 | """ 8 | 9 | from collections.abc import Sequence 10 | 11 | import sqlalchemy as sa 12 | from alembic import op 13 | 14 | revision: str = "d7ce6e6090f5" 15 | down_revision: str | None = "81f374066bbf" 16 | branch_labels: str | Sequence[str] | None = None 17 | depends_on: str | Sequence[str] | None = None 18 | 19 | 20 | def upgrade() -> None: 21 | op.create_table( 22 | "requests_counter", 23 | sa.Column( 24 | "url", 25 | sa.VARCHAR(length=64), 26 | nullable=False, 27 | ), 28 | sa.Column( 29 | "counter", 30 | sa.BIGINT(), 31 | nullable=False, 32 | ), 33 | sa.Column( 34 | "id", 35 | sa.Integer(), 36 | nullable=False, 37 | ), 38 | sa.Column( 39 | "created_at", 40 | sa.DateTime(timezone=True), 41 | server_default=sa.text("now()"), 42 | nullable=False, 43 | ), 44 | sa.Column( 45 | "uuid", 46 | sa.UUID(), 47 | nullable=False, 48 | ), 49 | sa.PrimaryKeyConstraint("id"), 50 | sa.UniqueConstraint("url"), 51 | sa.UniqueConstraint("uuid"), 52 | ) 53 | 54 | 55 | def downgrade() -> None: 56 | op.drop_table("requests_counter") 57 | -------------------------------------------------------------------------------- /futuramaapi/repositories/migrations/versions/ca664de1bf44_add_system_message.py: -------------------------------------------------------------------------------- 1 | """Add system message 2 | 3 | Revision ID: ca664de1bf44 4 | Revises: d7ce6e6090f5 5 | Create Date: 2025-05-09 22:02:13.243371 6 | 7 | """ 8 | 9 | from collections.abc import Sequence 10 | 11 | import sqlalchemy as sa 12 | from alembic import op 13 | 14 | revision: str = "ca664de1bf44" 15 | down_revision: str | None = "d7ce6e6090f5" 16 | branch_labels: str | Sequence[str] | None = None 17 | depends_on: str | Sequence[str] | None = None 18 | 19 | 20 | def upgrade() -> None: 21 | op.create_table( 22 | "system_messages", 23 | sa.Column( 24 | "message", 25 | sa.TEXT(), 26 | nullable=False, 27 | ), 28 | sa.Column( 29 | "author_name", 30 | sa.VARCHAR(length=64), 31 | nullable=False, 32 | ), 33 | sa.Column( 34 | "id", 35 | sa.Integer(), 36 | nullable=False, 37 | ), 38 | sa.Column( 39 | "created_at", 40 | sa.DateTime(timezone=True), 41 | server_default=sa.text("now()"), 42 | nullable=False, 43 | ), 44 | sa.Column( 45 | "uuid", 46 | sa.UUID(), 47 | nullable=False, 48 | ), 49 | sa.PrimaryKeyConstraint("id"), 50 | sa.UniqueConstraint("author_name"), 51 | sa.UniqueConstraint("uuid"), 52 | ) 53 | 54 | 55 | def downgrade() -> None: 56 | op.drop_table("system_messages") 57 | -------------------------------------------------------------------------------- /futuramaapi/routers/rest/tokens/dependencies.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated 2 | 3 | from fastapi import Depends, HTTPException, status 4 | from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm 5 | from sqlalchemy.ext.asyncio.session import AsyncSession 6 | 7 | from futuramaapi.repositories.session import get_async_session 8 | from futuramaapi.routers.exceptions import ModelNotFoundError 9 | from futuramaapi.routers.rest.users.schemas import User, UserPasswordError 10 | 11 | from .schemas import DecodedTokenError, UserToken, UserTokenRefreshRequest 12 | 13 | _oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/tokens/users/auth") 14 | 15 | 16 | async def from_form_data( 17 | form_data: Annotated[OAuth2PasswordRequestForm, Depends()], 18 | session: AsyncSession = Depends(get_async_session), # noqa: B008 19 | ) -> User: 20 | try: 21 | user: User = await User.auth(session, form_data.username, form_data.password) 22 | except (ModelNotFoundError, UserPasswordError): 23 | raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) from None 24 | 25 | return user 26 | 27 | 28 | def refresh_token( 29 | token: Annotated[str, Depends(_oauth2_scheme)], 30 | data: UserTokenRefreshRequest, 31 | ) -> UserToken: 32 | token_: UserToken = UserToken( 33 | access_token=token, 34 | refresh_token=data.refresh_token, 35 | ) 36 | 37 | try: 38 | token_.refresh() 39 | except DecodedTokenError: 40 | raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) from None 41 | 42 | return token_ 43 | -------------------------------------------------------------------------------- /futuramaapi/routers/services/seasons/get_season.py: -------------------------------------------------------------------------------- 1 | from abc import ABC 2 | 3 | from fastapi import HTTPException, status 4 | from pydantic import Field 5 | from sqlalchemy import Select, select 6 | from sqlalchemy.exc import NoResultFound 7 | from sqlalchemy.orm import selectinload 8 | 9 | from futuramaapi.helpers.pydantic import BaseModel 10 | from futuramaapi.repositories.models import SeasonModel 11 | from futuramaapi.repositories.session import session_manager 12 | from futuramaapi.routers.services._base import BaseService 13 | 14 | 15 | class GetSeasonResponse(BaseModel): 16 | class Episode(BaseModel): 17 | id: int 18 | name: str 19 | broadcast_number: int = Field(alias="number") 20 | production_code: str = Field( 21 | examples=[ 22 | "1ACV01", 23 | ], 24 | ) 25 | 26 | id: int 27 | episodes: list[Episode] 28 | 29 | 30 | class GetSeasonService(BaseService, ABC): 31 | pk: int 32 | 33 | @property 34 | def statement(self) -> Select: 35 | return select(SeasonModel).where(SeasonModel.id == self.pk).options(selectinload(SeasonModel.episodes)) 36 | 37 | async def __call__(self, *args, **kwargs) -> GetSeasonResponse: 38 | async with session_manager.session() as session: 39 | try: 40 | season_model: SeasonModel = (await session.execute(self.statement)).scalars().one() 41 | except NoResultFound: 42 | raise HTTPException( 43 | detail="Season not found", 44 | status_code=status.HTTP_404_NOT_FOUND, 45 | ) from None 46 | 47 | return GetSeasonResponse.model_validate(season_model) 48 | -------------------------------------------------------------------------------- /futuramaapi/helpers/pydantic.py: -------------------------------------------------------------------------------- 1 | import json 2 | import uuid 3 | from typing import Any, ClassVar 4 | 5 | import pydash 6 | from cryptography.fernet import Fernet 7 | from pydantic import BaseModel as BaseModelOrig 8 | from pydantic import ConfigDict, Field, SecretStr 9 | from pydash import camel_case 10 | 11 | from futuramaapi.core import settings 12 | from futuramaapi.helpers.hashers import PasswordHasherBase, hasher 13 | 14 | 15 | class BaseModel(BaseModelOrig): 16 | hasher: ClassVar[PasswordHasherBase] = hasher 17 | encryptor: ClassVar[Fernet] = settings.fernet 18 | 19 | model_config = ConfigDict( 20 | from_attributes=True, 21 | populate_by_name=True, 22 | alias_generator=camel_case, 23 | ) 24 | 25 | def to_dict(self, *, by_alias: bool = True, reveal_secrets: bool = False, exclude_unset=False) -> dict: 26 | result: dict = json.loads(self.model_dump_json(by_alias=by_alias, exclude_unset=exclude_unset)) 27 | if reveal_secrets is False: 28 | return result 29 | 30 | secret_dict: dict = {} 31 | name: str 32 | for name in self.model_fields_set: 33 | field: Any = getattr(self, name) 34 | if isinstance(field, SecretStr): 35 | secret_dict.update( 36 | { 37 | name: field.get_secret_value(), 38 | } 39 | ) 40 | return pydash.merge(result, secret_dict) 41 | 42 | 43 | class BaseTokenModel(BaseModel): 44 | def refresh_nonce(self) -> None: 45 | self.nonce = self._get_nonce() 46 | 47 | @staticmethod 48 | def _get_nonce() -> str: 49 | return uuid.uuid4().hex 50 | 51 | nonce: str = Field(default_factory=_get_nonce) 52 | -------------------------------------------------------------------------------- /futuramaapi/repositories/migrations/versions/2693764b6723_add_secret_message_model.py: -------------------------------------------------------------------------------- 1 | """Add secret message model 2 | 3 | Revision ID: 2693764b6723 4 | Revises: 4d5b68e5d9df 5 | Create Date: 2024-07-03 22:08:21.410931 6 | """ 7 | 8 | from collections.abc import Sequence 9 | 10 | import sqlalchemy as sa 11 | from alembic import op 12 | 13 | revision: str = "2693764b6723" 14 | down_revision: str | None = "4d5b68e5d9df" 15 | branch_labels: str | Sequence[str] | None = None 16 | depends_on: str | Sequence[str] | None = None 17 | 18 | 19 | def upgrade() -> None: 20 | op.create_table( 21 | "secret_messages", 22 | sa.Column( 23 | "text", 24 | sa.TEXT(), 25 | nullable=False, 26 | ), 27 | sa.Column( 28 | "visit_counter", 29 | sa.BIGINT(), 30 | nullable=False, 31 | ), 32 | sa.Column( 33 | "ip_address", 34 | sa.VARCHAR(length=64), 35 | nullable=False, 36 | ), 37 | sa.Column( 38 | "url", 39 | sa.VARCHAR(length=128), 40 | nullable=False, 41 | ), 42 | sa.Column( 43 | "id", 44 | sa.Integer(), 45 | nullable=False, 46 | ), 47 | sa.Column( 48 | "created_at", 49 | sa.DateTime(timezone=True), 50 | server_default=sa.text("now()"), 51 | nullable=False, 52 | ), 53 | sa.Column( 54 | "uuid", 55 | sa.UUID(), 56 | nullable=False, 57 | ), 58 | sa.PrimaryKeyConstraint("id"), 59 | sa.UniqueConstraint("url"), 60 | sa.UniqueConstraint("uuid"), 61 | ) 62 | 63 | 64 | def downgrade() -> None: 65 | op.drop_table("secret_messages") 66 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% set active_page = "index" %} 4 | 5 | {% block main_content %} 6 | {% if characters %} 7 |
10 |
13 |
16 | {% for character in characters %} 17 |
20 |
23 | {{ character.name }} 27 |
30 |

33 | Gender: {{ character.gender.value|capitalize }}. 34 |
35 | Status: {{ character.status.value|capitalize }}. 36 |

37 |
40 | 52 | 55 | {{ character.name }} 56 | 57 |
58 |
59 |
60 |
61 | {% endfor %} 62 |
63 |
64 |
65 | {% endif %} 66 | {% endblock %} 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Django # 2 | *.log 3 | *.pot 4 | *.pyc 5 | __pycache__ 6 | db.sqlite3 7 | media 8 | 9 | # Backup files # 10 | *.bak 11 | 12 | # PyCharm 13 | .idea/ 14 | 15 | # File-based project format 16 | *.iws 17 | 18 | # IntelliJ 19 | out/ 20 | 21 | # JIRA plugin 22 | atlassian-ide-plugin.xml 23 | 24 | # Python # 25 | *.py[cod] 26 | *$py.class 27 | 28 | # Distribution / packaging 29 | .Python build/ 30 | develop-eggs/ 31 | dist/ 32 | downloads/ 33 | eggs/ 34 | .eggs/ 35 | lib/ 36 | lib64/ 37 | parts/ 38 | sdist/ 39 | var/ 40 | wheels/ 41 | *.egg-info/ 42 | .installed.cfg 43 | *.egg 44 | *.manifest 45 | *.spec 46 | 47 | # Installer logs 48 | pip-log.txt 49 | pip-delete-this-directory.txt 50 | 51 | # Unit test / coverage reports 52 | htmlcov/ 53 | .tox/ 54 | .coverage 55 | .coverage.* 56 | .cache 57 | .pytest_cache/ 58 | nosetests.xml 59 | coverage.xml 60 | *.cover 61 | .hypothesis/ 62 | 63 | # Jupyter Notebook 64 | .ipynb_checkpoints 65 | 66 | # pyenv 67 | .python-version 68 | 69 | # celery 70 | celerybeat-schedule.* 71 | 72 | # SageMath parsed files 73 | *.sage.py 74 | 75 | # Environments 76 | .env 77 | .venv 78 | env/ 79 | venv/ 80 | ENV/ 81 | env.bak/ 82 | venv.bak/ 83 | 84 | # mkdocs documentation 85 | /site 86 | 87 | # mypy 88 | .mypy_cache/ 89 | 90 | # Sublime Text # 91 | *.tmlanguage.cache 92 | *.tmPreferences.cache 93 | *.stTheme.cache 94 | *.sublime-workspace 95 | *.sublime-project 96 | 97 | # sftp configuration file 98 | sftp-config.json 99 | 100 | # Package control specific files Package 101 | Control.last-run 102 | Control.ca-list 103 | Control.ca-bundle 104 | Control.system-ca-bundle 105 | GitHub.sublime-settings 106 | 107 | # Custom 108 | instructions.txt 109 | log/ 110 | /.env.dev 111 | *.mo 112 | 113 | # WSGI 114 | pids/*.pid 115 | -------------------------------------------------------------------------------- /futuramaapi/routers/rest/tokens/api.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated 2 | 3 | from fastapi import APIRouter, Depends, status 4 | 5 | from futuramaapi.routers.exceptions import UnauthorizedResponse 6 | from futuramaapi.routers.rest.users.schemas import User 7 | 8 | from .dependencies import from_form_data, refresh_token 9 | from .schemas import UserToken 10 | 11 | router = APIRouter( 12 | prefix="/tokens", 13 | tags=["tokens"], 14 | ) 15 | 16 | 17 | @router.post( 18 | "/users/auth", 19 | responses={ 20 | status.HTTP_401_UNAUTHORIZED: { 21 | "model": UnauthorizedResponse, 22 | }, 23 | }, 24 | response_model=UserToken, 25 | name="user_token_auth", 26 | ) 27 | async def token_auth_user( 28 | user: Annotated[User, Depends(from_form_data)], 29 | ) -> UserToken: 30 | """Authenticate user. 31 | 32 | JSON Web Token (JWT) authentication is a popular method for securing web applications and APIs. 33 | It enables the exchange of digitally signed tokens between a client (user) and a server, 34 | to authenticate and authorize users. 35 | 36 | Use a token in a response to get secured stored data of your user. 37 | """ 38 | return UserToken.from_user(user) 39 | 40 | 41 | @router.post( 42 | "/users/refresh", 43 | responses={ 44 | status.HTTP_401_UNAUTHORIZED: { 45 | "model": UnauthorizedResponse, 46 | }, 47 | }, 48 | response_model=UserToken, 49 | name="user_token_auth_refresh", 50 | ) 51 | async def refresh_token_auth_user( 52 | token: Annotated[UserToken, Depends(refresh_token)], 53 | ) -> UserToken: 54 | """Refresh JWT. 55 | 56 | The Refresh JWT Token endpoint extends the lifespan of JSON Web Tokens (JWTs) without requiring user 57 | reauthentication. This API feature ensures uninterrupted access to secured resources. 58 | """ 59 | return token 60 | -------------------------------------------------------------------------------- /futuramaapi/routers/rest/notifications/api.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated 2 | 3 | from fastapi import APIRouter, Depends, HTTPException, Path, Request, status 4 | from sqlalchemy.ext.asyncio.session import AsyncSession 5 | from sse_starlette.sse import EventSourceResponse 6 | 7 | from futuramaapi.repositories import INT32 8 | from futuramaapi.repositories.session import get_async_session 9 | from futuramaapi.routers.exceptions import ModelNotFoundError 10 | 11 | from .schemas import CharacterNotification 12 | 13 | router = APIRouter( 14 | prefix="/notifications", 15 | tags=["notifications"], 16 | ) 17 | 18 | 19 | @router.get( 20 | "/sse/characters/{character_id}", 21 | response_class=EventSourceResponse, 22 | responses={ 23 | status.HTTP_200_OK: { 24 | "model": CharacterNotification, 25 | } 26 | }, 27 | status_code=status.HTTP_200_OK, 28 | ) 29 | async def character_sse( 30 | character_id: Annotated[ 31 | int, 32 | Path( 33 | le=INT32, 34 | ), 35 | ], 36 | request: Request, 37 | session: AsyncSession = Depends(get_async_session), # noqa: B008 38 | ) -> EventSourceResponse: 39 | """Retrieve character path. 40 | 41 | Server-Sent Events (SSE) Endpoint for Character Paths: 42 | 43 | This SSE endpoint is designed for retrieving characters paths by passing the character ID. 44 | It facilitates real-time updates on character path. 45 | Exercise caution when using this endpoint to ensure responsible and accurate data retrieval. 46 | """ 47 | try: 48 | return await CharacterNotification.from_request(character_id, request, session) 49 | except ModelNotFoundError: 50 | raise HTTPException( 51 | status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, 52 | detail=f"Character with id={character_id} not found", 53 | ) from None 54 | -------------------------------------------------------------------------------- /futuramaapi/repositories/migrations/versions/81f374066bbf_add_auth_session.py: -------------------------------------------------------------------------------- 1 | """Add auth session 2 | 3 | Revision ID: 81f374066bbf 4 | Revises: 2693764b6723 5 | Create Date: 2024-07-12 16:11:24.004642 6 | 7 | """ 8 | 9 | from collections.abc import Sequence 10 | 11 | import sqlalchemy as sa 12 | from alembic import op 13 | 14 | revision: str = "81f374066bbf" 15 | down_revision: str | None = "2693764b6723" 16 | branch_labels: str | Sequence[str] | None = None 17 | depends_on: str | Sequence[str] | None = None 18 | 19 | 20 | def upgrade() -> None: 21 | op.create_table( 22 | "auth_sessions", 23 | sa.Column( 24 | "key", 25 | sa.VARCHAR(length=32), 26 | nullable=False, 27 | ), 28 | sa.Column( 29 | "ip_address", 30 | sa.VARCHAR(length=64), 31 | nullable=False, 32 | ), 33 | sa.Column( 34 | "user_id", 35 | sa.Integer(), 36 | nullable=False, 37 | ), 38 | sa.Column( 39 | "id", 40 | sa.Integer(), 41 | nullable=False, 42 | ), 43 | sa.Column( 44 | "created_at", 45 | sa.DateTime(timezone=True), 46 | server_default=sa.text("now()"), 47 | nullable=False, 48 | ), 49 | sa.Column( 50 | "uuid", 51 | sa.UUID(), 52 | nullable=False, 53 | ), 54 | sa.Column( 55 | "expired", 56 | sa.Boolean(), 57 | nullable=False, 58 | ), 59 | sa.ForeignKeyConstraint( 60 | ["user_id"], 61 | ["users.id"], 62 | ), 63 | sa.PrimaryKeyConstraint("id"), 64 | sa.UniqueConstraint("key"), 65 | sa.UniqueConstraint("uuid"), 66 | ) 67 | 68 | 69 | def downgrade() -> None: 70 | op.drop_table("auth_sessions") 71 | -------------------------------------------------------------------------------- /futuramaapi/repositories/migrations/versions/4d5b68e5d9df_add_links_model.py: -------------------------------------------------------------------------------- 1 | """Add links model 2 | 3 | Revision ID: 4d5b68e5d9df 4 | Revises: ee5656c8dc7f 5 | Create Date: 2024-06-25 22:36:03.747684 6 | 7 | """ 8 | 9 | from collections.abc import Sequence 10 | 11 | import sqlalchemy as sa 12 | from alembic import op 13 | 14 | revision: str = "4d5b68e5d9df" 15 | down_revision: str | None = "ee5656c8dc7f" 16 | branch_labels: str | Sequence[str] | None = None 17 | depends_on: str | Sequence[str] | None = None 18 | 19 | 20 | def upgrade() -> None: 21 | op.create_table( 22 | "links", 23 | sa.Column( 24 | "url", 25 | sa.VARCHAR(length=4096), 26 | nullable=False, 27 | ), 28 | sa.Column( 29 | "shortened", 30 | sa.VARCHAR(length=128), 31 | nullable=False, 32 | ), 33 | sa.Column( 34 | "user_id", 35 | sa.Integer(), 36 | nullable=False, 37 | ), 38 | sa.Column( 39 | "id", 40 | sa.Integer(), 41 | nullable=False, 42 | ), 43 | sa.Column( 44 | "created_at", 45 | sa.DateTime(timezone=True), 46 | server_default=sa.text("now()"), 47 | nullable=False, 48 | ), 49 | sa.Column( 50 | "uuid", 51 | sa.UUID(), 52 | nullable=False, 53 | ), 54 | sa.Column( 55 | "counter", 56 | sa.BigInteger(), 57 | nullable=False, 58 | default=0, 59 | ), 60 | sa.ForeignKeyConstraint( 61 | ["user_id"], 62 | ["users.id"], 63 | ), 64 | sa.PrimaryKeyConstraint("id"), 65 | sa.UniqueConstraint("shortened"), 66 | sa.UniqueConstraint("uuid"), 67 | ) 68 | 69 | 70 | def downgrade() -> None: 71 | op.drop_table("links") 72 | -------------------------------------------------------------------------------- /futuramaapi/routers/graphql/mixins.py: -------------------------------------------------------------------------------- 1 | from typing import ClassVar, Self, cast 2 | 3 | from fastapi_storages.base import StorageImage 4 | from sqlalchemy.ext.asyncio.session import AsyncSession 5 | from strawberry.types.field import StrawberryField 6 | 7 | from futuramaapi.core import settings 8 | from futuramaapi.repositories import Base, FilterStatementKwargs, ModelDoesNotExistError 9 | 10 | from .conversion import ConverterBase, converter 11 | 12 | 13 | class StrawberryDatabaseMixin: 14 | model: ClassVar[type[Base]] 15 | 16 | converter: ClassVar[ConverterBase] = converter 17 | 18 | @classmethod 19 | def get_fields(cls) -> list[StrawberryField]: 20 | return cls.__strawberry_definition__.fields # type: ignore[attr-defined] 21 | 22 | @staticmethod 23 | def to_img(field: StorageImage | None, /) -> str | None: 24 | if field is None: 25 | return None 26 | 27 | return settings.build_url(path=field._name) 28 | 29 | @classmethod 30 | def from_model(cls, instance: Base, /) -> Self: 31 | return cls.converter.to_strawberry(cls, instance) 32 | 33 | @classmethod 34 | async def get(cls, session: AsyncSession, id_: int, /) -> Self | None: 35 | try: 36 | obj: Base = await cls.model.get( 37 | session, 38 | id_, 39 | ) 40 | except ModelDoesNotExistError: 41 | return None 42 | 43 | return cls.from_model(obj) 44 | 45 | @classmethod 46 | async def paginate(cls, session: AsyncSession, kwargs: FilterStatementKwargs, /) -> Self: 47 | total: int = await cls.model.count(session) 48 | edges: list[Base] = cast(list[Base], await cls.model.filter(session, kwargs)) 49 | 50 | return cls( 51 | limit=kwargs.limit, # type: ignore[call-arg] 52 | offset=kwargs.offset, # type: ignore[call-arg] 53 | total=total, # type: ignore[call-arg] 54 | edges=cls.converter.get_edges(cls, edges), # type: ignore[call-arg] 55 | ) 56 | -------------------------------------------------------------------------------- /futuramaapi/routers/rest/notifications/schemas.py: -------------------------------------------------------------------------------- 1 | from asyncio import sleep 2 | from collections.abc import AsyncGenerator 3 | from datetime import datetime 4 | from random import randint 5 | 6 | from fastapi import Request 7 | from pydantic import Field 8 | from sqlalchemy.ext.asyncio.session import AsyncSession 9 | from sse_starlette import EventSourceResponse, ServerSentEvent 10 | 11 | from futuramaapi.helpers.pydantic import BaseModel 12 | from futuramaapi.routers.rest.characters.schemas import Character 13 | 14 | MIN_COORDINATE: int = 0 15 | MAX_COORDINATE: int = 2**6 16 | 17 | 18 | class CharacterNotification(BaseModel): 19 | class Notification(BaseModel): 20 | time: datetime = Field(default_factory=datetime.now) 21 | x: int = Field( 22 | description="Character X coordinate", 23 | ge=MIN_COORDINATE, 24 | le=MAX_COORDINATE, 25 | ) 26 | y: int = Field( 27 | description="Character Y coordinate", 28 | ge=MIN_COORDINATE, 29 | le=MAX_COORDINATE, 30 | ) 31 | 32 | item: Character 33 | notification: Notification 34 | 35 | @classmethod 36 | async def get_move(cls, request: Request, character: Character, /) -> AsyncGenerator[ServerSentEvent]: 37 | while True: 38 | if await request.is_disconnected(): 39 | # Can be removed. Do not trust lib, force connection close. 40 | break 41 | 42 | yield ServerSentEvent( 43 | data=cls( 44 | item=character, 45 | notification=cls.Notification( 46 | x=randint(MIN_COORDINATE, MAX_COORDINATE), # noqa: S311 47 | y=randint(MIN_COORDINATE, MAX_COORDINATE), # noqa: S311 48 | ), 49 | ).model_dump() 50 | ) 51 | await sleep( 52 | randint(1, 3), # noqa: S311 53 | ) 54 | 55 | @classmethod 56 | async def from_request(cls, id_: int, request: Request, session: AsyncSession, /) -> EventSourceResponse: 57 | character: Character = await Character.get(session, id_) 58 | return EventSourceResponse(cls.get_move(request, character)) 59 | -------------------------------------------------------------------------------- /futuramaapi/routers/rest/seasons/api.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated 2 | 3 | from fastapi import APIRouter, Path, status 4 | from fastapi_pagination import Page 5 | 6 | from futuramaapi.repositories import INT32 7 | from futuramaapi.routers.exceptions import NotFoundResponse 8 | from futuramaapi.routers.services.seasons.get_season import ( 9 | GetSeasonResponse, 10 | GetSeasonService, 11 | ) 12 | from futuramaapi.routers.services.seasons.list_seasons import ( 13 | ListSeasonResponse, 14 | ListSeasonsService, 15 | ) 16 | 17 | router = APIRouter( 18 | prefix="/seasons", 19 | tags=["seasons"], 20 | ) 21 | 22 | 23 | @router.get( 24 | "/{season_id}", 25 | status_code=status.HTTP_200_OK, 26 | responses={ 27 | status.HTTP_404_NOT_FOUND: { 28 | "model": NotFoundResponse, 29 | }, 30 | }, 31 | response_model=GetSeasonResponse, 32 | name="season", 33 | ) 34 | async def get_season( 35 | season_id: Annotated[ 36 | int, 37 | Path( 38 | le=INT32, 39 | ), 40 | ], 41 | ) -> GetSeasonResponse: 42 | """Retrieve specific season. 43 | 44 | Utilize this endpoint to retrieve detailed information about a specific Futurama season by providing its unique ID. 45 | The response includes details such as the list of seasons, season ID, and more. 46 | 47 | Can be used to gain in-depth insights into a particular season of Futurama. 48 | """ 49 | service: GetSeasonService = GetSeasonService(pk=season_id) 50 | return await service() 51 | 52 | 53 | @router.get( 54 | "", 55 | status_code=status.HTTP_200_OK, 56 | response_model=Page[ListSeasonResponse], 57 | name="seasons", 58 | ) 59 | async def get_seasons() -> Page[ListSeasonResponse]: 60 | """Retrieve specific seasons. 61 | 62 | Access a comprehensive list of all Futurama seasons using this endpoint, 63 | providing users with a complete overview of the series chronological progression. 64 | The response includes details such as the list of episodes, season ID, and other. 65 | 66 | This endpoint is valuable for those interested in exploring the entirety of Futurama's seasons or implementing 67 | features like season browsing on your site. 68 | """ 69 | service: ListSeasonsService = ListSeasonsService() 70 | return await service() 71 | -------------------------------------------------------------------------------- /futuramaapi/repositories/migrations/versions/ee5656c8dc7f_define_user_model.py: -------------------------------------------------------------------------------- 1 | """Define user model 2 | 3 | Revision ID: ee5656c8dc7f 4 | Revises: 1b86ee33d1ba 5 | Create Date: 2024-01-21 21:40:59.557432 6 | """ 7 | 8 | from collections.abc import Sequence 9 | 10 | import sqlalchemy as sa 11 | from alembic import op 12 | 13 | revision: str = "ee5656c8dc7f" 14 | down_revision: str | None = "1b86ee33d1ba" 15 | branch_labels: str | Sequence[str] | None = None 16 | depends_on: str | Sequence[str] | None = None 17 | 18 | 19 | def upgrade() -> None: 20 | op.create_table( 21 | "users", 22 | sa.Column( 23 | "name", 24 | sa.VARCHAR(length=64), 25 | nullable=False, 26 | ), 27 | sa.Column( 28 | "surname", 29 | sa.VARCHAR(length=64), 30 | nullable=False, 31 | ), 32 | sa.Column( 33 | "middle_name", 34 | sa.VARCHAR(length=64), 35 | nullable=True, 36 | ), 37 | sa.Column( 38 | "email", 39 | sa.VARCHAR(length=320), 40 | nullable=False, 41 | ), 42 | sa.Column( 43 | "username", 44 | sa.VARCHAR(length=64), 45 | nullable=False, 46 | ), 47 | sa.Column( 48 | "password", 49 | sa.VARCHAR(length=128), 50 | nullable=False, 51 | ), 52 | sa.Column( 53 | "is_confirmed", 54 | sa.Boolean(), 55 | nullable=True, 56 | ), 57 | sa.Column( 58 | "is_subscribed", 59 | sa.Boolean(), 60 | nullable=True, 61 | ), 62 | sa.Column( 63 | "id", 64 | sa.Integer(), 65 | nullable=False, 66 | ), 67 | sa.Column( 68 | "created_at", 69 | sa.DateTime(timezone=True), 70 | server_default=sa.text("now()"), 71 | nullable=False, 72 | ), 73 | sa.Column( 74 | "uuid", 75 | sa.UUID(), 76 | nullable=False, 77 | ), 78 | sa.PrimaryKeyConstraint("id"), 79 | sa.UniqueConstraint("email"), 80 | sa.UniqueConstraint("username"), 81 | sa.UniqueConstraint("uuid"), 82 | ) 83 | 84 | 85 | def downgrade() -> None: 86 | op.drop_table("users") 87 | -------------------------------------------------------------------------------- /futuramaapi/routers/rest/episodes/api.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated 2 | 3 | from fastapi import APIRouter, Depends, HTTPException, Path, status 4 | from fastapi_pagination import Page 5 | from sqlalchemy.ext.asyncio.session import AsyncSession 6 | 7 | from futuramaapi.repositories import INT32 8 | from futuramaapi.repositories.session import get_async_session 9 | from futuramaapi.routers.exceptions import ModelNotFoundError, NotFoundResponse 10 | 11 | from .schemas import Episode 12 | 13 | router = APIRouter( 14 | prefix="/episodes", 15 | tags=["episodes"], 16 | ) 17 | 18 | 19 | @router.get( 20 | "/{episode_id}", 21 | status_code=status.HTTP_200_OK, 22 | responses={ 23 | status.HTTP_404_NOT_FOUND: { 24 | "model": NotFoundResponse, 25 | }, 26 | }, 27 | response_model=Episode, 28 | name="episode", 29 | ) 30 | async def get_episode( 31 | episode_id: Annotated[ 32 | int, 33 | Path( 34 | le=INT32, 35 | ), 36 | ], 37 | session: AsyncSession = Depends(get_async_session), # noqa: B008 38 | ) -> Episode: 39 | """Retrieve specific episode. 40 | 41 | This endpoint allows you to retrieve detailed information about a specific Futurama episode by providing its 42 | unique ID. The response will include essential details such as the episode name, air date, duration, season, 43 | production ID, broadcast ID, and other relevant information. 44 | 45 | Can be used to get in-depth information about a particular 46 | episode of Futurama. 47 | """ 48 | try: 49 | return await Episode.get(session, episode_id) 50 | except ModelNotFoundError: 51 | raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) from None 52 | 53 | 54 | @router.get( 55 | "", 56 | status_code=status.HTTP_200_OK, 57 | response_model=Page[Episode], 58 | name="episodes", 59 | ) 60 | async def get_episodes( 61 | session: AsyncSession = Depends(get_async_session), # noqa: B008 62 | ) -> Page[Episode]: 63 | """Retrieve episodes. 64 | 65 | This endpoint provides a paginated list of Futurama episodes, offering a comprehensive overview 66 | of the entire series. 67 | 68 | Can be used to access a collection of episode names, air dates, seasons, durations, 69 | and other relevant details. It's particularly useful for those who want to explore the entire catalog of Futurama 70 | episodes or implement features such as episode browsing on your site. 71 | """ 72 | return await Episode.paginate(session) 73 | -------------------------------------------------------------------------------- /futuramaapi/middlewares/secure.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import ClassVar 3 | 4 | from starlette import status 5 | from starlette.datastructures import URL 6 | from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint 7 | from starlette.requests import Request 8 | from starlette.responses import RedirectResponse, Response 9 | from starlette.types import Scope 10 | 11 | from futuramaapi.core import settings 12 | 13 | logger = logging.getLogger(__name__) 14 | 15 | 16 | class HTTPSRedirectMiddleware(BaseHTTPMiddleware): 17 | https_port: int = 443 18 | http_port: int = 80 19 | insecure_to_secure: ClassVar[dict[str, str]] = { 20 | "http": "https", 21 | "ws": "wss", 22 | } 23 | 24 | def is_secure(self, headers: dict): 25 | try: 26 | host: str = headers["host"] 27 | except KeyError: 28 | logger.info("Host not found in headers") 29 | return False 30 | try: 31 | proto: str = headers["x-forwarded-proto"] 32 | except KeyError: 33 | logger.info("x-forwarded-proto not found in headers") 34 | return False 35 | try: 36 | port: str = headers["x-forwarded-port"] 37 | except KeyError: 38 | logger.info("x-forwarded-port not found in headers") 39 | return False 40 | 41 | if host == settings.trusted_host and proto in ("https", "wss") and int(port) == self.https_port: 42 | return True 43 | return False 44 | 45 | def _fix_url(self, scope: Scope, /): 46 | url = URL(scope=scope) 47 | redirect_scheme = self.insecure_to_secure[url.scheme] 48 | netloc = url.hostname if url.port in (self.http_port, self.https_port) else url.netloc 49 | return url.replace(scheme=redirect_scheme, netloc=netloc) 50 | 51 | @staticmethod 52 | def headers_to_dict(headers: list, /) -> dict: 53 | return {h[0].decode(): h[1].decode() for h in headers} 54 | 55 | async def dispatch( 56 | self, 57 | request: Request, 58 | call_next: RequestResponseEndpoint, 59 | ) -> Response: 60 | headers: dict = self.headers_to_dict(request.scope["headers"]) 61 | if self.is_secure(headers): 62 | return await call_next(request) 63 | 64 | url: URL = self._fix_url(request.scope) 65 | return RedirectResponse( 66 | url, 67 | status_code=status.HTTP_301_MOVED_PERMANENTLY, 68 | headers={h[0].decode(): h[1].decode() for h in request.scope["headers"]}, 69 | ) 70 | -------------------------------------------------------------------------------- /futuramaapi/routers/rest/tokens/schemas.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, ClassVar, Literal, Self 2 | 3 | from pydantic import Field 4 | 5 | from futuramaapi.helpers.pydantic import BaseModel, BaseTokenModel 6 | from futuramaapi.mixins.pydantic import BaseModelTokenMixin, DecodedTokenError 7 | 8 | if TYPE_CHECKING: 9 | from futuramaapi.routers.rest.users.schemas import User 10 | 11 | 12 | class _DecodedTokenBase(BaseTokenModel): 13 | type: Literal["access", "refresh"] 14 | 15 | 16 | class DecodedUserToken(_DecodedTokenBase, BaseModelTokenMixin): 17 | class _User(BaseModel): 18 | id: int 19 | 20 | user: _User 21 | 22 | @classmethod 23 | def from_user(cls, user: "User", type_: Literal["access", "refresh"], /) -> Self: 24 | return cls(type=type_, user=user.model_dump()) 25 | 26 | @classmethod 27 | def decode( 28 | cls, 29 | token: str, 30 | /, 31 | *, 32 | algorithm="HS256", 33 | allowed_type: Literal["access", "refresh"] = "access", 34 | ) -> Self: 35 | decoded_token: Self = super().decode(token, algorithm=algorithm) 36 | if decoded_token.type != allowed_type: 37 | raise DecodedTokenError() from None 38 | 39 | return decoded_token 40 | 41 | 42 | class UserTokenRefreshRequest(BaseModel): 43 | refresh_token: str 44 | 45 | 46 | class UserToken(BaseModel): 47 | access_token: str = Field( 48 | alias="access_token", 49 | description="Keep in mind, that the field is not in a camel case. That's the standard.", 50 | ) 51 | refresh_token: str = Field( 52 | alias="refresh_token", 53 | description="Keep in mind, that the field is not in a camel case. That's the standard.", 54 | ) 55 | 56 | _default_access_seconds: ClassVar[int] = 15 * 60 57 | _default_refresh_seconds: ClassVar[int] = 5 * 24 * 60 * 60 58 | 59 | @classmethod 60 | def from_user( 61 | cls, 62 | user: "User", 63 | /, 64 | ) -> Self: 65 | access: DecodedUserToken = DecodedUserToken.from_user(user, "access") 66 | refresh: DecodedUserToken = DecodedUserToken.from_user(user, "refresh") 67 | return cls( 68 | access_token=access.tokenize(cls._default_access_seconds), 69 | refresh_token=refresh.tokenize(cls._default_refresh_seconds), 70 | ) 71 | 72 | def refresh(self) -> None: 73 | try: 74 | access: DecodedUserToken = DecodedUserToken.decode(self.access_token) 75 | except DecodedTokenError: 76 | raise 77 | 78 | try: 79 | refresh: DecodedUserToken = DecodedUserToken.decode(self.refresh_token, allowed_type="refresh") 80 | except DecodedTokenError: 81 | raise 82 | 83 | access.refresh_nonce() 84 | refresh.refresh_nonce() 85 | 86 | self.access_token = access.tokenize(self._default_access_seconds) 87 | self.refresh_token = refresh.tokenize(self._default_refresh_seconds) 88 | -------------------------------------------------------------------------------- /futuramaapi/repositories/migrations/env.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from logging.config import fileConfig 3 | 4 | from alembic import context 5 | from sqlalchemy import pool 6 | from sqlalchemy.engine import Connection 7 | from sqlalchemy.ext.asyncio import async_engine_from_config 8 | 9 | from futuramaapi.core import settings 10 | from futuramaapi.repositories import ( 11 | Base, 12 | models, # noqa: F401, do not remove. 13 | ) 14 | 15 | # this is the Alembic Config object, which provides 16 | # access to the values within the .ini file in use. 17 | config = context.config 18 | config.set_main_option("sqlalchemy.url", str(settings.database_url)) 19 | 20 | # Interpret the config file for Python logging. 21 | # This line sets up loggers basically. 22 | if config.config_file_name is not None: 23 | fileConfig(config.config_file_name) 24 | 25 | # add your model's MetaData object here 26 | # for 'autogenerate' support 27 | # from myapp import mymodel 28 | # target_metadata = mymodel.Base.metadata 29 | target_metadata = Base.metadata 30 | 31 | # other values from the config, defined by the needs of env.py, 32 | # can be acquired: 33 | # my_important_option = config.get_main_option("my_important_option") 34 | # ... etc. 35 | 36 | 37 | def run_migrations_offline() -> None: 38 | """Run migrations in 'offline' mode. 39 | 40 | This configures the context with just a URL 41 | and not an Engine, though an Engine is acceptable 42 | here as well. By skipping the Engine creation 43 | we don't even need a DBAPI to be available. 44 | 45 | Calls to context.execute() here emit the given string to the 46 | script output. 47 | 48 | """ 49 | url = config.get_main_option("sqlalchemy.url") 50 | context.configure( 51 | url=url, 52 | target_metadata=target_metadata, 53 | literal_binds=True, 54 | dialect_opts={"paramstyle": "named"}, 55 | ) 56 | 57 | with context.begin_transaction(): 58 | context.run_migrations() 59 | 60 | 61 | def do_run_migrations(connection: Connection) -> None: 62 | context.configure(connection=connection, target_metadata=target_metadata) 63 | 64 | with context.begin_transaction(): 65 | context.run_migrations() 66 | 67 | 68 | async def run_async_migrations() -> None: 69 | """In this scenario we need to create an Engine 70 | and associate a connection with the context. 71 | 72 | """ 73 | 74 | connectable = async_engine_from_config( 75 | config.get_section(config.config_ini_section, {}), 76 | prefix="sqlalchemy.", 77 | poolclass=pool.NullPool, 78 | ) 79 | 80 | async with connectable.connect() as connection: 81 | await connection.run_sync(do_run_migrations) 82 | 83 | await connectable.dispose() 84 | 85 | 86 | def run_migrations_online() -> None: 87 | """Run migrations in 'online' mode.""" 88 | 89 | asyncio.run(run_async_migrations()) 90 | 91 | 92 | if context.is_offline_mode(): 93 | run_migrations_offline() 94 | else: 95 | run_migrations_online() 96 | -------------------------------------------------------------------------------- /futuramaapi/routers/rest/crypto/api.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Depends, HTTPException, Request, status 2 | from sqlalchemy.ext.asyncio.session import AsyncSession 3 | 4 | from futuramaapi.repositories.session import get_async_session 5 | from futuramaapi.routers.exceptions import ModelNotFoundError, NotFoundResponse 6 | 7 | from .schemas import ( 8 | SecretMessage, 9 | SecretMessageCreateRequest, 10 | SecretMessageCreateResponse, 11 | ) 12 | 13 | router = APIRouter( 14 | prefix="/crypto", 15 | tags=["crypto"], 16 | ) 17 | 18 | 19 | @router.post( 20 | "/secret_message", 21 | status_code=status.HTTP_201_CREATED, 22 | response_model=SecretMessageCreateResponse, 23 | name="create_secret_message", 24 | ) 25 | async def create_secret_message( 26 | data: SecretMessageCreateRequest, 27 | session: AsyncSession = Depends(get_async_session), # noqa: B008 28 | ) -> SecretMessageCreateResponse: 29 | """Create Secret message.""" 30 | message: SecretMessage = await SecretMessage.create( 31 | session, 32 | data, 33 | extra_fields={ 34 | "ip_address": "", 35 | }, 36 | ) 37 | return SecretMessageCreateResponse(**message.model_dump()) 38 | 39 | 40 | @router.get( 41 | "/secret_message/{url}", 42 | status_code=status.HTTP_200_OK, 43 | response_model=SecretMessage, 44 | responses={ 45 | status.HTTP_404_NOT_FOUND: { 46 | "model": NotFoundResponse, 47 | }, 48 | }, 49 | name="get_secret_message", 50 | ) 51 | async def get_secret_message( 52 | url: str, 53 | request: Request, 54 | session: AsyncSession = Depends(get_async_session), # noqa: B008 55 | ) -> SecretMessage: 56 | """Get Secret message. 57 | 58 | Message will be shown only once. No excuses. 59 | After it's shown we delete the message itself and fill the message with random data, 60 | so it can't be decrypted or hacked or whatever. It's absolutely safe. 61 | 62 | If someone sent you a link, you follow the link, and you see ``visitCounter`` more than 1 - bad news. 63 | The URL has 128 length, which means there are 36 in power of 128 possibilities, it's more than stars in the 64 | universe, no chances anyone can ever accidentally access URL that was provided for you. 65 | 66 | One more time: If a person wants to send you a hidden message that is only for you, you follow the link, and 67 | it shows you some random data and ``visitCounter`` more than 1 it means someone already read the message, 68 | be aware! 69 | 70 | From our side we store the message in encrypted way, I won't be saying we can't read the message, BUT 71 | we respect privacy and don't reveal any private date, moreover after 1st successful shown the secret message 72 | we change the secret message with some random data, so no one, absolutely no one can recover/hack/whatever 73 | your secret data. 74 | """ 75 | try: 76 | return await SecretMessage.get_once(session, request, url) 77 | except ModelNotFoundError: 78 | raise HTTPException( 79 | status_code=status.HTTP_404_NOT_FOUND, 80 | detail="Not Found", 81 | ) from None 82 | -------------------------------------------------------------------------------- /futuramaapi/repositories/session.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from collections.abc import AsyncIterator 3 | from contextlib import asynccontextmanager 4 | from typing import Any 5 | 6 | from pydantic import PostgresDsn 7 | from redis.asyncio import ConnectionPool, Redis 8 | from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine, AsyncSession, async_sessionmaker, create_async_engine 9 | 10 | from futuramaapi.core import settings 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | class SessionManager: 16 | def __init__(self, host: PostgresDsn, /, *, kwargs: dict[str, Any] | None = None) -> None: 17 | if kwargs is None: 18 | kwargs = {} 19 | 20 | self.engine: AsyncEngine | None = create_async_engine(str(host), **kwargs) 21 | self._session_maker: async_sessionmaker[AsyncSession] | None = async_sessionmaker( 22 | autocommit=False, 23 | bind=self.engine, 24 | expire_on_commit=False, 25 | ) 26 | self.redis_pool: ConnectionPool | None = settings.redis.pool 27 | 28 | async def close(self) -> None: 29 | if self.engine is None: 30 | raise Exception("DatabaseSessionManager is not initialized") 31 | 32 | if self.redis_pool is None: 33 | raise Exception("Redis pool is not initialized") 34 | 35 | await self.engine.dispose() 36 | await self.redis_pool.aclose() # type: ignore[attr-defined] 37 | 38 | self.redis_pool = None 39 | self.engine = None 40 | self._session_maker = None 41 | 42 | @asynccontextmanager 43 | async def connect(self) -> AsyncIterator[AsyncConnection]: 44 | if self.engine is None: 45 | raise RuntimeError("DatabaseSessionManager is not initialized") 46 | 47 | async with self.engine.begin() as connection: 48 | try: 49 | yield connection 50 | except Exception: 51 | await connection.rollback() 52 | raise 53 | 54 | @asynccontextmanager 55 | async def session(self) -> AsyncIterator[AsyncSession]: 56 | if self._session_maker is None: 57 | raise Exception("DatabaseSessionManager is not initialized") 58 | 59 | session = self._session_maker() 60 | try: 61 | yield session 62 | except Exception: 63 | await session.rollback() 64 | raise 65 | finally: 66 | await session.close() 67 | 68 | @asynccontextmanager 69 | async def redis_session( 70 | self, 71 | ) -> AsyncIterator[Redis]: 72 | if self.redis_pool is None: 73 | raise Exception("Redis pool is not initialized") 74 | 75 | session: Redis = Redis(connection_pool=self.redis_pool) 76 | try: 77 | yield session 78 | except Exception: 79 | logger.exception("Redis error") 80 | raise 81 | finally: 82 | await session.aclose() # type: ignore[attr-defined] 83 | 84 | 85 | session_manager: SessionManager = SessionManager( 86 | settings.database_url, 87 | kwargs={ 88 | "echo": False, 89 | "max_overflow": settings.pool_max_overflow, 90 | "pool_size": settings.pool_size, 91 | "pool_timeout": settings.pool_timeout, 92 | "pool_recycle": settings.pool_recycle, 93 | }, 94 | ) 95 | 96 | 97 | async def get_async_session(): 98 | async with session_manager.session() as session: 99 | yield session 100 | 101 | 102 | async def get_redis_session(): 103 | async with session_manager.redis_session() as session: 104 | yield session 105 | -------------------------------------------------------------------------------- /futuramaapi/routers/rest/randoms/api.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Depends, HTTPException, status 2 | from sqlalchemy.ext.asyncio.session import AsyncSession 3 | 4 | from futuramaapi.repositories.session import get_async_session 5 | from futuramaapi.routers.exceptions import ModelNotFoundError, NotFoundResponse 6 | from futuramaapi.routers.rest.characters.schemas import Character 7 | from futuramaapi.routers.rest.episodes.schemas import Episode 8 | from futuramaapi.routers.rest.seasons.schemas import Season 9 | 10 | router = APIRouter( 11 | prefix="/random", 12 | tags=["random"], 13 | ) 14 | 15 | 16 | @router.get( 17 | "/character", 18 | status_code=status.HTTP_200_OK, 19 | responses={ 20 | status.HTTP_404_NOT_FOUND: { 21 | "model": NotFoundResponse, 22 | }, 23 | }, 24 | response_model=Character, 25 | name="random_character", 26 | ) 27 | async def get_random_character( 28 | session: AsyncSession = Depends(get_async_session), # noqa: B008 29 | ) -> Character: 30 | """Retrieve random character. 31 | 32 | This endpoint allows users to retrieve detailed information about a randomly selected Futurama character. 33 | The response includes essential details such as the character's name, status, gender, species, image, 34 | and other relevant attributes. 35 | 36 | Can be used to discover and explore random characters from the Futurama universe, 37 | offering a fun and engaging way to learn about different personalities in the series. 38 | """ 39 | try: 40 | return await Character.get_random(session) 41 | except ModelNotFoundError: 42 | raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) from None 43 | 44 | 45 | @router.get( 46 | "/episode", 47 | status_code=status.HTTP_200_OK, 48 | responses={ 49 | status.HTTP_404_NOT_FOUND: { 50 | "model": NotFoundResponse, 51 | }, 52 | }, 53 | response_model=Episode, 54 | name="random_episode", 55 | ) 56 | async def get_random_episode( 57 | session: AsyncSession = Depends(get_async_session), # noqa: B008 58 | ) -> Episode: 59 | """Retrieve random episode. 60 | 61 | This endpoint allows users to retrieve detailed information about a randomly selected Futurama episode. 62 | The response includes essential details such as the episode's title, season, episode number, air date, synopsis, 63 | and other relevant information. 64 | 65 | Perfect for when you're not sure which Futurama episode to watch - use this endpoint to get a randomly 66 | selected episode and dive right in. 67 | """ 68 | try: 69 | return await Episode.get_random(session) 70 | except ModelNotFoundError: 71 | raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) from None 72 | 73 | 74 | @router.get( 75 | "/season", 76 | status_code=status.HTTP_200_OK, 77 | responses={ 78 | status.HTTP_404_NOT_FOUND: { 79 | "model": NotFoundResponse, 80 | }, 81 | }, 82 | response_model=Season, 83 | name="random_season", 84 | ) 85 | async def get_random_season( 86 | session: AsyncSession = Depends(get_async_session), # noqa: B008 87 | ) -> Season: 88 | """Retrieve random season. 89 | 90 | This endpoint allows users to retrieve information about a randomly selected season from the Futurama series. 91 | The response includes key details such as the season number, list of episodes, and other relevant metadata. 92 | 93 | Great for when you can't decide where to start—use this endpoint to randomly pick a season and enjoy a 94 | fresh batch of Futurama episodes. 95 | """ 96 | try: 97 | return await Season.get_random(session) 98 | except ModelNotFoundError: 99 | raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) from None 100 | -------------------------------------------------------------------------------- /alembic.ini: -------------------------------------------------------------------------------- 1 | # A generic, single database configuration. 2 | 3 | [alembic] 4 | # path to migration scripts 5 | script_location = futuramaapi/repositories/migrations 6 | 7 | # template used to generate migration file names; The default value is %%(rev)s_%%(slug)s 8 | # Uncomment the line below if you want the files to be prepended with date and time 9 | # file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s 10 | 11 | # sys.path path, will be prepended to sys.path if present. 12 | # defaults to the current working directory. 13 | prepend_sys_path = . 14 | 15 | # timezone to use when rendering the date within the migration file 16 | # as well as the filename. 17 | # If specified, requires the python-dateutil library that can be 18 | # installed by adding `alembic[tz]` to the pip requirements 19 | # string value is passed to dateutil.tz.gettz() 20 | # leave blank for localtime 21 | # timezone = 22 | 23 | # max length of characters to apply to the 24 | # "slug" field 25 | # truncate_slug_length = 40 26 | 27 | # set to 'true' to run the environment during 28 | # the 'revision' command, regardless of autogenerate 29 | # revision_environment = false 30 | 31 | # set to 'true' to allow .pyc and .pyo files without 32 | # a source .py file to be detected as revisions in the 33 | # versions/ directory 34 | # sourceless = false 35 | 36 | # version location specification; This defaults 37 | # to alembic/versions. When using multiple version 38 | # directories, initial revisions must be specified with --version-path. 39 | # The path separator used here should be the separator specified by "version_path_separator" below. 40 | # version_locations = %(here)s/bar:%(here)s/bat:alembic/versions 41 | 42 | # version path separator; As mentioned above, this is the character used to split 43 | # version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. 44 | # If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. 45 | # Valid values for version_path_separator are: 46 | # 47 | # version_path_separator = : 48 | # version_path_separator = ; 49 | # version_path_separator = space 50 | version_path_separator = os # Use os.pathsep. Default configuration used for new projects. 51 | 52 | # set to 'true' to search source files recursively 53 | # in each "version_locations" directory 54 | # new in Alembic version 1.10 55 | # recursive_version_locations = false 56 | 57 | # the output encoding used when revision files 58 | # are written from script.py.mako 59 | # output_encoding = utf-8 60 | 61 | sqlalchemy.url = 62 | 63 | 64 | [post_write_hooks] 65 | # post_write_hooks defines scripts or Python functions that are run 66 | # on newly generated revision scripts. See the documentation for further 67 | # detail and examples 68 | 69 | # format using "black" - use the console_scripts runner, against the "black" entrypoint 70 | # hooks = black 71 | # black.type = console_scripts 72 | # black.entrypoint = black 73 | # black.options = -l 79 REVISION_SCRIPT_FILENAME 74 | 75 | # lint with attempts to fix using "ruff" - use the exec runner, execute a binary 76 | # hooks = ruff 77 | # ruff.type = exec 78 | # ruff.executable = %(here)s/.venv/bin/ruff 79 | # ruff.options = --fix REVISION_SCRIPT_FILENAME 80 | 81 | # Logging configuration 82 | [loggers] 83 | keys = root,sqlalchemy,alembic 84 | 85 | [handlers] 86 | keys = console 87 | 88 | [formatters] 89 | keys = generic 90 | 91 | [logger_root] 92 | level = WARN 93 | handlers = console 94 | qualname = 95 | 96 | [logger_sqlalchemy] 97 | level = WARN 98 | handlers = 99 | qualname = sqlalchemy.engine 100 | 101 | [logger_alembic] 102 | level = INFO 103 | handlers = 104 | qualname = alembic 105 | 106 | [handler_console] 107 | class = StreamHandler 108 | args = (sys.stderr,) 109 | level = NOTSET 110 | formatter = generic 111 | 112 | [formatter_generic] 113 | format = %(levelname)-5.5s [%(name)s] %(message)s 114 | datefmt = %H:%M:%S 115 | -------------------------------------------------------------------------------- /futuramaapi/routers/graphql/conversion.py: -------------------------------------------------------------------------------- 1 | """ 2 | Actually wanted to move this code to ``futuramaapi.routers.graphql.mixins.StrawberryDatabaseMixin``, but happened 3 | as happened, in this case there won't be a need to pass type, cls and so on. 4 | """ 5 | 6 | from abc import ABC, abstractmethod 7 | from functools import singledispatch 8 | from typing import TYPE_CHECKING, Any, cast 9 | 10 | from fastapi_storages.base import StorageImage 11 | from strawberry.types.base import StrawberryList, StrawberryOptional, has_object_definition 12 | from strawberry.types.enum import EnumDefinition 13 | from strawberry.types.union import StrawberryUnion 14 | 15 | from futuramaapi.core import settings 16 | from futuramaapi.repositories import Base 17 | 18 | if TYPE_CHECKING: 19 | from strawberry.types.field import StrawberryField 20 | 21 | 22 | @singledispatch 23 | def _convert( 24 | type_: Any, 25 | data: Any, 26 | /, 27 | ): 28 | if has_object_definition(type_): 29 | if hasattr(type(data), "_strawberry_type"): 30 | type_ = type(data)._strawberry_type 31 | if hasattr(type_, "from_model"): 32 | return type_.from_model(data) 33 | return _convert(type_, data) 34 | 35 | if isinstance(data, StorageImage): 36 | if data is None: 37 | return None 38 | 39 | return settings.build_url(path=data._name) 40 | 41 | return data 42 | 43 | 44 | @_convert.register 45 | def _(type_: StrawberryOptional, data: Any, /): 46 | if data is None: 47 | return data 48 | 49 | return _convert(type_.of_type, data) 50 | 51 | 52 | @_convert.register 53 | def _(type_: StrawberryUnion, data: Any, /): 54 | for option_type in type_.types: 55 | if hasattr(option_type, "_pydantic_type"): 56 | source_type = option_type._pydantic_type 57 | else: 58 | source_type = cast(type, option_type) 59 | if isinstance(data, source_type): 60 | return _convert(option_type, data) 61 | 62 | 63 | @_convert.register 64 | def _(type_: EnumDefinition, data: Any, /): 65 | return data 66 | 67 | 68 | @_convert.register 69 | def _(type_: StrawberryList, data: Any, /) -> list: 70 | items: list = [] 71 | for item in data: 72 | items.append(_convert(type_.of_type, item)) 73 | 74 | return items 75 | 76 | 77 | class ConverterBase(ABC): 78 | @staticmethod 79 | @abstractmethod 80 | def to_strawberry[S]( 81 | cls: type[S], # noqa: PLW0211 82 | model_instance: Base, 83 | /, 84 | ) -> S: ... 85 | 86 | @staticmethod 87 | @abstractmethod 88 | def get_edges[S]( 89 | cls: type[S], # noqa: PLW0211 90 | model_instance: list[Base], 91 | /, 92 | ) -> list[S] | None: ... 93 | 94 | 95 | class ModelConverter(ConverterBase): 96 | @staticmethod 97 | def to_strawberry[S]( 98 | cls: type[S], # noqa: PLW0211 99 | model_instance: Base, 100 | /, 101 | ) -> S: 102 | kwargs: dict = {} 103 | 104 | field: StrawberryField 105 | for field in cls.__strawberry_definition__.fields: # type: ignore[attr-defined] 106 | data: Any = getattr(model_instance, field.python_name, None) 107 | if field.init: 108 | kwargs[field.python_name] = _convert( 109 | field.type, 110 | data, 111 | ) 112 | 113 | return cls(**kwargs) 114 | 115 | @staticmethod 116 | def get_edges[S]( 117 | cls: type[S], # noqa: PLW0211 118 | data: list[Base], 119 | /, 120 | ) -> list[S] | None: 121 | field: StrawberryField = next(f for f in cls.__strawberry_definition__.fields if f.python_name == "edges") # type: ignore[attr-defined] 122 | if field.init: 123 | return _convert( 124 | field.type, 125 | data, 126 | ) 127 | return None 128 | 129 | 130 | converter: ConverterBase = ModelConverter() 131 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About The Project 2 | 3 | Welcome to Futuramaapi – your go-to platform for diving into the realms of REST and GraphQL technologies. 4 | More than just a learning space, this project is a dynamic hub for anyone keen on mastering RESTful API design, 5 | crafting GraphQL queries, and exploring related technologies. 6 | 7 | But we're not just another tutorial. Futuramaapi doubles as a sandbox, 8 | inviting you to play with the latest tools and frameworks. 9 | It's not about theory; it's about hands-on experience, transforming what you learn into practical skills. 10 | 11 | Join a vibrant community, engage in experimentation, and collaborate with fellow enthusiasts. 12 | Whether you're a beginner or a seasoned developer, Futuramaapi is your space for continuous learning and innovation. 13 | 14 | Explore the future of API mastery with Futuramaapi – where learning meets experimentation. 15 | Join us in shaping the future of technology. 16 | 17 | ## Key Features 18 | 19 | ### RESTful API: 20 | Explore and understand the principles of REST through our comprehensive API implementation. 21 | 22 | ### GraphQL Integration: 23 | Dive into the world of GraphQL with seamless integration and interactive examples. 24 | 25 | ### Server-Sent Events (SSE): 26 | Experience real-time updates and notifications through the power of Server-Sent Events. 27 | 28 | ### OpenAPI Documentation: 29 | Easily navigate and interact with our APIs using OpenAPI documentation. 30 | 31 | ### Technologies: 32 | Built with HTTP/2, Hypercorn, Python 3.12, FastAPI, asynchronous programming, SQLAlchemy, alembic, 33 | PostgreSQL, CI/CD, Ruff, and more, 34 | this project embraces cutting-edge technologies to provide a modern development experience. 35 | 36 | ## Requirements 37 | 38 | 1. Python >= 3.12 39 | 2. PostgreSQL 40 | 3. poetry 41 | 42 | ## Installation 43 | 44 | 45 | ```commandline 46 | # Clone repo 47 | git clone git@github.com:koldakov/futuramaapi.git 48 | # Instal dependencies 49 | poetry install 50 | # Initiate pre-commit 51 | poetry run pre-commit install 52 | ``` 53 | 54 |

(back to top)

55 | 56 | ## Migrations 57 | 58 | If you create models in a new file please import it in env.py. 59 | Because alembic does not detect child classes. 60 | 61 | ```commandline 62 | poetry run alembic revision --autogenerate -m "Revision Name" 63 | poetry run alembic upgrade head 64 | ``` 65 | 66 |

(back to top)

67 | 68 | ## Development 69 | 70 | ```commandline 71 | # Export variables 72 | export $(cat .env | xargs) 73 | # Run server 74 | bash docker-entrypoint.sh 75 | ``` 76 | 77 |

(back to top)

78 | 79 | ## Contributing 80 | 81 | 1. Fork the Project 82 | 2. Open a Pull Request 83 | 3. Or just read here: [contributing](https://docs.github.com/en/get-started/quickstart/contributing-to-projects) 84 | 85 |

(back to top)

86 | 87 | ## Methodology 88 | 89 | 1. Do a lot, break a lot. 90 | 2. There are no difficult tasks, only interesting. 91 | 3. Mostly TBD. 92 | 93 |

(back to top)

94 | 95 | ## Important 96 | 97 | 1. Quality. 98 | 2. Security. 99 | 3. Google first. 100 | 101 |

(back to top)

102 | 103 | ## License 104 | 105 | Distributed under the Apache 2.0 License. See [LICENSE.md](LICENSE.md) for more information. 106 | 107 |

(back to top)

108 | 109 | ## Buy me a coffee if you want to support me 110 | 111 | https://www.buymeacoffee.com/aivCoffee 112 | 113 | ## Contact 114 | 115 | Hi all, 116 | 117 | How are you? Hope You've enjoyed the project. 118 | 119 | There are my contacts: 120 | 121 | - [Linkedin](https://www.linkedin.com/in/aiv/) 122 | - [Send an Email](mailto:coldie322@gmail.com?subject=[GitHub]-qworpa) 123 | 124 | Project Link: https://github.com/koldakov/futuramaapi 125 | 126 | Best regards, 127 | 128 | [Ivan Koldakov](https://www.linkedin.com/in/aiv/) 129 | -------------------------------------------------------------------------------- /futuramaapi/routers/rest/callbacks/schemas.py: -------------------------------------------------------------------------------- 1 | from asyncio import sleep 2 | from random import randint 3 | from typing import Literal, Self, cast 4 | 5 | from fastapi import BackgroundTasks 6 | from httpx import AsyncClient, Response 7 | from pydantic import Field, HttpUrl 8 | from sqlalchemy.ext.asyncio.session import AsyncSession 9 | 10 | from futuramaapi.helpers.pydantic import BaseModel 11 | from futuramaapi.routers.exceptions import ModelNotFoundError 12 | from futuramaapi.routers.rest.characters.schemas import Character 13 | from futuramaapi.routers.rest.episodes.schemas import Episode 14 | from futuramaapi.routers.rest.seasons.schemas import Season 15 | 16 | MIN_DELAY: int = 5 17 | MAX_DELAY: int = 10 18 | 19 | 20 | class DoesNotExist(BaseModel): 21 | id: int = Field( 22 | description="Requested Object ID.", 23 | ) 24 | detail: str = Field( 25 | default="Not found", 26 | examples=[ 27 | "Not found", 28 | ], 29 | ) 30 | 31 | 32 | class CallbackObjectResponse(BaseModel): 33 | # Can't use type even with noqa: A003, cause native type is being used for a arg typing below. 34 | kind: Literal["Character", "Episode", "Season"] = Field( 35 | alias="type", 36 | description="Requested Object type.", 37 | ) 38 | item: Character | Episode | Season | DoesNotExist 39 | 40 | @classmethod 41 | async def from_item( 42 | cls, 43 | session: AsyncSession, 44 | requested_object: type[Character | Episode | Season], 45 | id_: int, 46 | /, 47 | ) -> Self: 48 | item: Character | Episode | Season | DoesNotExist 49 | try: 50 | item = await requested_object.get(session, id_) 51 | except ModelNotFoundError: 52 | item = DoesNotExist( 53 | id=id_, 54 | ) 55 | return cls( 56 | type=cast(Literal["Character", "Episode", "Season"], requested_object.__name__), 57 | item=item, 58 | ) 59 | 60 | async def send_callback(self, url: HttpUrl, /) -> None: 61 | async with AsyncClient(http2=True) as client: 62 | callback_response: Response = await client.post( 63 | f"{url}", 64 | json=self.to_dict(), 65 | ) 66 | callback_response.raise_for_status() 67 | 68 | 69 | class CallbackRequest(BaseModel): 70 | callback_url: HttpUrl 71 | 72 | 73 | class CallbackResponse(BaseModel): 74 | @staticmethod 75 | def _generate_random_delay() -> int: 76 | return randint(MIN_DELAY, MAX_DELAY) # noqa: S311 77 | 78 | delay: int = Field( 79 | default_factory=_generate_random_delay, 80 | ge=MIN_DELAY, 81 | le=MAX_DELAY, 82 | description="Delay after which the callback will be sent.", 83 | ) 84 | 85 | async def process_background_task( 86 | self, 87 | session: AsyncSession, 88 | requested_object: type[Character | Episode | Season], 89 | request: CallbackRequest, 90 | id_: int, 91 | /, 92 | ) -> None: 93 | await sleep(self.delay) 94 | callback_response: CallbackObjectResponse = await CallbackObjectResponse.from_item( 95 | session, 96 | requested_object, 97 | id_, 98 | ) 99 | await session.close() 100 | await callback_response.send_callback(request.callback_url) 101 | 102 | @classmethod 103 | async def process( 104 | cls, 105 | session: AsyncSession, 106 | requested_object: type[Character | Episode | Season], 107 | request: CallbackRequest, 108 | id_: int, 109 | background_tasks: BackgroundTasks, 110 | /, 111 | ) -> Self: 112 | response: Self = cls() 113 | background_tasks.add_task( 114 | response.process_background_task, 115 | session, 116 | requested_object, 117 | request, 118 | id_, 119 | ) 120 | return response 121 | -------------------------------------------------------------------------------- /futuramaapi/routers/rest/users/dependencies.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated, Literal 2 | 3 | from fastapi import Depends, Form, HTTPException, Request, status 4 | from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm 5 | from pydantic import SecretStr 6 | from sqlalchemy.ext.asyncio.session import AsyncSession 7 | 8 | from futuramaapi.mixins.pydantic import DecodedTokenError 9 | from futuramaapi.repositories.models import AuthSessionModel 10 | from futuramaapi.repositories.session import get_async_session 11 | from futuramaapi.routers.exceptions import ModelNotFoundError 12 | from futuramaapi.routers.rest.tokens.schemas import DecodedUserToken 13 | from futuramaapi.routers.rest.users.schemas import User, UserPasswordError, UserUpdateRequest 14 | 15 | _oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/tokens/users/auth") 16 | 17 | 18 | async def _get_user_from_token( 19 | sig: str, 20 | session: AsyncSession, 21 | type_: Literal["access", "refresh"], 22 | /, 23 | ) -> User: 24 | try: 25 | decoded_token: DecodedUserToken = DecodedUserToken.decode(sig, allowed_type=type_) 26 | except DecodedTokenError: 27 | raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) from None 28 | 29 | try: 30 | user: User = await User.get(session, decoded_token.user.id) 31 | except ModelNotFoundError: 32 | raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) from None 33 | 34 | return user 35 | 36 | 37 | async def from_token( 38 | token: Annotated[str, Depends(_oauth2_scheme)], 39 | session: AsyncSession = Depends(get_async_session), # noqa: B008 40 | ) -> User: 41 | return await _get_user_from_token(token, session, "access") 42 | 43 | 44 | async def from_signature( 45 | sig: str, 46 | session: AsyncSession = Depends(get_async_session), # noqa: B008 47 | ) -> User: 48 | return await _get_user_from_token(sig, session, "access") 49 | 50 | 51 | async def from_form_signature( 52 | sig: Annotated[ 53 | str, 54 | Form(), 55 | ], 56 | session: AsyncSession = Depends(get_async_session), # noqa: B008 57 | ) -> User: 58 | return await _get_user_from_token(sig, session, "access") 59 | 60 | 61 | def password_from_form_data( 62 | password1: Annotated[ 63 | SecretStr, 64 | Form( 65 | min_length=8, 66 | max_length=128, 67 | ), 68 | ], 69 | password2: Annotated[ 70 | SecretStr, 71 | Form( 72 | min_length=8, 73 | max_length=128, 74 | ), 75 | ], 76 | ) -> UserUpdateRequest: 77 | if password1.get_secret_value() != password2.get_secret_value(): 78 | raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Passwords mismatch") from None 79 | 80 | return UserUpdateRequest(password=password1.get_secret_value()) 81 | 82 | 83 | async def cookie_user_from_form_data( 84 | request: Request, 85 | form_data: Annotated[OAuth2PasswordRequestForm, Depends()], 86 | session: AsyncSession = Depends(get_async_session), # noqa: B008 87 | ) -> User | None: 88 | try: 89 | user: User = await User.auth(session, form_data.username, form_data.password) 90 | except (ModelNotFoundError, UserPasswordError): 91 | return None 92 | 93 | auth_session: AuthSessionModel = AuthSessionModel() 94 | auth_session.user_id = user.id 95 | auth_session.ip_address = request.client.host 96 | 97 | session.add(auth_session) 98 | await session.commit() 99 | 100 | user._cookie_session = auth_session.key 101 | return user 102 | 103 | 104 | async def user_from_cookies( 105 | request: Request, 106 | session: AsyncSession = Depends(get_async_session), # noqa: B008 107 | ) -> User | None: 108 | try: 109 | session_id: str = request.cookies[User.cookie_auth_key] 110 | except KeyError: 111 | return None 112 | 113 | try: 114 | return await User.from_cookie_session_id(session, session_id) 115 | except ModelNotFoundError: 116 | raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) from None 117 | -------------------------------------------------------------------------------- /futuramaapi/routers/rest/root/schemas.py: -------------------------------------------------------------------------------- 1 | from datetime import UTC, datetime, timedelta 2 | from typing import TYPE_CHECKING, ClassVar, Self 3 | 4 | from fastapi import Response 5 | from pydantic import HttpUrl 6 | from sqlalchemy.ext.asyncio import AsyncSession 7 | from starlette.requests import Request 8 | 9 | from futuramaapi.core import settings 10 | from futuramaapi.helpers.pydantic import BaseModel, Field 11 | from futuramaapi.mixins.pydantic import BaseModelTemplateMixin, ProjectContext 12 | from futuramaapi.repositories import FilterStatementKwargs 13 | from futuramaapi.repositories.models import RequestsCounterModel, SystemMessage 14 | from futuramaapi.routers.rest.characters.schemas import Character 15 | from futuramaapi.routers.rest.users.schemas import User 16 | 17 | if TYPE_CHECKING: 18 | from collections.abc import Sequence 19 | 20 | 21 | class Root(BaseModel, BaseModelTemplateMixin): 22 | characters: list[Character] 23 | user_count: int = Field(alias="user_count") 24 | total_api_requests: int 25 | last_day_api_requests: int 26 | system_messages: list[str] 27 | 28 | template_name: ClassVar[str] = "index.html" 29 | 30 | @classmethod 31 | async def from_request(cls, session: AsyncSession, request: Request, /) -> Self: 32 | user_count: int = await User.count(session) 33 | characters: list[Character] = await Character.filter( 34 | session, 35 | FilterStatementKwargs( 36 | limit=6, 37 | ), 38 | ) 39 | total_requests: int = await RequestsCounterModel.get_total_requests() 40 | system_messages: Sequence[SystemMessage] = await SystemMessage.filter(session, FilterStatementKwargs()) 41 | last_day_api_requests: int = await RequestsCounterModel.get_requests_since( 42 | datetime.now(tz=UTC) - timedelta(days=1), 43 | ) 44 | 45 | return cls( 46 | characters=characters, 47 | user_count=user_count, 48 | total_api_requests=total_requests, 49 | last_day_api_requests=last_day_api_requests, 50 | system_messages=[system_message.message for system_message in system_messages], 51 | ) 52 | 53 | 54 | class About(BaseModel, BaseModelTemplateMixin): 55 | template_name: ClassVar[str] = "about.html" 56 | 57 | @property 58 | def project_context(self) -> ProjectContext: 59 | return ProjectContext( 60 | description="Practice with API, learn to code, and gain hands-on experience with SSE, callbacks, and more.", 61 | ) 62 | 63 | @classmethod 64 | async def from_request(cls, session: AsyncSession, request: Request, /) -> Self: 65 | return cls() 66 | 67 | 68 | class UserAuth(BaseModel, BaseModelTemplateMixin): 69 | template_name: ClassVar[str] = "auth.html" 70 | 71 | @property 72 | def project_context(self) -> ProjectContext: 73 | return ProjectContext( 74 | description="Log in to your Futurama API account to manage your account and access all your tools " 75 | "securely and easily.", 76 | ) 77 | 78 | @classmethod 79 | async def from_request(cls, session: AsyncSession, request: Request, /) -> Self: 80 | return cls() 81 | 82 | 83 | class SiteMap(BaseModel): 84 | _header: ClassVar[str] = '' 85 | _media_type: ClassVar[str] = "application/xml" 86 | _url_tag: ClassVar[str] = """%s""" 87 | _url_set_tag: ClassVar[str] = """%s""" 88 | 89 | base_url: HttpUrl = settings.build_url(is_static=False) 90 | urls: list[str] 91 | 92 | async def get_response(self) -> Response: 93 | urls: str = "" 94 | for url in self.urls: 95 | urls += self._url_tag % settings.build_url( 96 | path=url, 97 | is_static=False, 98 | ) 99 | url_set: str = self._url_set_tag % urls 100 | return Response( 101 | content=f"""{self._header}{url_set}""", 102 | media_type=self._media_type, 103 | ) 104 | 105 | @classmethod 106 | async def from_request(cls, request: Request) -> Self: 107 | from futuramaapi.apps import app 108 | 109 | return cls( 110 | urls=[url.path for url in app.public_urls], 111 | ) 112 | --------------------------------------------------------------------------------