Merge remote-tracking branch 'origin/wj' into wilm
This commit is contained in:
@@ -185,6 +185,22 @@ class Config:
|
||||
AUTH_RATELIMIT_WINDOW_SECONDS = int(
|
||||
os.environ.get('AUTH_RATELIMIT_WINDOW_SECONDS', 300))
|
||||
|
||||
# Swagger UI "try it" page at /api/docs/try, beside the read-only Redoc
|
||||
# page at /api/docs. One spec, two renderers.
|
||||
#
|
||||
# Off unless a site turns it on, and that is the point rather than caution:
|
||||
# Swagger UI's Execute button fires a REAL request at THIS server from the
|
||||
# operator's browser, carrying whatever session that browser already holds.
|
||||
# On a production box that is a one-click way to POST against live fleet
|
||||
# data from a page anyone can open. Redoc, which cannot send anything, stays
|
||||
# the default and is always on.
|
||||
#
|
||||
# When off the routes are never bound at all, so /api/docs/try is Flask's
|
||||
# own 404 and the Redoc page does not mention it. See
|
||||
# shopdb/core/api/docs.py.
|
||||
API_DOCS_TRYIT_ENABLED = os.environ.get(
|
||||
'API_DOCS_TRYIT_ENABLED', 'false').lower() == 'true'
|
||||
|
||||
DEFAULT_PAGE_SIZE = 20
|
||||
MAX_PAGE_SIZE = 100
|
||||
|
||||
@@ -214,6 +230,12 @@ class DevelopmentConfig(Config):
|
||||
DEBUG = True
|
||||
SQLALCHEMY_ECHO = True
|
||||
|
||||
# On in development: firing the request is the whole reason a developer
|
||||
# opens the API docs. Still read from the environment, so a shared dev
|
||||
# instance can turn it back off without editing this file.
|
||||
API_DOCS_TRYIT_ENABLED = os.environ.get(
|
||||
'API_DOCS_TRYIT_ENABLED', 'true').lower() == 'true'
|
||||
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get(
|
||||
'DATABASE_URL',
|
||||
'mysql+pymysql://root:changeme@127.0.0.1:3306/shopdb_flask',
|
||||
@@ -234,6 +256,16 @@ class TestingConfig(Config):
|
||||
# xdist distribution, so the failure moves around. The tests that are about
|
||||
# the limiter turn it on themselves.
|
||||
ENDPOINTS_ENROLL_RATELIMIT_ENABLED = False
|
||||
# Off, and stated here rather than inherited, because a test suite that
|
||||
# silently ran with it on would be testing a configuration no site ships.
|
||||
# The session-wide app in tests/conftest.py is what
|
||||
# tests/test_core/test_public_read_surface.py sweeps for ungated GET
|
||||
# routes, and the try-it page plus its two vendored assets are
|
||||
# deliberately unauthenticated. Keeping the default here means that sweep
|
||||
# keeps describing what production actually exposes; the tests that ARE
|
||||
# about the page build their own app with the flag on
|
||||
# (tests/test_core/test_api_docs_tryit.py).
|
||||
API_DOCS_TRYIT_ENABLED = False
|
||||
SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
|
||||
SQLALCHEMY_ENGINE_OPTIONS = {
|
||||
'connect_args': {'check_same_thread': False},
|
||||
|
||||
@@ -4,11 +4,21 @@ Served at /api/docs (relative to the mount). The Redoc bundle is vendored in
|
||||
staticdocs/ so this works fully offline on the air-gapped prod box - no CDN.
|
||||
The spec is docs/openapi.json in the repo (regenerate with
|
||||
scripts/gen_openapi.py after API changes).
|
||||
|
||||
Two renderers over ONE spec. Redoc is read-only by design and is the default,
|
||||
because what is published is a contract. Swagger UI at /api/docs/try adds the
|
||||
one thing Redoc will not do: fire a real request from the page, against this
|
||||
server, with whatever session the browser is already carrying. That is its
|
||||
whole value and the whole reason it is off unless a site turns it on - see
|
||||
API_DOCS_TRYIT_ENABLED in shopdb/config.py.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
from flask import Blueprint, Response, send_file, url_for
|
||||
from markupsafe import escape
|
||||
|
||||
from flask import Blueprint, Response, current_app, request, send_file, url_for
|
||||
|
||||
docs_bp = Blueprint('docs', __name__)
|
||||
|
||||
@@ -19,32 +29,168 @@ _SPEC = os.path.join(_REPO_ROOT, 'docs', 'openapi.json')
|
||||
_LLMS = os.path.join(_REPO_ROOT, 'docs', 'llms.txt')
|
||||
_REDOC = os.path.join(_HERE, 'staticdocs', 'redoc.standalone.js')
|
||||
|
||||
# Vendored from swagger-ui-dist 5.33.0. TWO files out of the ~30 in that dist,
|
||||
# because those are the two the page below actually loads:
|
||||
# swagger-ui.css - the stylesheet. Its images are inline data: URIs,
|
||||
# so it pulls nothing else. The trailing
|
||||
# sourceMappingURL comment was stripped: the .map
|
||||
# is not vendored, and left in place it turns every
|
||||
# devtools open into a 404 on a box with no
|
||||
# internet to fall back on.
|
||||
# swagger-ui-bundle.min.js - the renderer plus the core presets, self-
|
||||
# contained. NOT vendored beside it:
|
||||
# swagger-ui-standalone-preset.js (the topbar with
|
||||
# the "load any spec URL" box - this page serves
|
||||
# one spec on purpose), the ES bundles (module
|
||||
# builds of the same code), the .map files (~10 MB
|
||||
# of source maps that only devtools reads),
|
||||
# oauth2-redirect.html (no OAuth flow here), and
|
||||
# the favicons.
|
||||
# The upstream name is swagger-ui-bundle.js; the vendored copy carries .min.js,
|
||||
# which is what it is - a minified single-line build. That suffix is also what
|
||||
# scripts/check-naming-and-style.sh already filters vendored bundles by, so the
|
||||
# style gate does not report thousands of hits in code nobody here wrote.
|
||||
# swagger-ui.LICENSE.txt is the Apache-2.0 text that ships with the dist. It is
|
||||
# never served; it is here because redistributing the bundle requires it.
|
||||
_SWAGGER_CSS = os.path.join(_HERE, 'staticdocs', 'swagger-ui.css')
|
||||
_SWAGGER_JS = os.path.join(_HERE, 'staticdocs', 'swagger-ui-bundle.min.js')
|
||||
|
||||
_TRYIT_ENDPOINT = 'docs.swagger_tryit'
|
||||
|
||||
|
||||
def _tryit_is_mounted():
|
||||
"""True when the try-it page exists on THIS app.
|
||||
|
||||
Reads the url map rather than the config flag on purpose: the routes are
|
||||
bound once, at blueprint registration, so a config value changed after boot
|
||||
would have the Redoc page advertise a link that answers 404.
|
||||
"""
|
||||
return _TRYIT_ENDPOINT in current_app.view_functions
|
||||
|
||||
|
||||
@docs_bp.route('/', strict_slashes=False)
|
||||
def docs_index():
|
||||
"""Redoc page. url_for keeps the asset URLs correct under any mount."""
|
||||
spec_url = url_for('docs.openapi_spec')
|
||||
redoc_url = url_for('docs.redoc_js')
|
||||
# Mentioned only when it is actually there. An unconditional note would be
|
||||
# an advert for a 404 on every box that ships the shipped default.
|
||||
tryit_note = ''
|
||||
if _tryit_is_mounted():
|
||||
tryit_note = (
|
||||
'<p class="docs-note">'
|
||||
'This page is read-only. To send a real request against this '
|
||||
'server, use <a href="%s">the try-it view</a>.</p>'
|
||||
% url_for(_TRYIT_ENDPOINT)
|
||||
)
|
||||
# DARK MODE. Redoc does not read prefers-color-scheme on its own, and
|
||||
# the <redoc spec-url> element takes no theme - so the page goes through
|
||||
# Redoc.init instead, which does. The app has a dark theme and this was
|
||||
# the one page that stayed white when the rest of the site went dark.
|
||||
#
|
||||
# The page RE-INITIALISES when the OS scheme flips rather than restyling:
|
||||
# Redoc bakes its colours into styled-components at init, so a CSS
|
||||
# override reaches the chrome and misses the content.
|
||||
page = (
|
||||
'<!doctype html><html><head>'
|
||||
'<title>ShopDB API</title>'
|
||||
'<meta charset="utf-8">'
|
||||
'<meta name="viewport" content="width=device-width, initial-scale=1">'
|
||||
'<style>body{margin:0;padding:0}</style></head><body>'
|
||||
'<redoc spec-url="%s"></redoc>'
|
||||
'<style>'
|
||||
':root{color-scheme:light dark}'
|
||||
'body{margin:0;padding:0;background:#fff}'
|
||||
'.docs-note{font:14px/1.4 system-ui,sans-serif;margin:0;'
|
||||
'padding:10px 16px;background:#eef2f7;color:#0f172a;'
|
||||
'border-bottom:1px solid #cbd5e1}'
|
||||
'.docs-note a{color:#1d4ed8}'
|
||||
'@media (prefers-color-scheme: dark){'
|
||||
'body{background:#0f172a}'
|
||||
'.docs-note{background:#1e293b;color:#e2e8f0;'
|
||||
'border-bottom-color:#334155}'
|
||||
'.docs-note a{color:#7dd3fc}'
|
||||
'}'
|
||||
'</style></head><body>'
|
||||
'%s'
|
||||
'<div id="redoc"></div>'
|
||||
'<script src="%s"></script>'
|
||||
'</body></html>' % (spec_url, redoc_url)
|
||||
'<script>'
|
||||
'var SPEC = %s;'
|
||||
# Redoc computes text and the right-hand sample panel from the
|
||||
# background and gets both wrong on a dark one, so they are named
|
||||
# rather than left to it.
|
||||
'var DARK={colors:{primary:{main:"#7dd3fc"},'
|
||||
'text:{primary:"#e2e8f0",secondary:"#94a3b8"},'
|
||||
'border:{dark:"#334155",light:"#1e293b"}},'
|
||||
'sidebar:{backgroundColor:"#111827",textColor:"#e2e8f0",'
|
||||
'activeTextColor:"#7dd3fc"},'
|
||||
'rightPanel:{backgroundColor:"#020617",textColor:"#e2e8f0"},'
|
||||
'codeBlock:{backgroundColor:"#0b1220"},'
|
||||
'schema:{nestedBackground:"#0b1220"},'
|
||||
'typography:{fontFamily:"system-ui,sans-serif",'
|
||||
'code:{color:"#e2e8f0",backgroundColor:"#0b1220"}}};'
|
||||
'var LIGHT={colors:{primary:{main:"#1d4ed8"}},'
|
||||
'typography:{fontFamily:"system-ui,sans-serif"}};'
|
||||
'var q=window.matchMedia("(prefers-color-scheme: dark)");'
|
||||
'function draw(){'
|
||||
'document.getElementById("redoc").innerHTML="";'
|
||||
'Redoc.init(SPEC,{theme:q.matches?DARK:LIGHT},'
|
||||
'document.getElementById("redoc"));'
|
||||
'}'
|
||||
'draw();'
|
||||
# addEventListener is missing from MediaQueryList in older WebViews,
|
||||
# and a shop-floor kiosk browser is exactly where this gets read.
|
||||
'if(q.addEventListener){q.addEventListener("change",draw);}'
|
||||
'else if(q.addListener){q.addListener(draw);}'
|
||||
'</script>'
|
||||
'</body></html>' % (tryit_note, redoc_url, json.dumps(spec_url))
|
||||
)
|
||||
return Response(page, mimetype='text/html')
|
||||
|
||||
|
||||
_SPEC_CACHE = {}
|
||||
|
||||
|
||||
def _spec_with_server():
|
||||
"""The generated spec, with `servers` set to where this instance is mounted.
|
||||
|
||||
THE MOUNT IS A DEPLOYMENT FACT, NOT A BUILD FACT. gen_openapi.py writes
|
||||
servers: [{"url": "/"}] because it cannot know where the app will be hung,
|
||||
and wsgi.py moves MOUNT_PATH from PATH_INFO into SCRIPT_NAME at runtime. So
|
||||
an instance under /shopdb served a spec whose paths start at /api, and both
|
||||
renderers dutifully built https://host/api/apitokens - a URL that 404s.
|
||||
Anyone who copied it out of the docs page got a broken call, and Swagger's
|
||||
try-it fired at the wrong place.
|
||||
|
||||
request.script_root is the runtime answer: '' for a root install, '/shopdb'
|
||||
under the subpath method, and correct behind a proxy that sets the prefix.
|
||||
|
||||
The file is ~1 MB, so it is parsed once and cached by mtime; each request
|
||||
gets a shallow copy with only `servers` replaced, which keeps two requests
|
||||
on different mounts from racing on one dict.
|
||||
"""
|
||||
stamp = os.path.getmtime(_SPEC)
|
||||
if _SPEC_CACHE.get('stamp') != stamp:
|
||||
with open(_SPEC, 'r', encoding='utf-8') as handle:
|
||||
_SPEC_CACHE['spec'] = json.load(handle)
|
||||
_SPEC_CACHE['stamp'] = stamp
|
||||
|
||||
root = request.script_root or ''
|
||||
spec = dict(_SPEC_CACHE['spec'])
|
||||
spec['servers'] = [{
|
||||
'url': root or '/',
|
||||
'description': 'this instance',
|
||||
}]
|
||||
return spec
|
||||
|
||||
|
||||
@docs_bp.route('/openapi.json')
|
||||
def openapi_spec():
|
||||
"""The generated OpenAPI 3.1 spec (machine + LLM readable)."""
|
||||
if not os.path.isfile(_SPEC):
|
||||
return Response('{"error":"openapi.json not generated"}',
|
||||
status=404, mimetype='application/json')
|
||||
return send_file(_SPEC, mimetype='application/json')
|
||||
return Response(json.dumps(_spec_with_server()),
|
||||
mimetype='application/json')
|
||||
|
||||
|
||||
@docs_bp.route('/redoc.standalone.js')
|
||||
@@ -59,3 +205,92 @@ def llms_txt():
|
||||
if not os.path.isfile(_LLMS):
|
||||
return Response('llms.txt not found', status=404, mimetype='text/plain')
|
||||
return send_file(_LLMS, mimetype='text/plain')
|
||||
|
||||
|
||||
def swagger_tryit():
|
||||
"""Swagger UI over the SAME spec the Redoc page renders.
|
||||
|
||||
Every URL on the page comes from url_for, so the spec and both assets stay
|
||||
same-origin and correct under a mount path (prod runs under /shopdb).
|
||||
"""
|
||||
spec_url = url_for('docs.openapi_spec')
|
||||
css_url = url_for('docs.swagger_css')
|
||||
js_url = url_for('docs.swagger_js')
|
||||
redoc_page_url = url_for('docs.docs_index')
|
||||
page = (
|
||||
'<!doctype html><html><head>'
|
||||
'<title>ShopDB API - try it</title>'
|
||||
'<meta charset="utf-8">'
|
||||
'<meta name="viewport" content="width=device-width, initial-scale=1">'
|
||||
'<link rel="stylesheet" href="%(css)s">'
|
||||
'<style>body{margin:0;padding:0}'
|
||||
'.tryit-warn{font:14px/1.4 system-ui,sans-serif;background:#7f1d1d;'
|
||||
'color:#fff;margin:0;padding:10px 16px}'
|
||||
'.tryit-warn a{color:#fff}</style>'
|
||||
'</head><body>'
|
||||
'<p class="tryit-warn"><b>Execute sends a real request to this '
|
||||
'server</b>, as whoever is signed in on this browser. A write '
|
||||
'operation changes live data. The read-only contract is at '
|
||||
'<a href="%(redoc)s">/api/docs</a>.</p>'
|
||||
'<div id="swagger-ui"></div>'
|
||||
'<script src="%(js)s"></script>'
|
||||
'<script>\n'
|
||||
'window.ui = SwaggerUIBundle({\n'
|
||||
# json.dumps, NOT a bare "%s". SCRIPT_NAME is proxy-controlled and
|
||||
# ProxyFix honours X-Forwarded-Prefix, so a mount path carrying a quote
|
||||
# closed this string literal and executed what followed: SCRIPT_NAME of
|
||||
# /a"+alert(1)+" rendered url: "/a"+alert(1)+"/api/docs/openapi.json".
|
||||
# Escaping it as JSON is what makes the value data rather than code.
|
||||
' url: %(spec)s,\n'
|
||||
' dom_id: "#swagger-ui",\n'
|
||||
' presets: [SwaggerUIBundle.presets.apis],\n'
|
||||
' layout: "BaseLayout",\n'
|
||||
' deepLinking: true,\n'
|
||||
' filter: true,\n'
|
||||
# 573 operations. Expanded by default, the page spends many seconds
|
||||
# laying out before it answers a scroll.
|
||||
' docExpansion: "none",\n'
|
||||
' withCredentials: true,\n'
|
||||
# THE air-gap setting. Left unset, Swagger UI renders a "valid" badge
|
||||
# by handing this spec to the public swagger validator - an outbound
|
||||
# request that cannot succeed here, and that would ship the internal
|
||||
# API surface off the box if it ever could.
|
||||
' validatorUrl: null\n'
|
||||
'});\n'
|
||||
'</script>'
|
||||
'</body></html>'
|
||||
% {'css': escape(css_url), 'js': escape(js_url),
|
||||
'spec': json.dumps(spec_url),
|
||||
'redoc': escape(redoc_page_url)}
|
||||
)
|
||||
return Response(page, mimetype='text/html')
|
||||
|
||||
|
||||
def swagger_css():
|
||||
"""Vendored Swagger UI stylesheet (offline)."""
|
||||
return send_file(_SWAGGER_CSS, mimetype='text/css')
|
||||
|
||||
|
||||
def swagger_js():
|
||||
"""Vendored Swagger UI bundle (offline)."""
|
||||
return send_file(_SWAGGER_JS, mimetype='application/javascript')
|
||||
|
||||
|
||||
@docs_bp.record_once
|
||||
def _mount_tryit(state):
|
||||
"""Bind the try-it routes only when the flag is on for this app.
|
||||
|
||||
Binding at registration rather than checking the flag inside the view, so
|
||||
that when it is off the routes DO NOT EXIST: the URL gets Flask's own 404
|
||||
and confirms nothing, and the unauthenticated-GET sweep in
|
||||
tests/test_core/test_public_read_surface.py never has to make an exception
|
||||
for three routes that are only meant to be reachable on a dev box.
|
||||
"""
|
||||
if not state.app.config.get('API_DOCS_TRYIT_ENABLED'):
|
||||
return
|
||||
state.add_url_rule('/try', endpoint='swagger_tryit',
|
||||
view_func=swagger_tryit, strict_slashes=False)
|
||||
state.add_url_rule('/swagger-ui.css', endpoint='swagger_css',
|
||||
view_func=swagger_css)
|
||||
state.add_url_rule('/swagger-ui-bundle.min.js', endpoint='swagger_js',
|
||||
view_func=swagger_js)
|
||||
|
||||
File diff suppressed because one or more lines are too long
2
shopdb/core/api/staticdocs/swagger-ui-bundle.min.js
vendored
Normal file
2
shopdb/core/api/staticdocs/swagger-ui-bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
202
shopdb/core/api/staticdocs/swagger-ui.LICENSE.txt
Normal file
202
shopdb/core/api/staticdocs/swagger-ui.LICENSE.txt
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
2
shopdb/core/api/staticdocs/swagger-ui.css
vendored
Normal file
2
shopdb/core/api/staticdocs/swagger-ui.css
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user