diff --git a/.gitignore b/.gitignore index b21e3adf..b0baec20 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ server/**/lib/ server/**/bin/ server/**/pyvenv.cfg __pycache__/ + +tmp \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..e69de29b diff --git a/client/js/controllers/post_main_controller.js b/client/js/controllers/post_main_controller.js index bd338129..a2ee5199 100644 --- a/client/js/controllers/post_main_controller.js +++ b/client/js/controllers/post_main_controller.js @@ -169,6 +169,12 @@ class PostMainController extends BasePostController { this._view.sidebarControl.disableForm(); this._view.sidebarControl.clearMessages(); const post = e.detail.post; + if (e.detail.title !== undefined && e.detail.title !== null) { + post.title = e.detail.title; + } + if (e.detail.description !== undefined && e.detail.description !== null) { + post.description = e.detail.description; + } if (e.detail.safety !== undefined && e.detail.safety !== null) { post.safety = e.detail.safety; } diff --git a/client/js/models/post.js b/client/js/models/post.js index 01f81bf1..ffb81f6b 100644 --- a/client/js/models/post.js +++ b/client/js/models/post.js @@ -54,6 +54,14 @@ class Post extends events.EventTarget { return this._user; } + get title() { + return this._title; + } + + get description() { + return this._description; + } + get safety() { return this._safety; } @@ -250,6 +258,12 @@ class Post extends events.EventTarget { if (anonymous === true) { detail.anonymous = true; } + if (this._title !== this._orig._title) { + detail.title = this._title; + } + if (this._description !== this._orig._description) { + detail.description = this._description; + } if (this._safety !== this._orig._safety) { detail.safety = this._safety; } @@ -471,6 +485,8 @@ class Post extends events.EventTarget { _checksumMD5: response.checksumMD5, _creationTime: response.creationTime, _user: response.user, + _title: response.title, + _description: response.description, _safety: response.safety, _contentUrl: response.contentUrl, _fullContentUrl: new URL( diff --git a/doc/developer-utils/create-alembic-migration.sh b/doc/developer-utils/create-alembic-migration.sh index df7a29eb..9bb26399 100755 --- a/doc/developer-utils/create-alembic-migration.sh +++ b/doc/developer-utils/create-alembic-migration.sh @@ -10,15 +10,16 @@ fi # Create a dummy container WORKDIR="$(git rev-parse --show-toplevel)/server" IMAGE=$(docker build -q "${WORKDIR}") -CONTAINER=$(docker run -d ${IMAGE} tail -f /dev/null) +CONTAINER=$(docker run --network=host -d ${IMAGE} tail -f /dev/null) # Create the migration script docker exec -i \ -e PYTHONPATH='/opt/app' \ - -e POSTGRES_HOST='x' \ - -e POSTGRES_USER='x' \ - -e POSTGRES_PASSWORD='x' \ - ${CONTAINER} alembic revision -m "$1" + -e POSTGRES_HOST='localhost' \ + -e POSTGRES_PORT='15432' \ + -e POSTGRES_USER='szuru' \ + -e POSTGRES_PASSWORD='changeme' \ + ${CONTAINER} alembic revision --autogenerate -m "$1" # Copy the file over from the container docker cp ${CONTAINER}:/opt/app/szurubooru/migrations/versions/ \ diff --git a/docker-compose.local.yml b/docker-compose.local.yml new file mode 100644 index 00000000..9c26f002 --- /dev/null +++ b/docker-compose.local.yml @@ -0,0 +1,48 @@ +## Example Docker Compose configuration +## +## Use this as a template to set up docker-compose, or as guide to set up other +## orchestration services +version: '2' + +services: + + server: + image: szurubooru/server:latest + 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: + - "${MOUNT_DATA}:/data" + - "./server/config.yaml:/opt/app/config.yaml" + + client: + image: szurubooru/client:latest + depends_on: + - server + environment: + BACKEND_HOST: server + BASE_URL: + volumes: + - "${MOUNT_DATA}:/data:ro" + ports: + - "${PORT}:80" + + sql: + image: postgres:11-alpine + restart: unless-stopped + environment: + POSTGRES_USER: szuru + POSTGRES_PASSWORD: changeme + volumes: + - "${MOUNT_SQL}:/var/lib/postgresql/data" diff --git a/docker-compose.yml b/docker-compose.yml index 38e08b97..8b2900b1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,8 +2,6 @@ ## ## Use this as a template to set up docker-compose, or as guide to set up other ## orchestration services -version: '2' - services: server: @@ -15,14 +13,18 @@ services: ## or FQDNs/IP addresses if these services are running outside of Docker POSTGRES_HOST: sql ## Credentials for database: - POSTGRES_USER: - POSTGRES_PASSWORD: + POSTGRES_USER: szuru + POSTGRES_PASSWORD: changeme ## Commented Values are Default: #POSTGRES_DB: defaults to same as POSTGRES_USER #POSTGRES_PORT: 5432 #LOG_SQL: 0 (1 for verbose SQL logs) THREADS: + UID: ${UID} + GID: ${GID} + user: "${UID}:${GID}" volumes: + - "./server:/opt/app" - "${MOUNT_DATA}:/data" - "./server/config.yaml:/opt/app/config.yaml" @@ -44,5 +46,7 @@ services: environment: POSTGRES_USER: POSTGRES_PASSWORD: + ports: + - 15432:5432 volumes: - "${MOUNT_SQL}:/var/lib/postgresql/data" diff --git a/server/szurubooru/api/post_api.py b/server/szurubooru/api/post_api.py index daba7f7e..f4dae1e5 100644 --- a/server/szurubooru/api/post_api.py +++ b/server/szurubooru/api/post_api.py @@ -65,6 +65,14 @@ def create_post( ctx.user, "uploads:use_downloader" ), ) + title = "" + if ctx.has_param("title"): + title = ctx.get_param_as_string("title") + + description = "" + if ctx.has_param("description"): + description = ctx.get_param_as_string("description") + tag_names = ctx.get_param_as_string_list("tags", default=[]) safety = ctx.get_param_as_string("safety") source = ctx.get_param_as_string("source", default="") @@ -81,6 +89,8 @@ def create_post( ) if len(new_tags): auth.verify_privilege(ctx.user, "tags:create") + posts.update_post_title(post, title) + posts.update_post_description(post, description) posts.update_post_safety(post, safety) posts.update_post_source(post, source) posts.update_post_relations(post, relations) @@ -143,6 +153,14 @@ def update_post(ctx: rest.Context, params: Dict[str, str]) -> rest.Response: db.session.flush() for tag in new_tags: snapshots.create(tag, ctx.user) + if ctx.has_param("title"): + # for now we're just treating this fields as "content" + auth.verify_privilege(ctx.user, "posts:edit:content") + posts.update_post_title(post, ctx.get_param_as_string("title")) + if ctx.has_param("description"): + # for now we're just treating this fields as "content" + auth.verify_privilege(ctx.user, "posts:edit:content") + posts.update_post_description(post, ctx.get_param_as_string("description")) if ctx.has_param("safety"): auth.verify_privilege(ctx.user, "posts:edit:safety") posts.update_post_safety(post, ctx.get_param_as_string("safety")) diff --git a/server/szurubooru/func/posts.py b/server/szurubooru/func/posts.py index be2259cf..064da4f3 100644 --- a/server/szurubooru/func/posts.py +++ b/server/szurubooru/func/posts.py @@ -414,6 +414,8 @@ def create_post( post.creation_time = datetime.utcnow() post.flags = [] + post.title = "" + post.description = "" post.type = "" post.checksum = "" post.mime_type = "" @@ -441,6 +443,18 @@ def update_post_source(post: model.Post, source: Optional[str]) -> None: raise InvalidPostSourceError("Source is too long.") post.source = source or None +def update_post_title(post: model.Post, title: str) -> None: + assert post + if util.value_exceeds_column_size(title, model.Post.title): + raise InvalidPostSourceError("Title is too long.") + post.title = title + +def update_post_description(post: model.Post, description: str) -> None: + assert post + if util.value_exceeds_column_size(description, model.Post.description): + raise InvalidPostSourceError("Description is too long.") + post.description = description + @sa.events.event.listens_for(model.Post, "after_insert") def _after_post_insert( diff --git a/server/szurubooru/migrations/versions/0c149800a728_add_title_add_desc_to_posts.py b/server/szurubooru/migrations/versions/0c149800a728_add_title_add_desc_to_posts.py new file mode 100644 index 00000000..f34f20f9 --- /dev/null +++ b/server/szurubooru/migrations/versions/0c149800a728_add_title_add_desc_to_posts.py @@ -0,0 +1,24 @@ +''' +Add title and description to posts + +Revision ID: 0c149800a728 +Created at: 2024-12-03 08:21:21.113161 +''' + +import sqlalchemy as sa +from alembic import op + + + +revision = '0c149800a728' +down_revision = 'adcd63ff76a2' +branch_labels = None +depends_on = None + +def upgrade(): + op.add_column('post', sa.Column('title', sa.Unicode(length=512), nullable=False)) + op.add_column('post', sa.Column('description', sa.Unicode(length=2048), nullable=False)) + +def downgrade(): + op.drop_column('post', 'description') + op.drop_column('post', 'title') diff --git a/server/szurubooru/model/post.py b/server/szurubooru/model/post.py index 49e748dc..da621661 100644 --- a/server/szurubooru/model/post.py +++ b/server/szurubooru/model/post.py @@ -215,6 +215,8 @@ class Post(Base): flags_string = sa.Column("flags", sa.Unicode(32), default="") # content description + title = sa.Column("title", sa.Unicode(512), nullable=False, default="") + description = sa.Column("description", sa.Unicode(2048), nullable=False, default="") type = sa.Column("type", sa.Unicode(32), nullable=False) checksum = sa.Column("checksum", sa.Unicode(64), nullable=False) checksum_md5 = sa.Column("checksum_md5", sa.Unicode(32))