From 7f9950fe85f57217d0be67836ff6a85cbc280cad Mon Sep 17 00:00:00 2001 From: Dessalines Date: Tue, 5 Mar 2024 05:34:57 -0500 Subject: [PATCH] Add alt_text for posts. Fixes #1086 (#4477) * Add alt_text for posts. Fixes #1086 * Moving alt_text to attachment name. * Cleaning up mod action line. * Addressing PR comments * Addressing PR comments. * Fixing clones. --- api_tests/package.json | 10 +- api_tests/pnpm-lock.yaml | 213 +++++++++--------- api_tests/src/image.spec.ts | 3 + api_tests/src/shared.ts | 2 + crates/api_common/src/post.rs | 4 + crates/api_crud/src/post/create.rs | 18 +- crates/api_crud/src/post/update.rs | 43 ++-- crates/apub/src/objects/post.rs | 17 +- crates/apub/src/protocol/objects/page.rs | 15 +- crates/db_schema/src/impls/post.rs | 1 + crates/db_schema/src/schema.rs | 1 + crates/db_schema/src/source/post.rs | 6 +- crates/db_views/src/comment_view.rs | 1 + crates/db_views/src/post_view.rs | 1 + crates/utils/src/error.rs | 1 + crates/utils/src/utils/validation.rs | 13 ++ .../down.sql | 3 + .../up.sql | 3 + 18 files changed, 215 insertions(+), 140 deletions(-) create mode 100644 migrations/2024-02-27-204628_add_post_alt_text/down.sql create mode 100644 migrations/2024-02-27-204628_add_post_alt_text/up.sql diff --git a/api_tests/package.json b/api_tests/package.json index e4c40c003..9e7042f2d 100644 --- a/api_tests/package.json +++ b/api_tests/package.json @@ -20,14 +20,14 @@ }, "devDependencies": { "@types/jest": "^29.5.12", - "@types/node": "^20.11.19", - "@typescript-eslint/eslint-plugin": "^7.0.2", - "@typescript-eslint/parser": "^7.0.2", + "@types/node": "^20.11.22", + "@typescript-eslint/eslint-plugin": "^7.1.0", + "@typescript-eslint/parser": "^7.1.0", "download-file-sync": "^1.0.4", - "eslint": "^8.55.0", + "eslint": "^8.57.0", "eslint-plugin-prettier": "^5.0.1", "jest": "^29.5.0", - "lemmy-js-client": "0.19.4-alpha.4", + "lemmy-js-client": "0.19.4-alpha.6", "prettier": "^3.2.5", "ts-jest": "^29.1.0", "typescript": "^5.3.3" diff --git a/api_tests/pnpm-lock.yaml b/api_tests/pnpm-lock.yaml index 7c895cead..213111ab1 100644 --- a/api_tests/pnpm-lock.yaml +++ b/api_tests/pnpm-lock.yaml @@ -9,29 +9,29 @@ devDependencies: specifier: ^29.5.12 version: 29.5.12 '@types/node': - specifier: ^20.11.19 - version: 20.11.19 + specifier: ^20.11.22 + version: 20.11.22 '@typescript-eslint/eslint-plugin': - specifier: ^7.0.2 - version: 7.0.2(@typescript-eslint/parser@7.0.2)(eslint@8.56.0)(typescript@5.3.3) + specifier: ^7.1.0 + version: 7.1.0(@typescript-eslint/parser@7.1.0)(eslint@8.57.0)(typescript@5.3.3) '@typescript-eslint/parser': - specifier: ^7.0.2 - version: 7.0.2(eslint@8.56.0)(typescript@5.3.3) + specifier: ^7.1.0 + version: 7.1.0(eslint@8.57.0)(typescript@5.3.3) download-file-sync: specifier: ^1.0.4 version: 1.0.4 eslint: - specifier: ^8.55.0 - version: 8.56.0 + specifier: ^8.57.0 + version: 8.57.0 eslint-plugin-prettier: specifier: ^5.0.1 - version: 5.1.3(eslint@8.56.0)(prettier@3.2.5) + version: 5.1.3(eslint@8.57.0)(prettier@3.2.5) jest: specifier: ^29.5.0 - version: 29.7.0(@types/node@20.11.19) + version: 29.7.0(@types/node@20.11.22) lemmy-js-client: - specifier: 0.19.4-alpha.4 - version: 0.19.4-alpha.4 + specifier: 0.19.4-alpha.6 + version: 0.19.4-alpha.6 prettier: specifier: ^3.2.5 version: 3.2.5 @@ -386,13 +386,13 @@ packages: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true - /@eslint-community/eslint-utils@4.4.0(eslint@8.56.0): + /@eslint-community/eslint-utils@4.4.0(eslint@8.57.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 8.56.0 + eslint: 8.57.0 eslint-visitor-keys: 3.4.3 dev: true @@ -409,7 +409,7 @@ packages: debug: 4.3.4 espree: 9.6.1 globals: 13.24.0 - ignore: 5.3.0 + ignore: 5.3.1 import-fresh: 3.3.0 js-yaml: 4.1.0 minimatch: 3.1.2 @@ -418,8 +418,8 @@ packages: - supports-color dev: true - /@eslint/js@8.56.0: - resolution: {integrity: sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==} + /@eslint/js@8.57.0: + resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true @@ -464,7 +464,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.11.19 + '@types/node': 20.11.22 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -485,14 +485,14 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.19 + '@types/node': 20.11.22 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.11.19) + jest-config: 29.7.0(@types/node@20.11.22) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -520,7 +520,7 @@ packages: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.19 + '@types/node': 20.11.22 jest-mock: 29.7.0 dev: true @@ -547,7 +547,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.11.19 + '@types/node': 20.11.22 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -580,7 +580,7 @@ packages: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.22 - '@types/node': 20.11.19 + '@types/node': 20.11.22 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -668,7 +668,7 @@ packages: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.11.19 + '@types/node': 20.11.22 '@types/yargs': 17.0.32 chalk: 4.1.2 dev: true @@ -721,7 +721,7 @@ packages: engines: {node: '>= 8'} dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.17.0 + fastq: 1.17.1 dev: true /@pkgr/core@0.1.1: @@ -777,7 +777,7 @@ packages: /@types/graceful-fs@4.1.9: resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} dependencies: - '@types/node': 20.11.19 + '@types/node': 20.11.22 dev: true /@types/istanbul-lib-coverage@2.0.6: @@ -807,14 +807,14 @@ packages: resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} dev: true - /@types/node@20.11.19: - resolution: {integrity: sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==} + /@types/node@20.11.22: + resolution: {integrity: sha512-/G+IxWxma6V3E+pqK1tSl2Fo1kl41pK1yeCyDsgkF9WlVAme4j5ISYM2zR11bgLFJGLN5sVK40T4RJNuiZbEjA==} dependencies: undici-types: 5.26.5 dev: true - /@types/semver@7.5.7: - resolution: {integrity: sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==} + /@types/semver@7.5.8: + resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} dev: true /@types/stack-utils@2.0.3: @@ -831,8 +831,8 @@ packages: '@types/yargs-parser': 21.0.3 dev: true - /@typescript-eslint/eslint-plugin@7.0.2(@typescript-eslint/parser@7.0.2)(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-/XtVZJtbaphtdrWjr+CJclaCVGPtOdBpFEnvtNf/jRV0IiEemRrL0qABex/nEt8isYcnFacm3nPHYQwL+Wb7qg==} + /@typescript-eslint/eslint-plugin@7.1.0(@typescript-eslint/parser@7.1.0)(eslint@8.57.0)(typescript@5.3.3): + resolution: {integrity: sha512-j6vT/kCulhG5wBmGtstKeiVr1rdXE4nk+DT1k6trYkwlrvW9eOF5ZbgKnd/YR6PcM4uTEXa0h6Fcvf6X7Dxl0w==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: '@typescript-eslint/parser': ^7.0.0 @@ -843,13 +843,13 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 7.0.2(eslint@8.56.0)(typescript@5.3.3) - '@typescript-eslint/scope-manager': 7.0.2 - '@typescript-eslint/type-utils': 7.0.2(eslint@8.56.0)(typescript@5.3.3) - '@typescript-eslint/utils': 7.0.2(eslint@8.56.0)(typescript@5.3.3) - '@typescript-eslint/visitor-keys': 7.0.2 + '@typescript-eslint/parser': 7.1.0(eslint@8.57.0)(typescript@5.3.3) + '@typescript-eslint/scope-manager': 7.1.0 + '@typescript-eslint/type-utils': 7.1.0(eslint@8.57.0)(typescript@5.3.3) + '@typescript-eslint/utils': 7.1.0(eslint@8.57.0)(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 7.1.0 debug: 4.3.4 - eslint: 8.56.0 + eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.1 natural-compare: 1.4.0 @@ -860,8 +860,8 @@ packages: - supports-color dev: true - /@typescript-eslint/parser@7.0.2(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-GdwfDglCxSmU+QTS9vhz2Sop46ebNCXpPPvsByK7hu0rFGRHL+AusKQJ7SoN+LbLh6APFpQwHKmDSwN35Z700Q==} + /@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3): + resolution: {integrity: sha512-V1EknKUubZ1gWFjiOZhDSNToOjs63/9O0puCgGS8aDOgpZY326fzFu15QAUjwaXzRZjf/qdsdBrckYdv9YxB8w==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^8.56.0 @@ -870,27 +870,27 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 7.0.2 - '@typescript-eslint/types': 7.0.2 - '@typescript-eslint/typescript-estree': 7.0.2(typescript@5.3.3) - '@typescript-eslint/visitor-keys': 7.0.2 + '@typescript-eslint/scope-manager': 7.1.0 + '@typescript-eslint/types': 7.1.0 + '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 7.1.0 debug: 4.3.4 - eslint: 8.56.0 + eslint: 8.57.0 typescript: 5.3.3 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/scope-manager@7.0.2: - resolution: {integrity: sha512-l6sa2jF3h+qgN2qUMjVR3uCNGjWw4ahGfzIYsCtFrQJCjhbrDPdiihYT8FnnqFwsWX+20hK592yX9I2rxKTP4g==} + /@typescript-eslint/scope-manager@7.1.0: + resolution: {integrity: sha512-6TmN4OJiohHfoOdGZ3huuLhpiUgOGTpgXNUPJgeZOZR3DnIpdSgtt83RS35OYNNXxM4TScVlpVKC9jyQSETR1A==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 7.0.2 - '@typescript-eslint/visitor-keys': 7.0.2 + '@typescript-eslint/types': 7.1.0 + '@typescript-eslint/visitor-keys': 7.1.0 dev: true - /@typescript-eslint/type-utils@7.0.2(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-IKKDcFsKAYlk8Rs4wiFfEwJTQlHcdn8CLwLaxwd6zb8HNiMcQIFX9sWax2k4Cjj7l7mGS5N1zl7RCHOVwHq2VQ==} + /@typescript-eslint/type-utils@7.1.0(eslint@8.57.0)(typescript@5.3.3): + resolution: {integrity: sha512-UZIhv8G+5b5skkcuhgvxYWHjk7FW7/JP5lPASMEUoliAPwIH/rxoUSQPia2cuOj9AmDZmwUl1usKm85t5VUMew==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^8.56.0 @@ -899,23 +899,23 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 7.0.2(typescript@5.3.3) - '@typescript-eslint/utils': 7.0.2(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3) + '@typescript-eslint/utils': 7.1.0(eslint@8.57.0)(typescript@5.3.3) debug: 4.3.4 - eslint: 8.56.0 + eslint: 8.57.0 ts-api-utils: 1.2.1(typescript@5.3.3) typescript: 5.3.3 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/types@7.0.2: - resolution: {integrity: sha512-ZzcCQHj4JaXFjdOql6adYV4B/oFOFjPOC9XYwCaZFRvqN8Llfvv4gSxrkQkd2u4Ci62i2c6W6gkDwQJDaRc4nA==} + /@typescript-eslint/types@7.1.0: + resolution: {integrity: sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==} engines: {node: ^16.0.0 || >=18.0.0} dev: true - /@typescript-eslint/typescript-estree@7.0.2(typescript@5.3.3): - resolution: {integrity: sha512-3AMc8khTcELFWcKcPc0xiLviEvvfzATpdPj/DXuOGIdQIIFybf4DMT1vKRbuAEOFMwhWt7NFLXRkbjsvKZQyvw==} + /@typescript-eslint/typescript-estree@7.1.0(typescript@5.3.3): + resolution: {integrity: sha512-k7MyrbD6E463CBbSpcOnwa8oXRdHzH1WiVzOipK3L5KSML92ZKgUBrTlehdi7PEIMT8k0bQixHUGXggPAlKnOQ==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: typescript: '*' @@ -923,8 +923,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 7.0.2 - '@typescript-eslint/visitor-keys': 7.0.2 + '@typescript-eslint/types': 7.1.0 + '@typescript-eslint/visitor-keys': 7.1.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 @@ -936,30 +936,30 @@ packages: - supports-color dev: true - /@typescript-eslint/utils@7.0.2(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-PZPIONBIB/X684bhT1XlrkjNZJIEevwkKDsdwfiu1WeqBxYEEdIgVDgm8/bbKHVu+6YOpeRqcfImTdImx/4Bsw==} + /@typescript-eslint/utils@7.1.0(eslint@8.57.0)(typescript@5.3.3): + resolution: {integrity: sha512-WUFba6PZC5OCGEmbweGpnNJytJiLG7ZvDBJJoUcX4qZYf1mGZ97mO2Mps6O2efxJcJdRNpqweCistDbZMwIVHw==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^8.56.0 dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@types/json-schema': 7.0.15 - '@types/semver': 7.5.7 - '@typescript-eslint/scope-manager': 7.0.2 - '@typescript-eslint/types': 7.0.2 - '@typescript-eslint/typescript-estree': 7.0.2(typescript@5.3.3) - eslint: 8.56.0 + '@types/semver': 7.5.8 + '@typescript-eslint/scope-manager': 7.1.0 + '@typescript-eslint/types': 7.1.0 + '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3) + eslint: 8.57.0 semver: 7.6.0 transitivePeerDependencies: - supports-color - typescript dev: true - /@typescript-eslint/visitor-keys@7.0.2: - resolution: {integrity: sha512-8Y+YiBmqPighbm5xA2k4wKTxRzx9EkBu7Rlw+WHqMvRJ3RPz/BMBO9b2ru0LUNmXg120PHUXD5+SWFy2R8DqlQ==} + /@typescript-eslint/visitor-keys@7.1.0: + resolution: {integrity: sha512-FhUqNWluiGNzlvnDZiXad4mZRhtghdoKW6e98GoEOYSu5cND+E39rG5KwJMUzeENwm1ztYBRqof8wMLP+wNPIA==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 7.0.2 + '@typescript-eslint/types': 7.1.0 eslint-visitor-keys: 3.4.3 dev: true @@ -1276,7 +1276,7 @@ packages: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} dev: true - /create-jest@29.7.0(@types/node@20.11.19): + /create-jest@29.7.0(@types/node@20.11.22): resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -1285,7 +1285,7 @@ packages: chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.11.19) + jest-config: 29.7.0(@types/node@20.11.22) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -1414,7 +1414,7 @@ packages: engines: {node: '>=10'} dev: true - /eslint-plugin-prettier@5.1.3(eslint@8.56.0)(prettier@3.2.5): + /eslint-plugin-prettier@5.1.3(eslint@8.57.0)(prettier@3.2.5): resolution: {integrity: sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -1428,7 +1428,7 @@ packages: eslint-config-prettier: optional: true dependencies: - eslint: 8.56.0 + eslint: 8.57.0 prettier: 3.2.5 prettier-linter-helpers: 1.0.0 synckit: 0.8.8 @@ -1447,15 +1447,15 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint@8.56.0: - resolution: {integrity: sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==} + /eslint@8.57.0: + resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@eslint-community/regexpp': 4.10.0 '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.56.0 + '@eslint/js': 8.57.0 '@humanwhocodes/config-array': 0.11.14 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 @@ -1477,7 +1477,7 @@ packages: glob-parent: 6.0.2 globals: 13.24.0 graphemer: 1.4.0 - ignore: 5.3.0 + ignore: 5.3.1 imurmurhash: 0.1.4 is-glob: 4.0.3 is-path-inside: 3.0.3 @@ -1591,8 +1591,8 @@ packages: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} dev: true - /fastq@1.17.0: - resolution: {integrity: sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w==} + /fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} dependencies: reusify: 1.0.4 dev: true @@ -1637,13 +1637,13 @@ packages: resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} engines: {node: ^10.12.0 || >=12.0.0} dependencies: - flatted: 3.2.9 + flatted: 3.3.1 keyv: 4.5.4 rimraf: 3.0.2 dev: true - /flatted@3.2.9: - resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} + /flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} dev: true /form-data@4.0.0: @@ -1774,11 +1774,6 @@ packages: engines: {node: '>=10.17.0'} dev: true - /ignore@5.3.0: - resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==} - engines: {node: '>= 4'} - dev: true - /ignore@5.3.1: resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} engines: {node: '>= 4'} @@ -1944,7 +1939,7 @@ packages: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.19 + '@types/node': 20.11.22 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.1 @@ -1965,7 +1960,7 @@ packages: - supports-color dev: true - /jest-cli@29.7.0(@types/node@20.11.19): + /jest-cli@29.7.0(@types/node@20.11.22): resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -1979,10 +1974,10 @@ packages: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.11.19) + create-jest: 29.7.0(@types/node@20.11.22) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@20.11.19) + jest-config: 29.7.0(@types/node@20.11.22) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -1993,7 +1988,7 @@ packages: - ts-node dev: true - /jest-config@29.7.0(@types/node@20.11.19): + /jest-config@29.7.0(@types/node@20.11.22): resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -2008,7 +2003,7 @@ packages: '@babel/core': 7.23.9 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.19 + '@types/node': 20.11.22 babel-jest: 29.7.0(@babel/core@7.23.9) chalk: 4.1.2 ci-info: 3.9.0 @@ -2068,7 +2063,7 @@ packages: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.19 + '@types/node': 20.11.22 jest-mock: 29.7.0 jest-util: 29.7.0 dev: true @@ -2084,7 +2079,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 20.11.19 + '@types/node': 20.11.22 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -2135,7 +2130,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.11.19 + '@types/node': 20.11.22 jest-util: 29.7.0 dev: true @@ -2190,7 +2185,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.19 + '@types/node': 20.11.22 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -2221,7 +2216,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.19 + '@types/node': 20.11.22 chalk: 4.1.2 cjs-module-lexer: 1.2.3 collect-v8-coverage: 1.0.2 @@ -2273,7 +2268,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.11.19 + '@types/node': 20.11.22 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -2298,7 +2293,7 @@ packages: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.11.19 + '@types/node': 20.11.22 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -2310,13 +2305,13 @@ packages: resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@types/node': 20.11.19 + '@types/node': 20.11.22 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true - /jest@29.7.0(@types/node@20.11.19): + /jest@29.7.0(@types/node@20.11.22): resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -2329,7 +2324,7 @@ packages: '@jest/core': 29.7.0 '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@20.11.19) + jest-cli: 29.7.0(@types/node@20.11.22) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -2395,8 +2390,8 @@ packages: engines: {node: '>=6'} dev: true - /lemmy-js-client@0.19.4-alpha.4: - resolution: {integrity: sha512-ZsE/1dRiNAzOjGNtyv+1JH7AzKWBkHY4FH4EGrdhkKtNY7EKDWt4uEkc3Bde+cQg4YTdbzXyuY94gAVCFm+L1w==} + /lemmy-js-client@0.19.4-alpha.6: + resolution: {integrity: sha512-x4htMlpoZ7hzrhrIk82aompVxbpu2ZDWtmWNGraM0+27nUCDf6gYxJH5nb5R/o39BQe5KSHq6zoBdliBwAY40w==} dependencies: cross-fetch: 4.0.0 form-data: 4.0.0 @@ -2994,7 +2989,7 @@ packages: '@babel/core': 7.23.9 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.11.19) + jest: 29.7.0(@types/node@20.11.22) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 diff --git a/api_tests/src/image.spec.ts b/api_tests/src/image.spec.ts index 8035ff4d7..6414a8913 100644 --- a/api_tests/src/image.spec.ts +++ b/api_tests/src/image.spec.ts @@ -202,4 +202,7 @@ test("No image proxying if setting is disabled", async () => { gammaPost.post!.post.url?.startsWith("http://127.0.0.1:8551/pictrs/image/"), ).toBeTruthy(); expect(gammaPost.post!.post.body).toBe("![](http://example.com/image2.png)"); + + // Make sure the alt text got federated + expect(post.post_view.post.alt_text).toBe(gammaPost.post!.post.alt_text); }); diff --git a/api_tests/src/shared.ts b/api_tests/src/shared.ts index 14c952d67..af2629393 100644 --- a/api_tests/src/shared.ts +++ b/api_tests/src/shared.ts @@ -200,11 +200,13 @@ export async function createPost( body = randomString(10), // use example.com for consistent title and embed description name: string = randomString(5), + alt_text = randomString(10), ): Promise { let form: CreatePost = { name, url, body, + alt_text, community_id, }; return api.createPost(form); diff --git a/crates/api_common/src/post.rs b/crates/api_common/src/post.rs index 2cce24149..4993fc6e5 100644 --- a/crates/api_common/src/post.rs +++ b/crates/api_common/src/post.rs @@ -24,6 +24,8 @@ pub struct CreatePost { pub url: Option, /// An optional body for the post in markdown. pub body: Option, + /// An optional alt_text, usable for image posts. + pub alt_text: Option, /// A honeypot to catch bots. Should be None. pub honeypot: Option, pub nsfw: Option, @@ -116,6 +118,8 @@ pub struct EditPost { pub url: Option, /// An optional body for the post in markdown. pub body: Option, + /// An optional alt_text, usable for image posts. + pub alt_text: Option, pub nsfw: Option, pub language_id: Option, #[cfg_attr(feature = "full", ts(type = "string"))] diff --git a/crates/api_crud/src/post/create.rs b/crates/api_crud/src/post/create.rs index a5a9c013f..c52cfaf80 100644 --- a/crates/api_crud/src/post/create.rs +++ b/crates/api_crud/src/post/create.rs @@ -35,7 +35,13 @@ use lemmy_utils::{ spawn_try_task, utils::{ slurs::check_slurs, - validation::{check_url_scheme, clean_url_params, is_valid_body_field, is_valid_post_title}, + validation::{ + check_url_scheme, + clean_url_params, + is_valid_alt_text_field, + is_valid_body_field, + is_valid_post_title, + }, }, }; use tracing::Instrument; @@ -50,17 +56,19 @@ pub async fn create_post( ) -> Result, LemmyError> { let local_site = LocalSite::read(&mut context.pool()).await?; - let slur_regex = local_site_to_slur_regex(&local_site); - check_slurs(&data.name, &slur_regex)?; - let body = process_markdown_opt(&data.body, &slur_regex, &context).await?; honeypot_check(&data.honeypot)?; + let slur_regex = local_site_to_slur_regex(&local_site); + check_slurs(&data.name, &slur_regex)?; + + let body = process_markdown_opt(&data.body, &slur_regex, &context).await?; let data_url = data.url.as_ref(); let url = data_url.map(clean_url_params); // TODO no good way to handle a "clear" let custom_thumbnail = data.custom_thumbnail.as_ref().map(clean_url_params); is_valid_post_title(&data.name)?; is_valid_body_field(&body, true)?; + is_valid_alt_text_field(&data.alt_text)?; check_url_scheme(&url)?; check_url_scheme(&custom_thumbnail)?; @@ -122,8 +130,10 @@ pub async fn create_post( let post_form = PostInsertForm::builder() .name(data.name.trim().to_string()) + .url_content_type(metadata.content_type) .url(url) .body(body) + .alt_text(data.alt_text.clone()) .community_id(data.community_id) .creator_id(local_user_view.person.id) .nsfw(data.nsfw) diff --git a/crates/api_crud/src/post/update.rs b/crates/api_crud/src/post/update.rs index e858d9b30..6db65dffe 100644 --- a/crates/api_crud/src/post/update.rs +++ b/crates/api_crud/src/post/update.rs @@ -27,7 +27,13 @@ use lemmy_utils::{ error::{LemmyError, LemmyErrorExt, LemmyErrorType}, utils::{ slurs::check_slurs_opt, - validation::{check_url_scheme, clean_url_params, is_valid_body_field, is_valid_post_title}, + validation::{ + check_url_scheme, + clean_url_params, + is_valid_alt_text_field, + is_valid_body_field, + is_valid_post_title, + }, }, }; use std::ops::Deref; @@ -54,6 +60,7 @@ pub async fn update_post( } is_valid_body_field(&body, true)?; + is_valid_alt_text_field(&data.alt_text)?; check_url_scheme(&url)?; check_url_scheme(&custom_thumbnail)?; @@ -73,22 +80,24 @@ pub async fn update_post( } // Fetch post links and thumbnail if url was updated - let (embed_title, embed_description, embed_video_url, metadata_thumbnail) = match &url { - Some(url) => { - // Only generate the thumbnail if there's no custom thumbnail provided, - // otherwise it will save it in pictrs - let generate_thumbnail = custom_thumbnail.is_none(); + let (embed_title, embed_description, embed_video_url, metadata_thumbnail, metadata_content_type) = + match &url { + Some(url) => { + // Only generate the thumbnail if there's no custom thumbnail provided, + // otherwise it will save it in pictrs + let generate_thumbnail = custom_thumbnail.is_none() || orig_post.thumbnail_url.is_none(); - let metadata = fetch_link_metadata(url, generate_thumbnail, &context).await?; - ( - Some(metadata.opengraph_data.title), - Some(metadata.opengraph_data.description), - Some(metadata.opengraph_data.embed_video_url), - Some(metadata.thumbnail), - ) - } - _ => Default::default(), - }; + let metadata = fetch_link_metadata(url, generate_thumbnail, &context).await?; + ( + Some(metadata.opengraph_data.title), + Some(metadata.opengraph_data.description), + Some(metadata.opengraph_data.embed_video_url), + Some(metadata.thumbnail), + Some(metadata.content_type), + ) + } + _ => Default::default(), + }; let url = match url { Some(url) => Some(proxy_image_link_opt_apub(Some(url), &context).await?), @@ -115,7 +124,9 @@ pub async fn update_post( let post_form = PostUpdateForm { name: data.name.clone(), url, + url_content_type: metadata_content_type, body: diesel_option_overwrite(body), + alt_text: diesel_option_overwrite(data.alt_text.clone()), nsfw: data.nsfw, embed_title, embed_description, diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index 681fef2c4..47d7f53c6 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -115,7 +115,13 @@ impl Object for ApubPost { let attachment = self .url .clone() - .map(|url| Attachment::new(url.into(), self.url_content_type.clone())) + .map(|url| { + Attachment::new( + url.into(), + self.url_content_type.clone(), + self.alt_text.clone(), + ) + }) .into_iter() .collect(); @@ -203,10 +209,11 @@ impl Object for ApubPost { // read existing, local post if any (for generating mod log) let old_post = page.id.dereference_local(context).await; + let first_attachment = page.attachment.first(); + let form = if !page.is_mod_action(context).await? { - let first_attachment = page.attachment.into_iter().map(Attachment::url).next(); - let url = if first_attachment.is_some() { - first_attachment + let url = if let Some(attachment) = first_attachment.cloned() { + Some(attachment.url()) } else if page.kind == PageType::Video { // we cant display videos directly, so insert a link to external video page Some(page.id.inner().clone()) @@ -215,6 +222,7 @@ impl Object for ApubPost { }; check_url_scheme(&url)?; + let alt_text = first_attachment.cloned().and_then(Attachment::alt_text); let local_site = LocalSite::read(&mut context.pool()).await.ok(); let allow_sensitive = local_site_opt_to_sensitive(&local_site); let page_is_sensitive = page.sensitive.unwrap_or(false); @@ -241,6 +249,7 @@ impl Object for ApubPost { name, url: url.map(Into::into), body, + alt_text, creator_id: creator.id, community_id: community.id, removed: None, diff --git a/crates/apub/src/protocol/objects/page.rs b/crates/apub/src/protocol/objects/page.rs index fbcb98254..d6111843e 100644 --- a/crates/apub/src/protocol/objects/page.rs +++ b/crates/apub/src/protocol/objects/page.rs @@ -82,6 +82,8 @@ pub(crate) struct Image { #[serde(rename = "type")] kind: ImageType, url: Url, + /// Used for alt_text + name: Option, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -90,6 +92,8 @@ pub(crate) struct Document { #[serde(rename = "type")] kind: DocumentType, url: Url, + /// Used for alt_text + name: Option, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -111,6 +115,14 @@ impl Attachment { Attachment::Document(d) => d.url, } } + + pub(crate) fn alt_text(self) -> Option { + match self { + Attachment::Image(i) => i.name, + Attachment::Document(d) => d.name, + _ => None, + } + } } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -168,12 +180,13 @@ impl Page { impl Attachment { /// Creates new attachment for a given link and mime type. - pub(crate) fn new(url: Url, media_type: Option) -> Attachment { + pub(crate) fn new(url: Url, media_type: Option, alt_text: Option) -> Attachment { let is_image = media_type.clone().unwrap_or_default().starts_with("image"); if is_image { Attachment::Image(Image { kind: Default::default(), url, + name: alt_text, }) } else { Attachment::Link(Link { diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index 7e2eec22b..a75fa4349 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -434,6 +434,7 @@ mod tests { name: "A test post".into(), url: None, body: None, + alt_text: None, creator_id: inserted_person.id, community_id: inserted_community.id, published: inserted_post.published, diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index bdde25566..739ff8482 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -705,6 +705,7 @@ diesel::table! { featured_community -> Bool, featured_local -> Bool, url_content_type -> Nullable, + alt_text -> Nullable, } } diff --git a/crates/db_schema/src/source/post.rs b/crates/db_schema/src/source/post.rs index 115c90eef..541d9c307 100644 --- a/crates/db_schema/src/source/post.rs +++ b/crates/db_schema/src/source/post.rs @@ -56,6 +56,8 @@ pub struct Post { /// Whether the post is featured to its site. pub featured_local: bool, pub url_content_type: Option, + /// An optional alt_text, usable for image posts. + pub alt_text: Option, } #[derive(Debug, Clone, TypedBuilder)] @@ -87,6 +89,7 @@ pub struct PostInsertForm { pub featured_community: Option, pub featured_local: Option, pub url_content_type: Option, + pub alt_text: Option, } #[derive(Debug, Clone, Default)] @@ -111,7 +114,8 @@ pub struct PostUpdateForm { pub language_id: Option, pub featured_community: Option, pub featured_local: Option, - pub url_content_type: Option, + pub url_content_type: Option>, + pub alt_text: Option>, } #[derive(PartialEq, Eq, Debug)] diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index 4a729c2b3..4be2f6f13 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -1063,6 +1063,7 @@ mod tests { creator_id: data.timmy_local_user_view.person.id, url: None, body: None, + alt_text: None, published: data.inserted_post.published, updated: None, community_id: data.inserted_community.id, diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index 80ca481f4..ed9cd0952 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -1556,6 +1556,7 @@ mod tests { creator_id: inserted_person.id, url: None, body: None, + alt_text: None, published: inserted_post.published, updated: None, community_id: inserted_community.id, diff --git a/crates/utils/src/error.rs b/crates/utils/src/error.rs index fddbc1e55..9da018960 100644 --- a/crates/utils/src/error.rs +++ b/crates/utils/src/error.rs @@ -101,6 +101,7 @@ pub enum LemmyErrorType { InvalidPostTitle, InvalidBodyField, BioLengthOverflow, + AltTextLengthOverflow, MissingTotpToken, MissingTotpSecret, IncorrectTotpToken, diff --git a/crates/utils/src/utils/validation.rs b/crates/utils/src/utils/validation.rs index 0777ac6f8..da989a61b 100644 --- a/crates/utils/src/utils/validation.rs +++ b/crates/utils/src/utils/validation.rs @@ -19,6 +19,7 @@ const ALLOWED_POST_URL_SCHEMES: [&str; 3] = ["http", "https", "magnet"]; const BODY_MAX_LENGTH: usize = 10000; const POST_BODY_MAX_LENGTH: usize = 50000; const BIO_MAX_LENGTH: usize = 300; +const ALT_TEXT_MAX_LENGTH: usize = 300; const SITE_NAME_MAX_LENGTH: usize = 20; const SITE_NAME_MIN_LENGTH: usize = 1; const SITE_DESCRIPTION_MAX_LENGTH: usize = 150; @@ -172,6 +173,18 @@ pub fn is_valid_bio_field(bio: &str) -> LemmyResult<()> { max_length_check(bio, BIO_MAX_LENGTH, LemmyErrorType::BioLengthOverflow) } +pub fn is_valid_alt_text_field(alt_text: &Option) -> LemmyResult<()> { + if let Some(alt_text) = alt_text { + max_length_check( + alt_text, + ALT_TEXT_MAX_LENGTH, + LemmyErrorType::AltTextLengthOverflow, + ) + } else { + Ok(()) + } +} + /// Checks the site name length, the limit as defined in the DB. pub fn site_name_length_check(name: &str) -> LemmyResult<()> { min_length_check(name, SITE_NAME_MIN_LENGTH, LemmyErrorType::SiteNameRequired)?; diff --git a/migrations/2024-02-27-204628_add_post_alt_text/down.sql b/migrations/2024-02-27-204628_add_post_alt_text/down.sql new file mode 100644 index 000000000..28a91cd0e --- /dev/null +++ b/migrations/2024-02-27-204628_add_post_alt_text/down.sql @@ -0,0 +1,3 @@ +ALTER TABLE post + DROP COLUMN alt_text; + diff --git a/migrations/2024-02-27-204628_add_post_alt_text/up.sql b/migrations/2024-02-27-204628_add_post_alt_text/up.sql new file mode 100644 index 000000000..4345d8cfc --- /dev/null +++ b/migrations/2024-02-27-204628_add_post_alt_text/up.sql @@ -0,0 +1,3 @@ +ALTER TABLE post + ADD COLUMN alt_text text; +