server/posts: generate appropriate URIs using config parameters only

This commit is contained in:
Shyam Sunder 2021-09-30 17:21:29 -04:00
parent 299ebfc4c8
commit 36a614d954
22 changed files with 314 additions and 187 deletions

View file

@ -20,7 +20,11 @@ http {
keepalive_timeout 65; keepalive_timeout 65;
proxy_cache_path /tmp/nginx-cache proxy_cache_path /tmp/nginx-cache
levels=1:2 keys_zone=spa_cache:1m max_size=50m inactive=60m use_temp_path=off; levels=1:2
keys_zone=spa_cache:4m
max_size=50m
inactive=60m
use_temp_path=off;
upstream backend { upstream backend {
server __BACKEND__:6666; server __BACKEND__:6666;
@ -88,6 +92,9 @@ http {
location / { location / {
tcp_nodelay on; tcp_nodelay on;
# remove unneeded auth headers to improve caching
proxy_set_header Authorization "";
proxy_cache spa_cache; proxy_cache spa_cache;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on; proxy_cache_background_update on;

View file

@ -80,8 +80,8 @@ user@host:szuru$ docker-compose down
If you want to host your website on, (`http://example.com/`) but want If you want to host your website on, (`http://example.com/`) but want
to serve the images on a different domain, (`http://static.example.com/`) to serve the images on a different domain, (`http://static.example.com/`)
then you can run the backend container with an additional environment then you can configure the `data_url` variable in your `config.yaml`
variable `DATA_URL=http://static.example.com/`. Make sure that this (ex: `data_url: http://static.example.com/`). Make sure that this
additional host has access contents to the `/data` volume mounted in the additional host has access contents to the `/data` volume mounted in the
backend. backend.
@ -89,12 +89,9 @@ user@host:szuru$ docker-compose down
Some users may wish to access the service at a different base URI, such Some users may wish to access the service at a different base URI, such
as `http://example.com/szuru/`, commonly when sharing multiple HTTP as `http://example.com/szuru/`, commonly when sharing multiple HTTP
services on one domain using a reverse proxy. This can be configured in services on one domain using a reverse proxy. For szurubooru to handle
either of the following ways: links properly, you must configure the reverse proxy to pass the new
URL prefix (in this case `/szuru`) in the `X-Forwarded-Prefix` header.
- Set the 'domain' value in `config.yaml` to include the prefix, i.e.:
`domain: "http://example.com/szuru" # omit trailing slash`
- Configure the reverse proxy to pass the `X-Forwarded-Prefix` header.
Note that this will require a reverse proxy to function. You should set Note that this will require a reverse proxy to function. You should set
your reverse proxy to proxy `http(s)://example.com/szuru` to your reverse proxy to proxy `http(s)://example.com/szuru` to
@ -111,7 +108,7 @@ user@host:szuru$ docker-compose down
proxy_set_header Connection "upgrade"; proxy_set_header Connection "upgrade";
proxy_set_header X-Forwarded-Prefix /szuru; proxy_set_header X-Forwarded-Prefix /szuru;
// optional... # optional...
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Scheme $scheme; proxy_set_header X-Scheme $scheme;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;

View file

@ -3,11 +3,28 @@
# shown in the website title and on the front page # shown in the website title and on the front page
name: szurubooru name: szurubooru
# full url to the homepage of this szurubooru site, with no trailing slash
domain: # example: http://example.com
# used to salt the users' password hashes and generate filenames for static content # used to salt the users' password hashes and generate filenames for static content
secret: change secret: change
# set to the root web address for your instance
# example values:
# - `/` (default) is used when the domain is unknown
# - `https://szuru.example.com/` if you know the specific domain
# and is required if you want email-based password reset
# - `/baseprefix` if you want to host szurubooru on a specific
# prefix and share the domain with other applications
# - `https://www.example.com/szuru` combines both of the above
# also see: "Setting a specific base URI for proxying" in INSTALL.md
base_url: /
# !!should not be changed for the normal docker installation!!
# set to the root web address for static image content
# if it is a relative path with no leading `/`, then this will be
# appended to the base url.
# see: "Using a seperate domain to host static files" in INSTALL.md
# for more info on when to modify
data_url: data/
# Delete thumbnails and source files on post delete # Delete thumbnails and source files on post delete
# Original functionality is no, to mitigate the impacts of admins going # Original functionality is no, to mitigate the impacts of admins going
# on unchecked post purges. # on unchecked post purges.
@ -171,7 +188,6 @@ privileges:
## ONLY SET THESE IF DEPLOYING OUTSIDE OF DOCKER ## ONLY SET THESE IF DEPLOYING OUTSIDE OF DOCKER
#debug: 0 # generate server logs? #debug: 0 # generate server logs?
#show_sql: 0 # show sql in server logs? #show_sql: 0 # show sql in server logs?
#data_url: /data/
#data_dir: /var/www/data #data_dir: /var/www/data
## usage: schema://user:password@host:port/database_name ## usage: schema://user:password@host:port/database_name
## example: postgres://szuru:dog@localhost:5432/szuru_test ## example: postgres://szuru:dog@localhost:5432/szuru_test

View file

@ -45,7 +45,7 @@ def get_info(ctx: rest.Context, _params: Dict[str, str] = {}) -> rest.Response:
"defaultUserRank": config.config["default_rank"], "defaultUserRank": config.config["default_rank"],
"enableSafety": config.config["enable_safety"], "enableSafety": config.config["enable_safety"],
"contactEmail": config.config["contact_email"], "contactEmail": config.config["contact_email"],
"canSendMails": bool(config.config["smtp"]["host"]), "canSendMails": util.can_send_mail(),
"privileges": util.snake_case_to_lower_camel_case_keys( "privileges": util.snake_case_to_lower_camel_case_keys(
config.config["privileges"] config.config["privileges"]
), ),
@ -74,17 +74,17 @@ def generate_manifest(
"name": config.config["name"], "name": config.config["name"],
"icons": [ "icons": [
{ {
"src": f"{ctx.url_prefix}/img/android-chrome-192x192.png", "src": util.add_url_prefix("/img/android-chrome-192x192.png"),
"type": "image/png", "type": "image/png",
"sizes": "192x192", "sizes": "192x192",
}, },
{ {
"src": f"{ctx.url_prefix}/img/android-chrome-512x512.png", "src": util.add_url_prefix("/img/android-chrome-512x512.png"),
"type": "image/png", "type": "image/png",
"sizes": "512x512", "sizes": "512x512",
}, },
], ],
"start_url": f"{ctx.url_prefix}/", "start_url": util.add_url_prefix(),
"theme_color": "#24aadd", "theme_color": "#24aadd",
"background_color": "#ffffff", "background_color": "#ffffff",
"display": "standalone", "display": "standalone",

View file

@ -1,9 +1,9 @@
from typing import Dict from typing import Callable, Dict
from yattag import Doc from yattag import Doc
from szurubooru import config, model, rest from szurubooru import config, model, rest
from szurubooru.func import auth, posts from szurubooru.func import auth, posts, util
_default_meta_tags = { _default_meta_tags = {
"viewport": "width=device-width, initial-scale=1, maximum-scale=1", "viewport": "width=device-width, initial-scale=1, maximum-scale=1",
@ -62,7 +62,8 @@ _apple_touch_startup_images = {
def _get_html_template( def _get_html_template(
meta_tags: Dict = {}, title: str = config.config["name"], prefix: str = "" title: str,
meta_tags: Dict = {},
) -> Doc: ) -> Doc:
doc = Doc() doc = Doc()
doc.asis("<!DOCTYPE html>") doc.asis("<!DOCTYPE html>")
@ -73,19 +74,21 @@ def _get_html_template(
doc.stag("meta", name=name, content=content) doc.stag("meta", name=name, content=content)
with doc.tag("title"): with doc.tag("title"):
doc.text(title) doc.text(title)
doc.stag("base", href=f"{prefix}/") doc.stag("base", href=util.add_url_prefix())
doc.stag( doc.stag(
"link", rel="manifest", href=f"{prefix}/api/manifest.json" "link",
rel="manifest",
href=util.add_url_prefix("/api/manifest.json"),
) )
doc.stag( doc.stag(
"link", "link",
href=f"{prefix}/css/app.min.css", href=util.add_url_prefix("/css/app.min.css"),
rel="stylesheet", rel="stylesheet",
type="text/css", type="text/css",
) )
doc.stag( doc.stag(
"link", "link",
href=f"{prefix}/css/vendor.min.css", href=util.add_url_prefix("/css/vendor.min.css"),
rel="stylesheet", rel="stylesheet",
type="text/css", type="text/css",
) )
@ -93,19 +96,21 @@ def _get_html_template(
"link", "link",
rel="shortcut icon", rel="shortcut icon",
type="image/png", type="image/png",
href=f"{prefix}/img/favicon.png", href=util.add_url_prefix("/img/favicon.png"),
) )
doc.stag( doc.stag(
"link", "link",
rel="apple-touch-icon", rel="apple-touch-icon",
sizes="180x180", sizes="180x180",
href=f"{prefix}/img/apple-touch-icon.png", href=util.add_url_prefix("/img/apple-touch-icon.png"),
) )
for res, media in _apple_touch_startup_images.items(): for res, media in _apple_touch_startup_images.items():
doc.stag( doc.stag(
"link", "link",
rel="apple-touch-startup-image", rel="apple-touch-startup-image",
href=f"{prefix}/img/apple-touch-startup-image-{res}.png", href=util.add_url_prefix(
f"/img/apple-touch-startup-image-{res}.png"
),
media=" and ".join( media=" and ".join(
f"({k}: {v})" for k, v in media.items() f"({k}: {v})" for k, v in media.items()
), ),
@ -116,11 +121,15 @@ def _get_html_template(
with doc.tag("div", id="content-holder"): with doc.tag("div", id="content-holder"):
pass pass
with doc.tag( with doc.tag(
"script", type="text/javascript", src="js/vendor.min.js" "script",
type="text/javascript",
src=util.add_url_prefix("js/vendor.min.js"),
): ):
pass pass
with doc.tag( with doc.tag(
"script", type="text/javascript", src="js/app.min.js" "script",
type="text/javascript",
src=util.add_url_prefix("js/app.min.js"),
): ):
pass pass
return doc.getvalue() return doc.getvalue()
@ -152,13 +161,17 @@ def get_post_html(
metadata = { metadata = {
"og:site_name": config.config["name"], "og:site_name": config.config["name"],
"og:url": f"{ctx.url_prefix}/post/{params['post_id']}", "og:url": util.add_url_prefix(f"post/{params['post_id']}"),
"og:title": title, "og:title": title,
"twitter:title": title, "twitter:title": title,
"og:type": "article", "og:type": "article",
} }
# Note: ctx.user will always be the anonymous user # Note: ctx.user will always be the anonymous user
if auth.has_privilege(ctx.user, "posts:view"): if auth.has_privilege(ctx.user, "posts:view"):
content_url = util.add_data_prefix(posts.get_post_content_path(post))
thumbnail_url = util.add_data_prefix(
posts.get_post_thumbnail_path(post)
)
metadata["og:article:published_time"] = post.creation_time.isoformat() metadata["og:article:published_time"] = post.creation_time.isoformat()
if post.last_edit_time: if post.last_edit_time:
metadata[ metadata[
@ -169,11 +182,9 @@ def get_post_html(
) )
if post.type in (model.Post.TYPE_VIDEO): if post.type in (model.Post.TYPE_VIDEO):
metadata["twitter:card"] = "player" metadata["twitter:card"] = "player"
metadata["og:video:url"] = posts.get_post_content_url(post) metadata["og:video:url"] = content_url
metadata["twitter:player:stream"] = posts.get_post_content_url( metadata["twitter:player:stream"] = content_url
post metadata["og:image:url"] = thumbnail_url
)
metadata["og:image:url"] = posts.get_post_thumbnail_url(post)
if post.canvas_width and post.canvas_height: if post.canvas_width and post.canvas_height:
metadata["og:video:width"] = str(post.canvas_width) metadata["og:video:width"] = str(post.canvas_width)
metadata["og:video:height"] = str(post.canvas_height) metadata["og:video:height"] = str(post.canvas_height)
@ -181,15 +192,13 @@ def get_post_html(
metadata["twitter:player:height"] = str(post.canvas_height) metadata["twitter:player:height"] = str(post.canvas_height)
else: else:
metadata["twitter:card"] = "summary_large_image" metadata["twitter:card"] = "summary_large_image"
metadata["og:image:url"] = posts.get_post_content_url(post) metadata["og:image:url"] = content_url
metadata["twitter:image"] = posts.get_post_content_url(post) metadata["twitter:image"] = content_url
return _get_html_template( return _get_html_template(title=title, meta_tags=metadata)
meta_tags=metadata, title=title, prefix=ctx.url_prefix
)
@rest.routes.get("/html/.*", accept="text/html") @rest.routes.get("/html/.*", accept="text/html")
def default_route( def default_route(
ctx: rest.Context, _params: Dict[str, str] = {} ctx: rest.Context, _params: Dict[str, str] = {}
) -> rest.Response: ) -> rest.Response:
return _get_html_template(prefix=ctx.url_prefix) return _get_html_template(title=config.config["name"])

View file

@ -2,7 +2,7 @@ from hashlib import md5
from typing import Dict from typing import Dict
from szurubooru import config, errors, rest from szurubooru import config, errors, rest
from szurubooru.func import auth, mailer, users, versions from szurubooru.func import auth, mailer, users, util, versions
MAIL_SUBJECT = "Password reset for {name}" MAIL_SUBJECT = "Password reset for {name}"
MAIL_BODY = ( MAIL_BODY = (
@ -24,8 +24,7 @@ def start_password_reset(
% (user_name) % (user_name)
) )
token = auth.generate_authentication_token(user) token = auth.generate_authentication_token(user)
url = util.add_url_prefix(f"password-reset/{user.name}:{token}")
url = f"{ctx.url_prefix}/password-reset/{user.name}:{token}"
mailer.send_mail( mailer.send_mail(
config.config["smtp"]["from"], config.config["smtp"]["from"],

View file

@ -9,7 +9,8 @@ _search_executor = search.Executor(search.configs.PoolSearchConfig())
def _serialize(ctx: rest.Context, pool: model.Pool) -> rest.Response: def _serialize(ctx: rest.Context, pool: model.Pool) -> rest.Response:
return pools.serialize_pool( return pools.serialize_pool(
pool, options=serialization.get_serialization_options(ctx) pool,
options=serialization.get_serialization_options(ctx),
) )

View file

@ -35,7 +35,9 @@ def _serialize_post(
ctx: rest.Context, post: Optional[model.Post] ctx: rest.Context, post: Optional[model.Post]
) -> rest.Response: ) -> rest.Response:
return posts.serialize_post( return posts.serialize_post(
post, ctx.user, options=serialization.get_serialization_options(ctx) post,
ctx.user,
options=serialization.get_serialization_options(ctx),
) )

View file

@ -31,7 +31,6 @@ def _docker_config() -> Dict:
return { return {
"debug": True, "debug": True,
"show_sql": int(os.getenv("LOG_SQL", 0)), "show_sql": int(os.getenv("LOG_SQL", 0)),
"data_url": os.getenv("DATA_URL", "data/"),
"data_dir": "/data/", "data_dir": "/data/",
"database": "postgres://%(user)s:%(pass)s@%(host)s:%(port)d/%(db)s" "database": "postgres://%(user)s:%(pass)s@%(host)s:%(port)d/%(db)s"
% { % {

View file

@ -85,13 +85,7 @@ def validate_config() -> None:
% (config.config["default_rank"]) % (config.config["default_rank"])
) )
for key in ["data_url", "data_dir"]: if not os.path.isabs(config.config["data_dir"] or ""):
if not config.config[key]:
raise errors.ConfigError(
"Service is not configured: %r is missing" % key
)
if not os.path.isabs(config.config["data_dir"]):
raise errors.ConfigError("data_dir must be an absolute path") raise errors.ConfigError("data_dir must be an absolute path")
if not config.config["database"]: if not config.config["database"]:

View file

@ -145,7 +145,8 @@ class PoolSerializer(serialization.BaseSerializer):
def serialize_pool( def serialize_pool(
pool: model.Pool, options: List[str] = [] pool: model.Pool,
options: List[str] = [],
) -> Optional[rest.Response]: ) -> Optional[rest.Response]:
if not pool: if not pool:
return None return None
@ -154,7 +155,8 @@ def serialize_pool(
def serialize_micro_pool(pool: model.Pool) -> Optional[rest.Response]: def serialize_micro_pool(pool: model.Pool) -> Optional[rest.Response]:
return serialize_pool( return serialize_pool(
pool, options=["id", "names", "category", "description", "postCount"] pool,
options=["id", "names", "category", "description", "postCount"],
) )

View file

@ -44,7 +44,9 @@ class PostAlreadyUploadedError(errors.ValidationError):
super().__init__( super().__init__(
"Post already uploaded (%d)" % other_post.post_id, "Post already uploaded (%d)" % other_post.post_id,
{ {
"otherPostUrl": get_post_content_url(other_post), "otherPostUrl": util.add_data_prefix(
get_post_content_path(other_post)
),
"otherPostId": other_post.post_id, "otherPostId": other_post.post_id,
}, },
) )
@ -105,25 +107,6 @@ def get_post_security_hash(id: int) -> str:
).hexdigest()[0:16] ).hexdigest()[0:16]
def get_post_content_url(post: model.Post) -> str:
assert post
return "%s/posts/%d_%s.%s" % (
config.config["data_url"].rstrip("/"),
post.post_id,
get_post_security_hash(post.post_id),
mime.get_extension(post.mime_type) or "dat",
)
def get_post_thumbnail_url(post: model.Post) -> str:
assert post
return "%s/generated-thumbnails/%d_%s.jpg" % (
config.config["data_url"].rstrip("/"),
post.post_id,
get_post_security_hash(post.post_id),
)
def get_post_content_path(post: model.Post) -> str: def get_post_content_path(post: model.Post) -> str:
assert post assert post
assert post.post_id assert post.post_id
@ -159,7 +142,11 @@ def serialize_note(note: model.PostNote) -> rest.Response:
class PostSerializer(serialization.BaseSerializer): class PostSerializer(serialization.BaseSerializer):
def __init__(self, post: model.Post, auth_user: model.User) -> None: def __init__(
self,
post: model.Post,
auth_user: model.User,
) -> None:
self.post = post self.post = post
self.auth_user = auth_user self.auth_user = auth_user
@ -241,10 +228,10 @@ class PostSerializer(serialization.BaseSerializer):
return self.post.canvas_height return self.post.canvas_height
def serialize_content_url(self) -> Any: def serialize_content_url(self) -> Any:
return get_post_content_url(self.post) return util.add_data_prefix(get_post_content_path(self.post))
def serialize_thumbnail_url(self) -> Any: def serialize_thumbnail_url(self) -> Any:
return get_post_thumbnail_url(self.post) return util.add_data_prefix(get_post_thumbnail_path(self.post))
def serialize_flags(self) -> Any: def serialize_flags(self) -> Any:
return self.post.flags return self.post.flags
@ -264,7 +251,7 @@ class PostSerializer(serialization.BaseSerializer):
{ {
post["id"]: post post["id"]: post
for post in [ for post in [
serialize_micro_post(rel, self.auth_user) serialize_micro_post(rel, self.auth_user, self.url_prefix)
for rel in self.post.relations for rel in self.post.relations
] ]
}.values(), }.values(),
@ -346,7 +333,9 @@ class PostSerializer(serialization.BaseSerializer):
def serialize_post( def serialize_post(
post: Optional[model.Post], auth_user: model.User, options: List[str] = [] post: Optional[model.Post],
auth_user: model.User,
options: List[str] = [],
) -> Optional[rest.Response]: ) -> Optional[rest.Response]:
if not post: if not post:
return None return None
@ -354,7 +343,8 @@ def serialize_post(
def serialize_micro_post( def serialize_micro_post(
post: model.Post, auth_user: model.User post: model.Post,
auth_user: model.User,
) -> Optional[rest.Response]: ) -> Optional[rest.Response]:
return serialize_post( return serialize_post(
post, auth_user=auth_user, options=["id", "thumbnailUrl"] post, auth_user=auth_user, options=["id", "thumbnailUrl"]

View file

@ -5,8 +5,9 @@ import tempfile
from contextlib import contextmanager from contextlib import contextmanager
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Any, Dict, Generator, List, Optional, Tuple, TypeVar, Union from typing import Any, Dict, Generator, List, Optional, Tuple, TypeVar, Union
from urllib.parse import urlparse, urlunparse
from szurubooru import errors from szurubooru import config, errors
T = TypeVar("T") T = TypeVar("T")
@ -176,3 +177,34 @@ def get_column_size(column: Any) -> Optional[int]:
def chunks(source_list: List[Any], part_size: int) -> Generator: def chunks(source_list: List[Any], part_size: int) -> Generator:
for i in range(0, len(source_list), part_size): for i in range(0, len(source_list), part_size):
yield source_list[i : i + part_size] yield source_list[i : i + part_size]
def _get_url_prefix_parts() -> str:
parsed_base_url = list(urlparse(config.config["base_url"]))
if not all(parsed_base_url[0:2]):
parsed_base_url[0:2] = ["", ""]
parsed_base_url[2] = parsed_base_url[2].rstrip("/")
return parsed_base_url[0:3] + ["", "", ""]
def _get_data_prefix_parts() -> str:
parsed_base_url = _get_url_prefix_parts()
parsed_data_url = list(urlparse(config.config["data_url"]))
if not all(parsed_data_url[0:2]):
parsed_data_url[0:2] = parsed_base_url[0:2]
if not parsed_data_url[2].startswith("/"):
parsed_data_url[2] = parsed_base_url[2] + "/" + parsed_data_url[2]
parsed_data_url[2] = parsed_data_url[2].rstrip("/")
return parsed_data_url[0:3] + ["", "", ""]
def add_url_prefix(url: str = "") -> str:
return urlunparse(_get_url_prefix_parts()) + "/" + url.lstrip("/")
def add_data_prefix(url: str = "") -> str:
return urlunparse(_get_data_prefix_parts()) + "/" + url.lstrip("/")
def can_send_mail() -> bool:
return bool(config.config["smtp"]["host"] and _get_url_prefix_parts()[1])

View file

@ -41,11 +41,6 @@ def _create_context(env: Dict[str, Any]) -> context.Context:
path = path.encode("latin-1").decode("utf-8") # PEP-3333 path = path.encode("latin-1").decode("utf-8") # PEP-3333
headers = _get_headers(env) headers = _get_headers(env)
if config.config["domain"]:
url_prefix = config.config["domain"].rstrip("/")
else:
url_prefix = headers.get("X-Forwarded-Prefix", "")
files = {} files = {}
params = dict(urllib.parse.parse_qsl(env.get("QUERY_STRING", ""))) params = dict(urllib.parse.parse_qsl(env.get("QUERY_STRING", "")))
@ -80,7 +75,6 @@ def _create_context(env: Dict[str, Any]) -> context.Context:
method=method, method=method,
url=path, url=path,
headers=headers, headers=headers,
url_prefix=url_prefix,
params=params, params=params,
files=files, files=files,
) )

View file

@ -16,7 +16,6 @@ class Context:
url: str, url: str,
headers: Dict[str, str] = None, headers: Dict[str, str] = None,
accept: str = None, accept: str = None,
url_prefix: str = None,
params: Request = None, params: Request = None,
files: Dict[str, bytes] = None, files: Dict[str, bytes] = None,
) -> None: ) -> None:
@ -24,7 +23,6 @@ class Context:
self.method = method self.method = method
self.url = url self.url = url
self.accept = accept self.accept = accept
self.url_prefix = url_prefix
self._headers = headers or {} self._headers = headers or {}
self._params = params or {} self._params = params or {}
self._files = files or {} self._files = files or {}

View file

@ -18,6 +18,7 @@ def test_info_api(
config_injector( config_injector(
{ {
"name": "test installation", "name": "test installation",
"base_url": "https://www.example.com",
"contact_email": "test@example.com", "contact_email": "test@example.com",
"enable_safety": True, "enable_safety": True,
"data_dir": str(directory), "data_dir": str(directory),
@ -97,9 +98,13 @@ def test_info_api(
def test_manifest(config_injector, context_factory): def test_manifest(config_injector, context_factory):
config_injector({"name": "test installation"}) config_injector(
{
"name": "test installation",
"base_url": "/someprefix",
}
)
ctx = context_factory() ctx = context_factory()
ctx.url_prefix = "/someprefix"
expected_manifest = { expected_manifest = {
"name": "test installation", "name": "test installation",
"icons": [ "icons": [

View file

@ -23,23 +23,24 @@ def test_get_post_html(
config_injector( config_injector(
{ {
"name": "test installation", "name": "test installation",
"data_url": "data/", "base_url": "/someprefix",
"data_url": "data",
} }
) )
ctx = context_factory()
ctx.url_prefix = "/someprefix"
post = post_factory(id=1, type=post_type) post = post_factory(id=1, type=post_type)
post.canvas_width = 1920 post.canvas_width = 1920
post.canvas_height = 1080 post.canvas_height = 1080
db.session.add(post) db.session.add(post)
db.session.flush() db.session.flush()
with patch("szurubooru.func.auth.has_privilege"), patch( with patch("szurubooru.func.auth.has_privilege"), patch(
"szurubooru.func.posts.get_post_content_url" "szurubooru.func.posts.get_post_content_path"
), patch("szurubooru.func.posts.get_post_thumbnail_url"): ), patch("szurubooru.func.posts.get_post_thumbnail_path"):
auth.has_privilege.return_value = view_priv auth.has_privilege.return_value = view_priv
posts.get_post_content_url.return_value = "/content-url" posts.get_post_content_path.return_value = "content-url"
posts.get_post_thumbnail_url.return_value = "/thumbnail-url" posts.get_post_thumbnail_path.return_value = "thumbnail-url"
ret = api.opengraph_api.get_post_html(ctx, {"post_id": 1}) ret = api.opengraph_api.get_post_html(
context_factory(), {"post_id": 1}
)
assert _make_meta_tag("og:site_name", "test installation") in ret assert _make_meta_tag("og:site_name", "test installation") in ret
assert _make_meta_tag("og:url", "/someprefix/post/1") in ret assert _make_meta_tag("og:url", "/someprefix/post/1") in ret
@ -59,16 +60,27 @@ def test_get_post_html(
) )
assert ( assert (
bool( bool(
_make_meta_tag("twitter:player:stream", "/content-url") in ret _make_meta_tag(
"twitter:player:stream", "/someprefix/data/content-url"
)
in ret
) )
== view_priv == view_priv
) )
assert ( assert (
bool(_make_meta_tag("og:video:url", "/content-url") in ret) bool(
_make_meta_tag("og:video:url", "/someprefix/data/content-url")
in ret
)
== view_priv == view_priv
) )
assert ( assert (
bool(_make_meta_tag("og:image:url", "/thumbnail-url") in ret) bool(
_make_meta_tag(
"og:image:url", "/someprefix/data/thumbnail-url"
)
in ret
)
== view_priv == view_priv
) )
assert ( assert (
@ -83,6 +95,9 @@ def test_get_post_html(
== view_priv == view_priv
) )
assert ( assert (
bool(_make_meta_tag("twitter:image", "/content-url") in ret) bool(
_make_meta_tag("twitter:image", "/someprefix/data/content-url")
in ret
)
== view_priv == view_priv
) )

View file

@ -11,7 +11,7 @@ def inject_config(config_injector):
config_injector( config_injector(
{ {
"secret": "x", "secret": "x",
"domain": "http://example.com", "base_url": "http://example.com",
"name": "Test instance", "name": "Test instance",
"smtp": { "smtp": {
"from": "noreply@example.com", "from": "noreply@example.com",
@ -27,13 +27,11 @@ def test_reset_sending_email(context_factory, user_factory):
) )
) )
db.session.flush() db.session.flush()
ctx = context_factory()
ctx.url_prefix = "http://example.com"
for initiating_user in ["u1", "user@example.com"]: for initiating_user in ["u1", "user@example.com"]:
with patch("szurubooru.func.mailer.send_mail"): with patch("szurubooru.func.mailer.send_mail"):
assert ( assert (
api.password_reset_api.start_password_reset( api.password_reset_api.start_password_reset(
ctx, {"user_name": initiating_user} context_factory(), {"user_name": initiating_user}
) )
== {} == {}
) )

View file

@ -41,19 +41,17 @@ def test_simple_updating(user_factory, pool_factory, context_factory):
): ):
posts.get_posts_by_ids.return_value = ([], []) posts.get_posts_by_ids.return_value = ([], [])
pools.serialize_pool.return_value = "serialized pool" pools.serialize_pool.return_value = "serialized pool"
result = api.pool_api.update_pool( ctx = context_factory(
context_factory( params={
params={ "version": 1,
"version": 1, "names": ["pool3"],
"names": ["pool3"], "category": "series",
"category": "series", "description": "desc",
"description": "desc", "posts": [1, 2],
"posts": [1, 2], },
}, user=auth_user,
user=auth_user,
),
{"pool_id": 1},
) )
result = api.pool_api.update_pool(ctx, {"pool_id": 1})
assert result == "serialized pool" assert result == "serialized pool"
pools.create_pool.assert_not_called() pools.create_pool.assert_not_called()
pools.update_pool_names.assert_called_once_with(pool, ["pool3"]) pools.update_pool_names.assert_called_once_with(pool, ["pool3"])

View file

@ -45,19 +45,18 @@ def test_creating_minimal_posts(context_factory, post_factory, user_factory):
posts.create_post.return_value = (post, []) posts.create_post.return_value = (post, [])
posts.serialize_post.return_value = "serialized post" posts.serialize_post.return_value = "serialized post"
result = api.post_api.create_post( ctx = context_factory(
context_factory( params={
params={ "safety": "safe",
"safety": "safe", "tags": ["tag1", "tag2"],
"tags": ["tag1", "tag2"], },
}, files={
files={ "content": "post-content",
"content": "post-content", "thumbnail": "post-thumbnail",
"thumbnail": "post-thumbnail", },
}, user=auth_user,
user=auth_user,
)
) )
result = api.post_api.create_post(ctx)
assert result == "serialized post" assert result == "serialized post"
posts.create_post.assert_called_once_with( posts.create_post.assert_called_once_with(
@ -102,22 +101,21 @@ def test_creating_full_posts(context_factory, post_factory, user_factory):
posts.create_post.return_value = (post, []) posts.create_post.return_value = (post, [])
posts.serialize_post.return_value = "serialized post" posts.serialize_post.return_value = "serialized post"
result = api.post_api.create_post( ctx = context_factory(
context_factory( params={
params={ "safety": "safe",
"safety": "safe", "tags": ["tag1", "tag2"],
"tags": ["tag1", "tag2"], "relations": [1, 2],
"relations": [1, 2], "source": "source",
"source": "source", "notes": ["note1", "note2"],
"notes": ["note1", "note2"], "flags": ["flag1", "flag2"],
"flags": ["flag1", "flag2"], },
}, files={
files={ "content": "post-content",
"content": "post-content", },
}, user=auth_user,
user=auth_user,
)
) )
result = api.post_api.create_post(ctx)
assert result == "serialized post" assert result == "serialized post"
posts.create_post.assert_called_once_with( posts.create_post.assert_called_once_with(
@ -333,7 +331,8 @@ def test_errors_not_spending_ids(
config_injector( config_injector(
{ {
"data_dir": str(tmpdir.mkdir("data")), "data_dir": str(tmpdir.mkdir("data")),
"data_url": "example.com", "base_url": "https://example.com/",
"data_url": "https://example.com/data",
"thumbnails": { "thumbnails": {
"post_width": 300, "post_width": 300,
"post_height": 300, "post_height": 300,

View file

@ -17,34 +17,6 @@ from szurubooru.func import (
) )
@pytest.mark.parametrize(
"input_mime_type,expected_url",
[
("image/jpeg", "http://example.com/posts/1_244c8840887984c4.jpg"),
("image/gif", "http://example.com/posts/1_244c8840887984c4.gif"),
("totally/unknown", "http://example.com/posts/1_244c8840887984c4.dat"),
],
)
def test_get_post_url(input_mime_type, expected_url, config_injector):
config_injector({"data_url": "http://example.com/", "secret": "test"})
post = model.Post()
post.post_id = 1
post.mime_type = input_mime_type
assert posts.get_post_content_url(post) == expected_url
@pytest.mark.parametrize("input_mime_type", ["image/jpeg", "image/gif"])
def test_get_post_thumbnail_url(input_mime_type, config_injector):
config_injector({"data_url": "http://example.com/", "secret": "test"})
post = model.Post()
post.post_id = 1
post.mime_type = input_mime_type
assert (
posts.get_post_thumbnail_url(post)
== "http://example.com/generated-thumbnails/1_244c8840887984c4.jpg"
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
"input_mime_type,expected_path", "input_mime_type,expected_path",
[ [
@ -53,7 +25,10 @@ def test_get_post_thumbnail_url(input_mime_type, config_injector):
("totally/unknown", "posts/1_244c8840887984c4.dat"), ("totally/unknown", "posts/1_244c8840887984c4.dat"),
], ],
) )
def test_get_post_content_path(input_mime_type, expected_path): def test_get_post_content_path(
input_mime_type, expected_path, config_injector
):
config_injector({"secret": "test"})
post = model.Post() post = model.Post()
post.post_id = 1 post.post_id = 1
post.mime_type = input_mime_type post.mime_type = input_mime_type
@ -61,7 +36,8 @@ def test_get_post_content_path(input_mime_type, expected_path):
@pytest.mark.parametrize("input_mime_type", ["image/jpeg", "image/gif"]) @pytest.mark.parametrize("input_mime_type", ["image/jpeg", "image/gif"])
def test_get_post_thumbnail_path(input_mime_type): def test_get_post_thumbnail_path(input_mime_type, config_injector):
config_injector({"secret": "test"})
post = model.Post() post = model.Post()
post.post_id = 1 post.post_id = 1
post.mime_type = input_mime_type post.mime_type = input_mime_type
@ -72,7 +48,8 @@ def test_get_post_thumbnail_path(input_mime_type):
@pytest.mark.parametrize("input_mime_type", ["image/jpeg", "image/gif"]) @pytest.mark.parametrize("input_mime_type", ["image/jpeg", "image/gif"])
def test_get_post_thumbnail_backup_path(input_mime_type): def test_get_post_thumbnail_backup_path(input_mime_type, config_injector):
config_injector({"secret": "test"})
post = model.Post() post = model.Post()
post.post_id = 1 post.post_id = 1
post.mime_type = input_mime_type post.mime_type = input_mime_type
@ -105,7 +82,13 @@ def test_serialize_post(
pool_category_factory, pool_category_factory,
config_injector, config_injector,
): ):
config_injector({"data_url": "http://example.com/", "secret": "test"}) config_injector(
{
"secret": "test",
"base_url": "http://example.com/",
"data_url": "http://example.com/",
}
)
with patch("szurubooru.func.comments.serialize_comment"), patch( with patch("szurubooru.func.comments.serialize_comment"), patch(
"szurubooru.func.users.serialize_micro_user" "szurubooru.func.users.serialize_micro_user"
), patch("szurubooru.func.posts.files.has"): ), patch("szurubooru.func.posts.files.has"):
@ -277,17 +260,15 @@ def test_serialize_post(
def test_serialize_micro_post(post_factory, user_factory): def test_serialize_micro_post(post_factory, user_factory):
with patch("szurubooru.func.posts.get_post_thumbnail_url"): with patch("szurubooru.func.posts.get_post_thumbnail_path"):
posts.get_post_thumbnail_url.return_value = ( posts.get_post_thumbnail_path.return_value = "thumb.png"
"https://example.com/thumb.png"
)
auth_user = user_factory() auth_user = user_factory()
post = post_factory() post = post_factory()
db.session.add(post) db.session.add(post)
db.session.flush() db.session.flush()
assert posts.serialize_micro_post(post, auth_user) == { assert posts.serialize_micro_post(post, auth_user) == {
"id": post.post_id, "id": post.post_id,
"thumbnailUrl": "https://example.com/thumb.png", "thumbnailUrl": "http://example.com/thumb.png",
} }
@ -519,7 +500,8 @@ def test_update_post_content_to_existing_content(
config_injector( config_injector(
{ {
"data_dir": str(tmpdir.mkdir("data")), "data_dir": str(tmpdir.mkdir("data")),
"data_url": "example.com", "base_url": "https://example.com/",
"data_url": "https://example.com/data",
"thumbnails": { "thumbnails": {
"post_width": 300, "post_width": 300,
"post_height": 300, "post_height": 300,

View file

@ -45,3 +45,93 @@ def test_parsing_date_time(fake_datetime, input, output):
) )
def test_icase_unique(input, output): def test_icase_unique(input, output):
assert util.icase_unique(input) == output assert util.icase_unique(input) == output
def test_url_generation(config_injector):
config_injector(
{
"base_url": "https://www.example.com/",
"data_url": "data/",
}
)
assert util.add_url_prefix() == "https://www.example.com/"
assert util.add_url_prefix("/post/1") == "https://www.example.com/post/1"
assert util.add_url_prefix("post/1") == "https://www.example.com/post/1"
assert util.add_data_prefix() == "https://www.example.com/data/"
assert (
util.add_data_prefix("posts/1.jpg")
== "https://www.example.com/data/posts/1.jpg"
)
assert (
util.add_data_prefix("/posts/1.jpg")
== "https://www.example.com/data/posts/1.jpg"
)
config_injector(
{
"base_url": "https://www.example.com/szuru/",
"data_url": "data/",
}
)
assert util.add_url_prefix() == "https://www.example.com/szuru/"
assert (
util.add_url_prefix("/post/1")
== "https://www.example.com/szuru/post/1"
)
assert (
util.add_url_prefix("post/1") == "https://www.example.com/szuru/post/1"
)
assert util.add_data_prefix() == "https://www.example.com/szuru/data/"
assert (
util.add_data_prefix("posts/1.jpg")
== "https://www.example.com/szuru/data/posts/1.jpg"
)
assert (
util.add_data_prefix("/posts/1.jpg")
== "https://www.example.com/szuru/data/posts/1.jpg"
)
config_injector(
{
"base_url": "https://www.example.com/szuru/",
"data_url": "/data/",
}
)
assert util.add_url_prefix() == "https://www.example.com/szuru/"
assert (
util.add_url_prefix("/post/1")
== "https://www.example.com/szuru/post/1"
)
assert (
util.add_url_prefix("post/1") == "https://www.example.com/szuru/post/1"
)
assert util.add_data_prefix() == "https://www.example.com/data/"
assert (
util.add_data_prefix("posts/1.jpg")
== "https://www.example.com/data/posts/1.jpg"
)
assert (
util.add_data_prefix("/posts/1.jpg")
== "https://www.example.com/data/posts/1.jpg"
)
config_injector(
{
"base_url": "https://www.example.com/szuru",
"data_url": "https://static.example.com/",
}
)
assert util.add_url_prefix() == "https://www.example.com/szuru/"
assert (
util.add_url_prefix("/post/1")
== "https://www.example.com/szuru/post/1"
)
assert (
util.add_url_prefix("post/1") == "https://www.example.com/szuru/post/1"
)
assert util.add_data_prefix() == "https://static.example.com/"
assert (
util.add_data_prefix("posts/1.jpg")
== "https://static.example.com/posts/1.jpg"
)
assert (
util.add_data_prefix("/posts/1.jpg")
== "https://static.example.com/posts/1.jpg"
)