diff --git a/boofilsic/urls.py b/boofilsic/urls.py index 101357ac..9f477aa6 100644 --- a/boofilsic/urls.py +++ b/boofilsic/urls.py @@ -17,8 +17,10 @@ from django.contrib import admin from django.urls import path, include from django.conf import settings from users.views import login +from common.api import api urlpatterns = [ + path("api/", api.urls), path("login/", login), path("markdownx/", include("markdownx.urls")), path("users/", include("users.urls")), diff --git a/catalog/api.py b/catalog/api.py index 25da154d..d6a8c0f6 100644 --- a/catalog/api.py +++ b/catalog/api.py @@ -1,69 +1,26 @@ -from ninja import NinjaAPI from .models import * from .common import * from .sites import * -from django.conf import settings -from datetime import date from ninja import Schema -from typing import List, Optional -from django.utils.baseconv import base62 -from django.shortcuts import render, get_object_or_404, redirect, reverse from django.http import Http404 - -api = NinjaAPI( - title=settings.SITE_INFO["site_name"], - version="1.0.0", - description=f"{settings.SITE_INFO['site_name']} API
Learn more", -) +from common.api import api -class ItemIn(Schema): - title: str - brief: str +class SearchResult(Schema): + code: int + items: list[ItemSchema] -class ItemOut(Schema): - uuid: str - title: str - brief: str - url: str - api_url: str - category: str +@api.post("/catalog/search", response=SearchResult) +def search_item(request, query: str, category: ItemCategory | None = None): + query = query.strip() + if not query: + return {"code": -1, "items": []} + result = Indexer.search(query, page=1, category=category) + return {"code": 0, "items": result.items} -class EditionIn(ItemIn): - subtitle: str = None - orig_title: str = None - author: list[str] - translator: list[str] - language: str = None - pub_house: str = None - pub_year: int = None - pub_month: int = None - binding: str = None - price: str = None - pages: str = None - series: str = None - imprint: str = None - - -class EditionOut(ItemOut): - subtitle: str = None - orig_title: str = None - author: list[str] - translator: list[str] - language: str = None - pub_house: str = None - pub_year: int = None - pub_month: int = None - binding: str = None - price: str = None - pages: str = None - series: str = None - imprint: str = None - - -@api.post("/catalog/fetch", response=ItemOut) +@api.post("/catalog/fetch", response=ItemSchema) def fetch_item(request, url: str): site = SiteManager.get_site_by_url(url) if not site: @@ -74,35 +31,85 @@ def fetch_item(request, url: str): return site.get_item() -@api.post("/book/") -def create_edition(request, payload: EditionIn): - edition = Edition.objects.create(**payload.dict()) - return {"id": edition.uuid} - - -@api.get("/book/{uuid}/", response=EditionOut) +@api.get("/book/{uuid}/", response=EditionSchema) def get_edition(request, uuid: str): - edition = get_object_or_404(Edition, uid=base62.decode(uuid)) - return edition + item = Edition.get_by_url(uuid) + if not item: + raise Http404(uuid) + return item -# @api.get("/book", response=List[EditionOut]) +@api.get("/movie/{uuid}/", response=MovieSchema) +def get_movie(request, uuid: str): + item = Movie.get_by_url(uuid) + if not item: + raise Http404(uuid) + return item + + +@api.get("/tvshow/{uuid}/", response=TVShowSchema) +def get_tvshow(request, uuid: str): + item = TVShow.get_by_url(uuid) + if not item: + raise Http404(uuid) + return item + + +@api.get("/tvseason/{uuid}/", response=TVSeasonSchema) +def get_tvseason(request, uuid: str): + item = TVSeason.get_by_url(uuid) + if not item: + raise Http404(uuid) + return item + + +@api.get("/podcast/{uuid}/", response=PodcastSchema) +def get_podcast(request, uuid: str): + item = Podcast.get_by_url(uuid) + if not item: + raise Http404(uuid) + return item + + +@api.get("/album/{uuid}/", response=AlbumSchema) +def get_album(request, uuid: str): + item = Album.get_by_url(uuid) + if not item: + raise Http404(uuid) + return item + + +@api.get("/game/{uuid}/", response=GameSchema) +def get_game(request, uuid: str): + item = Game.get_by_url(uuid) + if not item: + raise Http404(uuid) + return item + + +# @api.get("/book", response=List[EditionSchema]) # def list_editions(request): # qs = Edition.objects.all() # return qs -@api.put("/book/{uuid}/") -def update_edition(request, uuid: str, payload: EditionIn): - edition = get_object_or_404(Item, uid=base62.decode(uuid)) - for attr, value in payload.dict().items(): - setattr(edition, attr, value) - edition.save() - return {"success": True} +# @api.post("/book/") +# def create_edition(request, payload: EditionInSchema): +# edition = Edition.objects.create(**payload.dict()) +# return {"id": edition.uuid} -@api.delete("/book/{uuid}/") -def delete_edition(request, uuid: str): - edition = get_object_or_404(Edition, uid=base62.decode(uuid)) - edition.delete() - return {"success": True} +# @api.put("/book/{uuid}/") +# def update_edition(request, uuid: str, payload: EditionInSchema): +# edition = get_object_or_404(Item, uid=base62.decode(uuid)) +# for attr, value in payload.dict().items(): +# setattr(edition, attr, value) +# edition.save() +# return {"success": True} + + +# @api.delete("/book/{uuid}/") +# def delete_edition(request, uuid: str): +# edition = get_object_or_404(Edition, uid=base62.decode(uuid)) +# edition.delete() +# return {"success": True} diff --git a/catalog/apps.py b/catalog/apps.py index d5052fc8..0b71ad8b 100644 --- a/catalog/apps.py +++ b/catalog/apps.py @@ -11,5 +11,6 @@ class CatalogConfig(AppConfig): from catalog import sites from journal import models as journal_models from catalog.models import init_catalog_search_models + from catalog import api init_catalog_search_models() diff --git a/catalog/book/models.py b/catalog/book/models.py index 2ace462c..0e1c9c6b 100644 --- a/catalog/book/models.py +++ b/catalog/book/models.py @@ -20,10 +20,30 @@ work data seems asymmetric (a book links to a work, but may not listed in that w from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.utils.translation import gettext_lazy as _ -from catalog.common import * +from catalog.common.models import * from .utils import * +class EditionInSchema(ItemInSchema): + subtitle: str | None = None + orig_title: str | None = None + author: list[str] + translator: list[str] + language: str | None = None + pub_house: str | None = None + pub_year: int | None = None + pub_month: int | None = None + binding: str | None = None + price: str | None = None + pages: str | None = None + series: str | None = None + imprint: str | None = None + + +class EditionSchema(EditionInSchema, BaseSchema): + pass + + class Edition(Item): category = ItemCategory.Book url_path = "book" diff --git a/catalog/common/models.py b/catalog/common/models.py index ce28d5ca..4af1cc59 100644 --- a/catalog/common/models.py +++ b/catalog/common/models.py @@ -16,6 +16,7 @@ from .mixins import SoftDeleteMixin from django.conf import settings from users.models import User from django.db import connection +from ninja import Schema _logger = logging.getLogger(__name__) @@ -180,6 +181,29 @@ class LookupIdDescriptor(object): # TODO make it mixin of Field # return sid[0] in IdType.values() +class ExternalResourceSchema(Schema): + url: str + + +class BaseSchema(Schema): + uuid: str + url: str + api_url: str + category: ItemCategory + primary_lookup_id_type: str + primary_lookup_id_value: str + external_resources: list[ExternalResourceSchema] | None + + +class ItemInSchema(Schema): + title: str + brief: str + + +class ItemSchema(ItemInSchema, BaseSchema): + pass + + class Item(SoftDeleteMixin, PolymorphicModel): url_path = None # subclass must specify this category = None # subclass must specify this @@ -297,7 +321,7 @@ class Item(SoftDeleteMixin, PolymorphicModel): @property def api_url(self): - return f"/api/{self.url}" if self.url_path else None + return f"/api{self.url}" if self.url_path else None @property def class_name(self): diff --git a/catalog/game/models.py b/catalog/game/models.py index 3a934eb7..253688bf 100644 --- a/catalog/game/models.py +++ b/catalog/game/models.py @@ -1,8 +1,22 @@ -from catalog.common import * +from datetime import date +from catalog.common.models import * from django.utils.translation import gettext_lazy as _ from django.db import models +class GameInSchema(ItemInSchema): + genre: list[str] + developer: list[str] + publisher: list[str] + platform: list[str] + release_date: date | None = None + official_site: str | None = None + + +class GameSchema(GameInSchema, BaseSchema): + pass + + class Game(Item): category = ItemCategory.Game url_path = "game" diff --git a/catalog/models.py b/catalog/models.py index a390e743..5657da5e 100644 --- a/catalog/models.py +++ b/catalog/models.py @@ -1,10 +1,18 @@ -from .common.models import Item -from .book.models import Edition, Work, Series -from .movie.models import Movie -from .tv.models import TVShow, TVSeason, TVEpisode -from .music.models import Album -from .game.models import Game -from .podcast.models import Podcast +from .common.models import Item, ItemSchema +from .book.models import Edition, Work, Series, EditionSchema, EditionInSchema +from .movie.models import Movie, MovieSchema, MovieInSchema +from .tv.models import ( + TVShow, + TVSeason, + TVEpisode, + TVShowSchema, + TVShowInSchema, + TVSeasonSchema, + TVSeasonInSchema, +) +from .music.models import Album, AlbumSchema, AlbumInSchema +from .game.models import Game, GameSchema, GameInSchema +from .podcast.models import Podcast, PodcastSchema, PodcastInSchema from .performance.models import Performance from .collection.models import Collection as CatalogCollection from django.contrib.contenttypes.models import ContentType @@ -20,6 +28,13 @@ elif settings.SEARCH_BACKEND == "TYPESENSE": else: class Indexer: + @classmethod + def search(cls, q, page=1, category=None, tag=None, sort=None): + result = lambda: None + result.items = Item.objects.filter(title__contains=q)[:10] + result.num_pages = 1 + return result + @classmethod def update_model_indexable(cls, model): pass diff --git a/catalog/movie/models.py b/catalog/movie/models.py index ba850965..399907cf 100644 --- a/catalog/movie/models.py +++ b/catalog/movie/models.py @@ -1,8 +1,26 @@ -from catalog.common import * +from catalog.common.models import * from django.utils.translation import gettext_lazy as _ from django.db import models +class MovieInSchema(ItemInSchema): + orig_title: str | None = None + other_title: list[str] + director: list[str] + playwright: list[str] + actor: list[str] + genre: list[str] + language: list[str] + area: list[str] + year: int | None = None + site: str | None = None + duration: int | None = None + + +class MovieSchema(MovieInSchema, BaseSchema): + pass + + class Movie(Item): category = ItemCategory.Movie url_path = "movie" diff --git a/catalog/music/models.py b/catalog/music/models.py index 3656507b..80e435e4 100644 --- a/catalog/music/models.py +++ b/catalog/music/models.py @@ -1,8 +1,23 @@ -from catalog.common import * +from datetime import date +from catalog.common.models import * from django.utils.translation import gettext_lazy as _ from django.db import models +class AlbumInSchema(ItemInSchema): + other_title: str | None = None + genre: list[str] + artist: list[str] + company: list[str] + duration: int | None = None + release_date: date | None = None + track_list: str | None = None + + +class AlbumSchema(AlbumInSchema, BaseSchema): + pass + + class Album(Item): url_path = "album" category = ItemCategory.Music diff --git a/catalog/podcast/models.py b/catalog/podcast/models.py index 0cfc7aee..781f5d15 100644 --- a/catalog/podcast/models.py +++ b/catalog/podcast/models.py @@ -1,8 +1,18 @@ -from catalog.common import * +from catalog.common.models import * from django.db import models from django.utils.translation import gettext_lazy as _ +class PodcastInSchema(ItemInSchema): + genre: list[str] + hosts: list[str] + official_site: str | None = None + + +class PodcastSchema(PodcastInSchema, BaseSchema): + pass + + class Podcast(Item): category = ItemCategory.Podcast url_path = "podcast" diff --git a/catalog/sites/steam.py b/catalog/sites/steam.py index c4c45fe8..dbbc35b5 100644 --- a/catalog/sites/steam.py +++ b/catalog/sites/steam.py @@ -17,7 +17,7 @@ class Steam(AbstractSite): DEFAULT_MODEL = Game @classmethod - def id_to_url(self, id_value): + def id_to_url(cls, id_value): return "https://store.steampowered.com/app/" + str(id_value) def scrape(self): diff --git a/catalog/tv/models.py b/catalog/tv/models.py index dda96acf..dd59f5a8 100644 --- a/catalog/tv/models.py +++ b/catalog/tv/models.py @@ -8,7 +8,7 @@ TVEpisode is not fully implemented at the moment Three way linking between Douban / IMDB / TMDB are quite messy IMDB: -most widely used. +most widely used. no ID for Season, only for Show and Episode TMDB: @@ -24,11 +24,51 @@ tv specials are are shown as movies For now, we follow Douban convention, but keep an eye on it in case it breaks its own rules... """ -from simple_history.models import cached_property -from catalog.common import * +from functools import cached_property +from catalog.common.models import * from django.db import models from django.utils.translation import gettext_lazy as _ -import re + + +class TVShowInSchema(ItemInSchema): + season_count: int | None = None + orig_title: str | None = None + other_title: str | None = None + director: list[str] + playwright: list[str] + actor: list[str] + genre: list[str] + language: list[str] + area: list[str] + year: int | None = None + site: str | None = None + episode_count: int | None = None + single_episode_length: int | None = None + + +class TVShowSchema(TVShowInSchema, BaseSchema): + # seasons: list['TVSeason'] + pass + + +class TVSeasonInSchema(ItemInSchema): + season_number: int | None = None + orig_title: str | None = None + other_title: str | None = None + director: list[str] + playwright: list[str] + actor: list[str] + genre: list[str] + language: list[str] + area: list[str] + year: int | None = None + site: str | None = None + episode_count: int | None = None + + +class TVSeasonSchema(TVSeasonInSchema, BaseSchema): + show_uuid: str | None = None + pass class TVShow(Item): @@ -289,6 +329,10 @@ class TVSeason(Item): def all_seasons(self): return self.show.all_seasons if self.show else [] + @property + def show_uuid(self): + return self.show.uuid if self.show else None + class TVEpisode(Item): category = ItemCategory.TV diff --git a/catalog/urls.py b/catalog/urls.py index 6392a764..a8a16ac0 100644 --- a/catalog/urls.py +++ b/catalog/urls.py @@ -1,5 +1,4 @@ from django.urls import path, re_path -from .api import api from .views import * from .models import * @@ -85,5 +84,4 @@ urlpatterns = [ path("fetch_refresh/", fetch_refresh, name="fetch_refresh"), path("refetch", refetch, name="refetch"), path("unlink", unlink, name="unlink"), - path("api/", api.urls), ] diff --git a/common/api.py b/common/api.py new file mode 100644 index 00000000..653be17e --- /dev/null +++ b/common/api.py @@ -0,0 +1,8 @@ +from ninja import NinjaAPI +from django.conf import settings + +api = NinjaAPI( + title=settings.SITE_INFO["site_name"], + version="1.0.0", + description=f"{settings.SITE_INFO['site_name']} API
Learn more", +) diff --git a/common/static/css/api_doc.css b/common/static/css/api_doc.css new file mode 100644 index 00000000..47a78858 --- /dev/null +++ b/common/static/css/api_doc.css @@ -0,0 +1,1673 @@ +@charset "UTF-8"; +.swagger-ui html { + box-sizing: border-box +} + +.swagger-ui *, .swagger-ui :after, .swagger-ui :before { + box-sizing: inherit +} + +.swagger-ui body { + margin: 0; + background: #fafafa +} + +.swagger-ui .wrapper { + width: 100%; + max-width: 1460px; + margin: 0 auto; + padding: 0 20px +} + +.swagger-ui .opblock-tag-section { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column +} + +.swagger-ui .opblock-tag { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + padding: 10px 20px 10px 10px; + cursor: pointer; + -webkit-transition: all .2s; + transition: all .2s; + border-bottom: 1px solid rgba(59, 65, 81, .3); + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center +} + +.swagger-ui .opblock-tag:hover { + background: rgba(0, 0, 0, .02) +} + +.swagger-ui .opblock-tag { + font-size: 24px; + margin: 0 0 5px; + font-family: Titillium Web, sans-serif; + color: #3b4151 +} + +.swagger-ui .opblock-tag.no-desc span { + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1 +} + +.swagger-ui .opblock-tag svg { + -webkit-transition: all .4s; + transition: all .4s +} + +.swagger-ui .opblock-tag small { + font-size: 14px; + font-weight: 400; + padding: 0 10px; + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; + font-family: Open Sans, sans-serif; + color: #3b4151 +} + +.swagger-ui .parŠ°meter__type { + font-size: 12px; + padding: 5px 0; + font-family: Source Code Pro, monospace; + font-weight: 600; + color: #3b4151 +} + +.swagger-ui .view-line-link { + position: relative; + top: 3px; + width: 20px; + margin: 0 5px; + cursor: pointer; + -webkit-transition: all .5s; + transition: all .5s +} + +.swagger-ui .opblock { + margin: 0 0 15px; + border: 1px solid #000; + border-radius: 4px; + box-shadow: 0 0 3px rgba(0, 0, 0, .19) +} + +.swagger-ui .opblock.is-open .opblock-summary { + border-bottom: 1px solid #000 +} + +.swagger-ui .opblock .opblock-section-header { + padding: 8px 20px; + background: hsla(0, 0%, 100%, .8); + box-shadow: 0 1px 2px rgba(0, 0, 0, .1) +} + +.swagger-ui .opblock .opblock-section-header, .swagger-ui .opblock .opblock-section-header label { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center +} + +.swagger-ui .opblock .opblock-section-header label { + font-size: 12px; + font-weight: 700; + margin: 0; + font-family: Titillium Web, sans-serif; + color: #3b4151 +} + +.swagger-ui .opblock .opblock-section-header label span { + padding: 0 10px 0 0 +} + +.swagger-ui .opblock .opblock-section-header h4 { + font-size: 14px; + margin: 0; + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; + font-family: Titillium Web, sans-serif; + color: #3b4151 +} + +.swagger-ui .opblock .opblock-summary-method { + font-size: 14px; + font-weight: 700; + min-width: 80px; + padding: 6px 15px; + text-align: center; + border-radius: 3px; + background: #000; + text-shadow: 0 1px 0 rgba(0, 0, 0, .1); + font-family: Titillium Web, sans-serif; + color: #fff +} + +.swagger-ui .opblock .opblock-summary-path, .swagger-ui .opblock .opblock-summary-path__deprecated { + font-size: 16px; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + padding: 0 10px; + font-family: Source Code Pro, monospace; + font-weight: 600; + color: #3b4151; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center +} + +.swagger-ui .opblock .opblock-summary-path .view-line-link, .swagger-ui .opblock .opblock-summary-path__deprecated .view-line-link { + position: relative; + top: 2px; + width: 0; + margin: 0; + cursor: pointer; + -webkit-transition: all .5s; + transition: all .5s +} + +.swagger-ui .opblock .opblock-summary-path:hover .view-line-link, .swagger-ui .opblock .opblock-summary-path__deprecated:hover .view-line-link { + width: 18px; + margin: 0 5px +} + +.swagger-ui .opblock .opblock-summary-path__deprecated { + text-decoration: line-through +} + +.swagger-ui .opblock .opblock-summary-description { + font-size: 13px; + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; + font-family: Open Sans, sans-serif; + color: #3b4151 +} + +.swagger-ui .opblock .opblock-summary { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + padding: 5px; + cursor: pointer; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center +} + +.swagger-ui .opblock.opblock-post { + border-color: #49cc90; + background: rgba(73, 204, 144, .1) +} + +.swagger-ui .opblock.opblock-post .opblock-summary-method { + background: #49cc90 +} + +.swagger-ui .opblock.opblock-post .opblock-summary { + border-color: #49cc90 +} + +.swagger-ui .opblock.opblock-put { + border-color: #fca130; + background: rgba(252, 161, 48, .1) +} + +.swagger-ui .opblock.opblock-put .opblock-summary-method { + background: #fca130 +} + +.swagger-ui .opblock.opblock-put .opblock-summary { + border-color: #fca130 +} + +.swagger-ui .opblock.opblock-delete { + border-color: #f93e3e; + background: rgba(249, 62, 62, .1) +} + +.swagger-ui .opblock.opblock-delete .opblock-summary-method { + background: #f93e3e +} + +.swagger-ui .opblock.opblock-delete .opblock-summary { + border-color: #f93e3e +} + +.swagger-ui .opblock.opblock-get { + border-color: #61affe; + background: rgba(97, 175, 254, .1) +} + +.swagger-ui .opblock.opblock-get .opblock-summary-method { + background: #61affe +} + +.swagger-ui .opblock.opblock-get .opblock-summary { + border-color: #61affe +} + +.swagger-ui .opblock.opblock-patch { + border-color: #50e3c2; + background: rgba(80, 227, 194, .1) +} + +.swagger-ui .opblock.opblock-patch .opblock-summary-method { + background: #50e3c2 +} + +.swagger-ui .opblock.opblock-patch .opblock-summary { + border-color: #50e3c2 +} + +.swagger-ui .opblock.opblock-head { + border-color: #9012fe; + background: rgba(144, 18, 254, .1) +} + +.swagger-ui .opblock.opblock-head .opblock-summary-method { + background: #9012fe +} + +.swagger-ui .opblock.opblock-head .opblock-summary { + border-color: #9012fe +} + +.swagger-ui .opblock.opblock-options { + border-color: #0d5aa7; + background: rgba(13, 90, 167, .1) +} + +.swagger-ui .opblock.opblock-options .opblock-summary-method { + background: #0d5aa7 +} + +.swagger-ui .opblock.opblock-options .opblock-summary { + border-color: #0d5aa7 +} + +.swagger-ui .opblock.opblock-deprecated { + opacity: .6; + border-color: #ebebeb; + background: hsla(0, 0%, 92%, .1) +} + +.swagger-ui .opblock.opblock-deprecated .opblock-summary-method { + background: #ebebeb +} + +.swagger-ui .opblock.opblock-deprecated .opblock-summary { + border-color: #ebebeb +} + +.swagger-ui .tab { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + margin: 20px 0 10px; + padding: 0; + list-style: none +} + +.swagger-ui .tab li { + font-size: 12px; + min-width: 100px; + min-width: 90px; + padding: 0; + cursor: pointer; + font-family: Titillium Web, sans-serif; + color: #3b4151 +} + +.swagger-ui .tab li:first-of-type { + position: relative; + padding-left: 0 +} + +.swagger-ui .tab li:first-of-type:after { + position: absolute; + top: 0; + right: 6px; + width: 1px; + height: 100%; + content: ""; + background: rgba(0, 0, 0, .2) +} + +.swagger-ui .tab li.active { + font-weight: 700 +} + +.swagger-ui .opblock-description-wrapper, .swagger-ui .opblock-title_normal { + padding: 15px 20px +} + +.swagger-ui .opblock-description-wrapper, .swagger-ui .opblock-description-wrapper h4, .swagger-ui .opblock-title_normal, .swagger-ui .opblock-title_normal h4 { + font-size: 12px; + margin: 0 0 5px; + font-family: Open Sans, sans-serif; + color: #3b4151 +} + +.swagger-ui .opblock-description-wrapper p, .swagger-ui .opblock-title_normal p { + font-size: 14px; + margin: 0; + font-family: Open Sans, sans-serif; + color: #3b4151 +} + +.swagger-ui .execute-wrapper { + padding: 20px; + text-align: right +} + +.swagger-ui .execute-wrapper .btn { + width: 100%; + padding: 8px 40px +} + +.swagger-ui .body-param-options { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column +} + +.swagger-ui .body-param-options .body-param-edit { + padding: 10px 0 +} + +.swagger-ui .body-param-options label { + padding: 8px 0 +} + +.swagger-ui .body-param-options label select { + margin: 3px 0 0 +} + +.swagger-ui .responses-inner { + padding: 20px +} + +.swagger-ui .responses-inner h4, .swagger-ui .responses-inner h5 { + font-size: 12px; + margin: 10px 0 5px; + font-family: Open Sans, sans-serif; + color: #3b4151 +} + +.swagger-ui .response-col_status { + font-size: 14px; + font-family: Open Sans, sans-serif; + color: #3b4151 +} + +.swagger-ui .response-col_status .response-undocumented { + font-size: 11px; + font-family: Source Code Pro, monospace; + font-weight: 600; + color: #999 +} + +.swagger-ui .response-col_description__inner span { + font-size: 12px; + font-style: italic; + display: block; + margin: 10px 0; + padding: 10px; + border-radius: 4px; + background: #41444e; + font-family: Source Code Pro, monospace; + font-weight: 600; + color: #fff +} + +.swagger-ui .response-col_description__inner span p { + margin: 0 +} + +.swagger-ui .opblock-body pre { + font-size: 12px; + margin: 0; + padding: 10px; + white-space: pre-wrap; + border-radius: 4px; + background: #41444e; + font-family: Source Code Pro, monospace; + font-weight: 600; + color: #fff +} + +.swagger-ui .opblock-body pre span { + color: #fff!important +} + +.swagger-ui .scheme-container { + margin: 0 0 20px; + padding: 30px 0; + background: #fff; + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .15) +} + +.swagger-ui .scheme-container .schemes { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center +} + +.swagger-ui .scheme-container .schemes>label { + font-size: 12px; + font-weight: 700; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + margin: -20px 15px 0 0; + font-family: Titillium Web, sans-serif; + color: #3b4151 +} + +.swagger-ui .scheme-container .schemes>label select { + min-width: 130px; + text-transform: uppercase +} + +.swagger-ui .loading-container { + padding: 40px 0 60px +} + +.swagger-ui .loading-container .loading { + position: relative +} + +.swagger-ui .loading-container .loading:after { + font-size: 10px; + font-weight: 700; + position: absolute; + top: 50%; + left: 50%; + content: "loading"; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + text-transform: uppercase; + font-family: Titillium Web, sans-serif; + color: #3b4151 +} + +.swagger-ui .loading-container .loading:before { + position: absolute; + top: 50%; + left: 50%; + display: block; + width: 60px; + height: 60px; + margin: -30px; + content: ""; + -webkit-animation: rotation 1s infinite linear, opacity .5s; + animation: rotation 1s infinite linear, opacity .5s; + opacity: 1; + border: 2px solid rgba(85, 85, 85, .1); + border-top-color: rgba(0, 0, 0, .6); + border-radius: 100%; + -webkit-backface-visibility: hidden; + backface-visibility: hidden +} + +@-webkit-keyframes rotation { + to { + -webkit-transform: rotate(1turn); + transform: rotate(1turn) + } +} + +@keyframes rotation { + to { + -webkit-transform: rotate(1turn); + transform: rotate(1turn) + } +} + +@-webkit-keyframes blinker { + 50% { + opacity: 0 + } +} + +@keyframes blinker { + 50% { + opacity: 0 + } +} + +.swagger-ui .btn { + font-size: 14px; + font-weight: 700; + padding: 5px 23px; + -webkit-transition: all .3s; + transition: all .3s; + border: 2px solid #888; + border-radius: 4px; + background: transparent; + box-shadow: 0 1px 2px rgba(0, 0, 0, .1); + font-family: Titillium Web, sans-serif; + color: #3b4151 +} + +.swagger-ui .btn[disabled] { + cursor: not-allowed; + opacity: .3 +} + +.swagger-ui .btn:hover { + box-shadow: 0 0 5px rgba(0, 0, 0, .3) +} + +.swagger-ui .btn.cancel { + border-color: #ff6060; + font-family: Titillium Web, sans-serif; + color: #ff6060 +} + +.swagger-ui .btn.authorize { + line-height: 1; + display: inline; + color: #49cc90; + border-color: #49cc90 +} + +.swagger-ui .btn.authorize span { + float: left; + padding: 4px 20px 0 0 +} + +.swagger-ui .btn.authorize svg { + fill: #49cc90 +} + +.swagger-ui .btn.execute { + -webkit-animation: pulse 2s infinite; + animation: pulse 2s infinite; + color: #fff; + border-color: #4990e2 +} + +@-webkit-keyframes pulse { + 0% { + color: #fff; + background: #4990e2; + box-shadow: 0 0 0 0 rgba(73, 144, 226, .8) + } + 70% { + box-shadow: 0 0 0 5px rgba(73, 144, 226, 0) + } + to { + color: #fff; + background: #4990e2; + box-shadow: 0 0 0 0 rgba(73, 144, 226, 0) + } +} + +@keyframes pulse { + 0% { + color: #fff; + background: #4990e2; + box-shadow: 0 0 0 0 rgba(73, 144, 226, .8) + } + 70% { + box-shadow: 0 0 0 5px rgba(73, 144, 226, 0) + } + to { + color: #fff; + background: #4990e2; + box-shadow: 0 0 0 0 rgba(73, 144, 226, 0) + } +} + +.swagger-ui .btn-group { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + padding: 30px +} + +.swagger-ui .btn-group .btn { + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1 +} + +.swagger-ui .btn-group .btn:first-child { + border-radius: 4px 0 0 4px +} + +.swagger-ui .btn-group .btn:last-child { + border-radius: 0 4px 4px 0 +} + +.swagger-ui .authorization__btn { + padding: 0 10px; + border: none; + background: none +} + +.swagger-ui .authorization__btn.locked { + opacity: 1 +} + +.swagger-ui .authorization__btn.unlocked { + opacity: .4 +} + +.swagger-ui .expand-methods, .swagger-ui .expand-operation { + border: none; + background: none +} + +.swagger-ui .expand-methods svg, .swagger-ui .expand-operation svg { + width: 20px; + height: 20px +} + +.swagger-ui .expand-methods { + padding: 0 10px +} + +.swagger-ui .expand-methods:hover svg { + fill: #444 +} + +.swagger-ui .expand-methods svg { + -webkit-transition: all .3s; + transition: all .3s; + fill: #777 +} + +.swagger-ui button { + cursor: pointer; + outline: none +} + +.swagger-ui select { + font-size: 14px; + font-weight: 700; + padding: 5px 40px 5px 10px; + border: 2px solid #41444e; + border-radius: 4px; + background: #f7f7f7 url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAyMCI+ICAgIDxwYXRoIGQ9Ik0xMy40MTggNy44NTljLjI3MS0uMjY4LjcwOS0uMjY4Ljk3OCAwIC4yNy4yNjguMjcyLjcwMSAwIC45NjlsLTMuOTA4IDMuODNjLS4yNy4yNjgtLjcwNy4yNjgtLjk3OSAwbC0zLjkwOC0zLjgzYy0uMjctLjI2Ny0uMjctLjcwMSAwLS45NjkuMjcxLS4yNjguNzA5LS4yNjguOTc4IDBMMTAgMTFsMy40MTgtMy4xNDF6Ii8+PC9zdmc+) right 10px center no-repeat; + background-size: 20px; + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .25); + font-family: Titillium Web, sans-serif; + color: #3b4151; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none +} + +.swagger-ui select[multiple] { + margin: 5px 0; + padding: 5px; + background: #f7f7f7 +} + +.swagger-ui .opblock-body select { + min-width: 230px +} + +.swagger-ui label { + font-size: 12px; + font-weight: 700; + margin: 0 0 5px; + font-family: Titillium Web, sans-serif; + color: #3b4151 +} + +.swagger-ui input[type=email], .swagger-ui input[type=password], .swagger-ui input[type=search], .swagger-ui input[type=text] { + min-width: 100px; + margin: 5px 0; + padding: 8px 10px; + border: 1px solid #d9d9d9; + border-radius: 4px; + background: #fff +} + +.swagger-ui input[type=email].invalid, .swagger-ui input[type=password].invalid, .swagger-ui input[type=search].invalid, .swagger-ui input[type=text].invalid { + -webkit-animation: shake .4s 1; + animation: shake .4s 1; + border-color: #f93e3e; + background: #feebeb +} + +@-webkit-keyframes shake { + 10%, 90% { + -webkit-transform: translate3d(-1px, 0, 0); + transform: translate3d(-1px, 0, 0) + } + 20%, 80% { + -webkit-transform: translate3d(2px, 0, 0); + transform: translate3d(2px, 0, 0) + } + 30%, 50%, 70% { + -webkit-transform: translate3d(-4px, 0, 0); + transform: translate3d(-4px, 0, 0) + } + 40%, 60% { + -webkit-transform: translate3d(4px, 0, 0); + transform: translate3d(4px, 0, 0) + } +} + +@keyframes shake { + 10%, 90% { + -webkit-transform: translate3d(-1px, 0, 0); + transform: translate3d(-1px, 0, 0) + } + 20%, 80% { + -webkit-transform: translate3d(2px, 0, 0); + transform: translate3d(2px, 0, 0) + } + 30%, 50%, 70% { + -webkit-transform: translate3d(-4px, 0, 0); + transform: translate3d(-4px, 0, 0) + } + 40%, 60% { + -webkit-transform: translate3d(4px, 0, 0); + transform: translate3d(4px, 0, 0) + } +} + +.swagger-ui textarea { + font-size: 12px; + width: 100%; + min-height: 280px; + padding: 10px; + border: none; + border-radius: 4px; + outline: none; + background: hsla(0, 0%, 100%, .8); + font-family: Source Code Pro, monospace; + font-weight: 600; + color: #3b4151 +} + +.swagger-ui textarea:focus { + border: 2px solid #61affe +} + +.swagger-ui textarea.curl { + font-size: 12px; + min-height: 100px; + margin: 0; + padding: 10px; + resize: none; + border-radius: 4px; + background: #41444e; + font-family: Source Code Pro, monospace; + font-weight: 600; + color: #fff +} + +.swagger-ui .checkbox { + padding: 5px 0 10px; + -webkit-transition: opacity .5s; + transition: opacity .5s; + color: #333 +} + +.swagger-ui .checkbox label { + display: -webkit-box; + display: -ms-flexbox; + display: flex +} + +.swagger-ui .checkbox p { + font-weight: 400!important; + font-style: italic; + margin: 0!important; + font-family: Source Code Pro, monospace; + font-weight: 600; + color: #3b4151 +} + +.swagger-ui .checkbox input[type=checkbox] { + display: none +} + +.swagger-ui .checkbox input[type=checkbox]+label>.item { + position: relative; + top: 3px; + display: inline-block; + width: 16px; + height: 16px; + margin: 0 8px 0 0; + padding: 5px; + cursor: pointer; + border-radius: 1px; + background: #e8e8e8; + box-shadow: 0 0 0 2px #e8e8e8; + -webkit-box-flex: 0; + -ms-flex: none; + flex: none +} + +.swagger-ui .checkbox input[type=checkbox]+label>.item:active { + -webkit-transform: scale(.9); + transform: scale(.9) +} + +.swagger-ui .checkbox input[type=checkbox]:checked+label>.item { + background: #e8e8e8 url("data:image/svg+xml;charset=utf-8,%3Csvg width='10' height='8' viewBox='3 7 10 8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%2341474E' fill-rule='evenodd' d='M6.333 15L3 11.667l1.333-1.334 2 2L11.667 7 13 8.333z'/%3E%3C/svg%3E") 50% no-repeat +} + +.swagger-ui .dialog-ux { + position: fixed; + z-index: 9999; + top: 0; + right: 0; + bottom: 0; + left: 0 +} + +.swagger-ui .dialog-ux .backdrop-ux { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: rgba(0, 0, 0, .8) +} + +.swagger-ui .dialog-ux .modal-ux { + position: absolute; + z-index: 9999; + top: 50%; + left: 50%; + width: 100%; + min-width: 300px; + max-width: 650px; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + border: 1px solid #ebebeb; + border-radius: 4px; + background: #fff; + box-shadow: 0 10px 30px 0 rgba(0, 0, 0, .2) +} + +.swagger-ui .dialog-ux .modal-ux-content { + overflow-y: auto; + max-height: 540px; + padding: 20px +} + +.swagger-ui .dialog-ux .modal-ux-content p { + font-size: 12px; + margin: 0 0 5px; + color: #41444e; + font-family: Open Sans, sans-serif; + color: #3b4151 +} + +.swagger-ui .dialog-ux .modal-ux-content h4 { + font-size: 18px; + font-weight: 600; + margin: 15px 0 0; + font-family: Titillium Web, sans-serif; + color: #3b4151 +} + +.swagger-ui .dialog-ux .modal-ux-header { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + padding: 12px 0; + border-bottom: 1px solid #ebebeb; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center +} + +.swagger-ui .dialog-ux .modal-ux-header .close-modal { + padding: 0 10px; + border: none; + background: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none +} + +.swagger-ui .dialog-ux .modal-ux-header h3 { + font-size: 20px; + font-weight: 600; + margin: 0; + padding: 0 20px; + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; + font-family: Titillium Web, sans-serif; + color: #3b4151 +} + +.swagger-ui .model { + font-size: 12px; + font-weight: 300; + font-family: Source Code Pro, monospace; + font-weight: 600; + color: #3b4151 +} + +.swagger-ui .model-toggle { + font-size: 10px; + position: relative; + top: 6px; + display: inline-block; + margin: auto .3em; + cursor: pointer; + -webkit-transition: -webkit-transform .15s ease-in; + transition: -webkit-transform .15s ease-in; + transition: transform .15s ease-in; + transition: transform .15s ease-in, -webkit-transform .15s ease-in; + -webkit-transform: rotate(90deg); + transform: rotate(90deg); + -webkit-transform-origin: 50% 50%; + transform-origin: 50% 50% +} + +.swagger-ui .model-toggle.collapsed { + -webkit-transform: rotate(0deg); + transform: rotate(0deg) +} + +.swagger-ui .model-toggle:after { + display: block; + width: 20px; + height: 20px; + content: ""; + background: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z'/%3E%3C/svg%3E") 50% no-repeat; + background-size: 100% +} + +.swagger-ui .model-jump-to-path { + position: relative; + cursor: pointer +} + +.swagger-ui .model-jump-to-path .view-line-link { + position: absolute; + top: -.4em; + cursor: pointer +} + +.swagger-ui .model-title { + position: relative +} + +.swagger-ui .model-title:hover .model-hint { + visibility: visible +} + +.swagger-ui .model-hint { + position: absolute; + top: -1.8em; + visibility: hidden; + padding: .1em .5em; + white-space: nowrap; + color: #ebebeb; + border-radius: 4px; + background: rgba(0, 0, 0, .7) +} + +.swagger-ui section.models { + margin: 30px 0; + border: 1px solid rgba(59, 65, 81, .3); + border-radius: 4px +} + +.swagger-ui section.models.is-open { + padding: 0 0 20px +} + +.swagger-ui section.models.is-open h4 { + margin: 0 0 5px; + border-bottom: 1px solid rgba(59, 65, 81, .3) +} + +.swagger-ui section.models.is-open h4 svg { + -webkit-transform: rotate(90deg); + transform: rotate(90deg) +} + +.swagger-ui section.models h4 { + font-size: 16px; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + margin: 0; + padding: 10px 20px 10px 10px; + cursor: pointer; + -webkit-transition: all .2s; + transition: all .2s; + font-family: Titillium Web, sans-serif; + color: #777; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center +} + +.swagger-ui section.models h4 svg { + -webkit-transition: all .4s; + transition: all .4s +} + +.swagger-ui section.models h4 span { + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1 +} + +.swagger-ui section.models h4:hover { + background: rgba(0, 0, 0, .02) +} + +.swagger-ui section.models h5 { + font-size: 16px; + margin: 0 0 10px; + font-family: Titillium Web, sans-serif; + color: #777 +} + +.swagger-ui section.models .model-jump-to-path { + position: relative; + top: 5px +} + +.swagger-ui section.models .model-container { + margin: 0 20px 15px; + -webkit-transition: all .5s; + transition: all .5s; + border-radius: 4px; + background: rgba(0, 0, 0, .05) +} + +.swagger-ui section.models .model-container:hover { + background: rgba(0, 0, 0, .07) +} + +.swagger-ui section.models .model-container:first-of-type { + margin: 20px +} + +.swagger-ui section.models .model-container:last-of-type { + margin: 0 20px +} + +.swagger-ui section.models .model-box { + background: none +} + +.swagger-ui .model-box { + padding: 10px; + border-radius: 4px; + background: rgba(0, 0, 0, .1) +} + +.swagger-ui .model-box .model-jump-to-path { + position: relative; + top: 4px +} + +.swagger-ui .model-title { + font-size: 16px; + font-family: Titillium Web, sans-serif; + color: #555 +} + +.swagger-ui span>span.model, .swagger-ui span>span.model .brace-close { + padding: 0 0 0 10px +} + +.swagger-ui .prop-type { + color: #55a +} + +.swagger-ui .prop-enum { + display: block +} + +.swagger-ui .prop-format { + color: #999 +} + +.swagger-ui table { + width: 100%; + padding: 0 10px; + border-collapse: collapse +} + +.swagger-ui table.model tbody tr td { + padding: 0; + vertical-align: top +} + +.swagger-ui table.model tbody tr td:first-of-type { + width: 100px; + padding: 0 +} + +.swagger-ui table.headers td { + font-size: 12px; + font-weight: 300; + vertical-align: middle; + font-family: Source Code Pro, monospace; + font-weight: 600; + color: #3b4151 +} + +.swagger-ui table tbody tr td { + padding: 10px 0 0; + vertical-align: top +} + +.swagger-ui table tbody tr td:first-of-type { + width: 20%; + padding: 10px 0 +} + +.swagger-ui table thead tr td, .swagger-ui table thead tr th { + font-size: 12px; + font-weight: 700; + padding: 12px 0; + text-align: left; + border-bottom: 1px solid rgba(59, 65, 81, .2); + font-family: Open Sans, sans-serif; + color: #3b4151 +} + +.swagger-ui .parameters-col_description p { + font-size: 14px; + margin: 0; + font-family: Open Sans, sans-serif; + color: #3b4151 +} + +.swagger-ui .parameters-col_description input[type=text] { + width: 100%; + max-width: 340px +} + +.swagger-ui .parameter__name { + font-size: 16px; + font-weight: 400; + font-family: Titillium Web, sans-serif; + color: #3b4151 +} + +.swagger-ui .parameter__name.required { + font-weight: 700 +} + +.swagger-ui .parameter__name.required:after { + font-size: 10px; + position: relative; + top: -6px; + padding: 5px; + content: "required"; + color: rgba(255, 0, 0, .6) +} + +.swagger-ui .parameter__in { + font-size: 12px; + font-style: italic; + font-family: Source Code Pro, monospace; + font-weight: 600; + color: #888 +} + +.swagger-ui .table-container { + padding: 20px +} + +.swagger-ui .topbar { + padding: 8px 30px; + background-color: #89bf04 +} + +.swagger-ui .topbar .topbar-wrapper { + -ms-flex-align: center +} + +.swagger-ui .topbar .topbar-wrapper, .swagger-ui .topbar a { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + align-items: center +} + +.swagger-ui .topbar a { + font-size: 1.5em; + font-weight: 700; + text-decoration: none; + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; + -ms-flex-align: center; + font-family: Titillium Web, sans-serif; + color: #fff +} + +.swagger-ui .topbar a span { + margin: 0; + padding: 0 10px +} + +.swagger-ui .topbar .download-url-wrapper { + display: -webkit-box; + display: -ms-flexbox; + display: flex +} + +.swagger-ui .topbar .download-url-wrapper input[type=text] { + min-width: 350px; + margin: 0; + border: 2px solid #547f00; + border-radius: 4px 0 0 4px; + outline: none +} + +.swagger-ui .topbar .download-url-wrapper .download-url-button { + font-size: 16px; + font-weight: 700; + padding: 4px 40px; + border: none; + border-radius: 0 4px 4px 0; + background: #547f00; + font-family: Titillium Web, sans-serif; + color: #fff +} + +.swagger-ui .info { + margin: 50px 0 +} + +.swagger-ui .info hgroup.main { + margin: 0 0 20px +} + +.swagger-ui .info hgroup.main a { + font-size: 12px +} + +.swagger-ui .info p { + font-size: 14px; + font-family: Open Sans, sans-serif; + color: #3b4151 +} + +.swagger-ui .info code { + padding: 3px 5px; + border-radius: 4px; + background: rgba(0, 0, 0, .05); + font-family: Source Code Pro, monospace; + font-weight: 600; + color: #9012fe +} + +.swagger-ui .info a { + font-size: 14px; + -webkit-transition: all .4s; + transition: all .4s; + font-family: Open Sans, sans-serif; + color: #4990e2 +} + +.swagger-ui .info a:hover { + color: #1f69c0 +} + +.swagger-ui .info>div { + margin: 0 0 5px +} + +.swagger-ui .info .base-url { + font-size: 12px; + font-weight: 300!important; + margin: 0; + font-family: Source Code Pro, monospace; + font-weight: 600; + color: #3b4151 +} + +.swagger-ui .info .title { + font-size: 36px; + margin: 0; + font-family: Open Sans, sans-serif; + color: #3b4151 +} + +.swagger-ui .info .title small { + font-size: 10px; + position: relative; + top: -5px; + display: inline-block; + margin: 0 0 0 5px; + padding: 2px 4px; + vertical-align: super; + border-radius: 57px; + background: #7d8492 +} + +.swagger-ui .info .title small pre { + margin: 0; + font-family: Titillium Web, sans-serif; + color: #fff +} + +.swagger-ui .auth-btn-wrapper { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + padding: 10px 0; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center +} + +.swagger-ui .auth-wrapper { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; + -webkit-box-pack: end; + -ms-flex-pack: end; + justify-content: flex-end +} + +.swagger-ui .auth-wrapper .authorize { + padding-right: 20px +} + +.swagger-ui .auth-container { + margin: 0 0 10px; + padding: 10px 20px; + border-bottom: 1px solid #ebebeb +} + +.swagger-ui .auth-container:last-of-type { + margin: 0; + padding: 10px 20px; + border: 0 +} + +.swagger-ui .auth-container h4 { + margin: 5px 0 15px!important +} + +.swagger-ui .auth-container .wrapper { + margin: 0; + padding: 0 +} + +.swagger-ui .auth-container input[type=password], .swagger-ui .auth-container input[type=text] { + min-width: 230px +} + +.swagger-ui .auth-container .errors { + font-size: 12px; + padding: 10px; + border-radius: 4px; + font-family: Source Code Pro, monospace; + font-weight: 600; + color: #3b4151 +} + +.swagger-ui .scopes h2 { + font-size: 14px; + font-family: Titillium Web, sans-serif; + color: #3b4151 +} + +.swagger-ui .scope-def { + padding: 0 0 20px +} + +.swagger-ui .errors-wrapper { + margin: 20px; + padding: 10px 20px; + -webkit-animation: scaleUp .5s; + animation: scaleUp .5s; + border: 2px solid #f93e3e; + border-radius: 4px; + background: rgba(249, 62, 62, .1) +} + +.swagger-ui .errors-wrapper .error-wrapper { + margin: 0 0 10px +} + +.swagger-ui .errors-wrapper .errors h4 { + font-size: 14px; + margin: 0; + font-family: Source Code Pro, monospace; + font-weight: 600; + color: #3b4151 +} + +.swagger-ui .errors-wrapper hgroup { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center +} + +.swagger-ui .errors-wrapper hgroup h4 { + font-size: 20px; + margin: 0; + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; + font-family: Titillium Web, sans-serif; + color: #3b4151 +} + +@-webkit-keyframes scaleUp { + 0% { + -webkit-transform: scale(.8); + transform: scale(.8); + opacity: 0 + } + to { + -webkit-transform: scale(1); + transform: scale(1); + opacity: 1 + } +} + +@keyframes scaleUp { + 0% { + -webkit-transform: scale(.8); + transform: scale(.8); + opacity: 0 + } + to { + -webkit-transform: scale(1); + transform: scale(1); + opacity: 1 + } +} + +.swagger-ui .Resizer.vertical.disabled { + display: none +} + +/*# sourceMappingURL=swagger-ui.css.map*/ + +/** + * Swagger UI Theme Overrides + * + * Theme: Muted + * Author: Mark Ostrander + * Github: https://github.com/ostranme/swagger-ui-themes + */ + + .swagger-ui .opblock.opblock-post { + border-color: #DADFE1; + background: rgba(246, 246, 246, .1); + } + + .swagger-ui .opblock.opblock-post .opblock-summary-method { + background: #64908A; + } + + .swagger-ui .opblock.opblock-post .opblock-summary { + border-color: #DADFE1; + } + + .swagger-ui .opblock.opblock-put { + border-color: #DADFE1; + background: rgba(246, 246, 246, .1); + } + + .swagger-ui .opblock.opblock-put .opblock-summary-method { + background: #633B63; + } + + .swagger-ui .opblock.opblock-put .opblock-summary { + border-color: #DADFE1; + } + + .swagger-ui .opblock.opblock-delete { + border-color: #DADFE1; + background: rgba(246, 246, 246, .1); + } + + .swagger-ui .opblock.opblock-delete .opblock-summary-method { + background: #C1786A; + } + + .swagger-ui .opblock.opblock-delete .opblock-summary { + border-color: #DADFE1; + } + + .swagger-ui .opblock.opblock-get { + border-color: #DADFE1; + background: rgba(246, 246, 246, .1); + } + + .swagger-ui .opblock.opblock-get .opblock-summary-method { + background: #3F778E; + } + + .swagger-ui .opblock.opblock-get .opblock-summary { + border-color: #DADFE1; + } + + .swagger-ui .opblock.opblock-patch { + border-color: #DADFE1; + background: rgba(246, 246, 246, .1); + } + + .swagger-ui .opblock.opblock-patch .opblock-summary-method { + background: #C1976A; + } + + .swagger-ui .opblock.opblock-patch .opblock-summary { + border-color: #DADFE1; + } + + .swagger-ui .opblock.opblock-head { + border-color: #DADFE1; + background: rgba(246, 246, 246, .1); + } + + .swagger-ui .opblock.opblock-head .opblock-summary-method { + background: #C1976A; + } + + .swagger-ui .opblock.opblock-head .opblock-summary { + border-color: #DADFE1; + } + + .swagger-ui .opblock.opblock-options { + border-color: #DADFE1; + background: rgba(246, 246, 246, .1); + } + + .swagger-ui .opblock.opblock-options .opblock-summary-method { + background: #3F778E; + } + + .swagger-ui .opblock.opblock-options .opblock-summary { + border-color: #DADFE1; + } + + .swagger-ui .topbar { + padding: 8px 30px; + background-color: #6F7E88; + } + + .swagger-ui .topbar .download-url-wrapper input[type=text] { + min-width: 350px; + margin: 0; + border: 2px solid #DADFE1; + border-radius: 4px 0 0 4px; + outline: none; + } + + .swagger-ui .topbar .download-url-wrapper .download-url-button { + font-size: 16px; + font-weight: 700; + padding: 4px 40px; + border: none; + border-radius: 0 4px 4px 0; + background: #DADFE1; + font-family: Titillium Web, sans-serif; + color: #6F7E88; + } + + .swagger-ui .info a { + font-size: 14px; + -webkit-transition: all .4s; + transition: all .4s; + font-family: Open Sans, sans-serif; + color: #6F7E88; + } + + .swagger-ui .info a:hover { + color: #6F7E88; + } + + .swagger-ui .btn.authorize { + line-height: 1; + display: inline; + color: #6F7E88; + border-color: #6F7E88; + } + + .swagger-ui .btn.authorize svg { + fill: #6F7E88; + } diff --git a/common/templates/api_doc.html b/common/templates/api_doc.html new file mode 100644 index 00000000..ec8c8978 --- /dev/null +++ b/common/templates/api_doc.html @@ -0,0 +1,41 @@ + + + + + {{ api.title }} API Documentation + {% include "common_libs.html" with jquery=0 %} + + + +
+ {% include "partial/_navbar.html" %} + +
+
+ + + +
+ + diff --git a/common/urls.py b/common/urls.py index f0f36969..eb3dfd52 100644 --- a/common/urls.py +++ b/common/urls.py @@ -2,4 +2,8 @@ from django.urls import path from .views import * app_name = "common" -urlpatterns = [path("", home), path("home/", home, name="home")] +urlpatterns = [ + path("", home), + path("api_doc", api_doc), + path("home/", home, name="home"), +] diff --git a/common/views.py b/common/views.py index d0ce83b4..b9ffffbd 100644 --- a/common/views.py +++ b/common/views.py @@ -1,6 +1,7 @@ from django.shortcuts import redirect, render from django.urls import reverse from django.contrib.auth.decorators import login_required +from .api import api @login_required @@ -27,3 +28,11 @@ def error_404(request, exception=None): def error_500(request, exception=None): return render(request, "500.html", status=500) + + +def api_doc(request): + context = { + "api": api, + "openapi_json_url": reverse(f"{api.urls_namespace}:openapi-json"), + } + return render(request, "api_doc.html", context)