Skip to content

dev#86

Open
TatevikGr wants to merge 10 commits into
mainfrom
dev
Open

dev#86
TatevikGr wants to merge 10 commits into
mainfrom
dev

Conversation

@TatevikGr
Copy link
Copy Markdown
Collaborator

@TatevikGr TatevikGr commented May 18, 2026

Summary by CodeRabbit

  • New Features

    • Cursor-paginated subscribe-pages listing, public subscribe-page view, and public subscription endpoint.
  • Updates

    • Subscribe-pages create/get/update/delete request/response shapes refined (nullable fields, data array, 201 on create, 200 on fetch/update, 204 where applicable, 422 validation errors).
    • Bounces endpoint now returns items with pagination metadata.
    • Subscribe-page data schemas enhanced for structured key/value pairs and public payloads.
  • Chores

    • Updated dependency constraints.

Summary

Provide a general description of the code changes in your pull request …
were there any bugs you had fixed? If so, mention them. If these bugs have open
GitHub issues, be sure to tag them here as well, to keep the conversation
linked together.

Unit test

Are your changes covered with unit tests, and do they not break anything?

You can run the existing unit tests using this command:

vendor/bin/phpunit tests/

Code style

Have you checked that you code is well-documented and follows the PSR-2 coding
style?

You can check for this using this command:

vendor/bin/phpcs --standard=PSR2 src/ tests/

Other Information

If there's anything else that's important and relevant to your pull
request, mention that information here. This could include benchmarks,
or other information.

If you are updating any of the CHANGELOG files or are asked to update the
CHANGELOG files by reviewers, please add the CHANGELOG entry at the top of the file.

Thanks for contributing to phpList!

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 18, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR updates two composer dependencies to dev-dev and revises openapi.json: contact email changed; GET /api/v2/bounces 200 response now returns { items, pagination }; new cursor-paginated GET /api/v2/subscribe-pages and POST/PUT/GET changes for subscribe-pages (nullable fields, data array, status codes, 422 on create); /{id}/data now returns 204; added POST /subscribe-pages/{id}/lists/{listId}/subscribers using PublicSubscriptionRequest; added GET /api/v2/lists/{listId}/public; and added/updated related schemas.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant web_frontend_API as web-frontend API
  participant SubscribeService as SubscribePagesService
  participant DB as Database

  Client->>web_frontend_API: GET /api/v2/subscribe-pages?after_id&limit
  web_frontend_API->>SubscribeService: fetch page items (after_id, limit)
  SubscribeService->>DB: query SubscribePage records
  DB-->>SubscribeService: returns items + pagination
  SubscribeService-->>web_frontend_API: { items: SubscribePage[], pagination }
  web_frontend_API-->>Client: 200 { items, pagination }

  Client->>web_frontend_API: POST /api/v2/subscribe-pages (body with nullable data)
  web_frontend_API->>SubscribeService: validate and create SubscribePage
  SubscribeService->>DB: insert SubscribePage
  DB-->>SubscribeService: new id
  SubscribeService-->>web_frontend_API: 201 Created
  web_frontend_API-->>Client: 201
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The PR title 'dev' is too vague and generic—it doesn't convey what the changeset actually does. While the changes involve dependency updates and API schema modifications, the title provides zero meaningful information. Consider a more descriptive title like 'Update composer dependencies and API schemas' or 'Switch to dev-dev branch and add subscribe pages endpoints' to clearly communicate the primary changes.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
openapi.json (1)

4907-4933: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep this endpoint authenticated or strip the owner payload.

Line 4911 makes php-auth-pw optional, but Lines 4932-4933 still return SubscribePage, and that schema includes owner -> Administrator. That effectively documents unauthenticated access to admin email/login/privilege data. Either keep auth required here or return a reduced public schema instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openapi.json` around lines 4907 - 4933, The OpenAPI entry currently marks the
header "php-auth-pw" as optional while returning the full SubscribePage (which
contains owner -> Administrator), exposing admin data; either make the header
required or change the response to a public schema without owner. Fix by
updating the endpoint that uses path param "id": either set the "php-auth-pw"
header object's "required" to true, or replace the response schema "$ref":
"`#/components/schemas/SubscribePage`" with a new or existing public variant
(e.g., SubscribePagePublic) that omits the owner/Administrator fields and ensure
the components/schemas reflect that change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@composer.json`:
- Around line 50-54: composer.json currently pins phplist/core and
tatevikgr/rest-api-client to the floating alias "dev-dev", which makes installs
non-reproducible; update both entries to an immutable version (a specific tagged
version or a commit reference like "v1.2.3" or "dev-branch#commit-hash") instead
of "dev-dev", then run composer update to regenerate composer.lock and commit
the updated composer.json and the generated composer.lock so installs use exact
resolved package hashes; look for the package keys "phplist/core" and
"tatevikgr/rest-api-client" when making this change.

In `@openapi.json`:
- Around line 4745-4752: The OpenAPI parameter "after_id" documents the cursor
as "starting from 0" but its schema sets "default" and "minimum" to 1; update
the schema to match the description by changing the "default" and "minimum"
values for the "after_id" parameter to 0 so generated clients and validators
accept 0 as a valid starting cursor (or alternatively change the description to
"starting from 1" if you intend the cursor to start at 1).
- Around line 4843-4857: The OpenAPI contract allows the "data" property to be
nullable in write/input schemas but defines SubscribePage.data (and the shared
success response schema used by create/update) as a non-null array, causing a
mismatch; choose one canonical behavior and make schemas consistent: either mark
SubscribePage.data and all response schemas that reference it as "nullable":
true to accept null responses, or change the write/input schemas (the
occurrences at lines noted) to disallow null and require an empty array instead;
update the shared schema(s) (SubscribePage and the success response schema
references) so that all read and write definitions for "data" use the same
nullable setting.

---

Outside diff comments:
In `@openapi.json`:
- Around line 4907-4933: The OpenAPI entry currently marks the header
"php-auth-pw" as optional while returning the full SubscribePage (which contains
owner -> Administrator), exposing admin data; either make the header required or
change the response to a public schema without owner. Fix by updating the
endpoint that uses path param "id": either set the "php-auth-pw" header object's
"required" to true, or replace the response schema "$ref":
"`#/components/schemas/SubscribePage`" with a new or existing public variant
(e.g., SubscribePagePublic) that omits the owner/Administrator fields and ensure
the components/schemas reflect that change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a6d6e538-334f-4921-a0e2-8036874f1d90

📥 Commits

Reviewing files that changed from the base of the PR and between 97717a2 and 8557d0e.

📒 Files selected for processing (2)
  • composer.json
  • openapi.json

Comment thread composer.json
Comment on lines +50 to +54
"phplist/core": "dev-dev",
"symfony/twig-bundle": "^6.4",
"symfony/webpack-encore-bundle": "^2.2",
"symfony/security-bundle": "^6.4",
"tatevikgr/rest-api-client": "dev-main"
"tatevikgr/rest-api-client": "dev-dev"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Check composer.json constraints =="
python - <<'PY'
import json
with open("composer.json") as f:
    data = json.load(f)
req = data.get("require", {})
for name in ("phplist/core", "tatevikgr/rest-api-client"):
    print(f"{name}: {req.get(name)}")
PY

echo
echo "== Verify remote branches exist =="
git ls-remote --heads https://github.com/phpList/core.git dev dev-dev main || true
git ls-remote --heads https://github.com/tatevikgr/phplist-api-client.git dev dev-dev main || true

echo
echo "== Verify lockfile pins commits for these packages (if lockfile exists) =="
if [ -f composer.lock ]; then
  python - <<'PY'
import json
with open("composer.lock") as f:
    lock = json.load(f)
pkgs = {p["name"]: p for p in lock.get("packages", [])}
for name in ("phplist/core", "tatevikgr/rest-api-client"):
    p = pkgs.get(name)
    if not p:
        print(f"{name}: NOT FOUND in composer.lock")
        continue
    print(f"{name}: version={p.get('version')} reference={p.get('source',{}).get('reference')}")
PY
else
  echo "composer.lock not found"
fi

Repository: phpList/web-frontend

Length of output: 521


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== composer.json constraints =="
python - <<'PY'
import json
with open("composer.json") as f:
    data = json.load(f)
req = data.get("require", {})
for name in ("phplist/core", "tatevikgr/rest-api-client"):
    print(f"{name}: {req.get(name)}")
PY

echo
echo "== Check composer.lock exists (reproducibility) =="
if [ -f composer.lock ]; then
  echo "composer.lock: present"
else
  echo "composer.lock: MISSING"
fi

echo
echo "== Verify upstream 'dev' branches exist (dev-dev maps to branch 'dev') =="
git ls-remote --heads https://github.com/phpList/core.git dev || true
git ls-remote --heads https://github.com/tatevikgr/phplist-api-client.git dev || true

Repository: phpList/web-frontend

Length of output: 415


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== composer.json constraints =="
python - <<'PY'
import json
with open("composer.json") as f:
    data = json.load(f)
req = data.get("require", {})
for name in ("phplist/core", "tatevikgr/rest-api-client"):
    print(f"{name}: {req.get(name)}")
PY

echo
echo "== Check composer.lock exists (reproducibility) =="
if [ -f composer.lock ]; then
  echo "composer.lock: present"
else
  echo "composer.lock: MISSING"
fi

echo
echo "== Verify upstream 'dev' branches exist (dev-dev maps to branch 'dev') =="
git ls-remote --heads https://github.com/phpList/core.git dev || true
git ls-remote --heads https://github.com/tatevikgr/phplist-api-client.git dev || true

Repository: phpList/web-frontend

Length of output: 415


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== composer.lock reproducibility check =="
test -f composer.lock && echo "composer.lock: present" || echo "composer.lock: MISSING"

echo
echo "== upstream branch check (dev) for floating dev-dev constraints =="
git ls-remote --heads https://github.com/phpList/core.git dev || true
git ls-remote --heads https://github.com/tatevikgr/phplist-api-client.git dev || true

Repository: phpList/web-frontend

Length of output: 309


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== composer.lock reproducibility check =="
test -f composer.lock && echo "composer.lock: present" || echo "composer.lock: MISSING"

echo
echo "== upstream branch check (dev) for floating dev-dev constraints =="
git ls-remote --heads https://github.com/phpList/core.git dev || true
git ls-remote --heads https://github.com/tatevikgr/phplist-api-client.git dev || true

Repository: phpList/web-frontend

Length of output: 309


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== composer.lock reproducibility check =="
test -f composer.lock && echo "composer.lock: present" || echo "composer.lock: MISSING"

echo
echo "== upstream branch check (dev) for floating dev-dev constraints =="
git ls-remote --heads https://github.com/phpList/core.git dev || true
git ls-remote --heads https://github.com/tatevikgr/phplist-api-client.git dev || true

Repository: phpList/web-frontend

Length of output: 309


🏁 Script executed:

#!/bin/bash
set -euo pipefail

test -f composer.lock && echo "composer.lock: present" || echo "composer.lock: MISSING"

git ls-remote --heads https://github.com/phpList/core.git dev || true
git ls-remote --heads https://github.com/tatevikgr/phplist-api-client.git dev || true

Repository: phpList/web-frontend

Length of output: 199


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "composer.lock:"
test -f composer.lock && echo "present" || echo "MISSING"

echo
echo "upstream dev branches:"
git ls-remote --heads https://github.com/phpList/core.git dev || true
git ls-remote --heads https://github.com/tatevikgr/phplist-api-client.git dev || true

Repository: phpList/web-frontend

Length of output: 223


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "composer.lock:"
test -f composer.lock && echo "present" || echo "MISSING"

echo
echo "upstream dev branches:"
git ls-remote --heads https://github.com/phpList/core.git dev || true
git ls-remote --heads https://github.com/tatevikgr/phplist-api-client.git dev || true

echo
echo "composer.json constraints:"
python - <<'PY'
import json
with open("composer.json") as f:
    data = json.load(f)
req = data.get("require", {})
for name in ("phplist/core", "tatevikgr/rest-api-client"):
    print(f"{name}: {req.get(name)}")
PY

Repository: phpList/web-frontend

Length of output: 308


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "composer.lock:"
test -f composer.lock && echo "present" || echo "MISSING"

echo
echo "composer.json constraints:"
python - <<'PY'
import json
with open("composer.json") as f:
    data = json.load(f)
req = data.get("require", {})
for name in ("phplist/core", "tatevikgr/rest-api-client"):
    print(f"{name}: {req.get(name)}")
PY

echo
echo "upstream dev branches (what dev-dev tracks):"
git ls-remote --heads https://github.com/phpList/core.git dev || true
git ls-remote --heads https://github.com/tatevikgr/phplist-api-client.git dev || true

Repository: phpList/web-frontend

Length of output: 330


Pin phplist/core and tatevikgr/rest-api-client to immutable refs (and commit composer.lock)

composer.json sets both phplist/core and tatevikgr/rest-api-client to dev-dev, which tracks the upstream moving dev branches. With composer.lock missing, installs/deploys won’t be reproducible and can drift.

  • Replace dev-dev with a tag/commit reference (immutable).
  • Generate and commit composer.lock so Composer pins exact commit hashes.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@composer.json` around lines 50 - 54, composer.json currently pins
phplist/core and tatevikgr/rest-api-client to the floating alias "dev-dev",
which makes installs non-reproducible; update both entries to an immutable
version (a specific tagged version or a commit reference like "v1.2.3" or
"dev-branch#commit-hash") instead of "dev-dev", then run composer update to
regenerate composer.lock and commit the updated composer.json and the generated
composer.lock so installs use exact resolved package hashes; look for the
package keys "phplist/core" and "tatevikgr/rest-api-client" when making this
change.

Comment thread openapi.json
Comment on lines +4745 to +4752
"name": "after_id",
"in": "query",
"description": "Last id (starting from 0)",
"required": false,
"schema": {
"type": "integer"
"type": "integer",
"default": 1,
"minimum": 1
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align the after_id docs with the actual constraints.

Line 4747 says the cursor starts from 0, but Lines 4750-4752 set default: 1 and minimum: 1. That makes the documented starting cursor invalid for generated clients and validators.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openapi.json` around lines 4745 - 4752, The OpenAPI parameter "after_id"
documents the cursor as "starting from 0" but its schema sets "default" and
"minimum" to 1; update the schema to match the description by changing the
"default" and "minimum" values for the "after_id" parameter to 0 so generated
clients and validators accept 0 as a valid starting cursor (or alternatively
change the description to "starting from 1" if you intend the cursor to start at
1).

Comment thread openapi.json
Comment on lines +4843 to 4857
"data": {
"type": "array",
"items": {
"properties": {
"key": {
"type": "string"
},
"value": {
"type": "string"
}
},
"type": "object"
},
"nullable": true
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

The data field is nullable on write but non-null on read.

Lines 4843-4857 and 5000-5014 allow data: null, while Lines 8563-8568 define SubscribePage.data as a plain array and all success responses reuse that schema. A successful create/update can therefore violate its own documented response contract unless the backend always normalizes null to [].

💡 One simple contract fix
                     "data": {
                         "type": "array",
                         "items": {
                             "$ref": "`#/components/schemas/SubscribePageData`"
-                        }
+                        },
+                        "nullable": true
                     }

Also applies to: 5000-5014, 8563-8568

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openapi.json` around lines 4843 - 4857, The OpenAPI contract allows the
"data" property to be nullable in write/input schemas but defines
SubscribePage.data (and the shared success response schema used by
create/update) as a non-null array, causing a mismatch; choose one canonical
behavior and make schemas consistent: either mark SubscribePage.data and all
response schemas that reference it as "nullable": true to accept null responses,
or change the write/input schemas (the occurrences at lines noted) to disallow
null and require an empty array instead; update the shared schema(s)
(SubscribePage and the success response schema references) so that all read and
write definitions for "data" use the same nullable setting.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
openapi.json (1)

8625-8633: ⚡ Quick win

Tighten the public data schema.

This turns data into Record<string, string>[], while the rest of the subscribe-page contract uses the shared { key, value } item shape. That makes generated clients lose the field contract and invites drift between the admin and public variants for the same payload. If the public response is still key/value-based, reuse #/components/schemas/SubscribePageData; otherwise extract a dedicated named schema with an example so the new shape is explicit.

♻️ Possible cleanup
                     "data": {
                         "type": "array",
                         "items": {
-                            "type": "object",
-                            "additionalProperties": {
-                                "type": "string"
-                            }
+                            "$ref": "`#/components/schemas/SubscribePageData`"
                         }
                     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openapi.json` around lines 8625 - 8633, The public response's "data" schema
currently defines an array of free-form string maps (Record<string,string>),
which diverges from the shared key/value item shape; update the "data" schema so
it either references the existing components schema
"`#/components/schemas/SubscribePageData`" (to produce SubscribePageData[]), or
replace the anonymous schema with a new named component schema (e.g.,
"PublicSubscribePageData") that matches the expected { key, value } shape and
includes an example; ensure the "items" node uses $ref to that component so
generated clients keep the intended contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@openapi.json`:
- Around line 8625-8633: The public response's "data" schema currently defines
an array of free-form string maps (Record<string,string>), which diverges from
the shared key/value item shape; update the "data" schema so it either
references the existing components schema
"`#/components/schemas/SubscribePageData`" (to produce SubscribePageData[]), or
replace the anonymous schema with a new named component schema (e.g.,
"PublicSubscribePageData") that matches the expected { key, value } shape and
includes an example; ensure the "items" node uses $ref to that component so
generated clients keep the intended contract.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 153888a1-87e1-45fb-8127-97b6c138f417

📥 Commits

Reviewing files that changed from the base of the PR and between 8557d0e and c534d0f.

📒 Files selected for processing (1)
  • openapi.json

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openapi.json`:
- Around line 8402-8425: The PublicSubscriptionRequest schema currently allows
an empty object; update the schema for PublicSubscriptionRequest to include a
required array that at minimum lists "email" (e.g., add required: ["email"]) so
generated clients/validators enforce an email is present; keep existing property
definitions (email, confirmEmail, attributes) and their formats unchanged when
making this change.
- Around line 5191-5200: The public signup endpoint currently returns the
"Subscription" schema (which resolves to "SubscriberOnly" and exposes the
"unique_id" field), enabling anonymous actors to subscribe and immediately
confirm someone else’s address; change the signup response to a contract-safe
representation that does not include "unique_id"—either reference a new schema
(e.g., "SubscriberPublic" or "SubscriptionCreated") containing only
non-sensitive fields, or return a 202/204 with no body. Update the openapi
response for the public signup endpoint to remove the "$ref":
"`#/components/schemas/Subscription`" and point to the safe schema (or empty
response), and adjust or add a new components/schema so "SubscriberOnly" and its
"unique_id" remain private and are not returned by this endpoint; keep the
public confirm endpoint that consumes "unique_id" unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1f538e54-c19a-4e16-a1fd-f87aff17a8d1

📥 Commits

Reviewing files that changed from the base of the PR and between c534d0f and d877d01.

📒 Files selected for processing (1)
  • openapi.json

Comment thread openapi.json
Comment on lines +5191 to 5200
"201": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"data": {
"type": "string",
"nullable": true
}
},
"type": "object"
"type": "array",
"items": {
"$ref": "#/components/schemas/Subscription"
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Don’t return Subscription from this public signup endpoint.

Lines 5191-5200 expose Subscription[], which resolves to SubscriberOnly and includes unique_id at Lines 8897-8938. That same identifier is accepted by the public confirm endpoint at Lines 6484-6501, so an anonymous caller can subscribe someone else’s address here and immediately confirm it without mailbox access.

🔒 Contract-safe direction
-                    "201": {
-                        "description": "Success",
-                        "content": {
-                            "application/json": {
-                                "schema": {
-                                    "type": "array",
-                                    "items": {
-                                        "$ref": "`#/components/schemas/Subscription`"
-                                    }
-                                }
-                            }
-                        }
-                    },
+                    "202": {
+                        "description": "Accepted",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "type": "object",
+                                    "properties": {
+                                        "message": {
+                                            "type": "string",
+                                            "example": "If the address can be subscribed, a confirmation email has been sent."
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openapi.json` around lines 5191 - 5200, The public signup endpoint currently
returns the "Subscription" schema (which resolves to "SubscriberOnly" and
exposes the "unique_id" field), enabling anonymous actors to subscribe and
immediately confirm someone else’s address; change the signup response to a
contract-safe representation that does not include "unique_id"—either reference
a new schema (e.g., "SubscriberPublic" or "SubscriptionCreated") containing only
non-sensitive fields, or return a 202/204 with no body. Update the openapi
response for the public signup endpoint to remove the "$ref":
"`#/components/schemas/Subscription`" and point to the safe schema (or empty
response), and adjust or add a new components/schema so "SubscriberOnly" and its
"unique_id" remain private and are not returned by this endpoint; keep the
public confirm endpoint that consumes "unique_id" unchanged.

Comment thread openapi.json
Comment on lines +8402 to +8425
"PublicSubscriptionRequest": {
"properties": {
"email": {
"type": "string",
"format": "email",
"example": "lia@example.com"
},
"confirmEmail": {
"type": "string",
"format": "email",
"example": "lia@example.com"
},
"attributes": {
"type": "object",
"example": {
"firstname": "John",
"lastname": "Grigoryan",
"country": "Armenia"
},
"additionalProperties": true
}
},
"type": "object"
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Mark email as required in PublicSubscriptionRequest.

Right now this schema accepts {} because the body is required but none of its fields are. Generated clients and validators will miss the most basic contract check for the public subscribe flow.

🩹 Minimal schema fix
             "PublicSubscriptionRequest": {
+                "required": [
+                    "email"
+                ],
                 "properties": {
                     "email": {
                         "type": "string",
                         "format": "email",
                         "example": "lia@example.com"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openapi.json` around lines 8402 - 8425, The PublicSubscriptionRequest schema
currently allows an empty object; update the schema for
PublicSubscriptionRequest to include a required array that at minimum lists
"email" (e.g., add required: ["email"]) so generated clients/validators enforce
an email is present; keep existing property definitions (email, confirmEmail,
attributes) and their formats unchanged when making this change.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
openapi.json (1)

1391-1405: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Align the pagination field names with the bounces consumer.

This response now pulls in CursorPagination, but that schema documents has_more / next_cursor while assets/vue/components/bounces/BounceOverview.vue:212-239 reads pagination.hasMore / pagination.nextCursor. That leaves the updated /api/v2/bounces contract out of sync with the in-repo consumer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openapi.json` around lines 1391 - 1405, The API contract uses
CursorPagination (which currently documents has_more/next_cursor) but the
frontend consumer (BounceOverview.vue referencing pagination.hasMore and
pagination.nextCursor) expects camelCase; update the OpenAPI schema so the
pagination object matches the consumer: change the CursorPagination schema
properties to hasMore and nextCursor (or add equivalent camelCase aliases) and
ensure the /api/v2/bounces response still references
components/schemas/CursorPagination so the returned JSON fields match
pagination.hasMore and pagination.nextCursor used in BounceOverview.vue.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@openapi.json`:
- Around line 1391-1405: The API contract uses CursorPagination (which currently
documents has_more/next_cursor) but the frontend consumer (BounceOverview.vue
referencing pagination.hasMore and pagination.nextCursor) expects camelCase;
update the OpenAPI schema so the pagination object matches the consumer: change
the CursorPagination schema properties to hasMore and nextCursor (or add
equivalent camelCase aliases) and ensure the /api/v2/bounces response still
references components/schemas/CursorPagination so the returned JSON fields match
pagination.hasMore and pagination.nextCursor used in BounceOverview.vue.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 843abab5-311f-4485-9c22-7cf936093020

📥 Commits

Reviewing files that changed from the base of the PR and between d877d01 and 50a4960.

📒 Files selected for processing (1)
  • openapi.json

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants