mirror of
https://github.com/rr-/szurubooru.git
synced 2025-07-17 08:26:24 +00:00
Compare commits
2 Commits
6e28096b64
...
master
Author | SHA1 | Date | |
---|---|---|---|
f66a8d2a96 | |||
ee7e9ef2a3 |
@ -1,4 +1,5 @@
|
||||
node_modules/*
|
||||
public/
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
**/.gitignore
|
||||
|
@ -1,3 +1,26 @@
|
||||
FROM --platform=$BUILDPLATFORM node:lts-alpine as development
|
||||
WORKDIR /opt/app
|
||||
|
||||
RUN apk --no-cache add \
|
||||
dumb-init \
|
||||
nginx \
|
||||
git
|
||||
|
||||
RUN ln -sf /opt/app/nginx.conf.docker /etc/nginx/nginx.conf
|
||||
RUN rm -rf /var/www
|
||||
RUN ln -sf /opt/app/public/ /var/www
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm install
|
||||
|
||||
ARG BUILD_INFO="docker-development"
|
||||
ENV BUILD_INFO=${BUILD_INFO}
|
||||
ENV BACKEND_HOST="server"
|
||||
|
||||
CMD ["/opt/app/docker-start-dev.sh"]
|
||||
VOLUME ["/data"]
|
||||
|
||||
|
||||
FROM --platform=$BUILDPLATFORM node:lts as builder
|
||||
WORKDIR /opt/app
|
||||
|
||||
|
@ -315,7 +315,7 @@ function makeOutputDirs() {
|
||||
}
|
||||
|
||||
function watch() {
|
||||
let wss = new WebSocket.Server({ port: 8080 });
|
||||
let wss = new WebSocket.Server({ port: environment === "development" ? 8081 : 8080 });
|
||||
const liveReload = !process.argv.includes('--no-live-reload');
|
||||
|
||||
function emitReload() {
|
||||
|
@ -121,6 +121,7 @@
|
||||
.thumbnail
|
||||
width: 4em
|
||||
height: 3em
|
||||
background-position: 50% 30%
|
||||
li
|
||||
margin: 0 0.3em 0.3em 0
|
||||
display: inline-block
|
||||
|
17
client/docker-start-dev.sh
Executable file
17
client/docker-start-dev.sh
Executable file
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/dumb-init /bin/sh
|
||||
|
||||
# Integrate environment variables
|
||||
sed -i "s|__BACKEND__|${BACKEND_HOST}|" \
|
||||
/etc/nginx/nginx.conf
|
||||
|
||||
# Start server
|
||||
nginx &
|
||||
|
||||
# Watch source for changes and build app
|
||||
# FIXME: It's not ergonomic to run `npm i` outside of the build step.
|
||||
# However, the mounting of different directories into the
|
||||
# client container's /opt/app causes node_modules to disappear
|
||||
# (the mounting causes client/Dockerfile's RUN npm install
|
||||
# to silently clobber).
|
||||
# Find a way to move `npm i` into client/Dockerfile.
|
||||
npm i && npm run watch -- --polling
|
@ -2,10 +2,10 @@
|
||||
|
||||
# Integrate environment variables
|
||||
sed -i "s|__BACKEND__|${BACKEND_HOST}|" \
|
||||
/etc/nginx/nginx.conf
|
||||
/etc/nginx/nginx.conf
|
||||
sed -i "s|__BASEURL__|${BASE_URL:-/}|g" \
|
||||
/var/www/index.htm \
|
||||
/var/www/manifest.json
|
||||
/var/www/index.htm \
|
||||
/var/www/manifest.json
|
||||
|
||||
# Start server
|
||||
exec nginx
|
||||
|
@ -3,7 +3,7 @@
|
||||
const config = require("./config.js");
|
||||
|
||||
if (config.environment == "development") {
|
||||
var ws = new WebSocket("ws://" + location.hostname + ":8080");
|
||||
var ws = new WebSocket("ws://" + location.hostname + ":8081");
|
||||
ws.addEventListener("open", function (event) {
|
||||
console.log("Live-reloading websocket connected.");
|
||||
});
|
||||
|
54
docker-compose.dev.yml
Normal file
54
docker-compose.dev.yml
Normal file
@ -0,0 +1,54 @@
|
||||
## Docker Compose configuration for dev iteration
|
||||
##
|
||||
## Data is transient by using named vols.
|
||||
## Run: docker-compose -f ./docker-compose.dev.yml up
|
||||
services:
|
||||
|
||||
server:
|
||||
build:
|
||||
context: ./server
|
||||
target: development
|
||||
depends_on:
|
||||
- sql
|
||||
environment:
|
||||
## These should be the names of the dependent containers listed below,
|
||||
## or FQDNs/IP addresses if these services are running outside of Docker
|
||||
POSTGRES_HOST: sql
|
||||
## Credentials for database:
|
||||
POSTGRES_USER:
|
||||
POSTGRES_PASSWORD:
|
||||
## Commented Values are Default:
|
||||
#POSTGRES_DB: defaults to same as POSTGRES_USER
|
||||
#POSTGRES_PORT: 5432
|
||||
#LOG_SQL: 0 (1 for verbose SQL logs)
|
||||
THREADS:
|
||||
volumes:
|
||||
- "data:/data"
|
||||
- "./server/:/opt/app/"
|
||||
|
||||
client:
|
||||
build:
|
||||
context: ./client
|
||||
target: development
|
||||
depends_on:
|
||||
- server
|
||||
volumes:
|
||||
- "data:/data:ro"
|
||||
- "./client/:/opt/app/"
|
||||
- "/opt/app/public/"
|
||||
ports:
|
||||
- "${PORT}:80"
|
||||
- "8081:8081"
|
||||
|
||||
sql:
|
||||
image: postgres:11-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER:
|
||||
POSTGRES_PASSWORD:
|
||||
volumes:
|
||||
- "sql:/var/lib/postgresql/data"
|
||||
|
||||
volumes:
|
||||
data:
|
||||
sql:
|
@ -27,7 +27,6 @@ RUN apk --no-cache add \
|
||||
RUN pip3 install --no-cache-dir --disable-pip-version-check \
|
||||
"alembic>=0.8.5" \
|
||||
"coloredlogs==5.0" \
|
||||
gallery_dl \
|
||||
"pyheif==0.6.1" \
|
||||
"heif-image-plugin>=0.3.2" \
|
||||
yt-dlp \
|
||||
@ -62,7 +61,42 @@ ENTRYPOINT ["pytest", "--tb=short"]
|
||||
CMD ["szurubooru/"]
|
||||
|
||||
|
||||
FROM prereqs as development
|
||||
WORKDIR /opt/app
|
||||
|
||||
ARG PUID=1000
|
||||
ARG PGID=1000
|
||||
|
||||
RUN apk --no-cache add \
|
||||
dumb-init \
|
||||
py3-pip \
|
||||
py3-setuptools \
|
||||
py3-waitress \
|
||||
&& pip3 install --no-cache-dir --disable-pip-version-check \
|
||||
hupper \
|
||||
&& mkdir -p /opt/app /data \
|
||||
&& addgroup -g ${PGID} app \
|
||||
&& adduser -SDH -h /opt/app -g '' -G app -u ${PUID} app \
|
||||
&& chown -R app:app /opt/app /data
|
||||
|
||||
USER app
|
||||
CMD ["/opt/app/docker-start-dev.sh"]
|
||||
|
||||
ARG PORT=6666
|
||||
ENV PORT=${PORT}
|
||||
EXPOSE ${PORT}
|
||||
|
||||
ARG THREADS=4
|
||||
ENV THREADS=${THREADS}
|
||||
|
||||
VOLUME ["/data/"]
|
||||
|
||||
|
||||
FROM prereqs as release
|
||||
|
||||
COPY ./ /opt/app/
|
||||
RUN rm -rf /opt/app/szurubooru/tests
|
||||
|
||||
WORKDIR /opt/app
|
||||
|
||||
ARG PUID=1000
|
||||
|
8
server/docker-start-dev.sh
Executable file
8
server/docker-start-dev.sh
Executable file
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/dumb-init /bin/sh
|
||||
set -e
|
||||
cd /opt/app
|
||||
|
||||
alembic upgrade head
|
||||
|
||||
echo "Starting szurubooru API on port ${PORT} - Running on ${THREADS} threads"
|
||||
exec hupper -m waitress --port ${PORT} --threads ${THREADS} szurubooru.facade:app
|
@ -3,7 +3,7 @@ line-length = 79
|
||||
|
||||
[tool.isort]
|
||||
known_first_party = ["szurubooru"]
|
||||
known_third_party = ["PIL", "alembic", "coloredlogs", "freezegun", "gallery_dl", "nacl", "numpy", "pyrfc3339", "pytest", "pytz", "sqlalchemy", "yaml", "youtube_dl"]
|
||||
known_third_party = ["PIL", "alembic", "coloredlogs", "freezegun", "nacl", "numpy", "pyrfc3339", "pytest", "pytz", "sqlalchemy", "yaml", "youtube_dl"]
|
||||
multi_line_output = 3
|
||||
include_trailing_comma = true
|
||||
force_grid_wrap = 0
|
||||
|
@ -1,7 +1,6 @@
|
||||
alembic>=0.8.5
|
||||
certifi>=2017.11.5
|
||||
coloredlogs==5.0
|
||||
gallery_dl
|
||||
heif-image-plugin==0.3.2
|
||||
numpy>=1.8.2
|
||||
pillow-avif-plugin~=1.1.0
|
||||
|
@ -61,7 +61,7 @@ def create_post(
|
||||
auth.verify_privilege(ctx.user, "posts:create:identified")
|
||||
content = ctx.get_file(
|
||||
"content",
|
||||
use_downloader=auth.has_privilege(
|
||||
use_video_downloader=auth.has_privilege(
|
||||
ctx.user, "uploads:use_downloader"
|
||||
),
|
||||
)
|
||||
@ -128,7 +128,7 @@ def update_post(ctx: rest.Context, params: Dict[str, str]) -> rest.Response:
|
||||
post,
|
||||
ctx.get_file(
|
||||
"content",
|
||||
use_downloader=auth.has_privilege(
|
||||
use_video_downloader=auth.has_privilege(
|
||||
ctx.user, "uploads:use_downloader"
|
||||
),
|
||||
),
|
||||
|
@ -12,7 +12,7 @@ def create_temporary_file(
|
||||
content = ctx.get_file(
|
||||
"content",
|
||||
allow_tokens=False,
|
||||
use_downloader=auth.has_privilege(
|
||||
use_video_downloader=auth.has_privilege(
|
||||
ctx.user, "uploads:use_downloader"
|
||||
),
|
||||
)
|
||||
|
@ -21,22 +21,14 @@ class DownloadTooLargeError(DownloadError):
|
||||
pass
|
||||
|
||||
|
||||
def download(url: str, use_downloader: bool = False) -> bytes:
|
||||
def download(url: str, use_video_downloader: bool = False) -> bytes:
|
||||
assert url
|
||||
dl_error = None
|
||||
new_url = None
|
||||
if use_downloader:
|
||||
youtube_dl_error = None
|
||||
if use_video_downloader:
|
||||
try:
|
||||
new_url = _get_gallery_dl_content_url(url)
|
||||
url = _get_youtube_dl_content_url(url) or url
|
||||
except errors.ThirdPartyError as ex:
|
||||
dl_error = ex
|
||||
if new_url:
|
||||
url = new_url
|
||||
else:
|
||||
try:
|
||||
url = _get_youtube_dl_content_url(url) or url
|
||||
except errors.ThirdPartyError as ex:
|
||||
dl_error = ex
|
||||
youtube_dl_error = ex
|
||||
|
||||
request = urllib.request.Request(url)
|
||||
if config.config["user_agent"]:
|
||||
@ -63,10 +55,10 @@ def download(url: str, use_downloader: bool = False) -> bytes:
|
||||
) from ex
|
||||
|
||||
if (
|
||||
dl_error
|
||||
youtube_dl_error
|
||||
and mime.get_mime_type(content_buffer) == "application/octet-stream"
|
||||
):
|
||||
raise dl_error
|
||||
raise youtube_dl_error
|
||||
|
||||
return content_buffer
|
||||
|
||||
@ -89,21 +81,6 @@ def _get_youtube_dl_content_url(url: str) -> str:
|
||||
) from None
|
||||
|
||||
|
||||
def _get_gallery_dl_content_url(url: str) -> str:
|
||||
cmd = ["gallery-dl", "-q", "-g", url]
|
||||
try:
|
||||
return (
|
||||
subprocess.run(cmd, text=True, capture_output=True, check=True)
|
||||
.stdout.split("\n")[0]
|
||||
.strip()
|
||||
)
|
||||
except subprocess.CalledProcessError:
|
||||
raise errors.ThirdPartyError(
|
||||
"Could not extract content location from URL.",
|
||||
extra_fields={"URL": url},
|
||||
) from None
|
||||
|
||||
|
||||
def post_to_webhooks(payload: Dict[str, Any]) -> List[Thread]:
|
||||
threads = [
|
||||
Thread(target=_post_to_webhook, args=(webhook, payload), daemon=False)
|
||||
|
@ -48,7 +48,7 @@ class Context:
|
||||
self,
|
||||
name: str,
|
||||
default: Union[object, bytes] = MISSING,
|
||||
use_downloader: bool = False,
|
||||
use_video_downloader: bool = False,
|
||||
allow_tokens: bool = True,
|
||||
) -> bytes:
|
||||
if name in self._files and self._files[name]:
|
||||
@ -57,7 +57,7 @@ class Context:
|
||||
if name + "Url" in self._params:
|
||||
return net.download(
|
||||
self._params[name + "Url"],
|
||||
use_downloader=use_downloader,
|
||||
use_video_downloader=use_video_downloader,
|
||||
)
|
||||
|
||||
if allow_tokens and name + "Token" in self._params:
|
||||
|
@ -214,7 +214,7 @@ def test_creating_from_url_saves_source(
|
||||
)
|
||||
)
|
||||
net.download.assert_called_once_with(
|
||||
"example.com", use_downloader=False
|
||||
"example.com", use_video_downloader=False
|
||||
)
|
||||
posts.create_post.assert_called_once_with(
|
||||
b"content", ["tag1", "tag2"], auth_user
|
||||
@ -259,7 +259,7 @@ def test_creating_from_url_with_source_specified(
|
||||
)
|
||||
)
|
||||
net.download.assert_called_once_with(
|
||||
"example.com", use_downloader=True
|
||||
"example.com", use_video_downloader=True
|
||||
)
|
||||
posts.create_post.assert_called_once_with(
|
||||
b"content", ["tag1", "tag2"], auth_user
|
||||
|
@ -124,7 +124,7 @@ def test_uploading_from_url_saves_source(
|
||||
{"post_id": post.post_id},
|
||||
)
|
||||
net.download.assert_called_once_with(
|
||||
"example.com", use_downloader=True
|
||||
"example.com", use_video_downloader=True
|
||||
)
|
||||
posts.update_post_content.assert_called_once_with(post, b"content")
|
||||
posts.update_post_source.assert_called_once_with(post, "example.com")
|
||||
@ -156,7 +156,7 @@ def test_uploading_from_url_with_source_specified(
|
||||
{"post_id": post.post_id},
|
||||
)
|
||||
net.download.assert_called_once_with(
|
||||
"example.com", use_downloader=True
|
||||
"example.com", use_video_downloader=True
|
||||
)
|
||||
posts.update_post_content.assert_called_once_with(post, b"content")
|
||||
posts.update_post_source.assert_called_once_with(post, "example2.com")
|
||||
|
@ -79,7 +79,7 @@ def test_download():
|
||||
)
|
||||
def test_too_large_download(url):
|
||||
with pytest.raises(net.DownloadTooLargeError):
|
||||
net.download(url, use_downloader=True)
|
||||
net.download(url, use_video_downloader=True)
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
@ -103,7 +103,7 @@ def test_too_large_download(url):
|
||||
],
|
||||
)
|
||||
def test_content_download(url, expected_sha1):
|
||||
actual_content = net.download(url, use_downloader=True)
|
||||
actual_content = net.download(url, use_video_downloader=True)
|
||||
assert get_sha1(actual_content) == expected_sha1
|
||||
|
||||
|
||||
@ -113,7 +113,7 @@ def test_content_download(url, expected_sha1):
|
||||
def test_bad_content_downlaod():
|
||||
url = "http://info.cern.ch/hypertext/WWW/TheProject.html"
|
||||
with pytest.raises(errors.ThirdPartyError):
|
||||
net.download(url, use_downloader=True)
|
||||
net.download(url, use_video_downloader=True)
|
||||
|
||||
|
||||
def test_no_webhooks(config_injector):
|
||||
|
@ -29,7 +29,7 @@ def test_get_file_from_url():
|
||||
)
|
||||
assert ctx.get_file("key") == b"content"
|
||||
net.download.assert_called_once_with(
|
||||
"example.com", use_downloader=False
|
||||
"example.com", use_video_downloader=False
|
||||
)
|
||||
with pytest.raises(errors.ValidationError):
|
||||
assert ctx.get_file("non-existing")
|
||||
|
Reference in New Issue
Block a user