Feedback Flow is an application designed to help product testers manage their testing activities and feedback.
This application is specifically built for individuals who participate in product testing programs (such as "Amazon's Testers Club"), where sellers offer free or discounted products in exchange for honest reviews. The management of the products offered by sellers is outside the scope of this application.
If you find this tool useful, please consider giving it a star! 🤩
This application is designed for:
- Product testers who regularly participate in testing programs
- Individuals who need to track multiple product testing assignments
- Users who want to maintain organized records of their purchases, feedback, and refunds
- Testers who need to provide evidence of completed reviews to receive refunds
The application is not intended for sellers or product managers. It is specifically tailored for testers who need to manage their testing activities and provide proof of their work.
The application allows testers to:
- Track their product purchases
- Document their feedback
- Provide proof of published reviews
- Monitor refund status
In case of any dispute with a seller, the tester can use this application to prove they have purchased the product, provided their opinion, and that this opinion has been published on the required platform.
The application is based on the Vite, Auth0 & HeroUI Template, which is a starter template for a React 19 application with Auth0 and HeroUI 2.7, along with a backend running on a Worker C.
The application is deployed on a CDN as a static SPA (Single Page Application). It communicates with a REST API deployed on a free Cloudflare worker.
- A product tester is someone who tests a product and provides feedback on it.
- A product is an item or service that is tested by a tester.
- The seller is the person who sells the product.
- Feedback is the opinion given by the tester about the product.

The standard product testing process managed by this application is:
- A seller offers a product to a tester (usually at a discount or for free)
- The tester purchases the product
- The tester provides the seller with a screenshot as proof of purchase
- The tester tests the product
- The tester writes and submits their honest opinion on the product
- The feedback is published on the specified platform (e.g., Amazon)
- The tester provides the seller with a screenshot of the published feedback
- The seller refunds the tester (fully or partially)
There may be a delay between the purchase and the refund. Additionally, the refund amount may differ from the purchase amount, which is why the application tracks both values separately.
- Tester's name
- Tester's ID (a single tester can have multiple IDs)
- Purchase date
- Order number
- Purchase amount
- Screenshot of the proof of purchase
- Date feedback was submitted
- Date feedback was published
- Screenshot of the published feedback
- Date feedback was sent to the seller
- Refund date
- Refund amount
permission | description |
---|---|
admin:api | Administer the API |
read:api | Read one's own feedback data |
write:api | Write one's own feedback data |
backup:api | Manage the database |
- Add a tester (admin:api)
- Add an ID to a tester (admin:api)
- Add a product to test (write:api)
- Add a purchase (write:api)
- Add feedback (write:api)
- Publish feedback (write:api)
- Send feedback to the seller (write:api)
- Record a refund (write:api)
- View non-refunded feedbacks (read:api)
- View refunded feedbacks (read:api)
Authentication is handled by Auth0. The system is provided by the template. It is an OAuth 2.0 process that runs in the browser. The browser receives a JWT token, which it sends to the API. The API verifies the token and grants or denies access to the resource based on the permissions included in the token.
A Swagger ui is automatically generated for the API. It is available at /docs
. The API is secured with Auth0. The API uses the same authentication process as the application. The API requires a JWT token to be sent in the Authorization
header of each request. The token must be prefixed with Bearer
.
For easily adding the token, simply click on your name in the footer of the application. A modal will open with the token. Copy the token with the supplied button and paste it in the Authorization
field in Swagger. The token is valid for 24 hours. The API uses the same permissions as the application. The API uses the same database as the application.
The REST API exchanges all objects in JSON format. The API provides the following endpoints:
-
GET
/api/testers
- Retrieve all testers with pagination - requires admin:api permission- Optional parameters:
?page=1&limit=10&sort=name&order=asc
- Response:
{success: boolean, data: [{uuid: string, name: string, ids: string[]}], total: number, page: number, limit: number}
- Optional parameters:
-
POST
/api/tester
- Add a tester to the database (their ID is automatically generated with a UUID) - requires admin:api permission- Request:
{name: string, ids: string[]|string}
- Response:
{success: boolean, uuid: string}
- Request:
-
POST
/api/tester/ids
- Add an ID to the authenticated tester - requires admin:api permission- Request:
{name: string, id: string}
- Response:
{success: boolean, name: string, ids: [string]}
- Request:
-
GET
/api/tester
- Get information about the authenticated tester - requires admin:api permission- Response:
{success: boolean, data: {uuid: string, name: string, ids: [string]}}
- Response:
-
POST
/api/purchase
- Add a purchase to the database - requires write:api permission- Request:
{date: string, order: string, description: string, amount: number, screenshot: string}
- Response:
{success: boolean, id: string}
- Request:
-
GET
/api/purchase/:id
- Get information about a specific purchase - requires read:api permission- Response:
{success: boolean, data: {id: string, date: string, order: string, description: string, amount: number, screenshot: string}}
- Response:
-
DELETE
/api/purchase/:purchaseId
- Delete a purchase by ID - requires write:api permission- Response:
{success: boolean, message: string}
- Response:
-
GET
/api/purchase
- Get a list of the authenticated tester's purchases - requires read:api permission- Optional parameters:
?page=1&limit=10&sort=date&order=desc
- Response:
{success: boolean, data: [{id: string, date: string, order: string, description: string, amount: number}], total: number, page: number, limit: number}
- Optional parameters:
-
GET
/api/purchases/not-refunded
- Get a list of the authenticated tester's not-refunded purchases - requires read:api permission- Optional parameters:
?page=1&limit=10&sort=date&order=desc
- Response:
{success: boolean, data: [{id: string, date: string, order: string, description: string, amount: number}], total: number, page: number, limit: number}
- Optional parameters:
-
GET
/api/purchases/refunded
- Get a list of the authenticated tester's refunded purchases - requires read:api permission- Optional parameters:
?page=1&limit=10&sort=date&order=desc
- Response:
{success: boolean, data: [{id: string, date: string, order: string, description: string, amount: number}], total: number, page: number, limit: number}
- Optional parameters:
-
GET
/api/purchases/ready-to-refund
- Get a list of purchases ready for refund (with feedback and publication) - requires read:api permission- Optional parameters:
?page=1&limit=10&sort=date&order=desc
- Response:
{success: boolean, data: [{id: string, date: string, order: string, description: string, amount: number, feedback: string, feedbackDate: string, publicationDate: string, publicationScreenShot: string}], total: number, page: number, limit: number}
- Optional parameters:
-
GET
/api/purchase-status
- Get the status of all purchases with feedback/publication/refund status - requires read:api permission- Optional parameters:
?page=1&limit=10&sort=date&order=desc&limitToNotRefunded=false
- Response:
{success: boolean, data: [{id: string, date: string, order: string, description: string, amount: number, refunded: boolean, has_feedback: boolean, has_publication: boolean, has_refund: boolean}], total: number, page: number, limit: number}
- Optional parameters:
-
GET
/api/purchases/refunded-amount
- Get total amount of refunded purchases - requires read:api permission- Response:
{success: boolean, amount: number}
- Response:
-
GET
/api/purchases/not-refunded-amount
- Get total amount of non-refunded purchases - requires read:api permission- Response:
{success: boolean, amount: number}
- Response:
-
POST
/api/feedback
- Add feedback to the database - requires write:api permission- Request:
{date: string, purchase: string, feedback: string}
- Response:
{success: boolean, id: string}
- Request:
-
GET
/api/feedback/:id
- Get information about specific feedback - requires read:api permission- Response:
{success: boolean, data: {date: string, purchase: string, feedback: string}}
- Response:
-
POST
/api/publish
- Record the publication of feedback - requires write:api permission- Request:
{date: string, purchase: string, screenshot: string}
- Response:
{success: boolean, id: string}
- Request:
-
GET
/api/publish/:id
- Get information about a specific publication - requires read:api permission- Response:
{success: boolean, data: {date: string, purchase: string, screenshot: string}}
- Response:
-
POST
/api/refund
- Record a refund - requires write:api permission- Request:
{date: string, purchase: string, refundDate: string, amount: number, transactionId?: string}
- Response:
{success: boolean, id: string}
- Request:
-
GET
/api/refund/:id
- Get information about a specific refund - requires read:api permission- Response:
{success: boolean, data: {date: string, purchase: string, refundDate: string, amount: number, transactionId?: string}}
- Response:
-
GET
/api/stats/refund-balance
- Get balance between purchases and refunds - requires read:api permission- Response:
{success: boolean, purchasedAmount: number, refundedAmount: number, balance: number}
- Response:
-
GET
/api/stats/refund-delay
- Get statistics about refund delays - requires read:api permission- Response:
{success: boolean, data: [{purchaseId: string, purchaseAmount: number, refundAmount: number, delayInDays: number, purchaseDate: string, refundDate: string}], averageDelayInDays: number}
- Response:
-
GET
/api/stats/purchases
- Get purchase statistics overview - requires read:api permission- Response:
{success: boolean, data: {totalPurchases: number, totalRefundedPurchases: number, totalRefundedAmount: number}}
- Response:
-
GET
/api/backup/json
- Backup the database - requires backup:api permission- Response:
{success: boolean, data: {backup: string}}
- Response:
-
POST
/api/backup/json
- Restore the database - requires backup:api permission- Request:
{backup: string}
- Response:
{success: boolean}
- Request:
-
GET
/api/__d1/schema
- Get database table names - requires admin:api permission- Response:
{tables: string[], timestamp: string}
- Response:
-
GET
/api/__d1/schema_version
- Get database schema version - requires admin:api permission- Response:
{version: {version: number, description: string}, timestamp: string}
- Response:
-
GET
/api/__d1/schema_migrations
- Execute database schema migrations - requires admin:api permission- Response:
{migrations: string[], timestamp: string}
- Response:
The application is developed using React 19, Vite, and Tailwind CSS. The backend is developed using Cloudflare Workers and the Cloudflare D1 database.
The repository is structured as a monorepo with the following structure:
Clone the repository and install the dependencies:
git clone https://github.com/sctg-development/feedback-flow.git
cd feedback-flow/cloudflare-worker
npm ci
cd ../client
npm ci
See the Auth0.md file for detailed instructions on how to configure Auth0 for the application.
The application requires the following environment variables to be set in a .env
file in the root of the repository:
AUTH0_CLIENT_ID=your_auth0_client_id
AUTH0_CLIENT_SECRET=your_auth0_client_secret
AUTH0_DOMAIN=your_auth0_domain
AUTH0_SCOPE="openid profile email read:api write:api admin:api backup:api"
AUTH0_AUDIENCE="http://localhost:8787/api"
AUTH0_SUB=your_current_user_token_sub
API_BASE_URL=http://localhost:8787/api
CORS_ORIGIN=http://localhost:5173
READ_PERMISSION=read:api
WRITE_PERMISSION=write:api
ADMIN_PERMISSION=admin:api
BACKUP_PERMISSION=backup:api
CRYPTOKEN=any_random_string_to_encrypt_the_variables_in_the_repo
AMAZON_BASE_URL="https://www.amazon.fr/gp/your-account/order-details?orderID="
DB_BACKEND=memory # or d1
DB_MAX_IMAGE_SIZE=640
# This is the token you get from the application when you log in. It is used to authenticate the user with the API.
# It is not required to be set in the .env file, but it is used in the tests to authenticate the user.
AUTH0_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJnaXRodWJ8MTIzNDU2Nzg5MCIsIm5hbWUiOiJKb2huIERvZSIsImlhdCI6MTUxNjIzOTAyMiwicGVybWlzc2lvbnMiOiJyZWFkOmFwaSB3cml0ZTphcGkgYWRtaW46YXBpIGJhY2t1cDphcGkifQ.m1URdlBbuHa9_e3xN2MEMnkGm3ISbVBAuW7fWgL7fms"
STATISTICS_LIMIT=100 # number of purchases to uses in the statistics
To run the application, you need to start both the client and the server. In the root of the repository, run the following command:
-
Start the development server backend:
cd cloudflare-worker && npm run dev:env
-
Start the development server frontend in another terminal:
cd client && npm run dev:env
-
The application should now be running at http://localhost:5173 and the API at http://localhost:8787.
-
Connect to the application with your browser to http://localhost:5173.
-
Log in to the application with your GitHub account. Copy the token from the application, you can find it by clicking on your name in the appliation footer.
-
Copy the token and paste it in the
AUTH0_TOKEN
variable in the.env
file. -
Restart the Cloudflare Worker.
-
in the
cloudflare-worker
folder, run the following command test the worker and add some data to the TESTER user linked to your GitHub account:cd cloudflare-worker npm test
Main page (user with read-only permissions)
Generate a PDF report for purchases ready to refund
Add a new user (Admin menu, dark mode)