mirror of
https://github.com/rr-/szurubooru.git
synced 2025-07-17 08:26:24 +00:00
Compare commits
2 Commits
1ef928038f
...
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:
|
@ -61,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
|
||||
|
@ -97,11 +97,9 @@ privileges:
|
||||
'posts:create:anonymous': regular
|
||||
'posts:create:identified': regular
|
||||
'posts:list': anonymous
|
||||
'posts:list:unsafe': regular
|
||||
'posts:reverse_search': regular
|
||||
'posts:view': anonymous
|
||||
'posts:view:featured': anonymous
|
||||
'posts:view:unsafe': regular
|
||||
'posts:edit:content': power
|
||||
'posts:edit:flags': regular
|
||||
'posts:edit:notes': regular
|
||||
|
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
|
@ -114,8 +114,6 @@ def create_snapshots_for_post(
|
||||
def get_post(ctx: rest.Context, params: Dict[str, str]) -> rest.Response:
|
||||
auth.verify_privilege(ctx.user, "posts:view")
|
||||
post = _get_post(params)
|
||||
if post.safety == model.Post.SAFETY_UNSAFE:
|
||||
auth.verify_privilege(ctx.user, "posts:view:unsafe")
|
||||
return _serialize_post(ctx, post)
|
||||
|
||||
|
||||
|
@ -3,7 +3,7 @@ from typing import Any, Dict, Optional, Tuple
|
||||
import sqlalchemy as sa
|
||||
|
||||
from szurubooru import db, errors, model
|
||||
from szurubooru.func import auth, util
|
||||
from szurubooru.func import util
|
||||
from szurubooru.search import criteria, tokens
|
||||
from szurubooru.search.configs import util as search_util
|
||||
from szurubooru.search.configs.base_search_config import (
|
||||
@ -150,15 +150,6 @@ def _category_filter(
|
||||
return query.filter(expr)
|
||||
|
||||
|
||||
def _safety_filter(
|
||||
query: SaQuery, criterion: Optional[criteria.BaseCriterion], negated: bool
|
||||
) -> SaQuery:
|
||||
assert criterion
|
||||
return search_util.create_str_filter(
|
||||
model.Post.safety, _safety_transformer
|
||||
)(query, criterion, negated)
|
||||
|
||||
|
||||
class PostSearchConfig(BaseSearchConfig):
|
||||
def __init__(self) -> None:
|
||||
self.user = None # type: Optional[model.User]
|
||||
@ -217,22 +208,8 @@ class PostSearchConfig(BaseSearchConfig):
|
||||
return db.session.query(model.Post)
|
||||
|
||||
def finalize_query(self, query: SaQuery) -> SaQuery:
|
||||
if self.user and not auth.has_privilege(self.user, "posts:list:unsafe"):
|
||||
# exclude unsafe posts:
|
||||
query = _safety_filter(
|
||||
query,
|
||||
criteria.PlainCriterion(
|
||||
model.Post.SAFETY_UNSAFE, model.Post.SAFETY_UNSAFE
|
||||
),
|
||||
negated=True,
|
||||
)
|
||||
return query.order_by(model.Post.post_id.desc())
|
||||
|
||||
|
||||
@property
|
||||
def can_list_unsafe(self) -> bool:
|
||||
return self.user and auth.has_privilege(self.user, "posts:list:unsafe")
|
||||
|
||||
@property
|
||||
def id_column(self) -> SaColumn:
|
||||
return model.Post.post_id
|
||||
@ -386,7 +363,12 @@ class PostSearchConfig(BaseSearchConfig):
|
||||
model.Post.last_feature_time
|
||||
),
|
||||
),
|
||||
(["safety", "rating"], _safety_filter),
|
||||
(
|
||||
["safety", "rating"],
|
||||
search_util.create_str_filter(
|
||||
model.Post.safety, _safety_transformer
|
||||
),
|
||||
),
|
||||
(["note-text"], _note_filter),
|
||||
(
|
||||
["flag"],
|
||||
|
@ -93,10 +93,7 @@ class Executor:
|
||||
if token.name == "random":
|
||||
disable_eager_loads = True
|
||||
|
||||
|
||||
can_list_unsafe = getattr(self.config, "can_list_unsafe", False)
|
||||
|
||||
key = (id(self.config), hash(search_query), offset, limit, can_list_unsafe)
|
||||
key = (id(self.config), hash(search_query), offset, limit)
|
||||
if cache.has(key):
|
||||
return cache.get(key)
|
||||
|
||||
|
@ -14,8 +14,6 @@ def inject_config(config_injector):
|
||||
"privileges": {
|
||||
"posts:list": model.User.RANK_REGULAR,
|
||||
"posts:view": model.User.RANK_REGULAR,
|
||||
"posts:view:unsafe": model.User.RANK_REGULAR,
|
||||
"posts:list:unsafe": model.User.RANK_REGULAR,
|
||||
},
|
||||
}
|
||||
)
|
||||
@ -75,10 +73,7 @@ def test_trying_to_use_special_tokens_without_logging_in(
|
||||
):
|
||||
config_injector(
|
||||
{
|
||||
"privileges": {
|
||||
"posts:list": "anonymous",
|
||||
"posts:list:unsafe": "regular",
|
||||
},
|
||||
"privileges": {"posts:list": "anonymous"},
|
||||
}
|
||||
)
|
||||
with pytest.raises(errors.SearchError):
|
||||
@ -130,23 +125,3 @@ def test_trying_to_retrieve_single_without_privileges(
|
||||
context_factory(user=user_factory(rank=model.User.RANK_ANONYMOUS)),
|
||||
{"post_id": 999},
|
||||
)
|
||||
|
||||
|
||||
def test_trying_to_retrieve_unsafe_without_privileges(
|
||||
user_factory, context_factory, post_factory, config_injector
|
||||
):
|
||||
config_injector(
|
||||
{
|
||||
"privileges": {
|
||||
"posts:view": "anonymous",
|
||||
"posts:view:unsafe": "regular",
|
||||
},
|
||||
}
|
||||
)
|
||||
db.session.add(post_factory(id=1, safety=model.Post.SAFETY_UNSAFE))
|
||||
db.session.flush()
|
||||
with pytest.raises(errors.AuthError):
|
||||
api.post_api.get_post(
|
||||
context_factory(user=user_factory(rank=model.User.RANK_ANONYMOUS)),
|
||||
{"post_id": 1},
|
||||
)
|
||||
|
@ -3,12 +3,6 @@ from datetime import datetime
|
||||
import pytest
|
||||
|
||||
from szurubooru import db, errors, model, search
|
||||
from szurubooru.func import cache
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def purge_cache():
|
||||
cache.purge()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -60,15 +54,7 @@ def executor():
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def auth_executor(executor, user_factory, config_injector):
|
||||
config_injector(
|
||||
{
|
||||
"privileges": {
|
||||
"posts:list:unsafe": model.User.RANK_REGULAR,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
def auth_executor(executor, user_factory):
|
||||
def wrapper():
|
||||
auth_user = user_factory()
|
||||
db.session.add(auth_user)
|
||||
@ -929,28 +915,3 @@ def test_search_by_tag_category(
|
||||
)
|
||||
db.session.flush()
|
||||
verify_unpaged(input, expected_post_ids)
|
||||
|
||||
|
||||
def test_filter_unsafe_without_privilege(
|
||||
auth_executor,
|
||||
verify_unpaged,
|
||||
post_factory,
|
||||
):
|
||||
post1 = post_factory(id=1)
|
||||
post2 = post_factory(id=2, safety=model.Post.SAFETY_SKETCHY)
|
||||
post3 = post_factory(id=3, safety=model.Post.SAFETY_UNSAFE)
|
||||
db.session.add_all([post1, post2, post3])
|
||||
db.session.flush()
|
||||
user = auth_executor()
|
||||
user.rank = model.User.RANK_ANONYMOUS
|
||||
verify_unpaged("", [1, 2])
|
||||
verify_unpaged("safety:safe", [1])
|
||||
verify_unpaged("safety:safe,sketchy", [1, 2])
|
||||
verify_unpaged("safety:safe,sketchy,unsafe", [1, 2])
|
||||
# adjust user's rank and retry
|
||||
user.rank = model.User.RANK_REGULAR
|
||||
cache.purge()
|
||||
verify_unpaged("", [1, 2, 3])
|
||||
verify_unpaged("safety:safe", [1])
|
||||
verify_unpaged("safety:safe,sketchy", [1, 2])
|
||||
verify_unpaged("safety:safe,sketchy,unsafe", [1, 2, 3])
|
||||
|
@ -2,19 +2,10 @@ import unittest.mock
|
||||
|
||||
import pytest
|
||||
|
||||
from szurubooru import search, model
|
||||
from szurubooru import search
|
||||
from szurubooru.func import cache
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def inject_config(config_injector):
|
||||
config_injector(
|
||||
{
|
||||
"privileges": {"posts:list:unsafe": model.User.RANK_REGULAR},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_retrieving_from_cache():
|
||||
config = unittest.mock.MagicMock()
|
||||
with unittest.mock.patch("szurubooru.func.cache.has"), unittest.mock.patch(
|
||||
|
Reference in New Issue
Block a user