feat: extract cms from mp/cms — initial libreshop/cms
Some checks failed
Build and publish / build (push) Failing after 17s
Some checks failed
Build and publish / build (push) Failing after 17s
Source moved verbatim from mp/cms/ on 2026-04-29; mp was the first concrete adapter consuming the libreshop toolkit. Builds and publishes git.librete.ch/libreshop/cms on every main / v* push via the standard .gitea/workflows/build.yml shared across libreshop components.
This commit is contained in:
8
.dockerignore
Normal file
8
.dockerignore
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.tmp
|
||||||
|
.cache
|
||||||
|
.git
|
||||||
|
build
|
||||||
|
node_modules
|
||||||
|
.env
|
||||||
|
public/uploads/*
|
||||||
|
package-lock.json
|
||||||
16
.editorconfig
Normal file
16
.editorconfig
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[{package.json,*.yml}]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
55
.gitea/workflows/build.yml
Normal file
55
.gitea/workflows/build.yml
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
name: Build and publish
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
tags: ["v*"]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
# Required secrets:
|
||||||
|
# REGISTRY git.librete.ch
|
||||||
|
# REGISTRY_USER libretech-bot
|
||||||
|
# REGISTRY_PASS bot PAT (write:package; bot is in libreshop Owners team)
|
||||||
|
# Required variable:
|
||||||
|
# PUBLISH_ENABLED "true" to actually push (off = build-only on PRs)
|
||||||
|
#
|
||||||
|
# Image: git.librete.ch/libreshop/cms
|
||||||
|
# main pushes → :main + :sha-<short>
|
||||||
|
# tag pushes → :<tag> + :latest
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: git.librete.ch/libretech/runner-image:v1
|
||||||
|
timeout-minutes: 20
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login (only when publishing)
|
||||||
|
if: ${{ vars.PUBLISH_ENABLED == 'true' }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ secrets.REGISTRY }}
|
||||||
|
username: ${{ secrets.REGISTRY_USER }}
|
||||||
|
password: ${{ secrets.REGISTRY_PASS }}
|
||||||
|
|
||||||
|
- id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ${{ secrets.REGISTRY }}/libreshop/cms
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=ref,event=tag
|
||||||
|
type=sha,format=short
|
||||||
|
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/') }}
|
||||||
|
|
||||||
|
- uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: ${{ vars.PUBLISH_ENABLED == 'true' && github.event_name == 'push' }}
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
130
.gitignore
vendored
Normal file
130
.gitignore
vendored
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
############################
|
||||||
|
# OS X
|
||||||
|
############################
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
Icon
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
._*
|
||||||
|
|
||||||
|
|
||||||
|
############################
|
||||||
|
# Linux
|
||||||
|
############################
|
||||||
|
|
||||||
|
*~
|
||||||
|
|
||||||
|
|
||||||
|
############################
|
||||||
|
# Windows
|
||||||
|
############################
|
||||||
|
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
Desktop.ini
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
|
||||||
|
############################
|
||||||
|
# Packages
|
||||||
|
############################
|
||||||
|
|
||||||
|
*.7z
|
||||||
|
*.csv
|
||||||
|
*.dat
|
||||||
|
*.dmg
|
||||||
|
*.gz
|
||||||
|
*.iso
|
||||||
|
*.jar
|
||||||
|
*.rar
|
||||||
|
*.tar
|
||||||
|
*.zip
|
||||||
|
*.com
|
||||||
|
*.class
|
||||||
|
*.dll
|
||||||
|
*.exe
|
||||||
|
*.o
|
||||||
|
*.seed
|
||||||
|
*.so
|
||||||
|
*.swo
|
||||||
|
*.swp
|
||||||
|
*.swn
|
||||||
|
*.swm
|
||||||
|
*.out
|
||||||
|
*.pid
|
||||||
|
|
||||||
|
|
||||||
|
############################
|
||||||
|
# Logs and databases
|
||||||
|
############################
|
||||||
|
|
||||||
|
.tmp
|
||||||
|
*.log
|
||||||
|
*.sql
|
||||||
|
*.sqlite
|
||||||
|
*.sqlite3
|
||||||
|
database/dump.json
|
||||||
|
|
||||||
|
|
||||||
|
############################
|
||||||
|
# Misc.
|
||||||
|
############################
|
||||||
|
|
||||||
|
*#
|
||||||
|
ssl
|
||||||
|
.idea
|
||||||
|
nbproject
|
||||||
|
public/uploads/*
|
||||||
|
!public/uploads/.gitkeep
|
||||||
|
.strapi
|
||||||
|
############################
|
||||||
|
# Node.js
|
||||||
|
############################
|
||||||
|
|
||||||
|
lib-cov
|
||||||
|
lcov.info
|
||||||
|
pids
|
||||||
|
logs
|
||||||
|
results
|
||||||
|
node_modules
|
||||||
|
.node_history
|
||||||
|
|
||||||
|
############################
|
||||||
|
# Tests
|
||||||
|
############################
|
||||||
|
|
||||||
|
coverage
|
||||||
|
|
||||||
|
############################
|
||||||
|
# Strapi
|
||||||
|
############################
|
||||||
|
|
||||||
|
.env*
|
||||||
|
license.txt
|
||||||
|
exports
|
||||||
|
*.cache
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
.strapi-updater.json
|
||||||
|
|
||||||
|
# libreshop additions
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
node_modules/
|
||||||
|
.nuxt/
|
||||||
|
.output/
|
||||||
|
.cache/
|
||||||
|
.parcel-cache/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
coverage/
|
||||||
|
logs/
|
||||||
|
tmp/
|
||||||
9
.prettierrc.json
Normal file
9
.prettierrc.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/prettierrc",
|
||||||
|
"semi": true,
|
||||||
|
"tabWidth": 4,
|
||||||
|
"singleQuote": false,
|
||||||
|
"printWidth": 150,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"useTabs": true
|
||||||
|
}
|
||||||
8
CHANGELOG.md
Normal file
8
CHANGELOG.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to libreshop/cms are documented here.
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
- Extracted from `mp/cms/` (2026-04-29). The component history before
|
||||||
|
the extraction lives in the `muellerprints` repository.
|
||||||
30
Dockerfile
Normal file
30
Dockerfile
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
FROM node:22.14.0-alpine
|
||||||
|
|
||||||
|
RUN apk update && \
|
||||||
|
apk add --no-cache build-base gcc autoconf automake \
|
||||||
|
zlib-dev libpng-dev nasm bash vips-dev
|
||||||
|
|
||||||
|
ARG NODE_ENV
|
||||||
|
ENV NODE_ENV ${NODE_ENV}
|
||||||
|
ENV PORT 5555
|
||||||
|
|
||||||
|
WORKDIR /app/
|
||||||
|
|
||||||
|
COPY ./package.json ./
|
||||||
|
|
||||||
|
ENV PATH=/node_modules/.bin:$PATH
|
||||||
|
|
||||||
|
RUN npm i --no-audit --no-fund --progress=false --no-warnings --log-level=error
|
||||||
|
RUN npm i --ignore-scripts=false --foreground-scripts --verbose sharp
|
||||||
|
|
||||||
|
COPY ./ ./
|
||||||
|
|
||||||
|
RUN chmod +x ./docker-entrypoint.sh
|
||||||
|
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
||||||
|
|
||||||
|
#USER node
|
||||||
|
|
||||||
|
EXPOSE 5555
|
||||||
25
README.md
Normal file
25
README.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# libreshop/cms
|
||||||
|
|
||||||
|
Strapi-based content backend (catalogue, orders, customers).
|
||||||
|
|
||||||
|
Part of the [libreshop](https://git.librete.ch/libreshop) toolkit. Image
|
||||||
|
published at `git.librete.ch/libreshop/cms` on every push to `main`
|
||||||
|
and on `v*` tags.
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
This repo was extracted from `mp/cms/` on 2026-04-29; mp was the
|
||||||
|
first concrete adapter consuming the toolkit. mp's `compose.yml` now
|
||||||
|
pulls `git.librete.ch/libreshop/cms:<pin>` instead of building locally.
|
||||||
|
|
||||||
|
## Build locally
|
||||||
|
|
||||||
|
```
|
||||||
|
docker build -t libreshop/cms:dev .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adapter contract
|
||||||
|
|
||||||
|
See `docker-entrypoint.sh` and `Dockerfile` for the runtime surface.
|
||||||
|
Adapters configure the component via env vars and bind-mounted volumes;
|
||||||
|
do not patch the running container or rely on internal paths.
|
||||||
22
config/admin.ts
Normal file
22
config/admin.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
export default ({ env }) => ({
|
||||||
|
auth: {
|
||||||
|
secret: env("ADMIN_JWT_SECRET")
|
||||||
|
},
|
||||||
|
apiToken: {
|
||||||
|
salt: env("API_TOKEN_SALT")
|
||||||
|
},
|
||||||
|
transfer: {
|
||||||
|
token: {
|
||||||
|
salt: env("TRANSFER_TOKEN_SALT")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
autoOpen: false,
|
||||||
|
// watchIgnoreFiles: [
|
||||||
|
// "**/controllers/**",
|
||||||
|
// "**/database/**",
|
||||||
|
// ],
|
||||||
|
flags: {
|
||||||
|
nps: false,
|
||||||
|
promoteEE: false
|
||||||
|
}
|
||||||
|
});
|
||||||
7
config/api.ts
Normal file
7
config/api.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export default {
|
||||||
|
rest: {
|
||||||
|
defaultLimit: 100,
|
||||||
|
maxLimit: 1_000,
|
||||||
|
withCount: true
|
||||||
|
}
|
||||||
|
};
|
||||||
19
config/constants.ts
Normal file
19
config/constants.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { Environment } from "@paypal/paypal-server-sdk";
|
||||||
|
|
||||||
|
export const pdfApiUrl = process.env.PDF_API_ADDRESS!;
|
||||||
|
export const mailApiUrl = process.env.MAIL_API_ADDRESS!;
|
||||||
|
export const baseUrl = process.env.BASE_URL!;
|
||||||
|
|
||||||
|
export const paypalClientId = process.env.PAYPAL_CLIENT_ID!;
|
||||||
|
export const paypalClientSecret = process.env.PAYPAL_CLIENT_SECRET!;
|
||||||
|
export const paypalEnvironment = process.env.PAYPAL_ENVIRONMENT! === "production" ? Environment.Production : Environment.Sandbox;
|
||||||
|
|
||||||
|
export const adminEmail = process.env.ADMIN_EMAIL_ADDRESS!;
|
||||||
|
|
||||||
|
// TODO: Should be retrieved from DepotApi
|
||||||
|
export const vatIncludedDecimal = 1.19;
|
||||||
|
// TODO: Should be retrieved from DepotApi
|
||||||
|
export const vatDecimal = 0.19;
|
||||||
|
export const vatDecimalExcluded = 1 - vatDecimal;
|
||||||
|
|
||||||
|
export const maxProductsSitemap = 500;
|
||||||
79
config/database.ts
Normal file
79
config/database.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import path from "path";
|
||||||
|
|
||||||
|
export default ({ env }) => {
|
||||||
|
const client = env("DATABASE_CLIENT", "sqlite");
|
||||||
|
|
||||||
|
const connections = {
|
||||||
|
mysql: {
|
||||||
|
connection: {
|
||||||
|
connectionString: env("DATABASE_URL"),
|
||||||
|
host: env("DATABASE_HOST", "localhost"),
|
||||||
|
port: env.int("DATABASE_PORT", 3306),
|
||||||
|
database: env("DATABASE_NAME", "strapi"),
|
||||||
|
user: env("DATABASE_USERNAME", "strapi"),
|
||||||
|
password: env("DATABASE_PASSWORD", "strapi"),
|
||||||
|
ssl: env.bool("DATABASE_SSL", false) && {
|
||||||
|
key: env("DATABASE_SSL_KEY", undefined),
|
||||||
|
cert: env("DATABASE_SSL_CERT", undefined),
|
||||||
|
ca: env("DATABASE_SSL_CA", undefined),
|
||||||
|
capath: env("DATABASE_SSL_CAPATH", undefined),
|
||||||
|
cipher: env("DATABASE_SSL_CIPHER", undefined),
|
||||||
|
rejectUnauthorized: env.bool("DATABASE_SSL_REJECT_UNAUTHORIZED", true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pool: { min: env.int("DATABASE_POOL_MIN", 2), max: env.int("DATABASE_POOL_MAX", 10) }
|
||||||
|
},
|
||||||
|
mysql2: {
|
||||||
|
connection: {
|
||||||
|
host: env("DATABASE_HOST", "localhost"),
|
||||||
|
port: env.int("DATABASE_PORT", 3306),
|
||||||
|
database: env("DATABASE_NAME", "strapi"),
|
||||||
|
user: env("DATABASE_USERNAME", "strapi"),
|
||||||
|
password: env("DATABASE_PASSWORD", "strapi"),
|
||||||
|
ssl: env.bool("DATABASE_SSL", false) && {
|
||||||
|
key: env("DATABASE_SSL_KEY", undefined),
|
||||||
|
cert: env("DATABASE_SSL_CERT", undefined),
|
||||||
|
ca: env("DATABASE_SSL_CA", undefined),
|
||||||
|
capath: env("DATABASE_SSL_CAPATH", undefined),
|
||||||
|
cipher: env("DATABASE_SSL_CIPHER", undefined),
|
||||||
|
rejectUnauthorized: env.bool("DATABASE_SSL_REJECT_UNAUTHORIZED", true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pool: { min: env.int("DATABASE_POOL_MIN", 2), max: env.int("DATABASE_POOL_MAX", 10) }
|
||||||
|
},
|
||||||
|
postgres: {
|
||||||
|
connection: {
|
||||||
|
connectionString: env("DATABASE_URL"),
|
||||||
|
host: env("DATABASE_HOST", "localhost"),
|
||||||
|
port: env.int("DATABASE_PORT", 5432),
|
||||||
|
database: env("DATABASE_NAME", "strapi"),
|
||||||
|
user: env("DATABASE_USERNAME", "strapi"),
|
||||||
|
password: env("DATABASE_PASSWORD", "strapi"),
|
||||||
|
ssl: env.bool("DATABASE_SSL", false) && {
|
||||||
|
key: env("DATABASE_SSL_KEY", undefined),
|
||||||
|
cert: env("DATABASE_SSL_CERT", undefined),
|
||||||
|
ca: env("DATABASE_SSL_CA", undefined),
|
||||||
|
capath: env("DATABASE_SSL_CAPATH", undefined),
|
||||||
|
cipher: env("DATABASE_SSL_CIPHER", undefined),
|
||||||
|
rejectUnauthorized: env.bool("DATABASE_SSL_REJECT_UNAUTHORIZED", true)
|
||||||
|
},
|
||||||
|
schema: env("DATABASE_SCHEMA", "public")
|
||||||
|
},
|
||||||
|
pool: { min: env.int("DATABASE_POOL_MIN", 2), max: env.int("DATABASE_POOL_MAX", 10) }
|
||||||
|
},
|
||||||
|
sqlite: {
|
||||||
|
connection: {
|
||||||
|
filename: path.join(__dirname, "..", "..", env("DATABASE_FILENAME", ".tmp/data.db"))
|
||||||
|
},
|
||||||
|
useNullAsDefault: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
connection: {
|
||||||
|
client,
|
||||||
|
...connections[client],
|
||||||
|
acquireConnectionTimeout: env.int("DATABASE_CONNECTION_TIMEOUT", 60000)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
16
config/logger.ts
Normal file
16
config/logger.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { winston } from "@strapi/logger";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
transports: [
|
||||||
|
new winston.transports.Console({
|
||||||
|
format: winston.format.combine(
|
||||||
|
winston.format.printf(({ timestamp, level, message, ...rest }) => {
|
||||||
|
let restString = JSON.stringify(rest, undefined, 2);
|
||||||
|
restString = restString === "{}" ? "" : restString;
|
||||||
|
|
||||||
|
return `${timestamp}Z ${level}: ${message} ${restString}`;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
]
|
||||||
|
};
|
||||||
12
config/middlewares.ts
Normal file
12
config/middlewares.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export default [
|
||||||
|
"strapi::errors",
|
||||||
|
"strapi::security",
|
||||||
|
"strapi::cors",
|
||||||
|
// 'strapi::poweredBy',
|
||||||
|
"strapi::logger",
|
||||||
|
"strapi::query",
|
||||||
|
"strapi::body",
|
||||||
|
"strapi::session",
|
||||||
|
"strapi::favicon",
|
||||||
|
"strapi::public"
|
||||||
|
];
|
||||||
749
config/plugins.ts
Normal file
749
config/plugins.ts
Normal file
@@ -0,0 +1,749 @@
|
|||||||
|
export default ({ env }) => ({
|
||||||
|
upload: {
|
||||||
|
config: {
|
||||||
|
sizeLimit: 50 * 1024 * 1024
|
||||||
|
}
|
||||||
|
},
|
||||||
|
documentation: {
|
||||||
|
enabled: false,
|
||||||
|
config: {
|
||||||
|
openapi: "3.0.1",
|
||||||
|
info: {
|
||||||
|
version: "1.0.0",
|
||||||
|
title: "MUELLERPTINTS. Paperwork",
|
||||||
|
description: "API Documentation for MUELLERPRINTS. Paperwork",
|
||||||
|
termsOfService: false,
|
||||||
|
contact: {
|
||||||
|
name: "Michael W. Czechowski",
|
||||||
|
email: "mail@dailysh.it",
|
||||||
|
url: "https://dailysh.it"
|
||||||
|
},
|
||||||
|
license: "Copyright (C) 2024 Michael W. Czechowski",
|
||||||
|
externalDocs: false
|
||||||
|
},
|
||||||
|
"x-strapi-config": {
|
||||||
|
plugins: [],
|
||||||
|
path: "/documentation",
|
||||||
|
mutateDocumentation: (draft: any) => {
|
||||||
|
// Order endpoints - maintain existing modifications
|
||||||
|
// draft.paths["/orders/{uuid}/cart"].get.parameters[0].schema.type = "string";
|
||||||
|
// draft.paths["/orders/{uuid}/cart"].put.parameters[0].schema.type = "string";
|
||||||
|
// draft.paths["/orders/{uuid}/generate-delivery-note"].put.parameters[0].schema.type = "string";
|
||||||
|
// draft.paths["/orders/{uuid}/generate-invoice"].put.parameters[0].schema.type = "string";
|
||||||
|
// draft.paths["/orders/{uuid}/add-product/{productId}"].put.parameters[0].schema.type = "string";
|
||||||
|
// draft.paths["/orders/{uuid}/remove-product/{productId}"].put.parameters[0].schema.type = "string";
|
||||||
|
// draft.paths["/orders/{uuid}/checkout"].post.parameters[0].schema.type = "string";
|
||||||
|
// draft.paths["/orders/{uuid}/send-invoice"].put.parameters[0].schema.type = "string";
|
||||||
|
// draft.paths["/orders/{uuid}/send-delivery-note"].put.parameters[0].schema.type = "string";
|
||||||
|
//
|
||||||
|
// // Order request body modifications - maintain existing
|
||||||
|
// delete draft.paths["/orders/{uuid}/cart"].put.requestBody;
|
||||||
|
// delete draft.paths["/orders/{uuid}/add-product/{productId}"].put.requestBody;
|
||||||
|
// delete draft.paths["/orders/{uuid}/remove-product/{productId}"].put.requestBody;
|
||||||
|
// delete draft.paths["/orders/{uuid}/checkout"].post.requestBody;
|
||||||
|
//
|
||||||
|
// // Product publish endpoint - existing documentation
|
||||||
|
// draft.paths["/products/publish"].post.description = "Publish or unpublish products by filter";
|
||||||
|
// draft.paths["/products/publish"].post.requestBody = {
|
||||||
|
// required: true,
|
||||||
|
// content: {
|
||||||
|
// "application/json": {
|
||||||
|
// schema: {
|
||||||
|
// type: "object",
|
||||||
|
// required: ["filters"],
|
||||||
|
// properties: {
|
||||||
|
// filters: {
|
||||||
|
// type: "object",
|
||||||
|
// description: "Filters to select products (supports pattern, cover, ruling, pages)"
|
||||||
|
// },
|
||||||
|
// publish: {
|
||||||
|
// type: "boolean",
|
||||||
|
// description: "Whether to publish (true) or unpublish (false)",
|
||||||
|
// default: true
|
||||||
|
// },
|
||||||
|
// dryRun: {
|
||||||
|
// type: "boolean",
|
||||||
|
// description: "If true, no changes will be made but count will be returned",
|
||||||
|
// default: false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// draft.paths["/products/publish"].post.responses = {
|
||||||
|
// "200": {
|
||||||
|
// description: "Successfully published/unpublished products",
|
||||||
|
// content: {
|
||||||
|
// "application/json": {
|
||||||
|
// schema: {
|
||||||
|
// type: "object",
|
||||||
|
// properties: {
|
||||||
|
// published: {
|
||||||
|
// type: "integer",
|
||||||
|
// description: "Number of products published/unpublished"
|
||||||
|
// },
|
||||||
|
// dryRun: {
|
||||||
|
// type: "boolean",
|
||||||
|
// description: "Whether this was a dry run"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// "400": {
|
||||||
|
// description: "Bad request",
|
||||||
|
// content: {
|
||||||
|
// "application/json": {
|
||||||
|
// schema: {
|
||||||
|
// type: "object",
|
||||||
|
// properties: {
|
||||||
|
// error: {
|
||||||
|
// type: "string",
|
||||||
|
// description: "Error message"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// // Product allVariants endpoint
|
||||||
|
// if (draft.paths["/products/{id}/variants/all"]) {
|
||||||
|
// draft.paths["/products/{id}/variants/all"].get.description = "Get all variants for a product";
|
||||||
|
// draft.paths["/products/{id}/variants/all"].get.responses = {
|
||||||
|
// "200": {
|
||||||
|
// description: "Returns all variants of the product",
|
||||||
|
// content: {
|
||||||
|
// "application/json": {
|
||||||
|
// schema: {
|
||||||
|
// type: "array",
|
||||||
|
// items: {
|
||||||
|
// $ref: "#/components/schemas/Product"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// "404": {
|
||||||
|
// description: "Product not found",
|
||||||
|
// content: {
|
||||||
|
// "application/json": {
|
||||||
|
// schema: {
|
||||||
|
// type: "object",
|
||||||
|
// properties: {
|
||||||
|
// error: {
|
||||||
|
// type: "string",
|
||||||
|
// description: "Error message"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Product variantsByPattern endpoint
|
||||||
|
// if (draft.paths["/products/{id}/variants/pattern"]) {
|
||||||
|
// draft.paths["/products/{id}/variants/pattern"].get.description = "Get product variants grouped by pattern";
|
||||||
|
// draft.paths["/products/{id}/variants/pattern"].get.responses = {
|
||||||
|
// "200": {
|
||||||
|
// description: "Returns product variants grouped by pattern",
|
||||||
|
// content: {
|
||||||
|
// "application/json": {
|
||||||
|
// schema: {
|
||||||
|
// type: "object",
|
||||||
|
// properties: {
|
||||||
|
// allProductPattern: {
|
||||||
|
// type: "array",
|
||||||
|
// items: {
|
||||||
|
// $ref: "#/components/schemas/ProductPattern"
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// productVariants: {
|
||||||
|
// type: "array",
|
||||||
|
// items: {
|
||||||
|
// $ref: "#/components/schemas/Product"
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// patterns: {
|
||||||
|
// type: "array",
|
||||||
|
// items: {
|
||||||
|
// type: "object",
|
||||||
|
// properties: {
|
||||||
|
// id: {
|
||||||
|
// type: "string"
|
||||||
|
// },
|
||||||
|
// name: {
|
||||||
|
// type: "string"
|
||||||
|
// },
|
||||||
|
// description: {
|
||||||
|
// type: "string"
|
||||||
|
// },
|
||||||
|
// productVariant: {
|
||||||
|
// oneOf: [
|
||||||
|
// {
|
||||||
|
// $ref: "#/components/schemas/Product"
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// type: "null"
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// "400": {
|
||||||
|
// description: "Bad request",
|
||||||
|
// content: {
|
||||||
|
// "application/json": {
|
||||||
|
// schema: {
|
||||||
|
// type: "object",
|
||||||
|
// properties: {
|
||||||
|
// error: {
|
||||||
|
// type: "string",
|
||||||
|
// description: "Error message"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Product variants endpoint
|
||||||
|
// if (draft.paths["/products/{id}/variants"]) {
|
||||||
|
// draft.paths["/products/{id}/variants"].get.description = "Get product variants grouped by pages, cover, and ruling";
|
||||||
|
// draft.paths["/products/{id}/variants"].get.responses = {
|
||||||
|
// "200": {
|
||||||
|
// description: "Returns product variants grouped by pages, cover, and ruling",
|
||||||
|
// content: {
|
||||||
|
// "application/json": {
|
||||||
|
// schema: {
|
||||||
|
// type: "object",
|
||||||
|
// properties: {
|
||||||
|
// pages: {
|
||||||
|
// type: "array",
|
||||||
|
// items: {
|
||||||
|
// type: "object",
|
||||||
|
// properties: {
|
||||||
|
// id: {
|
||||||
|
// type: "string"
|
||||||
|
// },
|
||||||
|
// name: {
|
||||||
|
// type: "string"
|
||||||
|
// },
|
||||||
|
// productVariant: {
|
||||||
|
// oneOf: [
|
||||||
|
// {
|
||||||
|
// $ref: "#/components/schemas/Product"
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// type: "null"
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// cover: {
|
||||||
|
// type: "array",
|
||||||
|
// items: {
|
||||||
|
// type: "object",
|
||||||
|
// properties: {
|
||||||
|
// id: {
|
||||||
|
// type: "string"
|
||||||
|
// },
|
||||||
|
// name: {
|
||||||
|
// type: "string"
|
||||||
|
// },
|
||||||
|
// binding: {
|
||||||
|
// type: "string"
|
||||||
|
// },
|
||||||
|
// price: {
|
||||||
|
// type: "number"
|
||||||
|
// },
|
||||||
|
// productVariant: {
|
||||||
|
// oneOf: [
|
||||||
|
// {
|
||||||
|
// $ref: "#/components/schemas/Product"
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// type: "null"
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// ruling: {
|
||||||
|
// type: "array",
|
||||||
|
// items: {
|
||||||
|
// type: "object",
|
||||||
|
// properties: {
|
||||||
|
// id: {
|
||||||
|
// type: "string"
|
||||||
|
// },
|
||||||
|
// name: {
|
||||||
|
// type: "string"
|
||||||
|
// },
|
||||||
|
// productVariant: {
|
||||||
|
// oneOf: [
|
||||||
|
// {
|
||||||
|
// $ref: "#/components/schemas/Product"
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// type: "null"
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// "400": {
|
||||||
|
// description: "Bad request",
|
||||||
|
// content: {
|
||||||
|
// "application/json": {
|
||||||
|
// schema: {
|
||||||
|
// type: "object",
|
||||||
|
// properties: {
|
||||||
|
// error: {
|
||||||
|
// type: "string",
|
||||||
|
// description: "Error message"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Product Category endpoints
|
||||||
|
// if (draft.paths["/product-category"]) {
|
||||||
|
// draft.paths["/product-category"].get.description = "Get all product categories";
|
||||||
|
// draft.paths["/product-category"].get.responses = {
|
||||||
|
// "200": {
|
||||||
|
// description: "Returns all product categories",
|
||||||
|
// content: {
|
||||||
|
// "application/json": {
|
||||||
|
// schema: {
|
||||||
|
// type: "array",
|
||||||
|
// items: {
|
||||||
|
// $ref: "#/components/schemas/ProductCategory"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// "400": {
|
||||||
|
// description: "Bad request"
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (draft.paths["/product-category/corrupt"]) {
|
||||||
|
// draft.paths["/product-category/corrupt"].get.description = "Get all corrupted product categories";
|
||||||
|
// draft.paths["/product-category/corrupt"].get.responses = {
|
||||||
|
// "200": {
|
||||||
|
// description: "Returns all corrupted product categories",
|
||||||
|
// content: {
|
||||||
|
// "application/json": {
|
||||||
|
// schema: {
|
||||||
|
// type: "array",
|
||||||
|
// items: {
|
||||||
|
// $ref: "#/components/schemas/ProductCategory"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// "400": {
|
||||||
|
// description: "Bad request"
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (draft.paths["/product-category/{id}"]) {
|
||||||
|
// draft.paths["/product-category/{id}"].get.description = "Get a single product category";
|
||||||
|
// draft.paths["/product-category/{id}"].get.responses = {
|
||||||
|
// "200": {
|
||||||
|
// description: "Returns a single product category",
|
||||||
|
// content: {
|
||||||
|
// "application/json": {
|
||||||
|
// schema: {
|
||||||
|
// $ref: "#/components/schemas/ProductCategory"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// "400": {
|
||||||
|
// description: "Bad request"
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// draft.paths["/product-category/{id}"].put.description = "Update a product category";
|
||||||
|
// draft.paths["/product-category/{id}"].put.requestBody = {
|
||||||
|
// required: true,
|
||||||
|
// content: {
|
||||||
|
// "application/json": {
|
||||||
|
// schema: {
|
||||||
|
// type: "object",
|
||||||
|
// description: "The data to update the category with"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// draft.paths["/product-category/{id}"].put.responses = {
|
||||||
|
// "200": {
|
||||||
|
// description: "Returns the updated product category",
|
||||||
|
// content: {
|
||||||
|
// "application/json": {
|
||||||
|
// schema: {
|
||||||
|
// $ref: "#/components/schemas/ProductCategory"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// "400": {
|
||||||
|
// description: "Bad request"
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Update endpoint to match the actual route from product-category.ts
|
||||||
|
// if (draft.paths["/product-category/bulk"]) {
|
||||||
|
// draft.paths["/product-category/bulk"].put.description = "Update multiple product categories";
|
||||||
|
// draft.paths["/product-category/bulk"].put.requestBody = {
|
||||||
|
// required: true,
|
||||||
|
// content: {
|
||||||
|
// "application/json": {
|
||||||
|
// schema: {
|
||||||
|
// type: "object",
|
||||||
|
// properties: {
|
||||||
|
// categories: {
|
||||||
|
// type: "array",
|
||||||
|
// description: "Array of categories to update"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// draft.paths["/product-category/bulk"].put.responses = {
|
||||||
|
// "200": {
|
||||||
|
// description: "Returns the updated product categories",
|
||||||
|
// content: {
|
||||||
|
// "application/json": {
|
||||||
|
// schema: {
|
||||||
|
// type: "array",
|
||||||
|
// items: {
|
||||||
|
// $ref: "#/components/schemas/ProductCategory"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// "400": {
|
||||||
|
// description: "Bad request"
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
// // Remove the incorrect "/product-category/update" documentation since it doesn't exist in routes
|
||||||
|
//
|
||||||
|
// if (draft.paths["/product-category/fix"]) {
|
||||||
|
// draft.paths["/product-category/fix"].put.description = "Fix categories with missing cover or pattern";
|
||||||
|
// draft.paths["/product-category/fix"].put.responses = {
|
||||||
|
// "200": {
|
||||||
|
// description: "Returns information about fixed and failed categories",
|
||||||
|
// content: {
|
||||||
|
// "application/json": {
|
||||||
|
// schema: {
|
||||||
|
// type: "object",
|
||||||
|
// properties: {
|
||||||
|
// fixed: {
|
||||||
|
// type: "array",
|
||||||
|
// items: {
|
||||||
|
// $ref: "#/components/schemas/ProductCategory"
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// failed: {
|
||||||
|
// type: "array",
|
||||||
|
// items: {
|
||||||
|
// $ref: "#/components/schemas/ProductCategory"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// "400": {
|
||||||
|
// description: "Bad request"
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (draft.paths["/product-category/fix-images"]) {
|
||||||
|
// draft.paths["/product-category/fix-images"].put.description = "Fix product images in categories";
|
||||||
|
// draft.paths["/product-category/fix-images"].put.responses = {
|
||||||
|
// "200": {
|
||||||
|
// description: "Returns information about fixed and failed product images",
|
||||||
|
// content: {
|
||||||
|
// "application/json": {
|
||||||
|
// schema: {
|
||||||
|
// type: "object",
|
||||||
|
// properties: {
|
||||||
|
// fixed: {
|
||||||
|
// type: "array",
|
||||||
|
// items: {
|
||||||
|
// type: "object",
|
||||||
|
// properties: {
|
||||||
|
// categoryId: {
|
||||||
|
// type: "string"
|
||||||
|
// },
|
||||||
|
// productCount: {
|
||||||
|
// type: "integer"
|
||||||
|
// },
|
||||||
|
// products: {
|
||||||
|
// type: "array",
|
||||||
|
// items: {
|
||||||
|
// type: "string"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// failed: {
|
||||||
|
// type: "array",
|
||||||
|
// items: {
|
||||||
|
// type: "object",
|
||||||
|
// properties: {
|
||||||
|
// categoryId: {
|
||||||
|
// type: "string"
|
||||||
|
// },
|
||||||
|
// reason: {
|
||||||
|
// type: "string"
|
||||||
|
// },
|
||||||
|
// error: {
|
||||||
|
// type: "string"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// "400": {
|
||||||
|
// description: "Bad request"
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Content controller endpoints
|
||||||
|
// if (draft.paths["/content"]) {
|
||||||
|
// draft.paths["/content"].get.description = "Get all content entries";
|
||||||
|
// draft.paths["/content"].get.responses = {
|
||||||
|
// "200": {
|
||||||
|
// description: "Returns all content entries",
|
||||||
|
// content: {
|
||||||
|
// "application/json": {
|
||||||
|
// schema: {
|
||||||
|
// type: "array",
|
||||||
|
// items: {
|
||||||
|
// $ref: "#/components/schemas/Content"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Customer controller endpoints
|
||||||
|
// if (draft.paths["/customers"]) {
|
||||||
|
// draft.paths["/customers"].get.description = "Get all customers";
|
||||||
|
// draft.paths["/customers"].get.responses = {
|
||||||
|
// "200": {
|
||||||
|
// description: "Returns all customers",
|
||||||
|
// content: {
|
||||||
|
// "application/json": {
|
||||||
|
// schema: {
|
||||||
|
// type: "array",
|
||||||
|
// items: {
|
||||||
|
// $ref: "#/components/schemas/Customer"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Legal controller endpoints
|
||||||
|
// if (draft.paths["/legal"]) {
|
||||||
|
// draft.paths["/legal"].get.description = "Get all legal documents";
|
||||||
|
// draft.paths["/legal"].get.responses = {
|
||||||
|
// "200": {
|
||||||
|
// description: "Returns all legal documents",
|
||||||
|
// content: {
|
||||||
|
// "application/json": {
|
||||||
|
// schema: {
|
||||||
|
// type: "array",
|
||||||
|
// items: {
|
||||||
|
// $ref: "#/components/schemas/Legal"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Delivery controller endpoints
|
||||||
|
// if (draft.paths["/deliveries"]) {
|
||||||
|
// draft.paths["/deliveries"].get.description = "Get all deliveries";
|
||||||
|
// draft.paths["/deliveries"].get.responses = {
|
||||||
|
// "200": {
|
||||||
|
// description: "Returns all deliveries",
|
||||||
|
// content: {
|
||||||
|
// "application/json": {
|
||||||
|
// schema: {
|
||||||
|
// type: "array",
|
||||||
|
// items: {
|
||||||
|
// $ref: "#/components/schemas/Delivery"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Payment controller endpoints
|
||||||
|
// if (draft.paths["/payments"]) {
|
||||||
|
// draft.paths["/payments"].get.description = "Get all payments";
|
||||||
|
// draft.paths["/payments"].get.responses = {
|
||||||
|
// "200": {
|
||||||
|
// description: "Returns all payments",
|
||||||
|
// content: {
|
||||||
|
// "application/json": {
|
||||||
|
// schema: {
|
||||||
|
// type: "array",
|
||||||
|
// items: {
|
||||||
|
// $ref: "#/components/schemas/Payment"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (draft.paths["/orders/{uuid}"]) {
|
||||||
|
// draft.paths["/orders/{uuid}"].get.description = "Get order by UUID";
|
||||||
|
// draft.paths["/orders/{uuid}"].get.parameters[0].schema.type = "string";
|
||||||
|
// draft.paths["/orders/{uuid}"].get.responses = {
|
||||||
|
// "200": {
|
||||||
|
// description: "Returns order details",
|
||||||
|
// content: {
|
||||||
|
// "application/json": {
|
||||||
|
// schema: {
|
||||||
|
// $ref: "#/components/schemas/Order"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// "404": {
|
||||||
|
// description: "Order not found"
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (draft.paths["/orders/webhook"]) {
|
||||||
|
// draft.paths["/orders/webhook"].post.description = "Handle PayPal webhook events";
|
||||||
|
// draft.paths["/orders/webhook"].post.requestBody = {
|
||||||
|
// required: true,
|
||||||
|
// content: {
|
||||||
|
// "application/json": {
|
||||||
|
// schema: {
|
||||||
|
// type: "object",
|
||||||
|
// description: "PayPal webhook event payload"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// draft.paths["/orders/webhook"].post.responses = {
|
||||||
|
// "200": {
|
||||||
|
// description: "Webhook processed successfully"
|
||||||
|
// },
|
||||||
|
// "500": {
|
||||||
|
// description: "Internal server error"
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
servers: [
|
||||||
|
{
|
||||||
|
url: "/api",
|
||||||
|
description: "API server"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"strapi-prometheus": {
|
||||||
|
enabled: false,
|
||||||
|
config: {
|
||||||
|
// add prefix to all the prometheus metrics names.
|
||||||
|
prefix: "cms",
|
||||||
|
|
||||||
|
// use full url instead of matched url
|
||||||
|
// true => path label: `/api/models/1`
|
||||||
|
// false => path label: `/api/models/:id`
|
||||||
|
fullURL: false,
|
||||||
|
|
||||||
|
// include url query in the url label
|
||||||
|
// true => path label: `/api/models?limit=1`
|
||||||
|
// false => path label: `/api/models`
|
||||||
|
includeQuery: false,
|
||||||
|
|
||||||
|
// metrics that will be enabled, by default they are all enabled.
|
||||||
|
enabledMetrics: {
|
||||||
|
koa: true, // koa metrics
|
||||||
|
process: true, // metrics regarding the running process
|
||||||
|
http: true, // http metrics like response time and size
|
||||||
|
apollo: false // metrics regarding graphql
|
||||||
|
},
|
||||||
|
|
||||||
|
// interval at which rate metrics are collected in ms
|
||||||
|
interval: 30_000,
|
||||||
|
|
||||||
|
// set custom/default labels to all the prometheus metrics
|
||||||
|
customLabels: {
|
||||||
|
name: "strapi-prometheus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
13
config/server.ts
Normal file
13
config/server.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export default ({ env }) => ({
|
||||||
|
host: env("HOST", "0.0.0.0"),
|
||||||
|
port: env.int("PORT", 5555),
|
||||||
|
app: {
|
||||||
|
keys: env.array("APP_KEYS")
|
||||||
|
},
|
||||||
|
webhooks: {
|
||||||
|
populateRelations: env.bool("WEBHOOKS_POPULATE_RELATIONS", false)
|
||||||
|
},
|
||||||
|
logger: {
|
||||||
|
level: "info"
|
||||||
|
}
|
||||||
|
});
|
||||||
0
database/migrations/.gitkeep
Normal file
0
database/migrations/.gitkeep
Normal file
7
docker-entrypoint.sh
Normal file
7
docker-entrypoint.sh
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
if [ "$NODE_ENV" = "production" ]; then
|
||||||
|
npm run start
|
||||||
|
else
|
||||||
|
npm run develop
|
||||||
|
fi
|
||||||
BIN
favicon.png
Normal file
BIN
favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 497 B |
40
package.json
Normal file
40
package.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "cms",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"develop": "DEBUG=app:* npm run strapi develop -- --debug --bundler vite",
|
||||||
|
"start": "strapi start",
|
||||||
|
"build": "strapi build --debug",
|
||||||
|
"strapi": "strapi",
|
||||||
|
"lint:fix": "prettier --write \"{src,types,config}/**/*.{js,jsx,ts,tsx}\"",
|
||||||
|
"generate:types": "strapi ts:generate-types",
|
||||||
|
"import": "npm run strapi import -- -f database/export.tar.gz --force",
|
||||||
|
"export": "npm run strapi export -- --no-encrypt --file database/export_$(date +'%Y%m%d%H%M%S')",
|
||||||
|
"transfer-pull": "npm run strapi transfer -- --from $TRANSFER_URL --from-token $TRANSFER_FROM_TOKEN",
|
||||||
|
"transfer-push": "npm run strapi transfer -- --to $TRANSFER_URL --to-token $TRANSFER_TO_TOKEN --force"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@paypal/paypal-server-sdk": "^0.6.1",
|
||||||
|
"@strapi/plugin-documentation": "4.25.21",
|
||||||
|
"@strapi/plugin-i18n": "4.25.21",
|
||||||
|
"@strapi/plugin-users-permissions": "4.25.21",
|
||||||
|
"@strapi/strapi": "4.25.21",
|
||||||
|
"@strapi/utils": "4.25.21",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"pg": "^8.11.3",
|
||||||
|
"react": "^18.0.0",
|
||||||
|
"react-dom": "^18.0.0",
|
||||||
|
"react-router-dom": "^5.2.0",
|
||||||
|
"sharp": "^0.33.5",
|
||||||
|
"strapi-plugin-populate-deep": "^3.0.1",
|
||||||
|
"strapi-prometheus": "^1.9.2",
|
||||||
|
"styled-components": "^5.2.1",
|
||||||
|
"uuid": "^8.3.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/lodash": "^4.17.15",
|
||||||
|
"@types/node-fetch": "^2.6.11",
|
||||||
|
"@types/uuid": "^10.0.0",
|
||||||
|
"prettier": "^3.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
3
public/robots.txt
Executable file
3
public/robots.txt
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
# To prevent search engines from seeing the site altogether, uncomment the next two lines:
|
||||||
|
# User-Agent: *
|
||||||
|
# Disallow: /
|
||||||
0
public/uploads/.gitkeep
Executable file
0
public/uploads/.gitkeep
Executable file
9
src/admin/app.ts
Normal file
9
src/admin/app.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export default {
|
||||||
|
config: {
|
||||||
|
// Disable video tutorials
|
||||||
|
tutorials: false,
|
||||||
|
// Disable notifications about new Strapi releases
|
||||||
|
notifications: { releases: false }
|
||||||
|
},
|
||||||
|
bootstrap() {}
|
||||||
|
};
|
||||||
5
src/admin/tsconfig.json
Normal file
5
src/admin/tsconfig.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"extends": "@strapi/typescript-utils/tsconfigs/admin",
|
||||||
|
"include": ["../plugins/**/admin/src/**/*", "./"],
|
||||||
|
"exclude": ["node_modules/", "build/", "dist/", "**/*.test.ts"]
|
||||||
|
}
|
||||||
0
src/api/.gitkeep
Normal file
0
src/api/.gitkeep
Normal file
28
src/api/customer/content-types/customer/schema.json
Normal file
28
src/api/customer/content-types/customer/schema.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"kind": "collectionType",
|
||||||
|
"collectionName": "customers",
|
||||||
|
"info": {
|
||||||
|
"singularName": "customer",
|
||||||
|
"pluralName": "customers",
|
||||||
|
"displayName": "Customer",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"draftAndPublish": false
|
||||||
|
},
|
||||||
|
"pluginOptions": {},
|
||||||
|
"attributes": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"address": {
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"addressStructured": {
|
||||||
|
"type": "component",
|
||||||
|
"component": "address.structured-address",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/api/customer/controllers/customer.ts
Normal file
7
src/api/customer/controllers/customer.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* customer controller
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreController("api::customer.customer");
|
||||||
508
src/api/customer/documentation/1.0.0/customer.json
Normal file
508
src/api/customer/documentation/1.0.0/customer.json
Normal file
@@ -0,0 +1,508 @@
|
|||||||
|
{
|
||||||
|
"/customers": {
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/CustomerListResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Customer"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "sort",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Sort by attributes ascending (asc) or descending (desc)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[withCount]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Return page/pageSize (default: true)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[page]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page number (default: 0)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[pageSize]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page size (default: 25)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[start]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Offset value (default: 0)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[limit]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Number of entities to return (default: 25)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fields",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Fields to return (ex: title,author)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "populate",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Relations to return",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "filters",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Filters to apply",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"style": "deepObject"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "locale",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Locale to apply",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "get/customers"
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/CustomerResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Customer"
|
||||||
|
],
|
||||||
|
"parameters": [],
|
||||||
|
"operationId": "post/customers",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/CustomerRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/customers/{id}": {
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/CustomerResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Customer"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "get/customers/{id}"
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/CustomerResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Customer"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "put/customers/{id}",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/CustomerRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Customer"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "delete/customers/{id}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/api/customer/routes/customer.ts
Normal file
7
src/api/customer/routes/customer.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* customer router
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreRouter("api::customer.customer");
|
||||||
108
src/api/customer/services/customer.ts
Normal file
108
src/api/customer/services/customer.ts
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
import { ID } from "@strapi/database/dist/types";
|
||||||
|
import { StructuredAddress } from "../../../../types";
|
||||||
|
|
||||||
|
// Define a detailed type for customer creation
|
||||||
|
interface CustomerCreateData {
|
||||||
|
name: string;
|
||||||
|
address: string;
|
||||||
|
email?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default factories.createCoreService("api::customer.customer", ({ strapi }) => ({
|
||||||
|
/**
|
||||||
|
* Extract customer name from address
|
||||||
|
* @param address Full address string
|
||||||
|
* @returns Object with name and cleaned address
|
||||||
|
*/
|
||||||
|
extractCustomerDetails(address: string): { name: string; cleanedAddress: string } {
|
||||||
|
// If address is empty or null, use default values
|
||||||
|
if (!address || address.trim() === "") {
|
||||||
|
return {
|
||||||
|
name: "Unknown Customer",
|
||||||
|
cleanedAddress: address || ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split address by newline and take the first line as name
|
||||||
|
const addressLines = address.split("\n").map((line) => line.trim());
|
||||||
|
const name = addressLines[0] || "Unknown Customer";
|
||||||
|
|
||||||
|
// Remove the first line to get the cleaned address
|
||||||
|
const cleanedAddress = addressLines.slice(1).join("\n").trim();
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
cleanedAddress: cleanedAddress || address
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find or create a customer based on address
|
||||||
|
* @param address Full address string
|
||||||
|
* @param structuredAddress Optional structured address object
|
||||||
|
* @returns Customer ID
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Create or update customer with both legacy and structured address
|
||||||
|
*/
|
||||||
|
async findOrCreateCustomer(address: string, structuredAddress?: StructuredAddress): Promise<ID> {
|
||||||
|
try {
|
||||||
|
// Extract name and cleaned address
|
||||||
|
const { name, cleanedAddress } = this.extractCustomerDetails(address);
|
||||||
|
|
||||||
|
// Try to find by structured address first, then fallback to legacy
|
||||||
|
let existingCustomers = [];
|
||||||
|
|
||||||
|
if (structuredAddress) {
|
||||||
|
existingCustomers = await strapi.entityService.findMany("api::customer.customer", {
|
||||||
|
filters: {
|
||||||
|
$or: [
|
||||||
|
{
|
||||||
|
addressStructured: {
|
||||||
|
streetAddress: structuredAddress.streetAddress,
|
||||||
|
postalCode: structuredAddress.postalCode,
|
||||||
|
addressLevel2: structuredAddress.addressLevel2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ address: cleanedAddress }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
existingCustomers = await strapi.entityService.findMany("api::customer.customer", {
|
||||||
|
filters: { address: cleanedAddress }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingCustomers && existingCustomers.length > 0) {
|
||||||
|
const customer = existingCustomers[0];
|
||||||
|
|
||||||
|
// Update with structured data if provided
|
||||||
|
if (structuredAddress) {
|
||||||
|
await strapi.entityService.update("api::customer.customer", customer.id, {
|
||||||
|
data: {
|
||||||
|
addressStructured: structuredAddress
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return customer.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new customer with both formats
|
||||||
|
const newCustomer = await strapi.entityService.create("api::customer.customer", {
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
address: cleanedAddress,
|
||||||
|
...(structuredAddress && { addressStructured: structuredAddress })
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return newCustomer.id;
|
||||||
|
} catch (error) {
|
||||||
|
strapi.log.error("app:e:customer-service: Error managing customer", { error, address });
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
27
src/api/delivery/content-types/delivery/schema.json
Normal file
27
src/api/delivery/content-types/delivery/schema.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"kind": "collectionType",
|
||||||
|
"collectionName": "deliveries",
|
||||||
|
"info": {
|
||||||
|
"singularName": "delivery",
|
||||||
|
"pluralName": "deliveries",
|
||||||
|
"displayName": "Delivery",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"privateAttributes": ["created_at", "updated_at", "created_by", "updated_by", "createdAt", "updatedAt"],
|
||||||
|
"draftAndPublish": false
|
||||||
|
},
|
||||||
|
"pluginOptions": {},
|
||||||
|
"attributes": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"type": "decimal"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/api/delivery/controllers/delivery.ts
Normal file
7
src/api/delivery/controllers/delivery.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* delivery controller
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreController("api::delivery.delivery");
|
||||||
508
src/api/delivery/documentation/1.0.0/delivery.json
Normal file
508
src/api/delivery/documentation/1.0.0/delivery.json
Normal file
@@ -0,0 +1,508 @@
|
|||||||
|
{
|
||||||
|
"/deliveries": {
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/DeliveryListResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Delivery"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "sort",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Sort by attributes ascending (asc) or descending (desc)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[withCount]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Return page/pageSize (default: true)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[page]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page number (default: 0)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[pageSize]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page size (default: 25)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[start]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Offset value (default: 0)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[limit]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Number of entities to return (default: 25)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fields",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Fields to return (ex: title,author)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "populate",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Relations to return",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "filters",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Filters to apply",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"style": "deepObject"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "locale",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Locale to apply",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "get/deliveries"
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/DeliveryResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Delivery"
|
||||||
|
],
|
||||||
|
"parameters": [],
|
||||||
|
"operationId": "post/deliveries",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/DeliveryRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/deliveries/{id}": {
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/DeliveryResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Delivery"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "get/deliveries/{id}"
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/DeliveryResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Delivery"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "put/deliveries/{id}",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/DeliveryRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Delivery"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "delete/deliveries/{id}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/api/delivery/routes/delivery.ts
Normal file
7
src/api/delivery/routes/delivery.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* delivery router
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreRouter("api::delivery.delivery");
|
||||||
7
src/api/delivery/services/delivery.ts
Normal file
7
src/api/delivery/services/delivery.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* delivery service
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreService("api::delivery.delivery");
|
||||||
35
src/api/legal/content-types/legal/schema.json
Normal file
35
src/api/legal/content-types/legal/schema.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"kind": "singleType",
|
||||||
|
"collectionName": "legals",
|
||||||
|
"info": {
|
||||||
|
"singularName": "legal",
|
||||||
|
"pluralName": "legals",
|
||||||
|
"displayName": "Legal",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"privateAttributes": ["created_at", "updated_at", "created_by", "updated_by"],
|
||||||
|
"draftAndPublish": false
|
||||||
|
},
|
||||||
|
"pluginOptions": {},
|
||||||
|
"attributes": {
|
||||||
|
"imprint": {
|
||||||
|
"type": "richtext"
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"type": "richtext"
|
||||||
|
},
|
||||||
|
"terms": {
|
||||||
|
"type": "richtext"
|
||||||
|
},
|
||||||
|
"delivery": {
|
||||||
|
"type": "richtext"
|
||||||
|
},
|
||||||
|
"payment": {
|
||||||
|
"type": "richtext"
|
||||||
|
},
|
||||||
|
"privacy": {
|
||||||
|
"type": "richtext"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/api/legal/controllers/legal.ts
Normal file
38
src/api/legal/controllers/legal.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* legal controller
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreController("api::legal.legal", ({ strapi }) => ({
|
||||||
|
/**
|
||||||
|
* Get all legal page paths for sitemap generation
|
||||||
|
* @returns {Promise<string[]>} Array of legal page paths
|
||||||
|
*/
|
||||||
|
sitemap: async (ctx): Promise<string[]> => {
|
||||||
|
try {
|
||||||
|
strapi.log.verbose("Generating legal sitemap");
|
||||||
|
|
||||||
|
const legal = await strapi.entityService.findMany("api::legal.legal");
|
||||||
|
|
||||||
|
if (!legal) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
strapi.log.silly(`Legal pages response: ${JSON.stringify(legal)}`);
|
||||||
|
|
||||||
|
const internalFields = ["id", "createdAt", "updatedAt", "createdBy", "updatedBy"];
|
||||||
|
const legalPages = Object.keys(legal).filter((key) => !internalFields.includes(key));
|
||||||
|
|
||||||
|
const legalPaths = legalPages.map((page) => `/legal/${page}`);
|
||||||
|
|
||||||
|
strapi.log.verbose(`Generated legal sitemap with ${legalPaths.length} entries`);
|
||||||
|
strapi.log.silly(`Legal sitemap: ${JSON.stringify(legalPaths)}`);
|
||||||
|
|
||||||
|
return legalPaths;
|
||||||
|
} catch (error) {
|
||||||
|
strapi.log.error(error);
|
||||||
|
return ctx.badRequest("Could not generate legal sitemap");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
325
src/api/legal/documentation/1.0.0/legal.json
Normal file
325
src/api/legal/documentation/1.0.0/legal.json
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
{
|
||||||
|
"/legal": {
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/LegalResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Legal"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "sort",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Sort by attributes ascending (asc) or descending (desc)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[withCount]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Return page/pageSize (default: true)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[page]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page number (default: 0)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[pageSize]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page size (default: 25)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[start]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Offset value (default: 0)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[limit]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Number of entities to return (default: 25)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fields",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Fields to return (ex: title,author)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "populate",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Relations to return",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "filters",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Filters to apply",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"style": "deepObject"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "locale",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Locale to apply",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "get/legal"
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/LegalResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Legal"
|
||||||
|
],
|
||||||
|
"parameters": [],
|
||||||
|
"operationId": "put/legal",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/LegalRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Legal"
|
||||||
|
],
|
||||||
|
"parameters": [],
|
||||||
|
"operationId": "delete/legal"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/api/legal/routes/legal-sitemap.ts
Normal file
13
src/api/legal/routes/legal-sitemap.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export default {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
path: "/sitemap/legal",
|
||||||
|
handler: "legal.sitemap",
|
||||||
|
config: {
|
||||||
|
auth: false,
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
7
src/api/legal/routes/legal.ts
Normal file
7
src/api/legal/routes/legal.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* legal router
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreRouter("api::legal.legal");
|
||||||
7
src/api/legal/services/legal.ts
Normal file
7
src/api/legal/services/legal.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* legal service
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreService("api::legal.legal");
|
||||||
170
src/api/order/content-types/order/lifecycles.ts
Normal file
170
src/api/order/content-types/order/lifecycles.ts
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
import { Order } from "../../../../../types";
|
||||||
|
import { ID } from "@strapi/database/dist/types";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
|
// In-memory processing state tracker
|
||||||
|
// Using order UUIDs as keys to track which orders are currently being processed
|
||||||
|
const processingOrders = new Map<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
invoiceInProgress: boolean;
|
||||||
|
lastProcessingStarted: number;
|
||||||
|
}
|
||||||
|
>();
|
||||||
|
|
||||||
|
// Cleanup function to prevent memory leaks
|
||||||
|
const cleanupStaleProcessingEntries = () => {
|
||||||
|
const now = Date.now();
|
||||||
|
const TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes timeout
|
||||||
|
|
||||||
|
for (const [uuid, state] of processingOrders.entries()) {
|
||||||
|
if (now - state.lastProcessingStarted > TIMEOUT_MS) {
|
||||||
|
processingOrders.delete(uuid);
|
||||||
|
strapi.log.verbose(`app:v:order-lifecycles: Cleaned up stale processing state for order ${uuid}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
beforeCreate(event: { params: { data: { uuid: any } } }) {
|
||||||
|
// Generate UUID for new orders
|
||||||
|
if (!event.params.data.uuid) {
|
||||||
|
event.params.data.uuid = uuidv4();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async beforeUpdate(event: {
|
||||||
|
params: {
|
||||||
|
data: Order;
|
||||||
|
where: { id: ID };
|
||||||
|
};
|
||||||
|
}) {
|
||||||
|
const { data, where } = event.params;
|
||||||
|
const orderId = where.id;
|
||||||
|
|
||||||
|
strapi.log.debug(`app:d:order-lifecycles: Before order update ${JSON.stringify({ orderId, data })}`);
|
||||||
|
|
||||||
|
// Track what changed for smarter processing
|
||||||
|
const changes = {
|
||||||
|
paymentAuthorised: "paymentAuthorised" in data,
|
||||||
|
addressUpdated: "invoiceAddress" in data
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle address update and customer assignment
|
||||||
|
if (changes.addressUpdated && data.invoiceAddress) {
|
||||||
|
try {
|
||||||
|
const customerService = strapi.service("api::customer.customer");
|
||||||
|
|
||||||
|
// Find or create customer using the address string only
|
||||||
|
// The structured address component will be handled by the controller
|
||||||
|
const customer = await customerService.findOrCreateCustomer(data.invoiceAddress);
|
||||||
|
|
||||||
|
strapi.log.verbose(
|
||||||
|
"app:d:order-lifecycles: Attaching customer to order",
|
||||||
|
JSON.stringify({
|
||||||
|
order: orderId,
|
||||||
|
customer
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Attach customer to order
|
||||||
|
data.customer = customer;
|
||||||
|
|
||||||
|
// Generate invoice and delivery number in format R-[customer.id]-[order.id]
|
||||||
|
// Only generate if they don't already exist
|
||||||
|
if (!data.invoiceNumber) {
|
||||||
|
data.invoiceNumber = `R-${customer}-${orderId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.deliveryNoteNumber) {
|
||||||
|
data.deliveryNoteNumber = `L-${customer}-${orderId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
strapi.log.debug("app:d:order-lifecycles: Generated invoice number", {
|
||||||
|
invoiceNumber: data.invoiceNumber,
|
||||||
|
deliveryNoteNumber: data.deliveryNoteNumber,
|
||||||
|
order: orderId
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
strapi.log.error("app:e:order-lifecycles: Error in beforeUpdate", {
|
||||||
|
error
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async afterCreate(event: { result: Order }) {
|
||||||
|
const { result } = event;
|
||||||
|
strapi.log.verbose(
|
||||||
|
"app:v:order-lifecycles: Order created",
|
||||||
|
JSON.stringify({
|
||||||
|
id: result.id,
|
||||||
|
uuid: result.uuid
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
async afterUpdate(event: { result: Order }) {
|
||||||
|
const { result } = event;
|
||||||
|
|
||||||
|
// Periodically clean up stale processing entries
|
||||||
|
cleanupStaleProcessingEntries();
|
||||||
|
|
||||||
|
// Log order update with relevant fields
|
||||||
|
strapi.log.debug("app:d:order-lifecycles: ", {
|
||||||
|
order: {
|
||||||
|
id: result.id,
|
||||||
|
uuid: result.uuid,
|
||||||
|
paymentAuthorised: result.paymentAuthorised,
|
||||||
|
invoice: result.invoice,
|
||||||
|
invoiceSent: result.invoiceSent
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check processing conditions
|
||||||
|
const shouldProcessInvoice = result.paymentAuthorised && !result.invoiceSent;
|
||||||
|
|
||||||
|
if (!shouldProcessInvoice) {
|
||||||
|
return; // Early exit if conditions aren't met
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get order UUID to track processing state
|
||||||
|
const orderUUID = result.uuid;
|
||||||
|
|
||||||
|
// Check if this order is already being processed
|
||||||
|
const processingState = processingOrders.get(orderUUID);
|
||||||
|
if (processingState && processingState.invoiceInProgress) {
|
||||||
|
strapi.log.verbose("app:v:order-lifecycles: Skipping duplicate invoice generation", {
|
||||||
|
orderId: result.id,
|
||||||
|
uuid: orderUUID,
|
||||||
|
processingStarted: new Date(processingState.lastProcessingStarted).toISOString()
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark this order as being processed
|
||||||
|
processingOrders.set(orderUUID, {
|
||||||
|
invoiceInProgress: true,
|
||||||
|
lastProcessingStarted: Date.now()
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
strapi.log.info("app:i:order-lifecycles: Starting invoice generation", { orderId: result.id, uuid: orderUUID });
|
||||||
|
|
||||||
|
// Generate and upload the invoice PDF
|
||||||
|
const order = await strapi.service("api::order.order").uploadPdf(result, "invoice", "Rechnung");
|
||||||
|
|
||||||
|
// Send the invoice email
|
||||||
|
await strapi.service("api::order.order").sendInvoice(orderUUID);
|
||||||
|
|
||||||
|
strapi.log.info("app:i:order-lifecycles: Successfully processed invoice for order", { orderId: result.id, uuid: orderUUID });
|
||||||
|
} catch (error) {
|
||||||
|
strapi.log.error("app:e:order-lifecycles: Error processing invoice", { error, orderId: result.id, uuid: orderUUID });
|
||||||
|
// Don't rethrow to prevent transaction failure
|
||||||
|
} finally {
|
||||||
|
// Remove the processing marker once complete
|
||||||
|
processingOrders.delete(orderUUID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
128
src/api/order/content-types/order/schema.json
Normal file
128
src/api/order/content-types/order/schema.json
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
{
|
||||||
|
"kind": "collectionType",
|
||||||
|
"collectionName": "orders",
|
||||||
|
"info": {
|
||||||
|
"singularName": "order",
|
||||||
|
"pluralName": "orders",
|
||||||
|
"displayName": "Order",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"draftAndPublish": false
|
||||||
|
},
|
||||||
|
"pluginOptions": {},
|
||||||
|
"attributes": {
|
||||||
|
"date": {
|
||||||
|
"type": "date"
|
||||||
|
},
|
||||||
|
"customer": {
|
||||||
|
"type": "relation",
|
||||||
|
"relation": "oneToOne",
|
||||||
|
"target": "api::customer.customer"
|
||||||
|
},
|
||||||
|
"invoice": {
|
||||||
|
"type": "media",
|
||||||
|
"multiple": false,
|
||||||
|
"required": false,
|
||||||
|
"allowedTypes": [
|
||||||
|
"files"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"deliveryNote": {
|
||||||
|
"type": "media",
|
||||||
|
"multiple": false,
|
||||||
|
"required": false,
|
||||||
|
"allowedTypes": [
|
||||||
|
"images",
|
||||||
|
"files",
|
||||||
|
"videos",
|
||||||
|
"audios"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": {
|
||||||
|
"type": "text",
|
||||||
|
"unique": true,
|
||||||
|
"private": true
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"delivery": {
|
||||||
|
"type": "relation",
|
||||||
|
"relation": "oneToOne",
|
||||||
|
"target": "api::delivery.delivery"
|
||||||
|
},
|
||||||
|
"payment": {
|
||||||
|
"type": "relation",
|
||||||
|
"relation": "oneToOne",
|
||||||
|
"target": "api::payment.payment"
|
||||||
|
},
|
||||||
|
"VAT": {
|
||||||
|
"type": "decimal"
|
||||||
|
},
|
||||||
|
"subtotal": {
|
||||||
|
"type": "decimal"
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"type": "decimal"
|
||||||
|
},
|
||||||
|
"uuid": {
|
||||||
|
"type": "string",
|
||||||
|
"unique": true
|
||||||
|
},
|
||||||
|
"cart": {
|
||||||
|
"displayName": "cart",
|
||||||
|
"type": "component",
|
||||||
|
"repeatable": true,
|
||||||
|
"component": "products.cart"
|
||||||
|
},
|
||||||
|
"paymentAuthorised": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"paymentStatus": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"emailSent": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"acceptedTermsAndConditionsAt": {
|
||||||
|
"type": "datetime"
|
||||||
|
},
|
||||||
|
"invoiceSent": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"deliveryNoteSent": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"invoiceNumber": {
|
||||||
|
"type": "string",
|
||||||
|
"unique": true
|
||||||
|
},
|
||||||
|
"deliveryNoteNumber": {
|
||||||
|
"type": "string",
|
||||||
|
"unique": false
|
||||||
|
},
|
||||||
|
"deliveryTrackingNumber": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"deliveryAddress": {
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"invoiceAddress": {
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"invoiceAddressStructured": {
|
||||||
|
"type": "component",
|
||||||
|
"component": "address.structured-address",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
"deliveryAddressStructured": {
|
||||||
|
"type": "component",
|
||||||
|
"component": "address.structured-address",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
307
src/api/order/controllers/order.ts
Normal file
307
src/api/order/controllers/order.ts
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
import { sanitize } from "@strapi/utils";
|
||||||
|
import { Order } from "../../../../types";
|
||||||
|
import { paypalApi } from "../../../services/PayPalApi";
|
||||||
|
import { orderDefaultParams } from "../services/order";
|
||||||
|
import { calculateTotalProductPrice } from "../../product/services/product";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The order controller is a funnel for the client to interact with the order service.
|
||||||
|
* From creating until finalizing an order, the controller is responsible for handling
|
||||||
|
* the business logic.
|
||||||
|
*
|
||||||
|
* ---------------------------------------
|
||||||
|
* INIT
|
||||||
|
* ---------------------------------------
|
||||||
|
* 1. Create order
|
||||||
|
* ---------------------------------------
|
||||||
|
* PRE-ORDER (must have order uuid)
|
||||||
|
* ---------------------------------------
|
||||||
|
* 1.1. Find order
|
||||||
|
* 2. Add/remove product from order
|
||||||
|
* ---------------------------------------
|
||||||
|
* ORDER VALIDATION (must have at least one product)
|
||||||
|
* ---------------------------------------
|
||||||
|
* 3. Update order
|
||||||
|
* 3.1. Contact info (email)
|
||||||
|
* 3.2. Confirm terms and conditions
|
||||||
|
* 3.3. Address (invoice, delivery)
|
||||||
|
* 3.4. Delivery method
|
||||||
|
* ---------------------------------------
|
||||||
|
* ORDER PAYMENT (order must include all above properties)
|
||||||
|
* ---------------------------------------
|
||||||
|
* 4. Checkout order (create adyen payment session)
|
||||||
|
* ---------------------------------------
|
||||||
|
* POST-ORDER (payment must be authorised)
|
||||||
|
* ---------------------------------------
|
||||||
|
* 5. Finalize order (generate invoice, delivery note)
|
||||||
|
* 6. Send invoice and delivery note via email
|
||||||
|
*/
|
||||||
|
export default factories.createCoreController("api::order.order", ({ strapi }) => ({
|
||||||
|
create: async (_) => {
|
||||||
|
const order = await strapi.entityService.create("api::order.order", { data: {} });
|
||||||
|
strapi.log.debug(`app:d:order-controller: Order created: ${JSON.stringify({ order })}`);
|
||||||
|
return await sanitize.contentAPI.output(order, strapi.getModel("api::order.order"));
|
||||||
|
},
|
||||||
|
|
||||||
|
update: async (ctx) => {
|
||||||
|
const { id } = await sanitize.contentAPI.query(ctx.params, strapi.getModel("api::order.order"));
|
||||||
|
strapi.log.verbose(`app:v:order-controller: - Updating order ID${id}`, orderDefaultParams);
|
||||||
|
const order = await strapi.db.query("api::order.order").findOne({ where: { id }, ...orderDefaultParams });
|
||||||
|
if (!order) return ctx.notFound("Order not found");
|
||||||
|
|
||||||
|
const sanitizedBody = await sanitize.contentAPI.input(ctx.request.body, strapi.getModel("api::order.order"));
|
||||||
|
strapi.log.verbose("app:v:order-controller: - Updating order", { sanitizedBody });
|
||||||
|
|
||||||
|
const updatedOrder = await strapi.entityService.update("api::order.order", order.id, {
|
||||||
|
// @ts-expect-error
|
||||||
|
data: sanitizedBody?.data,
|
||||||
|
...orderDefaultParams
|
||||||
|
});
|
||||||
|
strapi.log.verbose("app:v:order-controller: ✔ Updating order");
|
||||||
|
return await sanitize.contentAPI.output(updatedOrder, strapi.getModel("api::order.order"));
|
||||||
|
},
|
||||||
|
|
||||||
|
findOne: async (ctx) => {
|
||||||
|
const { id } = await sanitize.contentAPI.query(ctx.params, strapi.getModel("api::order.order"));
|
||||||
|
const order: Order = await strapi.db.query("api::order.order").findOne({
|
||||||
|
where: { id },
|
||||||
|
...orderDefaultParams
|
||||||
|
});
|
||||||
|
order.cart = order?.cart.map((item) => ({
|
||||||
|
...item,
|
||||||
|
product: {
|
||||||
|
...item.product,
|
||||||
|
totalProductPrice: calculateTotalProductPrice(item.product)
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
if (!order) return ctx.notFound("Order not found");
|
||||||
|
return await sanitize.contentAPI.output(order, strapi.getModel("api::order.order"));
|
||||||
|
},
|
||||||
|
|
||||||
|
findOneByUuid: async (ctx) => {
|
||||||
|
const { uuid } = await sanitize.contentAPI.query(ctx.params, strapi.getModel("api::order.order"));
|
||||||
|
const order: Order = await strapi.db.query("api::order.order").findOne({
|
||||||
|
where: { uuid },
|
||||||
|
...orderDefaultParams
|
||||||
|
});
|
||||||
|
order.cart = order?.cart.map((item) => ({
|
||||||
|
...item,
|
||||||
|
product: {
|
||||||
|
...item.product,
|
||||||
|
totalProductPrice: calculateTotalProductPrice(item.product)
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
if (!order) return ctx.notFound("Order not found");
|
||||||
|
return await sanitize.contentAPI.output(order, strapi.getModel("api::order.order"));
|
||||||
|
},
|
||||||
|
|
||||||
|
updateByUuid: async (ctx) => {
|
||||||
|
const { uuid } = await sanitize.contentAPI.query(ctx.params, strapi.getModel("api::order.order"));
|
||||||
|
const order = await strapi.db.query("api::order.order").findOne({ where: { uuid } });
|
||||||
|
if (!order) return ctx.notFound("Order not found");
|
||||||
|
|
||||||
|
const sanitizedBody = await sanitize.contentAPI.input(ctx.request.body, strapi.getModel("api::order.order"));
|
||||||
|
|
||||||
|
// Handle customer creation/update if structured addresses are provided
|
||||||
|
// @ts-expect-error
|
||||||
|
if (sanitizedBody?.data?.invoiceAddressStructured) {
|
||||||
|
const customerAddress =
|
||||||
|
// @ts-expect-error
|
||||||
|
sanitizedBody.data.invoiceAddress ||
|
||||||
|
// @ts-expect-error
|
||||||
|
`${sanitizedBody.data.invoiceAddressStructured.givenName} ${sanitizedBody.data.invoiceAddressStructured.familyName}\n${sanitizedBody.data.invoiceAddressStructured.streetAddress}\n${sanitizedBody.data.invoiceAddressStructured.postalCode} ${sanitizedBody.data.invoiceAddressStructured.addressLevel2}`;
|
||||||
|
// @ts-expect-error
|
||||||
|
sanitizedBody.data.customer = await strapi
|
||||||
|
.service("api::customer.customer")
|
||||||
|
// @ts-expect-error
|
||||||
|
.findOrCreateCustomer(customerAddress, sanitizedBody.data.invoiceAddressStructured);
|
||||||
|
}
|
||||||
|
|
||||||
|
const orderUnsafe = await strapi.service("api::order.order").update(order.id, {
|
||||||
|
// @ts-expect-error
|
||||||
|
data: sanitizedBody?.data,
|
||||||
|
...orderDefaultParams
|
||||||
|
});
|
||||||
|
|
||||||
|
return await sanitize.contentAPI.output(orderUnsafe, strapi.getModel("api::order.order"));
|
||||||
|
},
|
||||||
|
|
||||||
|
addProduct: async (ctx) => {
|
||||||
|
const { uuid, productId } = await sanitize.contentAPI.query(ctx.params, strapi.getModel("api::order.order"));
|
||||||
|
const order = (await strapi.service("api::order.order").findOneByUuid(uuid)) as Order;
|
||||||
|
if (!order) return ctx.notFound("Order not found");
|
||||||
|
|
||||||
|
const product = parseInt(productId as string);
|
||||||
|
const count = parseInt(ctx.query.count as string) || 1;
|
||||||
|
const cart = order.cart || [];
|
||||||
|
const existingIndex = cart.findIndex((item) => item.product.id === product);
|
||||||
|
|
||||||
|
if (existingIndex !== -1) {
|
||||||
|
// Product already in cart
|
||||||
|
cart[existingIndex].count += count;
|
||||||
|
} else {
|
||||||
|
// @ts-expect-error
|
||||||
|
cart.push({ count, product });
|
||||||
|
}
|
||||||
|
|
||||||
|
strapi.log.info(`app:v:order-controller: - Adding product to cart ${order.id}`, { totalBefore: order.total });
|
||||||
|
|
||||||
|
const orderUnsafe = await strapi.service("api::order.order").update(order.id, {
|
||||||
|
data: { cart: cart },
|
||||||
|
...orderDefaultParams
|
||||||
|
});
|
||||||
|
strapi.log.info("app:v:order-controller: ✔ Adding product to cart", { totalAfter: orderUnsafe.total });
|
||||||
|
return await sanitize.contentAPI.output(orderUnsafe, strapi.getModel("api::order.order"));
|
||||||
|
},
|
||||||
|
|
||||||
|
removeProduct: async (ctx) => {
|
||||||
|
const { uuid, productId } = await sanitize.contentAPI.query(ctx.params, strapi.getModel("api::order.order"));
|
||||||
|
const count = parseInt(ctx.query.count as string) || 1;
|
||||||
|
const order = (await strapi.service("api::order.order").findOneByUuid(uuid)) as Order;
|
||||||
|
const product = parseInt(productId as string);
|
||||||
|
|
||||||
|
if (!order) return ctx.notFound("Order not found");
|
||||||
|
strapi.log.info("app:v:order-controller: - Removing product from cart", { totalBefore: order.total });
|
||||||
|
|
||||||
|
const updatedCart = [...(order.cart || [])];
|
||||||
|
let existingIndex = -1;
|
||||||
|
|
||||||
|
if (typeof productId === "string") {
|
||||||
|
existingIndex = updatedCart.findIndex((item) => item.product.id === parseInt(productId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingIndex === -1) return ctx.badRequest("Product not found in cart");
|
||||||
|
|
||||||
|
if (updatedCart[existingIndex].count <= count) {
|
||||||
|
updatedCart.splice(existingIndex, 1);
|
||||||
|
} else {
|
||||||
|
updatedCart[existingIndex].count -= count;
|
||||||
|
}
|
||||||
|
|
||||||
|
const orderUnsafe = await strapi.service("api::order.order").update(order.id, {
|
||||||
|
data: { cart: updatedCart },
|
||||||
|
...orderDefaultParams
|
||||||
|
});
|
||||||
|
strapi.log.info("app:v:order-controller: ✔ Removing product from cart", { totalAfter: order.total });
|
||||||
|
return await sanitize.contentAPI.output(orderUnsafe, strapi.getModel("api::order.order"));
|
||||||
|
},
|
||||||
|
|
||||||
|
checkout: async (ctx) => {
|
||||||
|
const { uuid } = await sanitize.contentAPI.query(ctx.params, strapi.getModel("api::order.order"));
|
||||||
|
const { returnUrl } = await sanitize.contentAPI.query(ctx.query, strapi.getModel("api::order.order"));
|
||||||
|
const order = await strapi.db.query("api::order.order").findOne({
|
||||||
|
where: { uuid },
|
||||||
|
...orderDefaultParams
|
||||||
|
});
|
||||||
|
if (!order) return ctx.notFound("Order not found");
|
||||||
|
|
||||||
|
return await paypalApi.createSessionOrThrow(returnUrl as string, order);
|
||||||
|
},
|
||||||
|
|
||||||
|
capturePayment: async (ctx) => {
|
||||||
|
const { uuid, paypalOrderId } = await sanitize.contentAPI.query(ctx.params, strapi.getModel("api::order.order"));
|
||||||
|
strapi.log.info(`app:i:order-controller: Capturing payment for order ${uuid} with PayPal order ${paypalOrderId}`);
|
||||||
|
|
||||||
|
const order = await strapi.db.query("api::order.order").findOne({
|
||||||
|
where: { uuid },
|
||||||
|
...orderDefaultParams
|
||||||
|
});
|
||||||
|
if (!order) return ctx.notFound("Order not found");
|
||||||
|
|
||||||
|
if (order.paymentAuthorised) {
|
||||||
|
strapi.log.info(`app:i:order-controller: Payment already authorised for order ${uuid}`);
|
||||||
|
return { success: true, alreadyCaptured: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Capture the payment via PayPal server SDK
|
||||||
|
const captureResult = await paypalApi.checkPaymentStatus({ orderID: paypalOrderId as string });
|
||||||
|
|
||||||
|
if (captureResult.status === "COMPLETED") {
|
||||||
|
// Update order with payment info
|
||||||
|
const updatedOrder = await strapi.entityService.update("api::order.order", order.id, {
|
||||||
|
data: {
|
||||||
|
paymentAuthorised: true,
|
||||||
|
paymentStatus: "authorised",
|
||||||
|
paypalOrderId: paypalOrderId as string
|
||||||
|
},
|
||||||
|
...orderDefaultParams
|
||||||
|
});
|
||||||
|
|
||||||
|
strapi.log.info(`app:i:order-controller: Payment captured successfully for order ${uuid}`);
|
||||||
|
return await sanitize.contentAPI.output(updatedOrder, strapi.getModel("api::order.order"));
|
||||||
|
} else {
|
||||||
|
strapi.log.error(`app:e:order-controller: Payment capture failed for order ${uuid}`, { captureResult });
|
||||||
|
return ctx.badRequest("Payment capture failed", { status: captureResult.status });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
strapi.log.error(`app:e:order-controller: Error capturing payment for order ${uuid}`, { error });
|
||||||
|
return ctx.internalServerError("Payment capture failed");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
generateInvoice: async (ctx) => {
|
||||||
|
const { uuid } = await sanitize.contentAPI.query(ctx.params, strapi.getModel("api::order.order"));
|
||||||
|
strapi.log.info(`app:i:order-controller: Started invoice generation`);
|
||||||
|
const orderUnsafe = (await strapi.service("api::order.order").findOneByUuid(uuid)) as Order;
|
||||||
|
|
||||||
|
if (!orderUnsafe.paymentAuthorised) {
|
||||||
|
return ctx.locked("Payment not authorised, cannot generate invoice");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const updatedOrderUnsafe = await strapi.service("api::order.order").uploadPdf(orderUnsafe, "invoice", "Rechnung");
|
||||||
|
return await sanitize.contentAPI.output(updatedOrderUnsafe, strapi.getModel("api::order.order"));
|
||||||
|
} catch (error) {
|
||||||
|
strapi.log.error("app:e:order-controller: Error uploading delivery note pdf", { error });
|
||||||
|
return ctx.internalServerError("Could not upload delivery note");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
generateDeliveryNote: async (ctx) => {
|
||||||
|
const { uuid } = await sanitize.contentAPI.query(ctx.params, strapi.getModel("api::order.order"));
|
||||||
|
strapi.log.info(`app:i:order-controller: Started delivery note generation`);
|
||||||
|
const orderUnsafe = (await strapi.service("api::order.order").findOneByUuid(uuid)) as Order;
|
||||||
|
|
||||||
|
if (!orderUnsafe.paymentAuthorised) {
|
||||||
|
return ctx.locked("Payment not authorised, cannot generate delivery note");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const updatedOrderUnsafe = await strapi.service("api::order.order").uploadPdf(orderUnsafe, "deliveryNote", "Lieferschein");
|
||||||
|
return await sanitize.contentAPI.output(updatedOrderUnsafe, strapi.getModel("api::order.order"));
|
||||||
|
} catch (error) {
|
||||||
|
strapi.log.error("app:e:order-controller: Error uploading delivery note pdf", { error });
|
||||||
|
return ctx.internalServerError("Could not upload delivery note");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
sendInvoice: async (ctx) => {
|
||||||
|
const { uuid } = await sanitize.contentAPI.query(ctx.params, strapi.getModel("api::order.order"));
|
||||||
|
strapi.log.info("app:i:order-controller: Sending invoice for order", { uuid });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const updatedOrderUnsafe = await strapi.service("api::order.order").sendInvoice(uuid, undefined);
|
||||||
|
return await sanitize.contentAPI.output(updatedOrderUnsafe, strapi.getModel("api::order.order"));
|
||||||
|
} catch (error) {
|
||||||
|
strapi.log.error("app:e:order-controller: Error sending invoice", { error });
|
||||||
|
return ctx.internalServerError("Could not send invoice");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
sendDeliveryNote: async (ctx) => {
|
||||||
|
const { uuid } = await sanitize.contentAPI.query(ctx.params, strapi.getModel("api::order.order"));
|
||||||
|
strapi.log.info("app:i:order-controller: Sending delivery note for", { uuid });
|
||||||
|
return await strapi.service("api::order.order").sendDeliveryNote(uuid, undefined);
|
||||||
|
},
|
||||||
|
|
||||||
|
webhook: async (ctx) => {
|
||||||
|
try {
|
||||||
|
return await paypalApi.handleWebhook(ctx.request.body);
|
||||||
|
} catch (error) {
|
||||||
|
strapi.log.error(`paypal-api: ${JSON.stringify({ error })}`);
|
||||||
|
return ctx.internalServerError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
1344
src/api/order/documentation/1.0.0/order.json
Normal file
1344
src/api/order/documentation/1.0.0/order.json
Normal file
File diff suppressed because it is too large
Load Diff
124
src/api/order/routes/order.ts
Normal file
124
src/api/order/routes/order.ts
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
export default {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
path: "/orders",
|
||||||
|
handler: "order.create",
|
||||||
|
config: {
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
path: "/orders/webhook",
|
||||||
|
handler: "order.webhook",
|
||||||
|
config: {
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
path: "/orders",
|
||||||
|
handler: "order.find",
|
||||||
|
config: {
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
path: "/orders/:id",
|
||||||
|
handler: "order.findOne",
|
||||||
|
config: {
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
path: "/orders/:uuid/cart",
|
||||||
|
handler: "order.findOneByUuid",
|
||||||
|
config: {
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
path: "/orders/:id",
|
||||||
|
handler: "order.update",
|
||||||
|
config: {
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
path: "/orders/:uuid/cart",
|
||||||
|
handler: "order.updateByUuid",
|
||||||
|
config: {
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
path: "/orders/:uuid/add-product/:productId",
|
||||||
|
handler: "order.addProduct",
|
||||||
|
config: {
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
path: "/orders/:uuid/remove-product/:productId",
|
||||||
|
handler: "order.removeProduct",
|
||||||
|
config: {
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
path: "/orders/:uuid/checkout",
|
||||||
|
handler: "order.checkout",
|
||||||
|
config: {
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
path: "/orders/:uuid/capture/:paypalOrderId",
|
||||||
|
handler: "order.capturePayment",
|
||||||
|
config: {
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
path: "/orders/:uuid/generate-invoice",
|
||||||
|
handler: "order.generateInvoice",
|
||||||
|
config: {
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
path: "/orders/:uuid/generate-delivery-note",
|
||||||
|
handler: "order.generateDeliveryNote",
|
||||||
|
config: {
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
path: "/orders/:uuid/send-invoice",
|
||||||
|
handler: "order.sendInvoice",
|
||||||
|
config: {
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
path: "/orders/:uuid/send-delivery-note",
|
||||||
|
handler: "order.sendDeliveryNote",
|
||||||
|
config: {
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
269
src/api/order/services/order.ts
Normal file
269
src/api/order/services/order.ts
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
import path from "path";
|
||||||
|
import fs from "fs";
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
import { sanitize } from "@strapi/utils";
|
||||||
|
import { ID } from "@strapi/database/dist/types";
|
||||||
|
import { vatDecimal, vatIncludedDecimal, baseUrl, adminEmail } from "../../../../config/constants";
|
||||||
|
import { calculateTotalProductPrice, productDefaultParams } from "../../product/services/product";
|
||||||
|
import { CartProduct, Order, PdfBody } from "../../../../types";
|
||||||
|
import { pdfApi } from "../../../services/PdfApi";
|
||||||
|
import { invoiceEmailTemplate, deliveryNoteEmailTemplate } from "../../../templates/emailTemplates";
|
||||||
|
import _, { template } from "lodash";
|
||||||
|
import { PdfMessageBody, MessageBody, mailApi } from "../../../services/MailApi";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This service is responsible for handling the order logic.
|
||||||
|
*/
|
||||||
|
export default factories.createCoreService("api::order.order", ({ strapi }) => ({
|
||||||
|
findOne: async (id: ID) => {
|
||||||
|
return await strapi.entityService.findOne("api::order.order", id, orderDefaultParams);
|
||||||
|
},
|
||||||
|
|
||||||
|
findOneByUuid: async (uuid: string) => {
|
||||||
|
const params = {
|
||||||
|
filters: {
|
||||||
|
uuid: {
|
||||||
|
$eq: uuid
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...orderDefaultParams
|
||||||
|
};
|
||||||
|
const orderUnsafe = await strapi.db.query("api::order.order").findOne({
|
||||||
|
where: { uuid },
|
||||||
|
...orderDefaultParams
|
||||||
|
});
|
||||||
|
orderUnsafe.cart = orderUnsafe?.cart.map((item: CartProduct) => ({
|
||||||
|
...item,
|
||||||
|
product: {
|
||||||
|
...item.product,
|
||||||
|
totalProductPrice: calculateTotalProductPrice(item.product)
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
return (await sanitize.contentAPI.output(orderUnsafe, strapi.getModel("api::order.order"))) as Order;
|
||||||
|
},
|
||||||
|
|
||||||
|
uploadPdf: async (orderUnsafe: Order, field: "invoice" | "deliveryNote", name = "Upload") => {
|
||||||
|
strapi.log.verbose(`app:v:order-service: – Generating ${field} pdf`);
|
||||||
|
const pdfBody = field === "invoice" ? invoicePdfBody(orderUnsafe) : deliveryNotePdfBody(orderUnsafe);
|
||||||
|
strapi.log.debug(`app:d:order-service: - PDF body`, pdfBody);
|
||||||
|
const blob = field === "invoice" ? await pdfApi.generateInvoice(pdfBody) : await pdfApi.generateDeliveryNote(pdfBody);
|
||||||
|
strapi.log.verbose(`app:v:order-service: ✔ Generating ${field} pdf`);
|
||||||
|
|
||||||
|
// Save blob to a temp folder
|
||||||
|
const tempFolder = path.join(__dirname, "temp");
|
||||||
|
if (!fs.existsSync(tempFolder)) {
|
||||||
|
fs.mkdirSync(tempFolder);
|
||||||
|
}
|
||||||
|
const deliveryNotePath = path.join(tempFolder, `${name}.pdf`);
|
||||||
|
fs.writeFileSync(deliveryNotePath, Buffer.from(blob));
|
||||||
|
|
||||||
|
const uploadData = {
|
||||||
|
data: {
|
||||||
|
ref: "api::order.order",
|
||||||
|
refId: orderUnsafe.id,
|
||||||
|
field
|
||||||
|
},
|
||||||
|
files: {
|
||||||
|
path: deliveryNotePath,
|
||||||
|
name: `${name}_${pdfBody.date.replace(/[^a-z0-9]/gi, "_").toLowerCase()}`,
|
||||||
|
type: "application/pdf",
|
||||||
|
size: fs.statSync(deliveryNotePath).size
|
||||||
|
}
|
||||||
|
};
|
||||||
|
strapi.log.verbose(`app:v:order-service: - Uploading ${field} pdf`);
|
||||||
|
await strapi.plugins.upload.services.upload.upload(uploadData);
|
||||||
|
strapi.log.verbose(`app:v:order-service: ✔ Uploading ${field} pdf`);
|
||||||
|
|
||||||
|
return await strapi.service("api::order.order").findOne(orderUnsafe.id);
|
||||||
|
},
|
||||||
|
|
||||||
|
sendInvoice: async (uuid: string, blob?: any) => {
|
||||||
|
strapi.log.info(`app:i:order-service: - Sending invoice for order ${uuid}`);
|
||||||
|
const oderUnsafe = (await strapi.service("api::order.order").findOneByUuid(uuid)) as Partial<Order>;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const emailConfig: MessageBody = {
|
||||||
|
to_email: oderUnsafe.email,
|
||||||
|
subject: template(invoiceEmailTemplate.subject)(oderUnsafe),
|
||||||
|
message: template(invoiceEmailTemplate.text)({ baseUrl, ...oderUnsafe }),
|
||||||
|
html: template(invoiceEmailTemplate.html)({ baseUrl, ...oderUnsafe })
|
||||||
|
};
|
||||||
|
|
||||||
|
strapi.log.debug(`app:d:order-service: - Email config ${emailConfig.html}`);
|
||||||
|
|
||||||
|
// Send email to customer
|
||||||
|
await mailApi.sendTextMessage(emailConfig);
|
||||||
|
// Send email to administator
|
||||||
|
await mailApi.sendTextMessage({
|
||||||
|
...emailConfig,
|
||||||
|
to_email: adminEmail
|
||||||
|
});
|
||||||
|
strapi.log.info(`app:i:order-service: ✔ Sending invoice for order ${uuid}`);
|
||||||
|
|
||||||
|
return await strapi.db.query("api::order.order").update({
|
||||||
|
where: {
|
||||||
|
id: oderUnsafe.id
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
invoiceSent: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
strapi.log.error(`app:e:order-service: χ Sending invoice for order ${uuid}`);
|
||||||
|
strapi.log.debug(`app:d:order-service: Error: ${JSON.stringify({ error })}`);
|
||||||
|
return new Error("Could not send invoice");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
sendDeliveryNote: async (uuid: string, blob?: ArrayBuffer) => {
|
||||||
|
strapi.log.info(`app:i:order-service: - Sending delivery note for order ${uuid}`);
|
||||||
|
const oderUnsafe = (await strapi.service("api::order.order").findOneByUuid(uuid)) as Partial<Order>;
|
||||||
|
try {
|
||||||
|
const emailConfig: MessageBody = {
|
||||||
|
to_email: oderUnsafe.email,
|
||||||
|
subject: template(deliveryNoteEmailTemplate.subject)(oderUnsafe),
|
||||||
|
message: template(deliveryNoteEmailTemplate.text)(oderUnsafe),
|
||||||
|
html: template(deliveryNoteEmailTemplate.html)({ ...oderUnsafe, baseUrl })
|
||||||
|
};
|
||||||
|
await mailApi.sendTextMessage(emailConfig);
|
||||||
|
strapi.log.info(`app:i:order-service: ✔ Sending delivery note for order ${uuid}`);
|
||||||
|
|
||||||
|
return await strapi.db.query("api::order.order").update({
|
||||||
|
where: {
|
||||||
|
id: oderUnsafe.id
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
deliveryNoteSent: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
strapi.log.error(`app:e:order-service: χ Sending delivery note for order ${uuid}`);
|
||||||
|
strapi.log.debug(`app:d:order-service: Error: ${JSON.stringify({ error })}`);
|
||||||
|
return new Error("Could not send delivery note");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
update: async (id: ID, params: Record<string, any>): Promise<Order> => {
|
||||||
|
const dataUnsafe = (await strapi.entityService.update("api::order.order", id, params)) as Order;
|
||||||
|
strapi.log.verbose(`app:v:order-service: Updated order ${id} with initial params`);
|
||||||
|
let data: { subtotal: number; total: number; VAT: number };
|
||||||
|
if (dataUnsafe?.cart) {
|
||||||
|
const productsTotal = dataUnsafe.cart.reduce((v, p) => v + (calculateTotalProductPrice(p.product) ?? 0) * p.count, 0);
|
||||||
|
const productsVAT = dataUnsafe.cart.reduce((v, p) => {
|
||||||
|
const totalProductPrice = calculateTotalProductPrice(p.product) ?? 0;
|
||||||
|
const amount = totalProductPrice / vatIncludedDecimal;
|
||||||
|
const tax = totalProductPrice - amount;
|
||||||
|
return v + tax * p.count;
|
||||||
|
}, 0);
|
||||||
|
const deliveryPrice = dataUnsafe.delivery?.price ?? 0;
|
||||||
|
const paymentPrice = dataUnsafe.payment?.price ?? 0;
|
||||||
|
strapi.log.debug(`app:d:order-service: Calculating totals`, { productsTotal, deliveryPrice, paymentPrice });
|
||||||
|
|
||||||
|
const subtotal = productsTotal;
|
||||||
|
const total = Math.round((subtotal + deliveryPrice + paymentPrice) * 100) / 100;
|
||||||
|
const VAT = Math.round((subtotal / vatIncludedDecimal) * vatDecimal * 100) / 100;
|
||||||
|
|
||||||
|
strapi.log.verbose(`app:v:order-service: Calculated totals`, { productsTotal, total, VAT, subtotal });
|
||||||
|
data = {
|
||||||
|
subtotal,
|
||||||
|
total,
|
||||||
|
VAT
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const orderUnsafe = await strapi.entityService.update("api::order.order", id, { data, ...orderDefaultParams });
|
||||||
|
strapi.log.verbose(`app:v:order-service: Updated order ${id} with calculated totals`);
|
||||||
|
orderUnsafe.cart = orderUnsafe?.cart.map((item: CartProduct) => ({
|
||||||
|
...item,
|
||||||
|
product: {
|
||||||
|
...item.product,
|
||||||
|
totalProductPrice: calculateTotalProductPrice(item.product)
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
return (await sanitize.contentAPI.output(orderUnsafe, strapi.getModel("api::order.order"))) as Order;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const orderDefaultParams = {
|
||||||
|
populate: {
|
||||||
|
invoice: true,
|
||||||
|
deliveryNote: true,
|
||||||
|
delivery: true,
|
||||||
|
payment: true,
|
||||||
|
customer: {
|
||||||
|
populate: {
|
||||||
|
addressStructured: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
invoiceAddressStructured: true,
|
||||||
|
deliveryAddressStructured: true,
|
||||||
|
cart: {
|
||||||
|
populate: {
|
||||||
|
product: productDefaultParams
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const invoicePdfBody = (order: Order): PdfBody => {
|
||||||
|
const pdfBody = defaultPdfBody(order);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...pdfBody,
|
||||||
|
subject: "RECHNUNG",
|
||||||
|
to: {
|
||||||
|
name: `${order.customer.addressStructured?.givenName} ${order.customer.addressStructured?.familyName}` || "",
|
||||||
|
address: [
|
||||||
|
order.customer.addressStructured?.streetAddress || "",
|
||||||
|
`${order.customer.addressStructured?.postalCode} ${order.customer.addressStructured?.addressLevel2}` || ""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
nr: {
|
||||||
|
...pdfBody.nr,
|
||||||
|
invoice: order.invoiceNumber
|
||||||
|
}
|
||||||
|
} as PdfBody;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deliveryNotePdfBody = (order: Order): PdfBody => {
|
||||||
|
const pdfBody = defaultPdfBody(order);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...pdfBody,
|
||||||
|
subject: "LIEFERSCHEIN",
|
||||||
|
to: {
|
||||||
|
name: "",
|
||||||
|
address: order.deliveryAddress.split("\n")
|
||||||
|
},
|
||||||
|
nr: {
|
||||||
|
...pdfBody.nr,
|
||||||
|
shipping: order.deliveryNoteNumber
|
||||||
|
}
|
||||||
|
} as PdfBody;
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultPdfBody = (order: Order): Partial<PdfBody> => ({
|
||||||
|
date: new Date(order.acceptedTermsAndConditionsAt).toLocaleDateString("de-DE"),
|
||||||
|
nr: {
|
||||||
|
customer: `${order.customer.id}`,
|
||||||
|
order: `${order.id}`
|
||||||
|
},
|
||||||
|
service:
|
||||||
|
order.cart?.map((cartProduct) => ({
|
||||||
|
description: cartProduct.product.name,
|
||||||
|
price: {
|
||||||
|
per_unit: calculateTotalProductPrice(cartProduct.product) || 0,
|
||||||
|
total: (calculateTotalProductPrice(cartProduct.product) || 0) * cartProduct.count
|
||||||
|
},
|
||||||
|
count: cartProduct.count,
|
||||||
|
nr: `P${cartProduct.product.id}`
|
||||||
|
})) || [],
|
||||||
|
currency: "\\euro",
|
||||||
|
body: "Vielen Dank für Ihren Einkauf und Ihr Vertrauen.",
|
||||||
|
total: order.total,
|
||||||
|
subtotal: order.subtotal,
|
||||||
|
VAT: {
|
||||||
|
amount: order.VAT,
|
||||||
|
rate: vatDecimal * 100
|
||||||
|
},
|
||||||
|
shipping: order.delivery?.price ?? 0
|
||||||
|
});
|
||||||
26
src/api/payment/content-types/payment/schema.json
Normal file
26
src/api/payment/content-types/payment/schema.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"kind": "collectionType",
|
||||||
|
"collectionName": "payments",
|
||||||
|
"info": {
|
||||||
|
"singularName": "payment",
|
||||||
|
"pluralName": "payments",
|
||||||
|
"displayName": "Payment",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"privateAttributes": ["created_at", "updated_at", "created_by", "updated_by", "createdAt", "updatedAt"],
|
||||||
|
"draftAndPublish": false
|
||||||
|
},
|
||||||
|
"pluginOptions": {},
|
||||||
|
"attributes": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"type": "decimal"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/api/payment/controllers/payment.ts
Normal file
7
src/api/payment/controllers/payment.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* payment controller
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreController("api::payment.payment");
|
||||||
508
src/api/payment/documentation/1.0.0/payment.json
Normal file
508
src/api/payment/documentation/1.0.0/payment.json
Normal file
@@ -0,0 +1,508 @@
|
|||||||
|
{
|
||||||
|
"/payments": {
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/PaymentListResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Payment"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "sort",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Sort by attributes ascending (asc) or descending (desc)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[withCount]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Return page/pageSize (default: true)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[page]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page number (default: 0)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[pageSize]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page size (default: 25)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[start]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Offset value (default: 0)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[limit]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Number of entities to return (default: 25)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fields",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Fields to return (ex: title,author)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "populate",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Relations to return",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "filters",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Filters to apply",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"style": "deepObject"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "locale",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Locale to apply",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "get/payments"
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/PaymentResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Payment"
|
||||||
|
],
|
||||||
|
"parameters": [],
|
||||||
|
"operationId": "post/payments",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/PaymentRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/payments/{id}": {
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/PaymentResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Payment"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "get/payments/{id}"
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/PaymentResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Payment"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "put/payments/{id}",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/PaymentRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Payment"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "delete/payments/{id}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/api/payment/routes/payment.ts
Normal file
7
src/api/payment/routes/payment.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* payment router
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreRouter("api::payment.payment");
|
||||||
7
src/api/payment/services/payment.ts
Normal file
7
src/api/payment/services/payment.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* payment service
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreService("api::payment.payment");
|
||||||
213
src/api/product-category/controllers/product-category.ts
Normal file
213
src/api/product-category/controllers/product-category.ts
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
/**
|
||||||
|
* Product category controller
|
||||||
|
*/
|
||||||
|
import { ProductCategory, ProductImage } from "../../../../types";
|
||||||
|
|
||||||
|
export default () => ({
|
||||||
|
// Get all categories
|
||||||
|
categories: async (ctx, next) => {
|
||||||
|
try {
|
||||||
|
ctx.body = await strapi.service("api::product-category.product-category").categories();
|
||||||
|
} catch (err) {
|
||||||
|
ctx.badRequest("Product category controller error", { moreDetails: err });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
corruptCategories: async (ctx, next) => {
|
||||||
|
try {
|
||||||
|
const categories = await strapi.service("api::product-category.product-category").categories();
|
||||||
|
const corruptedCategories = categories.filter((category) => !category?.product_cover || !category?.product_pattern);
|
||||||
|
strapi.log.info(`app:i:product-category-controller: Found ${corruptedCategories?.length} categories with missing cover or pattern`);
|
||||||
|
strapi.log.silly("app:d:product-category-controller:", { corruptedCategories });
|
||||||
|
ctx.body = corruptedCategories;
|
||||||
|
} catch (err) {
|
||||||
|
ctx.badRequest("Product category controller error", { moreDetails: err });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get single category
|
||||||
|
category: async (ctx, next) => {
|
||||||
|
try {
|
||||||
|
const { id } = ctx.params;
|
||||||
|
ctx.body = await strapi.service("api::product-category.product-category").category(id);
|
||||||
|
} catch (err) {
|
||||||
|
ctx.badRequest("Product category controller error", { moreDetails: err });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Update single category
|
||||||
|
updateCategory: async (ctx, next) => {
|
||||||
|
try {
|
||||||
|
const { id } = ctx.params;
|
||||||
|
const data = ctx.request.body;
|
||||||
|
ctx.body = await strapi.service("api::product-category.product-category").updateCategory(id, data);
|
||||||
|
} catch (err) {
|
||||||
|
ctx.badRequest("Product category controller error", { moreDetails: err });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Update multiple categories
|
||||||
|
updateCategories: async (ctx, next) => {
|
||||||
|
try {
|
||||||
|
const { where, data } = ctx.request.body;
|
||||||
|
|
||||||
|
if (!where || !data) {
|
||||||
|
return ctx.badRequest("Request body must include 'where' and 'data' properties");
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.body = await strapi.service("api::product-category.product-category").updateCategories({ where, data });
|
||||||
|
} catch (err) {
|
||||||
|
ctx.badRequest("Product category controller error", { moreDetails: err });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
fixCategories: async (ctx, next) => {
|
||||||
|
try {
|
||||||
|
// find categories with empty cover or pattern
|
||||||
|
const categories: ProductCategory[] = await strapi.service("api::product-category.product-category").categories({
|
||||||
|
filters: {
|
||||||
|
$or: [{ product_cover: null }, { product_pattern: null }]
|
||||||
|
},
|
||||||
|
populate: {
|
||||||
|
product_cover: true,
|
||||||
|
product_pattern: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
strapi.log.info(`app:i:product-category-controller: Found ${categories?.length ?? 0} categories with missing cover or pattern`);
|
||||||
|
strapi.log.silly("app:d:product-category-controller:", { categories });
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
fixed: [],
|
||||||
|
failed: []
|
||||||
|
};
|
||||||
|
// fix categories with empty cover or pattern by name, e.g. "Notizheft – Geometrische Muster #03"
|
||||||
|
for (const category of categories) {
|
||||||
|
const [coverName, patternName] = (category.name as string).split(" – ");
|
||||||
|
const patterns = await strapi.entityService.findMany("api::product-pattern.product-pattern", {
|
||||||
|
filters: {
|
||||||
|
name: patternName
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const coverNameChunks = coverName.split(" ");
|
||||||
|
const covers = await strapi.entityService.findMany("api::product-cover.product-cover", {
|
||||||
|
filters: {
|
||||||
|
$or: coverNameChunks.map(($containsi: string) => ({ name: { $containsi } }))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const pattern = patterns.pop();
|
||||||
|
const cover = covers.pop();
|
||||||
|
|
||||||
|
strapi.log.silly("app:d:product-category-controller:", { cover, pattern });
|
||||||
|
|
||||||
|
if (!pattern || !cover) {
|
||||||
|
result.failed.push(category);
|
||||||
|
strapi.log.warn(`app:w:product-category-controller: Could not find pattern or cover for category ${category?.id}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const updatedCategory: ProductCategory = await strapi.service("api::product-category.product-category").updateCategory(category?.id, {
|
||||||
|
product_pattern: category?.product_pattern ?? pattern?.id,
|
||||||
|
product_cover: category?.product_cover ?? cover?.id
|
||||||
|
});
|
||||||
|
result.fixed.push(updatedCategory);
|
||||||
|
strapi.log.info(`app:i:product-category-controller: Updated category ${category?.id} with pattern ${pattern?.id}`);
|
||||||
|
strapi.log.silly("app:d:product-category-controller:", { updatedCategory });
|
||||||
|
}
|
||||||
|
|
||||||
|
strapi.log.info(
|
||||||
|
`app:i:product-category-controller: Fixed ${result.fixed.length} / Failed ${result.failed.length} categories with missing cover or pattern`
|
||||||
|
);
|
||||||
|
|
||||||
|
ctx.body = result;
|
||||||
|
} catch (err) {
|
||||||
|
ctx.badRequest("Product category controller error", { moreDetails: err });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
fixProductImages: async (ctx, next) => {
|
||||||
|
try {
|
||||||
|
// Find categories/product-images that have no products but have both cover and pattern
|
||||||
|
const categories = await strapi.service("api::product-category.product-category").categories({
|
||||||
|
populate: {
|
||||||
|
products: true,
|
||||||
|
product_cover: true,
|
||||||
|
product_pattern: true
|
||||||
|
},
|
||||||
|
filters: {
|
||||||
|
products: { $eq: null },
|
||||||
|
product_cover: { $ne: null },
|
||||||
|
product_pattern: { $ne: null }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
fixed: [],
|
||||||
|
failed: []
|
||||||
|
};
|
||||||
|
|
||||||
|
strapi.log.info(`app:i:product-category-controller: Found ${categories?.length ?? 0} categories with missing products`);
|
||||||
|
strapi.log.silly("app:d:product-category-controller:", { categories });
|
||||||
|
|
||||||
|
for (const category of categories) {
|
||||||
|
try {
|
||||||
|
const coverId = category.product_cover?.id;
|
||||||
|
const patternId = category.product_pattern?.id;
|
||||||
|
|
||||||
|
if (!coverId || !patternId) {
|
||||||
|
result.failed.push({
|
||||||
|
categoryId: category.id,
|
||||||
|
reason: "Missing cover or pattern ID"
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all products that match both the cover and pattern IDs
|
||||||
|
const matchingProducts = await strapi.entityService.findMany("api::product.product", {
|
||||||
|
filters: {
|
||||||
|
$and: [{ cover: { id: coverId } }, { pattern: { id: patternId } }]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!matchingProducts?.length) {
|
||||||
|
result.failed.push({
|
||||||
|
categoryId: category.id,
|
||||||
|
coverId,
|
||||||
|
patternId,
|
||||||
|
reason: "No matching products found"
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the category with the found products
|
||||||
|
const updatedCategory = await strapi.service("api::product-category.product-category").updateCategory(category.id, {
|
||||||
|
products: matchingProducts.map((product) => product.id)
|
||||||
|
});
|
||||||
|
|
||||||
|
result.fixed.push({
|
||||||
|
categoryId: category.id,
|
||||||
|
productCount: matchingProducts.length,
|
||||||
|
products: matchingProducts.map((p) => p.id)
|
||||||
|
});
|
||||||
|
|
||||||
|
strapi.log.info(`app:i:product-category-controller: Updated category ${category.id} with ${matchingProducts.length} products`);
|
||||||
|
strapi.log.silly("app:d:product-category-controller: Updated category details:", {
|
||||||
|
categoryId: category.id,
|
||||||
|
products: matchingProducts.map((p) => p.id)
|
||||||
|
});
|
||||||
|
} catch (categoryError) {
|
||||||
|
result.failed.push({
|
||||||
|
categoryId: category.id,
|
||||||
|
reason: "Error processing category",
|
||||||
|
error: categoryError.message
|
||||||
|
});
|
||||||
|
strapi.log.error(`app:e:product-category-controller: Error processing category ${category.id}:`, categoryError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
strapi.log.info(`app:i:product-category-controller: Fixed ${result.fixed.length} / Failed ${result.failed.length} categories`);
|
||||||
|
|
||||||
|
ctx.body = result;
|
||||||
|
} catch (err) {
|
||||||
|
ctx.badRequest("Product category controller error", { moreDetails: err });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
73
src/api/product-category/routes/product-category.ts
Normal file
73
src/api/product-category/routes/product-category.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
export default {
|
||||||
|
routes: [
|
||||||
|
// Get all categories
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
path: "/product-category",
|
||||||
|
handler: "product-category.categories",
|
||||||
|
config: {
|
||||||
|
policies: [],
|
||||||
|
middlewares: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Get single category
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
path: "/product-category/:id",
|
||||||
|
handler: "product-category.category",
|
||||||
|
config: {
|
||||||
|
policies: [],
|
||||||
|
middlewares: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
path: "/product-category/corrupt",
|
||||||
|
handler: "product-category.corruptCategories",
|
||||||
|
config: {
|
||||||
|
policies: [],
|
||||||
|
middlewares: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Bulk update categories
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
path: "/product-category/bulk",
|
||||||
|
handler: "product-category.updateCategories",
|
||||||
|
config: {
|
||||||
|
policies: [],
|
||||||
|
middlewares: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Bulk fix categories
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
path: "/product-category/fix",
|
||||||
|
handler: "product-category.fixCategories",
|
||||||
|
config: {
|
||||||
|
policies: [],
|
||||||
|
middlewares: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Bulk fix product images
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
path: "/product-category/fix-images",
|
||||||
|
handler: "product-category.fixProductImages",
|
||||||
|
config: {
|
||||||
|
policies: [],
|
||||||
|
middlewares: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Update single category
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
path: "/product-category/:id",
|
||||||
|
handler: "product-category.updateCategory",
|
||||||
|
config: {
|
||||||
|
policies: [],
|
||||||
|
middlewares: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
80
src/api/product-category/services/product-category.ts
Normal file
80
src/api/product-category/services/product-category.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* Extended product-category service
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface CategoryUpdate {
|
||||||
|
where: any;
|
||||||
|
data: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default () => ({
|
||||||
|
// Get all categories
|
||||||
|
categories: async (params?: Record<string, any>) => {
|
||||||
|
try {
|
||||||
|
return await strapi.entityService.findMany("api::product-image.product-image", {
|
||||||
|
populate: {
|
||||||
|
product_cover: true,
|
||||||
|
product_pattern: true,
|
||||||
|
images: true
|
||||||
|
},
|
||||||
|
...params
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get single category by ID
|
||||||
|
category: async (id: number) => {
|
||||||
|
try {
|
||||||
|
return await strapi.entityService.findOne("api::product-image.product-image", id, {
|
||||||
|
populate: {
|
||||||
|
product_cover: true,
|
||||||
|
product_pattern: true,
|
||||||
|
images: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Update single category
|
||||||
|
updateCategory: async (id: number, data: any) => {
|
||||||
|
try {
|
||||||
|
return await strapi.entityService.update("api::product-image.product-image", id, {
|
||||||
|
data,
|
||||||
|
populate: {
|
||||||
|
product_cover: true,
|
||||||
|
product_pattern: true,
|
||||||
|
images: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Update multiple categories using updateMany
|
||||||
|
updateCategories: async (updates: CategoryUpdate) => {
|
||||||
|
try {
|
||||||
|
const result = await strapi.db.query("api::product-image.product-image").updateMany(updates);
|
||||||
|
|
||||||
|
// If you need the updated records with populated relations
|
||||||
|
if (result.count > 0) {
|
||||||
|
return await strapi.entityService.findMany("api::product-image.product-image", {
|
||||||
|
filters: updates.where,
|
||||||
|
populate: {
|
||||||
|
product_cover: true,
|
||||||
|
product_pattern: true,
|
||||||
|
images: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { ProductCover } from "../../../../../types";
|
||||||
|
import { createComponentLifecycle } from "../../../../utils/createComponentLifecycle";
|
||||||
|
export default createComponentLifecycle<ProductCover>("cover");
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"kind": "collectionType",
|
||||||
|
"collectionName": "product_covers",
|
||||||
|
"info": {
|
||||||
|
"singularName": "product-cover",
|
||||||
|
"pluralName": "product-covers",
|
||||||
|
"displayName": "Product Covers",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"draftAndPublish": true
|
||||||
|
},
|
||||||
|
"pluginOptions": {},
|
||||||
|
"attributes": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"binding": {
|
||||||
|
"type": "enumeration",
|
||||||
|
"enum": [
|
||||||
|
"Fadenheftung",
|
||||||
|
"Steppstich",
|
||||||
|
"Wire-O-Bindung"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"slides": {
|
||||||
|
"type": "media",
|
||||||
|
"multiple": true,
|
||||||
|
"required": false,
|
||||||
|
"allowedTypes": [
|
||||||
|
"images"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"copyText": {
|
||||||
|
"type": "json"
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"type": "media",
|
||||||
|
"multiple": false,
|
||||||
|
"required": false,
|
||||||
|
"allowedTypes": [
|
||||||
|
"images"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"type": "media",
|
||||||
|
"multiple": false,
|
||||||
|
"required": false,
|
||||||
|
"allowedTypes": [
|
||||||
|
"images"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"type": "decimal"
|
||||||
|
},
|
||||||
|
"products": {
|
||||||
|
"type": "relation",
|
||||||
|
"relation": "oneToMany",
|
||||||
|
"target": "api::product.product",
|
||||||
|
"mappedBy": "cover"
|
||||||
|
},
|
||||||
|
"sort": {
|
||||||
|
"type": "integer",
|
||||||
|
"default": 0,
|
||||||
|
"min": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/api/product-cover/controllers/product-cover.ts
Normal file
7
src/api/product-cover/controllers/product-cover.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* product-cover controller
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreController("api::product-cover.product-cover");
|
||||||
508
src/api/product-cover/documentation/1.0.0/product-cover.json
Normal file
508
src/api/product-cover/documentation/1.0.0/product-cover.json
Normal file
@@ -0,0 +1,508 @@
|
|||||||
|
{
|
||||||
|
"/product-covers": {
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductCoverListResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product-cover"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "sort",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Sort by attributes ascending (asc) or descending (desc)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[withCount]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Return page/pageSize (default: true)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[page]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page number (default: 0)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[pageSize]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page size (default: 25)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[start]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Offset value (default: 0)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[limit]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Number of entities to return (default: 25)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fields",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Fields to return (ex: title,author)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "populate",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Relations to return",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "filters",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Filters to apply",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"style": "deepObject"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "locale",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Locale to apply",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "get/product-covers"
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductCoverResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product-cover"
|
||||||
|
],
|
||||||
|
"parameters": [],
|
||||||
|
"operationId": "post/product-covers",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductCoverRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/product-covers/{id}": {
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductCoverResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product-cover"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "get/product-covers/{id}"
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductCoverResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product-cover"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "put/product-covers/{id}",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductCoverRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product-cover"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "delete/product-covers/{id}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/api/product-cover/routes/product-cover.ts
Normal file
7
src/api/product-cover/routes/product-cover.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* product-cover router
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreRouter("api::product-cover.product-cover");
|
||||||
7
src/api/product-cover/services/product-cover.ts
Normal file
7
src/api/product-cover/services/product-cover.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* product-cover service
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreService("api::product-cover.product-cover");
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { ProductImage } from "../../../../../types";
|
||||||
|
import { createComponentLifecycle } from "../../../../utils/createComponentLifecycle";
|
||||||
|
export default createComponentLifecycle<ProductImage>("image");
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"kind": "collectionType",
|
||||||
|
"collectionName": "product_images",
|
||||||
|
"info": {
|
||||||
|
"singularName": "product-image",
|
||||||
|
"pluralName": "product-images",
|
||||||
|
"displayName": "Product Images",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"draftAndPublish": false
|
||||||
|
},
|
||||||
|
"pluginOptions": {},
|
||||||
|
"attributes": {
|
||||||
|
"images": {
|
||||||
|
"type": "media",
|
||||||
|
"multiple": true,
|
||||||
|
"required": false,
|
||||||
|
"allowedTypes": [
|
||||||
|
"images"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"products": {
|
||||||
|
"type": "relation",
|
||||||
|
"relation": "oneToMany",
|
||||||
|
"target": "api::product.product",
|
||||||
|
"mappedBy": "images"
|
||||||
|
},
|
||||||
|
"product_cover": {
|
||||||
|
"type": "relation",
|
||||||
|
"relation": "oneToOne",
|
||||||
|
"target": "api::product-cover.product-cover"
|
||||||
|
},
|
||||||
|
"product_pattern": {
|
||||||
|
"type": "relation",
|
||||||
|
"relation": "oneToOne",
|
||||||
|
"target": "api::product-pattern.product-pattern"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/api/product-image/controllers/product-image.ts
Normal file
7
src/api/product-image/controllers/product-image.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* product-image controller
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreController("api::product-image.product-image");
|
||||||
508
src/api/product-image/documentation/1.0.0/product-image.json
Normal file
508
src/api/product-image/documentation/1.0.0/product-image.json
Normal file
@@ -0,0 +1,508 @@
|
|||||||
|
{
|
||||||
|
"/product-images": {
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductImageListResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product-image"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "sort",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Sort by attributes ascending (asc) or descending (desc)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[withCount]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Return page/pageSize (default: true)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[page]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page number (default: 0)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[pageSize]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page size (default: 25)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[start]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Offset value (default: 0)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[limit]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Number of entities to return (default: 25)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fields",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Fields to return (ex: title,author)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "populate",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Relations to return",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "filters",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Filters to apply",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"style": "deepObject"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "locale",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Locale to apply",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "get/product-images"
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductImageResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product-image"
|
||||||
|
],
|
||||||
|
"parameters": [],
|
||||||
|
"operationId": "post/product-images",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductImageRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/product-images/{id}": {
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductImageResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product-image"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "get/product-images/{id}"
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductImageResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product-image"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "put/product-images/{id}",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductImageRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product-image"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "delete/product-images/{id}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/api/product-image/routes/product-image.ts
Normal file
7
src/api/product-image/routes/product-image.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* product-image router
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreRouter("api::product-image.product-image");
|
||||||
7
src/api/product-image/services/product-image.ts
Normal file
7
src/api/product-image/services/product-image.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* product-image service
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreService("api::product-image.product-image");
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { ProductPages } from "../../../../../types";
|
||||||
|
import { createComponentLifecycle } from "../../../../utils/createComponentLifecycle";
|
||||||
|
export default createComponentLifecycle<ProductPages>("pages");
|
||||||
42
src/api/product-page/content-types/product-page/schema.json
Normal file
42
src/api/product-page/content-types/product-page/schema.json
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"kind": "collectionType",
|
||||||
|
"collectionName": "product_pages",
|
||||||
|
"info": {
|
||||||
|
"singularName": "product-page",
|
||||||
|
"pluralName": "product-pages",
|
||||||
|
"displayName": "Product Pages",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"privateAttributes": [
|
||||||
|
"createdAt",
|
||||||
|
"updatedAt",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"created_by",
|
||||||
|
"updated_by",
|
||||||
|
"published_at",
|
||||||
|
"publishedAt",
|
||||||
|
"published_by",
|
||||||
|
"publishedBy"
|
||||||
|
],
|
||||||
|
"draftAndPublish": false
|
||||||
|
},
|
||||||
|
"pluginOptions": {},
|
||||||
|
"attributes": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"required": true,
|
||||||
|
"default": "name"
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"type": "decimal"
|
||||||
|
},
|
||||||
|
"products": {
|
||||||
|
"type": "relation",
|
||||||
|
"relation": "oneToMany",
|
||||||
|
"target": "api::product.product",
|
||||||
|
"mappedBy": "pages"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/api/product-page/controllers/product-page.ts
Normal file
7
src/api/product-page/controllers/product-page.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* product-page controller
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreController("api::product-page.product-page");
|
||||||
508
src/api/product-page/documentation/1.0.0/product-page.json
Normal file
508
src/api/product-page/documentation/1.0.0/product-page.json
Normal file
@@ -0,0 +1,508 @@
|
|||||||
|
{
|
||||||
|
"/product-pages": {
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductPageListResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product-page"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "sort",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Sort by attributes ascending (asc) or descending (desc)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[withCount]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Return page/pageSize (default: true)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[page]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page number (default: 0)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[pageSize]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page size (default: 25)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[start]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Offset value (default: 0)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[limit]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Number of entities to return (default: 25)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fields",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Fields to return (ex: title,author)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "populate",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Relations to return",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "filters",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Filters to apply",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"style": "deepObject"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "locale",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Locale to apply",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "get/product-pages"
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductPageResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product-page"
|
||||||
|
],
|
||||||
|
"parameters": [],
|
||||||
|
"operationId": "post/product-pages",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductPageRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/product-pages/{id}": {
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductPageResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product-page"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "get/product-pages/{id}"
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductPageResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product-page"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "put/product-pages/{id}",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductPageRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product-page"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "delete/product-pages/{id}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/api/product-page/routes/product-page.ts
Normal file
7
src/api/product-page/routes/product-page.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* product-page router
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreRouter("api::product-page.product-page");
|
||||||
7
src/api/product-page/services/product-page.ts
Normal file
7
src/api/product-page/services/product-page.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* product-page service
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreService("api::product-page.product-page");
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { ProductPattern } from "../../../../../types";
|
||||||
|
import { createComponentLifecycle } from "../../../../utils/createComponentLifecycle";
|
||||||
|
export default createComponentLifecycle<ProductPattern>("pattern");
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"kind": "collectionType",
|
||||||
|
"collectionName": "product_patterns",
|
||||||
|
"info": {
|
||||||
|
"singularName": "product-pattern",
|
||||||
|
"pluralName": "product-patterns",
|
||||||
|
"displayName": "Product Patterns",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"privateAttributes": [
|
||||||
|
"createdAt",
|
||||||
|
"updatedAt",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"created_by",
|
||||||
|
"updated_by",
|
||||||
|
"published_at",
|
||||||
|
"publishedAt",
|
||||||
|
"published_by",
|
||||||
|
"publishedBy"
|
||||||
|
],
|
||||||
|
"draftAndPublish": false
|
||||||
|
},
|
||||||
|
"pluginOptions": {},
|
||||||
|
"attributes": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"type": "media",
|
||||||
|
"multiple": false,
|
||||||
|
"required": false,
|
||||||
|
"allowedTypes": [
|
||||||
|
"images"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"products": {
|
||||||
|
"type": "relation",
|
||||||
|
"relation": "oneToMany",
|
||||||
|
"target": "api::product.product",
|
||||||
|
"mappedBy": "pattern"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/api/product-pattern/controllers/product-pattern.ts
Normal file
7
src/api/product-pattern/controllers/product-pattern.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* product-pattern controller
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreController("api::product-pattern.product-pattern");
|
||||||
508
src/api/product-pattern/documentation/1.0.0/product-pattern.json
Normal file
508
src/api/product-pattern/documentation/1.0.0/product-pattern.json
Normal file
@@ -0,0 +1,508 @@
|
|||||||
|
{
|
||||||
|
"/product-patterns": {
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductPatternListResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product-pattern"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "sort",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Sort by attributes ascending (asc) or descending (desc)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[withCount]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Return page/pageSize (default: true)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[page]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page number (default: 0)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[pageSize]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page size (default: 25)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[start]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Offset value (default: 0)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[limit]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Number of entities to return (default: 25)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fields",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Fields to return (ex: title,author)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "populate",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Relations to return",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "filters",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Filters to apply",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"style": "deepObject"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "locale",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Locale to apply",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "get/product-patterns"
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductPatternResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product-pattern"
|
||||||
|
],
|
||||||
|
"parameters": [],
|
||||||
|
"operationId": "post/product-patterns",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductPatternRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/product-patterns/{id}": {
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductPatternResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product-pattern"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "get/product-patterns/{id}"
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductPatternResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product-pattern"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "put/product-patterns/{id}",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductPatternRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product-pattern"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "delete/product-patterns/{id}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/api/product-pattern/routes/product-pattern.ts
Normal file
7
src/api/product-pattern/routes/product-pattern.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* product-pattern router
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreRouter("api::product-pattern.product-pattern");
|
||||||
7
src/api/product-pattern/services/product-pattern.ts
Normal file
7
src/api/product-pattern/services/product-pattern.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* product-pattern service
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreService("api::product-pattern.product-pattern");
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { ProductRuling } from "../../../../../types";
|
||||||
|
import { createComponentLifecycle } from "../../../../utils/createComponentLifecycle";
|
||||||
|
export default createComponentLifecycle<ProductRuling>("ruling");
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"kind": "collectionType",
|
||||||
|
"collectionName": "product_rulings",
|
||||||
|
"info": {
|
||||||
|
"singularName": "product-ruling",
|
||||||
|
"pluralName": "product-rulings",
|
||||||
|
"displayName": "Product Ruling",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"privateAttributes": [
|
||||||
|
"createdAt",
|
||||||
|
"updatedAt",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"created_by",
|
||||||
|
"updated_by",
|
||||||
|
"published_at",
|
||||||
|
"publishedAt",
|
||||||
|
"published_by",
|
||||||
|
"publishedBy"
|
||||||
|
],
|
||||||
|
"draftAndPublish": false
|
||||||
|
},
|
||||||
|
"pluginOptions": {},
|
||||||
|
"attributes": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"type": "decimal"
|
||||||
|
},
|
||||||
|
"products": {
|
||||||
|
"type": "relation",
|
||||||
|
"relation": "oneToMany",
|
||||||
|
"target": "api::product.product",
|
||||||
|
"mappedBy": "ruling"
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"allowedTypes": [
|
||||||
|
"images"
|
||||||
|
],
|
||||||
|
"type": "media",
|
||||||
|
"multiple": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/api/product-ruling/controllers/product-ruling.ts
Normal file
7
src/api/product-ruling/controllers/product-ruling.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* product-ruling controller
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreController("api::product-ruling.product-ruling");
|
||||||
508
src/api/product-ruling/documentation/1.0.0/product-ruling.json
Normal file
508
src/api/product-ruling/documentation/1.0.0/product-ruling.json
Normal file
@@ -0,0 +1,508 @@
|
|||||||
|
{
|
||||||
|
"/product-rulings": {
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductRulingListResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product-ruling"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "sort",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Sort by attributes ascending (asc) or descending (desc)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[withCount]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Return page/pageSize (default: true)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[page]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page number (default: 0)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[pageSize]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page size (default: 25)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[start]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Offset value (default: 0)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[limit]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Number of entities to return (default: 25)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fields",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Fields to return (ex: title,author)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "populate",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Relations to return",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "filters",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Filters to apply",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"style": "deepObject"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "locale",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Locale to apply",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "get/product-rulings"
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductRulingResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product-ruling"
|
||||||
|
],
|
||||||
|
"parameters": [],
|
||||||
|
"operationId": "post/product-rulings",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductRulingRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/product-rulings/{id}": {
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductRulingResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product-ruling"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "get/product-rulings/{id}"
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductRulingResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product-ruling"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "put/product-rulings/{id}",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductRulingRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product-ruling"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "delete/product-rulings/{id}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/api/product-ruling/routes/product-ruling.ts
Normal file
7
src/api/product-ruling/routes/product-ruling.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* product-ruling router
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreRouter("api::product-ruling.product-ruling");
|
||||||
7
src/api/product-ruling/services/product-ruling.ts
Normal file
7
src/api/product-ruling/services/product-ruling.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* product-ruling service
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreService("api::product-ruling.product-ruling");
|
||||||
83
src/api/product/content-types/product/lifecycles.ts
Normal file
83
src/api/product/content-types/product/lifecycles.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import { Product } from "../../../../../types";
|
||||||
|
import { calculateTotalProductPrice } from "../../services/product";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
async beforeCreate(event: {
|
||||||
|
params: {
|
||||||
|
data: any;
|
||||||
|
};
|
||||||
|
}) {
|
||||||
|
const { params } = event;
|
||||||
|
strapi.log.verbose("app:v:product-lifecycle: Before create", {
|
||||||
|
params
|
||||||
|
});
|
||||||
|
const cover = await strapi.entityService.findOne("api::product-cover.product-cover", params.data?.cover, { fields: ["price"] });
|
||||||
|
const pattern = await strapi.entityService.findOne("api::product-pattern.product-pattern", params.data?.pattern);
|
||||||
|
const pages = await strapi.entityService.findOne("api::product-page.product-page", params.data?.pages, { fields: ["price"] });
|
||||||
|
const ruling = await strapi.entityService.findOne("api::product-ruling.product-ruling", params.data?.ruling, { fields: ["price"] });
|
||||||
|
const product = {
|
||||||
|
...params.data,
|
||||||
|
cover,
|
||||||
|
pattern,
|
||||||
|
pages,
|
||||||
|
ruling
|
||||||
|
} as Partial<Product>;
|
||||||
|
strapi.log.verbose("app:v:product-lifecycle: Created product", product);
|
||||||
|
|
||||||
|
params.data.totalPrice = calculateTotalProductPrice(product);
|
||||||
|
},
|
||||||
|
|
||||||
|
async afterCreate(event: { result: Product }) {
|
||||||
|
const { result } = event;
|
||||||
|
strapi.log.verbose("app:v:product-lifecycle: Created", {
|
||||||
|
id: result.id
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async beforeUpdate(event: { params: any }) {
|
||||||
|
const { params } = event;
|
||||||
|
strapi.log.verbose("app:v:product-lifecycle: Before update", {
|
||||||
|
params
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async afterUpdate(event: { result: Product }) {
|
||||||
|
const { result } = event;
|
||||||
|
strapi.log.verbose("app:v:product-lifecycle: Updated", {
|
||||||
|
id: result.id
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async beforeDelete(event: { params: Product }) {
|
||||||
|
const { params } = event;
|
||||||
|
strapi.log.verbose("app:v:product-lifecycle: Before delete", {
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// async afterFindOne(event: { result: any; }) {
|
||||||
|
// const { result } = event;
|
||||||
|
// strapi.log.verbose("app:v:product-lifecycle: Found one", {
|
||||||
|
// result
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
|
||||||
|
// async afterFindMany(event: any) {
|
||||||
|
// strapi.log.verbose("app:v:product-lifecycle: Before found many", {
|
||||||
|
// event
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// const resultWithTotal = event.result.map((product: Product) => {
|
||||||
|
// return {
|
||||||
|
// ...product,
|
||||||
|
// totalProductPrice: calculateTotalProductPrice(product)
|
||||||
|
// };
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// strapi.log.verbose("app:v:product-lifecycle: Added total product prices", resultWithTotal.map((p: Product) => p.totalProductPrice));
|
||||||
|
//
|
||||||
|
// event.result = resultWithTotal;
|
||||||
|
//
|
||||||
|
// return event;
|
||||||
|
// }
|
||||||
|
};
|
||||||
59
src/api/product/content-types/product/schema.json
Normal file
59
src/api/product/content-types/product/schema.json
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"kind": "collectionType",
|
||||||
|
"collectionName": "products",
|
||||||
|
"info": {
|
||||||
|
"singularName": "product",
|
||||||
|
"pluralName": "products",
|
||||||
|
"displayName": "Product",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"draftAndPublish": true
|
||||||
|
},
|
||||||
|
"pluginOptions": {},
|
||||||
|
"attributes": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"totalPrice": {
|
||||||
|
"type": "decimal"
|
||||||
|
},
|
||||||
|
"cover": {
|
||||||
|
"type": "relation",
|
||||||
|
"relation": "manyToOne",
|
||||||
|
"target": "api::product-cover.product-cover",
|
||||||
|
"inversedBy": "products"
|
||||||
|
},
|
||||||
|
"pattern": {
|
||||||
|
"type": "relation",
|
||||||
|
"relation": "manyToOne",
|
||||||
|
"target": "api::product-pattern.product-pattern",
|
||||||
|
"inversedBy": "products"
|
||||||
|
},
|
||||||
|
"pages": {
|
||||||
|
"type": "relation",
|
||||||
|
"relation": "manyToOne",
|
||||||
|
"target": "api::product-page.product-page",
|
||||||
|
"inversedBy": "products"
|
||||||
|
},
|
||||||
|
"ruling": {
|
||||||
|
"type": "relation",
|
||||||
|
"relation": "manyToOne",
|
||||||
|
"target": "api::product-ruling.product-ruling",
|
||||||
|
"inversedBy": "products"
|
||||||
|
},
|
||||||
|
"slug": {
|
||||||
|
"type": "uid",
|
||||||
|
"targetField": "name"
|
||||||
|
},
|
||||||
|
"images": {
|
||||||
|
"type": "relation",
|
||||||
|
"relation": "manyToOne",
|
||||||
|
"target": "api::product-image.product-image",
|
||||||
|
"inversedBy": "products"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
442
src/api/product/controllers/product.ts
Normal file
442
src/api/product/controllers/product.ts
Normal file
@@ -0,0 +1,442 @@
|
|||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
import { sanitize } from "@strapi/utils";
|
||||||
|
import { Result, PaginatedResult } from "@strapi/types/dist/modules/entity-service/result";
|
||||||
|
import { ID } from "@strapi/database/dist/types";
|
||||||
|
import { Product, ProductPattern } from "../../../../types";
|
||||||
|
import { productDefaultParams, calculateTotalProductPrice } from "../services/product";
|
||||||
|
import { maxProductsSitemap } from "../../../../config/constants";
|
||||||
|
|
||||||
|
export interface ProductVariantParams {
|
||||||
|
populate: {
|
||||||
|
pattern: any;
|
||||||
|
cover: any;
|
||||||
|
ruling: any;
|
||||||
|
pages: any;
|
||||||
|
images: any;
|
||||||
|
};
|
||||||
|
pagination: {
|
||||||
|
page: number;
|
||||||
|
pageSize: number;
|
||||||
|
};
|
||||||
|
filters?: any;
|
||||||
|
publicationState?: "live" | "preview";
|
||||||
|
}
|
||||||
|
|
||||||
|
const productVariantParams: ProductVariantParams = {
|
||||||
|
populate: {
|
||||||
|
pattern: { fields: ["id"] },
|
||||||
|
cover: { fields: ["id"] },
|
||||||
|
ruling: { fields: ["id"] },
|
||||||
|
pages: { fields: ["id"] },
|
||||||
|
images: {
|
||||||
|
populate: {
|
||||||
|
images: {
|
||||||
|
fields: ["formats", "url"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pagination: { page: 1, pageSize: 999 },
|
||||||
|
publicationState: "live"
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Product controller with custom actions
|
||||||
|
*/
|
||||||
|
export default factories.createCoreController("api::product.product", ({ strapi }) => ({
|
||||||
|
/**
|
||||||
|
* Find a single product by ID
|
||||||
|
*/
|
||||||
|
findOne: async (ctx) => {
|
||||||
|
const { id } = await sanitize.contentAPI.query(ctx.params, strapi.getModel("api::product.product"));
|
||||||
|
const productUnsafe = await strapi.service("api::product.product").findOne(id, productDefaultParams);
|
||||||
|
const product = (await sanitize.contentAPI.output(productUnsafe, strapi.getModel("api::product.product"))) as Product;
|
||||||
|
return {
|
||||||
|
...product,
|
||||||
|
totalProductPrice: calculateTotalProductPrice(product)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find products with the lowest price ruling and pages
|
||||||
|
*/
|
||||||
|
find: async (ctx): Promise<{ data: Result<"api::product.product">[]; meta: { pagination: any } }> => {
|
||||||
|
try {
|
||||||
|
const params = await sanitize.contentAPI.query(ctx.query, strapi.getModel("api::product.product"));
|
||||||
|
const filtersParams = (params?.filters as Record<string, any>) ?? {};
|
||||||
|
const populateParams = (params?.populate as Record<string, any>) ?? {};
|
||||||
|
const paginationParams = (params?.pagination as Record<string, any>) ?? {};
|
||||||
|
|
||||||
|
const mergedParams = {
|
||||||
|
...productDefaultParams,
|
||||||
|
filters: filtersParams,
|
||||||
|
populate: {
|
||||||
|
...productDefaultParams.populate,
|
||||||
|
...populateParams
|
||||||
|
},
|
||||||
|
pagination: paginationParams
|
||||||
|
};
|
||||||
|
strapi.log.debug(JSON.stringify({ mergedParams }));
|
||||||
|
|
||||||
|
const response = await strapi.service("api::product.product").find(mergedParams);
|
||||||
|
const { pagination } = response;
|
||||||
|
const dataUnsafe = (await sanitize.contentAPI.output(
|
||||||
|
response.results,
|
||||||
|
strapi.getModel("api::product.product")
|
||||||
|
)) as Result<"api::product.product">[];
|
||||||
|
const data = dataUnsafe.map((product: Product) => {
|
||||||
|
return {
|
||||||
|
...product,
|
||||||
|
totalProductPrice: calculateTotalProductPrice(product)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return { data, meta: { pagination } };
|
||||||
|
} catch (error) {
|
||||||
|
strapi.log.error(error);
|
||||||
|
return ctx.badRequest("Could not fetch products");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all variants for a product
|
||||||
|
* @returns {Promise<Product[]>} All variants of the product
|
||||||
|
*/
|
||||||
|
allVariants: async (ctx): Promise<Product[]> => {
|
||||||
|
try {
|
||||||
|
const { id } = await sanitize.contentAPI.query(ctx.params, strapi.getModel("api::product.product"));
|
||||||
|
strapi.log.verbose(`Fetching variants for product ${id}`);
|
||||||
|
|
||||||
|
const product = await getProductDetails(id as ID);
|
||||||
|
const variants = await strapi.entityService.findMany<"api::product.product", ProductVariantParams>("api::product.product", {
|
||||||
|
...productVariantParams,
|
||||||
|
filters: {
|
||||||
|
pattern: { id: { $eq: product.pattern.id } }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!variants) {
|
||||||
|
return ctx.notFound(`Could not find variants for product ${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(variants.map((variant) => sanitize.contentAPI.output(variant, strapi.getModel("api::product.product")))) as Promise<
|
||||||
|
Product[]
|
||||||
|
>;
|
||||||
|
} catch (error) {
|
||||||
|
strapi.log.error(error);
|
||||||
|
return ctx.badRequest("Could not fetch product variants");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get variants by pattern for a product
|
||||||
|
* @returns {Promise<{ allProductPattern: ProductPattern[], productVariants: Product[], patterns: Array<ProductPattern & { productVariant: Product | undefined }> }>} Variants grouped by pattern
|
||||||
|
*/
|
||||||
|
variantsByPattern: async (
|
||||||
|
ctx
|
||||||
|
): Promise<{
|
||||||
|
allProductPattern: ProductPattern[];
|
||||||
|
productVariants: Product[];
|
||||||
|
patterns: Array<ProductPattern & { productVariant: Product | undefined }>;
|
||||||
|
}> => {
|
||||||
|
try {
|
||||||
|
const { id } = await sanitize.contentAPI.query(ctx.params, strapi.getModel("api::product.product"));
|
||||||
|
strapi.log.verbose(`Fetching variants by pattern for product ${id}`);
|
||||||
|
|
||||||
|
const product = await getProductDetails(id as ID);
|
||||||
|
const { pattern, cover, ruling, pages } = product;
|
||||||
|
|
||||||
|
const productVariants = await strapi.entityService.findMany<"api::product.product", ProductVariantParams>("api::product.product", {
|
||||||
|
...productVariantParams,
|
||||||
|
filters: {
|
||||||
|
$and: [
|
||||||
|
{ id: { $ne: id } },
|
||||||
|
{ pattern: { id: { $ne: pattern.id } } },
|
||||||
|
{ cover: { id: { $eq: cover.id } } },
|
||||||
|
{ ruling: { id: { $eq: ruling.id } } },
|
||||||
|
{ pages: { id: { $eq: pages.id } } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const sanitizedVariants = (await Promise.all(
|
||||||
|
productVariants.map((variant) => sanitize.contentAPI.output(variant, strapi.getModel("api::product.product")))
|
||||||
|
)) as Product[];
|
||||||
|
|
||||||
|
const allProductPatternResponse = (await strapi.service("api::product-pattern.product-pattern").find({
|
||||||
|
fields: ["id", "name", "description"],
|
||||||
|
populate: { image: { fields: ["url"] } },
|
||||||
|
filters: { id: { $ne: pattern.id } },
|
||||||
|
publicationState: "live"
|
||||||
|
})) as PaginatedResult<"api::product-pattern.product-pattern">;
|
||||||
|
|
||||||
|
const allProductPattern = allProductPatternResponse.results;
|
||||||
|
|
||||||
|
return {
|
||||||
|
allProductPattern,
|
||||||
|
productVariants: sanitizedVariants,
|
||||||
|
patterns: allProductPattern.map((pattern) => ({
|
||||||
|
...pattern,
|
||||||
|
productVariant: sanitizedVariants.find((variant) => variant?.pattern?.id === pattern.id)
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
strapi.log.error(error);
|
||||||
|
return ctx.badRequest("Could not fetch variants by pattern");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get variants for a product
|
||||||
|
* @returns {Promise<{ pages: any[], cover: any[], ruling: any[] }>} Variants grouped by pages, cover, and ruling
|
||||||
|
*/
|
||||||
|
variants: async (
|
||||||
|
ctx
|
||||||
|
): Promise<{
|
||||||
|
pages: any;
|
||||||
|
cover: any;
|
||||||
|
ruling: any;
|
||||||
|
}> => {
|
||||||
|
try {
|
||||||
|
const { id } = await sanitize.contentAPI.query(ctx.params, strapi.getModel("api::product.product"));
|
||||||
|
strapi.log.verbose(`Fetching variants for product ${id}`);
|
||||||
|
|
||||||
|
const product = await getProductDetails(id as ID);
|
||||||
|
|
||||||
|
const allProductPatternVariants = await strapi.entityService.findMany<"api::product.product", ProductVariantParams>(
|
||||||
|
"api::product.product",
|
||||||
|
{
|
||||||
|
...productVariantParams,
|
||||||
|
filters: {
|
||||||
|
$and: [{ pattern: { id: { $eq: product.pattern.id } } }, { id: { $ne: id } }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const [allProductPages, allProductRulings, allProductCovers] = await Promise.all([
|
||||||
|
strapi
|
||||||
|
.service("api::product-page.product-page")
|
||||||
|
.find({ fields: ["id", "name"] }) as PaginatedResult<"api::product-pattern.product-pattern">,
|
||||||
|
strapi.service("api::product-ruling.product-ruling").find({
|
||||||
|
fields: ["id", "name"],
|
||||||
|
populate: { icon: { fields: ["url"] } }
|
||||||
|
}) as PaginatedResult<"api::product-ruling.product-ruling">,
|
||||||
|
strapi.service("api::product-cover.product-cover").find({
|
||||||
|
fields: ["id", "name", "binding", "price"],
|
||||||
|
populate: { icon: { fields: ["url"] } }
|
||||||
|
}) as PaginatedResult<"api::product-cover.product-cover">
|
||||||
|
]);
|
||||||
|
|
||||||
|
const sanitizedVariants = (await Promise.all(
|
||||||
|
allProductPatternVariants.map((variant) => sanitize.contentAPI.output(variant, strapi.getModel("api::product.product")))
|
||||||
|
)) as Product[];
|
||||||
|
|
||||||
|
// For pages variants: find products with same pattern, cover, and ruling, but different pages
|
||||||
|
const pagesVariants = allProductPages.results.map((pages) => ({
|
||||||
|
...pages,
|
||||||
|
productVariant: sanitizedVariants.find(
|
||||||
|
(variant) =>
|
||||||
|
variant.id !== product.id &&
|
||||||
|
variant.pages.id === pages.id &&
|
||||||
|
variant.cover.id === product.cover.id &&
|
||||||
|
variant.ruling.id === product.ruling.id
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
|
||||||
|
// For cover variants: find products with same pattern and ruling but different covers
|
||||||
|
const coverVariants = allProductCovers.results.map((cover) => {
|
||||||
|
// Find any product with this cover and the same pattern
|
||||||
|
const matchingVariant = sanitizedVariants.find(
|
||||||
|
(variant) =>
|
||||||
|
variant.id !== product.id &&
|
||||||
|
variant.cover.id === cover.id &&
|
||||||
|
variant.pattern.id === product.pattern.id &&
|
||||||
|
variant.ruling.id === product.ruling.id
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...cover,
|
||||||
|
productVariant: matchingVariant
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// For ruling variants: find products with same pattern, cover, and pages, but different ruling
|
||||||
|
const rulingVariants = allProductRulings.results.map((ruling) => ({
|
||||||
|
...ruling,
|
||||||
|
productVariant: sanitizedVariants.find(
|
||||||
|
(variant) =>
|
||||||
|
variant.id !== product.id &&
|
||||||
|
variant.ruling.id === ruling.id &&
|
||||||
|
variant.pages.id === product.pages.id &&
|
||||||
|
variant.cover.id === product.cover.id
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
pages: pagesVariants,
|
||||||
|
cover: coverVariants,
|
||||||
|
ruling: rulingVariants
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
strapi.log.error(error);
|
||||||
|
return ctx.badRequest("Could not fetch product variants");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publish or unpublish products by filter
|
||||||
|
* @returns {Promise<{published: number, dryRun: boolean}>} The number of products published/unpublished and dryRun status
|
||||||
|
*/
|
||||||
|
publishByFilter: async (ctx): Promise<{ published: number; dryRun: boolean }> => {
|
||||||
|
try {
|
||||||
|
const { filters, publish = true, dryRun = false } = ctx.request.body;
|
||||||
|
|
||||||
|
if (!filters) {
|
||||||
|
return ctx.badRequest("Filters are required");
|
||||||
|
}
|
||||||
|
|
||||||
|
strapi.log.debug(`Publishing products with filters: ${JSON.stringify(filters)}, publish: ${publish}, dryRun: ${dryRun}`);
|
||||||
|
|
||||||
|
return await strapi.service("api::product.product").publishByFilter(filters, publish, dryRun);
|
||||||
|
} catch (error) {
|
||||||
|
strapi.log.error(error);
|
||||||
|
return ctx.badRequest("Could not publish products");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find products with only the cheapest variant per cover+pattern combination
|
||||||
|
*/
|
||||||
|
findCheapest: async (ctx): Promise<{ data: Result<"api::product.product">[]; meta: { pagination: any } }> => {
|
||||||
|
try {
|
||||||
|
const params = await sanitize.contentAPI.query(ctx.query, strapi.getModel("api::product.product"));
|
||||||
|
const filtersParams = (params?.filters as Record<string, any>) ?? {};
|
||||||
|
const populateParams = (params?.populate as Record<string, any>) ?? {};
|
||||||
|
const paginationParams = (params?.pagination as Record<string, any>) ?? {};
|
||||||
|
|
||||||
|
// First get all products
|
||||||
|
const mergedParams = {
|
||||||
|
...productDefaultParams,
|
||||||
|
filters: filtersParams,
|
||||||
|
populate: {
|
||||||
|
...productDefaultParams.populate,
|
||||||
|
...populateParams
|
||||||
|
},
|
||||||
|
pagination: { pageSize: 999 } // Temporarily get all to filter
|
||||||
|
};
|
||||||
|
|
||||||
|
const allProducts = await strapi.service("api::product.product").find(mergedParams);
|
||||||
|
const allProductsSanitized = (await sanitize.contentAPI.output(
|
||||||
|
allProducts.results,
|
||||||
|
strapi.getModel("api::product.product")
|
||||||
|
)) as Product[];
|
||||||
|
|
||||||
|
// Group products by cover+pattern combination
|
||||||
|
const productGroups = new Map();
|
||||||
|
|
||||||
|
allProductsSanitized.forEach((product) => {
|
||||||
|
const key = `${product.cover.id}-${product.pattern.id}`;
|
||||||
|
if (!productGroups.has(key) || calculateTotalProductPrice(product) < calculateTotalProductPrice(productGroups.get(key))) {
|
||||||
|
productGroups.set(key, product);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert map values to array and sort by pattern.id
|
||||||
|
const cheapestProducts = Array.from(productGroups.values()).sort((a, b) => {
|
||||||
|
const idA = a.pattern?.id ?? 0;
|
||||||
|
const idB = b.pattern?.id ?? 0;
|
||||||
|
return idA - idB;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Apply pagination manually
|
||||||
|
const page = parseInt(paginationParams.page) || 1;
|
||||||
|
const pageSize = parseInt(paginationParams.pageSize) || 24;
|
||||||
|
const total = cheapestProducts.length;
|
||||||
|
const pageCount = Math.ceil(total / pageSize);
|
||||||
|
const start = (page - 1) * pageSize;
|
||||||
|
const end = start + pageSize;
|
||||||
|
const paginatedProducts = cheapestProducts.slice(start, end);
|
||||||
|
|
||||||
|
// Add total price to each product
|
||||||
|
const data = paginatedProducts.map((product) => ({
|
||||||
|
...product,
|
||||||
|
totalProductPrice: calculateTotalProductPrice(product)
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
meta: {
|
||||||
|
pagination: {
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
pageCount,
|
||||||
|
total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
strapi.log.error(error);
|
||||||
|
return ctx.badRequest("Could not fetch cheapest products");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all published product paths for sitemap generation
|
||||||
|
* @returns {Promise<string[]>} Array of product paths
|
||||||
|
*/
|
||||||
|
sitemap: async (ctx): Promise<string[]> => {
|
||||||
|
try {
|
||||||
|
strapi.log.verbose("Generating product sitemap");
|
||||||
|
|
||||||
|
const products = await strapi.entityService.findMany("api::product.product", {
|
||||||
|
fields: ["slug"],
|
||||||
|
filters: {
|
||||||
|
publishedAt: { $notNull: true }
|
||||||
|
},
|
||||||
|
pagination: {
|
||||||
|
page: 1,
|
||||||
|
pageSize: maxProductsSitemap
|
||||||
|
},
|
||||||
|
sort: { updatedAt: "desc" }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!products || !products.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const productPaths = products.map((product) => `/details/${product.slug}`);
|
||||||
|
|
||||||
|
strapi.log.verbose(`Generated product sitemap with ${productPaths.length} entries`);
|
||||||
|
strapi.log.silly(`Product sitemap: ${JSON.stringify(productPaths)}`);
|
||||||
|
|
||||||
|
return productPaths;
|
||||||
|
} catch (error) {
|
||||||
|
strapi.log.error(error);
|
||||||
|
return ctx.badRequest("Could not generate product sitemap");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to get product details
|
||||||
|
* @param {ID} id - The product ID
|
||||||
|
* @returns {Promise<Product>} The product details
|
||||||
|
* @throws {Error} If the product is not found
|
||||||
|
*/
|
||||||
|
async function getProductDetails(id: ID): Promise<Product> {
|
||||||
|
const product = await strapi.entityService.findOne("api::product.product", id, {
|
||||||
|
fields: ["id", "name"],
|
||||||
|
populate: {
|
||||||
|
pattern: { fields: ["id"] },
|
||||||
|
cover: { fields: ["id"] },
|
||||||
|
ruling: { fields: ["id"] },
|
||||||
|
pages: { fields: ["id"] }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!product) {
|
||||||
|
throw new Error(`Could not find product with ID ${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (await sanitize.contentAPI.output(product, strapi.getModel("api::product.product"))) as Promise<Product>;
|
||||||
|
}
|
||||||
754
src/api/product/documentation/1.0.0/product.json
Normal file
754
src/api/product/documentation/1.0.0/product.json
Normal file
@@ -0,0 +1,754 @@
|
|||||||
|
{
|
||||||
|
"/products": {
|
||||||
|
"post": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product"
|
||||||
|
],
|
||||||
|
"parameters": [],
|
||||||
|
"operationId": "post/products",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductListResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "sort",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Sort by attributes ascending (asc) or descending (desc)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[withCount]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Return page/pageSize (default: true)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[page]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page number (default: 0)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[pageSize]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page size (default: 25)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[start]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Offset value (default: 0)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[limit]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Number of entities to return (default: 25)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fields",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Fields to return (ex: title,author)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "populate",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Relations to return",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "filters",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Filters to apply",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"style": "deepObject"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "locale",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Locale to apply",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "get/products"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/products/{id}": {
|
||||||
|
"put": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "put/products/{id}",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "get/products/{id}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/products/{id}/variants/all": {
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "get/products/{id}/variants/all"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/products/{id}/variants/pattern": {
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "get/products/{id}/variants/pattern"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/products/{id}/variants": {
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "get/products/{id}/variants"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/products/publish": {
|
||||||
|
"post": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Product"
|
||||||
|
],
|
||||||
|
"parameters": [],
|
||||||
|
"operationId": "post/products/publish",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProductRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
90
src/api/product/routes/product.ts
Normal file
90
src/api/product/routes/product.ts
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
export default {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
path: "/products",
|
||||||
|
handler: "product.create",
|
||||||
|
config: {
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
path: "/products",
|
||||||
|
handler: "product.find",
|
||||||
|
config: {
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
path: "/promo-products",
|
||||||
|
handler: "product.findCheapest",
|
||||||
|
config: {
|
||||||
|
auth: false,
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
path: "/sitemap/products",
|
||||||
|
handler: "product.sitemap",
|
||||||
|
config: {
|
||||||
|
auth: false,
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
path: "/products/:id",
|
||||||
|
handler: "product.update",
|
||||||
|
config: {
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
path: "/products/:id",
|
||||||
|
handler: "product.findOne",
|
||||||
|
config: {
|
||||||
|
auth: false,
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
path: "/products/:id/variants/all",
|
||||||
|
handler: "product.allVariants",
|
||||||
|
config: {
|
||||||
|
auth: false,
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
path: "/products/:id/variants/pattern",
|
||||||
|
handler: "product.variantsByPattern",
|
||||||
|
config: {
|
||||||
|
auth: false,
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
path: "/products/:id/variants",
|
||||||
|
handler: "product.variants",
|
||||||
|
config: {
|
||||||
|
auth: false,
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
path: "/products/publish",
|
||||||
|
handler: "product.publishByFilter",
|
||||||
|
config: {
|
||||||
|
policies: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
153
src/api/product/services/product.ts
Normal file
153
src/api/product/services/product.ts
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
/**
|
||||||
|
* This service is responsible for handling the product logic.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
import { Product } from "../../../../types";
|
||||||
|
|
||||||
|
export default factories.createCoreService("api::product.product", ({ strapi }) => ({
|
||||||
|
find: async (params: Record<string, any>) => {
|
||||||
|
const filtersParams = (params?.filters as Record<string, any>) ?? {};
|
||||||
|
const populateParams = (params?.populate as Record<string, any>) ?? {};
|
||||||
|
const paginationParams = (params?.pagination as Record<string, any>) ?? {};
|
||||||
|
return strapi.entityService.findPage("api::product.product", {
|
||||||
|
...productDefaultParams,
|
||||||
|
populate: {
|
||||||
|
...productDefaultParams.populate,
|
||||||
|
...populateParams
|
||||||
|
},
|
||||||
|
filters: filtersParams,
|
||||||
|
pageSize: 30,
|
||||||
|
...paginationParams
|
||||||
|
});
|
||||||
|
// const resultsUnsafe = response.results;
|
||||||
|
// const results = resultsUnsafe.map((product) => {
|
||||||
|
// return {
|
||||||
|
// ...product,
|
||||||
|
// totalProductPrice: calculateTotalProductPrice(product)
|
||||||
|
// };
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// return { ...response, results };
|
||||||
|
},
|
||||||
|
|
||||||
|
findOne: async (id: number) => {
|
||||||
|
const product = await strapi.entityService.findOne<"api::product.product", ProductParams>("api::product.product", id, productDefaultParams);
|
||||||
|
return {
|
||||||
|
...product,
|
||||||
|
totalProductPrice: calculateTotalProductPrice(product)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publish or unpublish products by filter
|
||||||
|
* @param {Record<string, any>} filters - The filters to apply
|
||||||
|
* @param {boolean} publish - Whether to publish or unpublish
|
||||||
|
* @param {boolean} dryRun - Whether to perform a dry run
|
||||||
|
* @returns {Promise<{published: number, products: number[], dryRun: boolean}>} The number of products published/unpublished and dryRun status
|
||||||
|
*/
|
||||||
|
publishByFilter: async (
|
||||||
|
filters: Record<string, any>,
|
||||||
|
publish: boolean = true,
|
||||||
|
dryRun: boolean = false
|
||||||
|
): Promise<{ published: any; products: number[]; dryRun: boolean }> => {
|
||||||
|
const query = strapi.db.query("api::product.product");
|
||||||
|
|
||||||
|
// Build the filter query - complex filtering based on product relations
|
||||||
|
const dbFilters = {
|
||||||
|
$and: [
|
||||||
|
...Object.entries(filters).map(([key, value]) => {
|
||||||
|
// Handle relation filters
|
||||||
|
if (["pattern", "cover", "ruling", "pages"].includes(key) && typeof value === "object") {
|
||||||
|
return {
|
||||||
|
[key]: value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Handle direct property filters
|
||||||
|
return {
|
||||||
|
[key]: value
|
||||||
|
};
|
||||||
|
})
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find products matching the filters
|
||||||
|
const products = await query.findMany({
|
||||||
|
where: dbFilters,
|
||||||
|
populate: ["pattern", "cover", "ruling", "pages"]
|
||||||
|
});
|
||||||
|
|
||||||
|
strapi.log.info(`Found ${products.length} products matching filters${dryRun ? " (dry run)" : ""}`);
|
||||||
|
|
||||||
|
// If dry run, just return the count
|
||||||
|
if (dryRun) {
|
||||||
|
return {
|
||||||
|
published: products.length,
|
||||||
|
products: products.map((product) => product.id),
|
||||||
|
dryRun: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update publication state for matching products
|
||||||
|
if (products.length > 0) {
|
||||||
|
const updatePromises = products.map((product) => {
|
||||||
|
return query.update({
|
||||||
|
where: { id: product.id },
|
||||||
|
data: {
|
||||||
|
publishedAt: publish ? new Date() : null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(updatePromises);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
published: products.length,
|
||||||
|
products: products.map((product) => product.id),
|
||||||
|
dryRun: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const calculateTotalProductPrice = (product: Partial<Product>): number => {
|
||||||
|
return (product?.cover?.price ?? 0) + (product?.pages?.price ?? 0) + (product?.ruling?.price ?? 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ProductParams {
|
||||||
|
populate?: {
|
||||||
|
pattern: any;
|
||||||
|
cover: any;
|
||||||
|
ruling: any;
|
||||||
|
pages: any;
|
||||||
|
images: {
|
||||||
|
populate: {
|
||||||
|
images: any;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
publicationState?: "live" | "preview";
|
||||||
|
}
|
||||||
|
|
||||||
|
export const productDefaultParams: ProductParams = {
|
||||||
|
populate: {
|
||||||
|
pattern: true,
|
||||||
|
cover: {
|
||||||
|
populate: {
|
||||||
|
slides: {
|
||||||
|
fields: ["formats", "url"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ruling: true,
|
||||||
|
pages: true,
|
||||||
|
images: {
|
||||||
|
populate: {
|
||||||
|
images: {
|
||||||
|
fields: ["formats", "url"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
publicationState: "live"
|
||||||
|
};
|
||||||
36
src/api/vat/content-types/vat/schema.json
Normal file
36
src/api/vat/content-types/vat/schema.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"kind": "singleType",
|
||||||
|
"collectionName": "vats",
|
||||||
|
"info": {
|
||||||
|
"singularName": "vat",
|
||||||
|
"pluralName": "vats",
|
||||||
|
"displayName": "VAT"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"privateAttributes": [
|
||||||
|
"createdAt",
|
||||||
|
"updatedAt",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"created_by",
|
||||||
|
"updated_by",
|
||||||
|
"published_at",
|
||||||
|
"publishedAt",
|
||||||
|
"published_by",
|
||||||
|
"publishedBy"
|
||||||
|
],
|
||||||
|
"draftAndPublish": false
|
||||||
|
},
|
||||||
|
"pluginOptions": {},
|
||||||
|
"attributes": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"percent": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"decimal": {
|
||||||
|
"type": "decimal"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/api/vat/controllers/vat.ts
Normal file
7
src/api/vat/controllers/vat.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* vat controller
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreController("api::vat.vat");
|
||||||
325
src/api/vat/documentation/1.0.0/vat.json
Normal file
325
src/api/vat/documentation/1.0.0/vat.json
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
{
|
||||||
|
"/vat": {
|
||||||
|
"get": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/VatResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Vat"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "sort",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Sort by attributes ascending (asc) or descending (desc)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[withCount]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Return page/pageSize (default: true)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[page]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page number (default: 0)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[pageSize]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page size (default: 25)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[start]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Offset value (default: 0)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pagination[limit]",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Number of entities to return (default: 25)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fields",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Fields to return (ex: title,author)",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "populate",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Relations to return",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "filters",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Filters to apply",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"style": "deepObject"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "locale",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Locale to apply",
|
||||||
|
"deprecated": false,
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"operationId": "get/vat"
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/VatResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Vat"
|
||||||
|
],
|
||||||
|
"parameters": [],
|
||||||
|
"operationId": "put/vat",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/VatRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Vat"
|
||||||
|
],
|
||||||
|
"parameters": [],
|
||||||
|
"operationId": "delete/vat"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/api/vat/routes/vat.ts
Normal file
7
src/api/vat/routes/vat.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* vat router
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreRouter("api::vat.vat");
|
||||||
7
src/api/vat/services/vat.ts
Normal file
7
src/api/vat/services/vat.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* vat service
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreService("api::vat.vat");
|
||||||
22
src/api/website/content-types/website/schema.json
Normal file
22
src/api/website/content-types/website/schema.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"kind": "singleType",
|
||||||
|
"collectionName": "websites",
|
||||||
|
"info": {
|
||||||
|
"singularName": "website",
|
||||||
|
"pluralName": "websites",
|
||||||
|
"displayName": "Website",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"draftAndPublish": false
|
||||||
|
},
|
||||||
|
"pluginOptions": {},
|
||||||
|
"attributes": {
|
||||||
|
"website": {
|
||||||
|
"displayName": "Website in Menu",
|
||||||
|
"type": "component",
|
||||||
|
"repeatable": true,
|
||||||
|
"component": "websites.website-in-menu"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/api/website/controllers/website.ts
Normal file
7
src/api/website/controllers/website.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* website controller
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { factories } from "@strapi/strapi";
|
||||||
|
|
||||||
|
export default factories.createCoreController("api::website.website");
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user