add carts
This commit is contained in:
@@ -28,6 +28,26 @@
|
||||
"name": "Languages",
|
||||
"description": "Language and translation endpoints"
|
||||
},
|
||||
{
|
||||
"name": "Products",
|
||||
"description": "Product listing and description endpoints (under /api/v1/restricted, requires authentication)"
|
||||
},
|
||||
{
|
||||
"name": "Product Description",
|
||||
"description": "Product description management and translation endpoints (under /api/v1/restricted/product-description, requires authentication)"
|
||||
},
|
||||
{
|
||||
"name": "Menu",
|
||||
"description": "Menu and routing endpoints (under /api/v1/restricted/menu, requires authentication)"
|
||||
},
|
||||
{
|
||||
"name": "Search",
|
||||
"description": "MeiliSearch endpoints (under /api/v1/restricted/meili-search, requires authentication)"
|
||||
},
|
||||
{
|
||||
"name": "Locale",
|
||||
"description": "Locale selection endpoints (under /api/v1/restricted/langs-and-countries, requires authentication)"
|
||||
},
|
||||
{
|
||||
"name": "Repo",
|
||||
"description": "Repository time tracking data endpoints (under /api/v1/restricted/repo, requires authentication)"
|
||||
@@ -1018,6 +1038,670 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/restricted/list-products/get-listing": {
|
||||
"get": {
|
||||
"tags": ["Products"],
|
||||
"summary": "Get product listing",
|
||||
"description": "Returns a paginated list of products with their basic information. Requires authentication.",
|
||||
"operationId": "getProductListing",
|
||||
"security": [
|
||||
{
|
||||
"CookieAuth": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "p",
|
||||
"in": "query",
|
||||
"description": "Page number (1-based)",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "elems",
|
||||
"in": "query",
|
||||
"description": "Number of items per page",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"default": 30
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Product listing retrieved successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request parameters",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Not authenticated",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/restricted/product-description/get-product-description": {
|
||||
"get": {
|
||||
"tags": ["Product Description"],
|
||||
"summary": "Get product description",
|
||||
"description": "Returns the product description for a given product ID and language. Requires authentication.",
|
||||
"operationId": "getProductDescription",
|
||||
"security": [
|
||||
{
|
||||
"CookieAuth": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "productID",
|
||||
"in": "query",
|
||||
"description": "Product ID",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "uint"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "productLangID",
|
||||
"in": "query",
|
||||
"description": "Language ID for the product description",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "uint"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Product description retrieved successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request parameters",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Not authenticated",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/restricted/product-description/save-product-description": {
|
||||
"post": {
|
||||
"tags": ["Product Description"],
|
||||
"summary": "Save product description",
|
||||
"description": "Saves the product description for a given product ID in the specified language. Requires authentication.",
|
||||
"operationId": "saveProductDescription",
|
||||
"security": [
|
||||
{
|
||||
"CookieAuth": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "productID",
|
||||
"in": "query",
|
||||
"description": "Product ID",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "uint"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "productLangID",
|
||||
"in": "query",
|
||||
"description": "Language ID for the product description",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "uint"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProductDescriptionUpdate"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Product description saved successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request parameters or body",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Not authenticated",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/restricted/product-description/translate-product-description": {
|
||||
"get": {
|
||||
"tags": ["Product Description"],
|
||||
"summary": "Translate product description",
|
||||
"description": "Translates the product description from one language to another using AI (OpenAI or Google). Requires authentication.",
|
||||
"operationId": "translateProductDescription",
|
||||
"security": [
|
||||
{
|
||||
"CookieAuth": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "productID",
|
||||
"in": "query",
|
||||
"description": "Product ID",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "uint"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "productFromLangID",
|
||||
"in": "query",
|
||||
"description": "Source language ID",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "uint"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "productToLangID",
|
||||
"in": "query",
|
||||
"description": "Target language ID",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "uint"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "model",
|
||||
"in": "query",
|
||||
"description": "AI model to use for translation (OpenAI or Google)",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"enum": ["OpenAI", "Google"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Product description translated successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request parameters",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Not authenticated",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/restricted/menu/get-menu": {
|
||||
"get": {
|
||||
"tags": ["Menu"],
|
||||
"summary": "Get menu structure",
|
||||
"description": "Returns the menu structure for the current language. Requires authentication.",
|
||||
"operationId": "getMenu",
|
||||
"security": [
|
||||
{
|
||||
"CookieAuth": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Menu retrieved successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Not authenticated",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/restricted/menu/get-routes": {
|
||||
"get": {
|
||||
"tags": ["Menu"],
|
||||
"summary": "Get routes",
|
||||
"description": "Returns the routing structure for the current language. Requires authentication.",
|
||||
"operationId": "getRoutes",
|
||||
"security": [
|
||||
{
|
||||
"CookieAuth": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Routes retrieved successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Not authenticated",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/restricted/meili-search/search": {
|
||||
"get": {
|
||||
"tags": ["Search"],
|
||||
"summary": "Search products",
|
||||
"description": "Searches products using MeiliSearch. Requires authentication.",
|
||||
"operationId": "searchProducts",
|
||||
"security": [
|
||||
{
|
||||
"CookieAuth": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "query",
|
||||
"in": "query",
|
||||
"description": "Search query string",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"description": "Maximum number of results",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "uint"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "id_category",
|
||||
"in": "query",
|
||||
"description": "Filter by category ID",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "uint"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "price_lower_bound",
|
||||
"in": "query",
|
||||
"description": "Lower price bound",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "price_upper_bound",
|
||||
"in": "query",
|
||||
"description": "Upper price bound",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Search results retrieved successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request parameters",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Not authenticated",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/restricted/meili-search/create-index": {
|
||||
"get": {
|
||||
"tags": ["Search"],
|
||||
"summary": "Create search index",
|
||||
"description": "Creates a MeiliSearch index for products. Requires superadmin access.",
|
||||
"operationId": "createSearchIndex",
|
||||
"security": [
|
||||
{
|
||||
"CookieAuth": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Index created successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Not authenticated",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/restricted/meili-search/test": {
|
||||
"get": {
|
||||
"tags": ["Search"],
|
||||
"summary": "Test MeiliSearch",
|
||||
"description": "Tests the MeiliSearch connection. Requires superadmin access.",
|
||||
"operationId": "testMeiliSearch",
|
||||
"security": [
|
||||
{
|
||||
"CookieAuth": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "MeiliSearch test successful",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Not authenticated",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/restricted/langs-and-countries/get-languages": {
|
||||
"get": {
|
||||
"tags": ["Locale"],
|
||||
"summary": "Get available languages",
|
||||
"description": "Returns a list of available languages for the application. Requires authentication.",
|
||||
"operationId": "getAvailableLanguages",
|
||||
"security": [
|
||||
{
|
||||
"CookieAuth": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Languages retrieved successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Not authenticated",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/restricted/langs-and-countries/get-countries": {
|
||||
"get": {
|
||||
"tags": ["Locale"],
|
||||
"summary": "Get countries and currencies",
|
||||
"description": "Returns a list of countries with their associated currencies. Requires authentication.",
|
||||
"operationId": "getCountriesAndCurrencies",
|
||||
"security": [
|
||||
{
|
||||
"CookieAuth": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Countries and currencies retrieved successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Not authenticated",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
@@ -1163,6 +1847,272 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ApiResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "Response message"
|
||||
},
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "Response data"
|
||||
},
|
||||
"count": {
|
||||
"type": "integer",
|
||||
"description": "Number of items returned"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ProductDescriptionUpdate": {
|
||||
"type": "object",
|
||||
"description": "Map of fields to update for product description",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": {
|
||||
"name": "Product Name",
|
||||
"description": "<p>Product description in HTML</p>",
|
||||
"description_short": "<p>Short description</p>",
|
||||
"meta_title": "Meta Title",
|
||||
"meta_description": "Meta description text",
|
||||
"available_now": "In Stock",
|
||||
"available_later": "Out of Stock",
|
||||
"usage": "<p>Usage instructions</p>"
|
||||
}
|
||||
},
|
||||
"ProductDescription": {
|
||||
"type": "object",
|
||||
"description": "Product description in a specific language",
|
||||
"properties": {
|
||||
"product_id": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"description": "Product ID"
|
||||
},
|
||||
"shop_id": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"description": "Shop ID"
|
||||
},
|
||||
"lang_id": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"description": "Language ID"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Product name"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "Full product description (HTML)"
|
||||
},
|
||||
"description_short": {
|
||||
"type": "string",
|
||||
"description": "Short product description (HTML)"
|
||||
},
|
||||
"link_rewrite": {
|
||||
"type": "string",
|
||||
"description": "URL-friendly slug"
|
||||
},
|
||||
"meta_description": {
|
||||
"type": "string",
|
||||
"description": "Meta description"
|
||||
},
|
||||
"meta_keywords": {
|
||||
"type": "string",
|
||||
"description": "Meta keywords"
|
||||
},
|
||||
"meta_title": {
|
||||
"type": "string",
|
||||
"description": "Meta title"
|
||||
},
|
||||
"available_now": {
|
||||
"type": "string",
|
||||
"description": "Text shown when item is in stock"
|
||||
},
|
||||
"available_later": {
|
||||
"type": "string",
|
||||
"description": "Text shown when item is out of stock"
|
||||
},
|
||||
"delivery_in_stock": {
|
||||
"type": "string",
|
||||
"description": "Delivery in stock text"
|
||||
},
|
||||
"delivery_out_stock": {
|
||||
"type": "string",
|
||||
"description": "Delivery out of stock text"
|
||||
},
|
||||
"usage": {
|
||||
"type": "string",
|
||||
"description": "Usage instructions (HTML)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Country": {
|
||||
"type": "object",
|
||||
"description": "Country with its currency",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"description": "Country ID"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Country name"
|
||||
},
|
||||
"flag": {
|
||||
"type": "string",
|
||||
"description": "Flag emoji or code"
|
||||
},
|
||||
"currency_id": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"description": "Currency ID"
|
||||
},
|
||||
"currency_iso_code": {
|
||||
"type": "string",
|
||||
"description": "Currency ISO code (e.g., EUR, USD)"
|
||||
},
|
||||
"currency_name": {
|
||||
"type": "string",
|
||||
"description": "Currency name"
|
||||
}
|
||||
}
|
||||
},
|
||||
"MenuItem": {
|
||||
"type": "object",
|
||||
"description": "Menu item structure",
|
||||
"properties": {
|
||||
"category_id": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"description": "Category ID"
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Menu item label"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/components/schemas/MenuItemParams"
|
||||
},
|
||||
"children": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/MenuItem"
|
||||
},
|
||||
"description": "Child menu items"
|
||||
}
|
||||
}
|
||||
},
|
||||
"MenuItemParams": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"category_id": {
|
||||
"type": "integer",
|
||||
"format": "uint"
|
||||
},
|
||||
"link_rewrite": {
|
||||
"type": "string"
|
||||
},
|
||||
"locale": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Route": {
|
||||
"type": "object",
|
||||
"description": "Application route",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"description": "Route ID"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Route name"
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "Route path"
|
||||
},
|
||||
"component": {
|
||||
"type": "string",
|
||||
"description": "Vue component path"
|
||||
},
|
||||
"layout": {
|
||||
"type": "string",
|
||||
"description": "Layout type"
|
||||
},
|
||||
"meta": {
|
||||
"type": "object",
|
||||
"description": "Route metadata"
|
||||
},
|
||||
"is_active": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the route is active"
|
||||
},
|
||||
"sort_order": {
|
||||
"type": "integer",
|
||||
"description": "Sort order"
|
||||
},
|
||||
"children": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Route"
|
||||
},
|
||||
"description": "Child routes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"MeiliSearchResult": {
|
||||
"type": "object",
|
||||
"description": "MeiliSearch product result",
|
||||
"properties": {
|
||||
"id_product": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"description": "Product ID"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Product name"
|
||||
},
|
||||
"active": {
|
||||
"type": "integer",
|
||||
"description": "Active status"
|
||||
},
|
||||
"price": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "Product price"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "Product description"
|
||||
},
|
||||
"description_short": {
|
||||
"type": "string",
|
||||
"description": "Short description"
|
||||
},
|
||||
"reference": {
|
||||
"type": "string",
|
||||
"description": "Product reference"
|
||||
},
|
||||
"id_category": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"description": "Category ID"
|
||||
},
|
||||
"category_name": {
|
||||
"type": "string",
|
||||
"description": "Category name"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Language": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -22,6 +22,7 @@ type Config struct {
|
||||
I18n I18n
|
||||
Pdf PdfPrinter
|
||||
GoogleTranslate GoogleTranslateConfig
|
||||
Image ImageConfig
|
||||
}
|
||||
|
||||
type I18n struct {
|
||||
@@ -32,6 +33,10 @@ type ServerConfig struct {
|
||||
Host string `env:"SERVER_HOST,0.0.0.0"`
|
||||
}
|
||||
|
||||
type ImageConfig struct {
|
||||
ImagePrefix string `env:"IMAGE_PREFIX"`
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
Host string `env:"DB_HOST,localhost"`
|
||||
Port int `env:"DB_PORT"`
|
||||
@@ -167,6 +172,10 @@ func load() *Config {
|
||||
slog.Error("not possible to load env variables for google translate : ", err.Error(), "")
|
||||
}
|
||||
|
||||
err = loadEnv(&cfg.Image)
|
||||
if err != nil {
|
||||
slog.Error("not possible to load env variables for google translate : ", err.Error(), "")
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@ import (
|
||||
"git.ma-al.com/goc_daniel/b2b/app/utils/response"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/samber/lo"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ListProductsHandler handles endpoints that receive, save and translate product descriptions.
|
||||
@@ -45,11 +43,6 @@ func (h *ListProductsHandler) GetListing(c fiber.Ctx) error {
|
||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||
}
|
||||
|
||||
// overrides := map[string]string{
|
||||
// "override_country": c.Query("override_country", ""),
|
||||
// "override_currency": c.Query("override_currency", ""),
|
||||
// }
|
||||
|
||||
id_lang, ok := c.Locals("langID").(uint)
|
||||
if !ok {
|
||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||
@@ -67,60 +60,12 @@ func (h *ListProductsHandler) GetListing(c fiber.Ctx) error {
|
||||
|
||||
var columnMapping map[string]string = map[string]string{}
|
||||
|
||||
// var columnMapping map[string]string = map[string]string{
|
||||
// "product_id": "id",
|
||||
// "price": "price_taxed",
|
||||
// "name": "name",
|
||||
// "category_id": "category_id",
|
||||
// "feature_id": "feature_id",
|
||||
// "feature": "feature_name",
|
||||
// "value_id": "value_id",
|
||||
// "value": "value_name",
|
||||
// "status": "active_sale",
|
||||
// "stock": "in_stock",
|
||||
// }
|
||||
|
||||
func ParseProductFilters(c fiber.Ctx) (find.Paging, *filters.FiltersList, error) {
|
||||
var p find.Paging
|
||||
fl := filters.NewFiltersList()
|
||||
// productFilters := new(model.ProductFilters)
|
||||
|
||||
// err := c.Bind().Query(productFilters)
|
||||
// if err != nil {
|
||||
// return p, &fl, err
|
||||
// }
|
||||
|
||||
// if productFilters.Name != "" {
|
||||
// fl.Append(filters.Where("name LIKE ?", fmt.Sprintf("%%%s%%", productFilters.Name)))
|
||||
// }
|
||||
|
||||
// if productFilters.Sort != "" {
|
||||
// ord, err := query_params.ParseOrdering[model.Product](c, columnMapping)
|
||||
// if err != nil {
|
||||
// return p, &fl, err
|
||||
// }
|
||||
// for _, o := range ord {
|
||||
// fl.Append(filters.Order(o.Column, o.IsDesc))
|
||||
// }
|
||||
// }
|
||||
|
||||
// if len(productFilters.Features) > 0 {
|
||||
// fl.Append(featureValueFilters(productFilters.Features))
|
||||
// }
|
||||
|
||||
// fl.Append(query_params.ParseWhereScopes[model.Product](c, []string{"name"}, columnMapping)...)
|
||||
|
||||
pageNum, pageElems := query_params.ParsePagination(c)
|
||||
p = find.Paging{Page: pageNum, Elements: pageElems}
|
||||
|
||||
return p, &fl, nil
|
||||
}
|
||||
|
||||
type FeatVal = map[uint][]uint
|
||||
|
||||
func featureValueFilters(feats FeatVal) filters.Filter {
|
||||
filt := func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("value_id IN ?", lo.Flatten(lo.Values(feats))).Group("id").Having("COUNT(id) = ?", len(lo.Keys(feats)))
|
||||
}
|
||||
return filters.NewFilter(filters.FEAT_VAL_PRODUCT_FILTER, filt)
|
||||
}
|
||||
|
||||
@@ -24,18 +24,33 @@ func MenuHandlerRoutes(r fiber.Router) fiber.Router {
|
||||
handler := NewMenuHandler()
|
||||
|
||||
r.Get("/get-menu", handler.GetMenu)
|
||||
r.Get("/get-routes", handler.GetRouting)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (h *MenuHandler) GetMenu(c fiber.Ctx) error {
|
||||
id_lang, ok := c.Locals("langID").(uint)
|
||||
lang_id, ok := c.Locals("langID").(uint)
|
||||
if !ok {
|
||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||
}
|
||||
|
||||
menu, err := h.menuService.GetMenu(id_lang)
|
||||
menu, err := h.menuService.GetMenu(lang_id)
|
||||
if err != nil {
|
||||
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||
}
|
||||
|
||||
return c.JSON(response.Make(&menu, 0, i18n.T_(c, response.Message_OK)))
|
||||
}
|
||||
|
||||
func (h *MenuHandler) GetRouting(c fiber.Ctx) error {
|
||||
lang_id, ok := c.Locals("langID").(uint)
|
||||
if !ok {
|
||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||
}
|
||||
menu, err := h.menuService.GetRoutes(lang_id)
|
||||
if err != nil {
|
||||
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||
|
||||
@@ -114,6 +114,10 @@ func (s *Server) Setup() error {
|
||||
carts := s.restricted.Group("/carts")
|
||||
restricted.CartsHandlerRoutes(carts)
|
||||
|
||||
s.api.All("*", func(c fiber.Ctx) error {
|
||||
return c.SendStatus(fiber.StatusNotFound)
|
||||
})
|
||||
|
||||
// // Restricted routes example
|
||||
// restricted := s.api.Group("/restricted")
|
||||
// restricted.Use(middleware.AuthMiddleware())
|
||||
|
||||
@@ -62,9 +62,9 @@ type Product struct {
|
||||
DeliveryDays uint `gorm:"column:delivery_days" json:"delivery_days" form:"delivery_days"`
|
||||
}
|
||||
type ProductInList struct {
|
||||
ProductID uint `gorm:"column:ID;primaryKey" json:"product_id" form:"product_id"`
|
||||
ProductID uint `gorm:"column:product_id;primaryKey" json:"product_id" form:"product_id"`
|
||||
Name string `gorm:"column:name" json:"name" form:"name"`
|
||||
ImageID uint `gorm:"column:id_image"`
|
||||
ImageID string `gorm:"column:id_image"`
|
||||
LinkRewrite string `gorm:"column:link_rewrite"`
|
||||
Active uint `gorm:"column:active" json:"active" form:"active"`
|
||||
}
|
||||
|
||||
21
app/model/routing.go
Normal file
21
app/model/routing.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package model
|
||||
|
||||
type Route struct {
|
||||
ID uint `gorm:"primaryKey;autoIncrement"`
|
||||
Name string `gorm:"type:varchar(255);not null;unique"`
|
||||
Path *string `gorm:"type:varchar(255);default:null"`
|
||||
Component string `gorm:"type:varchar(255);not null;comment:path to component file"`
|
||||
Layout *string `gorm:"type:varchar(50);default:'default';comment:'default | empty'"`
|
||||
Meta *string `gorm:"type:longtext;default:'{}'"`
|
||||
IsActive *bool `gorm:"type:tinyint;default:1"`
|
||||
SortOrder *int `gorm:"type:int;default:0"`
|
||||
|
||||
ParentID *uint `gorm:"index"`
|
||||
Parent *Route `gorm:"constraint:OnUpdate:RESTRICT,OnDelete:SET NULL;foreignKey:ParentID"`
|
||||
|
||||
Children []Route `gorm:"foreignKey:ParentID"`
|
||||
}
|
||||
|
||||
func (Route) TableName() string {
|
||||
return "b2b_routes"
|
||||
}
|
||||
@@ -30,22 +30,12 @@ func (repo *CategoriesRepo) GetAllCategories(id_lang uint) ([]model.ScannedCateg
|
||||
ps_category.is_root_category AS is_root_category,
|
||||
ps_category_lang.link_rewrite AS link_rewrite,
|
||||
ps_lang.iso_code AS iso_code
|
||||
`).
|
||||
Joins(`
|
||||
LEFT JOIN ps_category_lang
|
||||
ON ps_category_lang.id_category = ps_category.id_category
|
||||
AND ps_category_lang.id_shop = ?
|
||||
AND ps_category_lang.id_lang = ?
|
||||
`, constdata.SHOP_ID, id_lang).
|
||||
Joins(`
|
||||
LEFT JOIN ps_category_shop
|
||||
ON ps_category_shop.id_category = ps_category.id_category
|
||||
AND ps_category_shop.id_shop = ?
|
||||
`, constdata.SHOP_ID).
|
||||
Joins(`
|
||||
JOIN ps_lang
|
||||
ON ps_lang.id_lang = ps_category_lang.id_lang
|
||||
`).
|
||||
FROM ps_category
|
||||
LEFT JOIN ps_category_lang ON ps_category_lang.id_category = ps_category.id_category AND ps_category_lang.id_shop = ? AND ps_category_lang.id_lang = ?
|
||||
LEFT JOIN ps_category_shop ON ps_category_shop.id_category = ps_category.id_category AND ps_category_shop.id_shop = ?
|
||||
JOIN ps_lang ON ps_lang.id_lang = ps_category_lang.id_lang
|
||||
`,
|
||||
constdata.SHOP_ID, id_lang, constdata.SHOP_ID).
|
||||
Scan(&allCategories).Error
|
||||
|
||||
return allCategories, err
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package listProductsRepo
|
||||
|
||||
import (
|
||||
"git.ma-al.com/goc_daniel/b2b/app/config"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/db"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||
@@ -22,20 +23,6 @@ func (repo *ListProductsRepo) GetListing(id_lang uint, p find.Paging, filt *filt
|
||||
var listing []model.ProductInList
|
||||
var total int64
|
||||
|
||||
// var resultIDs []uint
|
||||
// q := db.DB.
|
||||
// // SQL_CALC_FOUND_ROWS is a neat trick which works on MariaDB and
|
||||
// // MySQL. It works when followed by `SELECT FOUND_ROWS();`. To learn
|
||||
// // more see: https://mariarawmodel.com/kb/en/found_rows/
|
||||
// // WARN: This might not work on different SQL databases
|
||||
// Select("DISTINCT SQL_CALC_FOUND_ROWS id").
|
||||
// // Debug().
|
||||
// Scopes(view.FromDBViewForDisplay(langID, countryIso)).
|
||||
// Scopes(scopesForFiltersOnDisplay(db.DB, langID, countryIso, filt)).
|
||||
// Scopes(filt.OfCategory(filters.ORDER_FILTER)...).
|
||||
// Limit(p.Limit()).
|
||||
// Offset(p.Offset())
|
||||
|
||||
subQuery := db.DB.
|
||||
Table("ps_image").
|
||||
Select("id_product, MIN(id_image) AS id_image").
|
||||
@@ -44,12 +31,12 @@ func (repo *ListProductsRepo) GetListing(id_lang uint, p find.Paging, filt *filt
|
||||
err := db.DB.
|
||||
Table("ps_product").
|
||||
Select(`
|
||||
ps_product.id_product AS id,
|
||||
ps_product.id_product AS product_id,
|
||||
ps_product_lang.name AS name,
|
||||
ps_product.active AS active,
|
||||
ps_product_lang.link_rewrite AS link_rewrite,
|
||||
COALESCE(ps_image_shop.id_image, any_image.id_image) AS id_image
|
||||
`).
|
||||
COALESCE(CONCAT( ?, '/', ps_image_shop.id_image, '-small_default/', ps_product_lang.link_rewrite, '.webp'), CONCAT( ?, '/', any_image.id_image, '-small_default/', ps_product_lang.link_rewrite, '.webp')) AS id_image
|
||||
`, config.Get().Image.ImagePrefix, config.Get().Image.ImagePrefix).
|
||||
Joins(`
|
||||
LEFT JOIN ps_product_lang
|
||||
ON ps_product_lang.id_product = ps_product.id_product
|
||||
|
||||
25
app/repos/routesRepo/routesRepo.go
Normal file
25
app/repos/routesRepo/routesRepo.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package routesrepo
|
||||
|
||||
import (
|
||||
"git.ma-al.com/goc_daniel/b2b/app/db"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||
)
|
||||
|
||||
type UIRoutesRepo interface {
|
||||
GetRoutes(langId uint) ([]model.Route, error)
|
||||
}
|
||||
|
||||
type RoutesRepo struct{}
|
||||
|
||||
func New() UIRoutesRepo {
|
||||
return &RoutesRepo{}
|
||||
}
|
||||
|
||||
func (p *RoutesRepo) GetRoutes(langId uint) ([]model.Route, error) {
|
||||
routes := []model.Route{}
|
||||
err := db.DB.Find(&routes).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return routes, nil
|
||||
}
|
||||
@@ -20,40 +20,10 @@ func New() *ListProductsService {
|
||||
func (s *ListProductsService) GetListing(id_lang uint, p find.Paging, filters *filters.FiltersList) (find.Found[model.ProductInList], error) {
|
||||
var products find.Found[model.ProductInList]
|
||||
|
||||
// currencyIso := c.Cookies("currency_iso", "")
|
||||
// countryIso := c.Cookies("country_iso", "")
|
||||
|
||||
// if overrides["override_currency"] != "" {
|
||||
// currencyIso = overrides["override_currency"]
|
||||
// }
|
||||
// if overrides["override_country"] != "" {
|
||||
// countryIso = overrides["override_country"]
|
||||
// }
|
||||
|
||||
products, err := s.listProductsRepo.GetListing(id_lang, p, filters)
|
||||
if err != nil {
|
||||
return products, err
|
||||
}
|
||||
|
||||
// var loopErr error
|
||||
// parallel.ForEach(products.Items, func(t model.Product, i int) {
|
||||
// // products.Items[i].PriceTaxed *= currRate.Rate.InexactFloat64()
|
||||
// // products.Items[i].PriceTaxed = tiny_util.RoundUpMonetary(products.Items[i].PriceTaxed)
|
||||
|
||||
// if products.Items[i].Name.IsNull() {
|
||||
// translation, err := s.listProductsRepo.GetTranslation(ctx, products.Items[i].ID, defaults.DefaultLanguageID)
|
||||
// if err != nil {
|
||||
// loopErr = err
|
||||
// return
|
||||
// }
|
||||
// products.Items[i].Name = nullable.FromPrimitiveString(translation.Name)
|
||||
// products.Items[i].DescriptionShort = nullable.FromPrimitiveString(translation.DescriptionShort)
|
||||
// products.Items[i].LinkRewrite = nullable.FromPrimitiveString(translation.LinkRewrite)
|
||||
// }
|
||||
// })
|
||||
// if loopErr != nil {
|
||||
// return products, errs.Handled(span, loopErr, errs.InternalError, errs.ERR_TODO)
|
||||
// }
|
||||
|
||||
return products, nil
|
||||
}
|
||||
|
||||
@@ -5,23 +5,26 @@ import (
|
||||
|
||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/repos/categoriesRepo"
|
||||
routesRepo "git.ma-al.com/goc_daniel/b2b/app/repos/routesRepo"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||
)
|
||||
|
||||
type MenuService struct {
|
||||
categoriesRepo categoriesRepo.UICategoriesRepo
|
||||
routesRepo routesRepo.UIRoutesRepo
|
||||
}
|
||||
|
||||
func New() *MenuService {
|
||||
return &MenuService{
|
||||
categoriesRepo: categoriesRepo.New(),
|
||||
routesRepo: routesRepo.New(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MenuService) GetMenu(id_lang uint) (model.Category, error) {
|
||||
func (s *MenuService) GetMenu(id_lang uint) (*model.Category, error) {
|
||||
all_categories, err := s.categoriesRepo.GetAllCategories(id_lang)
|
||||
if err != nil {
|
||||
return model.Category{}, err
|
||||
return &model.Category{}, err
|
||||
}
|
||||
|
||||
// find the root
|
||||
@@ -35,7 +38,7 @@ func (s *MenuService) GetMenu(id_lang uint) (model.Category, error) {
|
||||
}
|
||||
}
|
||||
if !root_found {
|
||||
return model.Category{}, responseErrors.ErrNoRootFound
|
||||
return &model.Category{}, responseErrors.ErrNoRootFound
|
||||
}
|
||||
|
||||
// now create the children and reorder them according to position
|
||||
@@ -57,7 +60,7 @@ func (s *MenuService) GetMenu(id_lang uint) (model.Category, error) {
|
||||
// finally, create the tree
|
||||
tree := s.createTree(root_index, &all_categories, &children_indices)
|
||||
|
||||
return tree, nil
|
||||
return &tree, nil
|
||||
}
|
||||
|
||||
func (s *MenuService) createTree(index int, all_categories *([]model.ScannedCategory), children_indices *(map[int][]ChildWithPosition)) model.Category {
|
||||
@@ -70,6 +73,10 @@ func (s *MenuService) createTree(index int, all_categories *([]model.ScannedCate
|
||||
return node
|
||||
}
|
||||
|
||||
func (s *MenuService) GetRoutes(id_lang uint) ([]model.Route, error) {
|
||||
return s.routesRepo.GetRoutes(id_lang)
|
||||
}
|
||||
|
||||
func (s *MenuService) scannedToNormalCategory(scanned model.ScannedCategory) model.Category {
|
||||
var normal model.Category
|
||||
// normal.Active = scanned.Active
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
)
|
||||
|
||||
type Paging struct {
|
||||
Page uint `json:"page_number" example:"5"`
|
||||
Elements uint `json:"elements_per_page" example:"30"`
|
||||
Page uint `json:"p" example:"5"`
|
||||
Elements uint `json:"elems" example:"30"`
|
||||
}
|
||||
|
||||
func (p Paging) Offset() int {
|
||||
|
||||
@@ -59,5 +59,8 @@ func ParseFieldFilters[T any](c fiber.Ctx, formColumnMapping ...map[string]strin
|
||||
func ParsePagination(c fiber.Ctx) (uint, uint) {
|
||||
pageNum, _ := strconv.ParseInt(c.Query("p", "1"), 10, 64)
|
||||
pageSize, _ := strconv.ParseInt(c.Query("elems", "30"), 10, 64)
|
||||
if pageSize > 100 {
|
||||
pageSize = 100
|
||||
}
|
||||
return uint(pageNum), uint(pageSize)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user