Initial commit: backend, storefront, vendor-panel added

This commit is contained in:
2025-08-01 11:05:32 +08:00
commit 08174125d2
2958 changed files with 310810 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
{
"name": "@mercurjs/reviews",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist",
"!dist/**/__tests__",
"!dist/**/__mocks__",
"!dist/**/__fixtures__"
],
"engines": {
"node": ">=20"
},
"license": "MIT",
"scripts": {
"build": "rimraf dist && tsc --build",
"migration:initial": " MIKRO_ORM_CLI_CONFIG=./mikro-orm.config.dev.ts medusa-mikro-orm migration:create --initial",
"migration:create": " MIKRO_ORM_CLI_CONFIG=./mikro-orm.config.dev.ts medusa-mikro-orm migration:create",
"migration:up": " MIKRO_ORM_CLI_CONFIG=./mikro-orm.config.dev.ts medusa-mikro-orm migration:up",
"orm:cache:clear": " MIKRO_ORM_CLI_CONFIG=./mikro-orm.config.dev.ts medusa-mikro-orm cache:clear"
},
"devDependencies": {
"@medusajs/framework": "2.8.6",
"@medusajs/test-utils": "2.8.6",
"@mercurjs/framework": "*",
"@mikro-orm/cli": "6.4.3",
"@mikro-orm/core": "6.4.3",
"@mikro-orm/migrations": "6.4.3",
"@mikro-orm/postgresql": "6.4.3",
"@swc/core": "^1.7.28",
"@swc/jest": "^0.2.36",
"jest": "^29.7.0",
"rimraf": "^3.0.2",
"tsc-alias": "^1.8.6",
"typescript": "^5.6.2"
},
"peerDependencies": {
"@medusajs/framework": "2.8.6",
"@mikro-orm/core": "6.4.3",
"@mikro-orm/migrations": "6.4.3",
"@mikro-orm/postgresql": "6.4.3",
"awilix": "^8.0.1"
}
}

View File

@@ -0,0 +1,12 @@
import { Module } from "@medusajs/framework/utils";
import ReviewModuleService from "./service";
export const REVIEW_MODULE = "review";
export * from "./utils";
export { ReviewModuleService };
export default Module(REVIEW_MODULE, {
service: ReviewModuleService,
});

View File

@@ -0,0 +1,116 @@
{
"namespaces": [
"public"
],
"name": "public",
"tables": [
{
"columns": {
"id": {
"name": "id",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"reference": {
"name": "reference",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"enumItems": [
"product",
"seller"
],
"mappedType": "enum"
},
"rating": {
"name": "rating",
"type": "integer",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "integer"
},
"customer_note": {
"name": "customer_note",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "text"
},
"seller_note": {
"name": "seller_note",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "text"
},
"created_at": {
"name": "created_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"length": 6,
"default": "now()",
"mappedType": "datetime"
},
"updated_at": {
"name": "updated_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"length": 6,
"default": "now()",
"mappedType": "datetime"
},
"deleted_at": {
"name": "deleted_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"length": 6,
"mappedType": "datetime"
}
},
"name": "review",
"schema": "public",
"indexes": [
{
"keyName": "IDX_review_deleted_at",
"columnNames": [],
"composite": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_review_deleted_at\" ON \"review\" (deleted_at) WHERE deleted_at IS NULL"
},
{
"keyName": "review_pkey",
"columnNames": [
"id"
],
"composite": false,
"primary": true,
"unique": true
}
],
"checks": [],
"foreignKeys": {}
}
]
}

View File

@@ -0,0 +1,14 @@
import { Migration } from '@mikro-orm/migrations';
export class Migration20250122122239 extends Migration {
async up(): Promise<void> {
this.addSql('create table if not exists "review" ("id" text not null, "reference" text check ("reference" in (\'product\', \'seller\')) not null, "rating" integer not null, "customer_note" text null, "customer_id" text not null, "seller_note" text null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "review_pkey" primary key ("id"));');
this.addSql('CREATE INDEX IF NOT EXISTS "IDX_review_deleted_at" ON "review" (deleted_at) WHERE deleted_at IS NULL;');
}
async down(): Promise<void> {
this.addSql('drop table if exists "review" cascade;');
}
}

View File

@@ -0,0 +1,13 @@
import { Migration } from '@mikro-orm/migrations';
export class Migration20250214113449 extends Migration {
async up(): Promise<void> {
this.addSql('alter table if exists "review" drop column if exists "customer_id";');
}
async down(): Promise<void> {
this.addSql('alter table if exists "review" add column if not exists "customer_id" text not null;');
}
}

View File

@@ -0,0 +1,9 @@
import { model } from '@medusajs/framework/utils'
export const Review = model.define('review', {
id: model.id({ prefix: 'rev' }).primaryKey(),
reference: model.enum(['product', 'seller']),
rating: model.number(),
customer_note: model.text().nullable(),
seller_note: model.text().nullable()
})

View File

@@ -0,0 +1,9 @@
import { MedusaService } from '@medusajs/framework/utils'
import { Review } from './models/review'
class ReviewModuleService extends MedusaService({
Review
}) {}
export default ReviewModuleService

View File

@@ -0,0 +1,64 @@
import { MedusaContainer } from '@medusajs/framework'
export async function getAvgRating(
container: MedusaContainer,
type: 'seller' | 'product',
id: string
): Promise<string | null> {
const knex = container.resolve('__pg_connection__')
const joinField = type === 'product' ? 'product_id' : 'seller_id'
const joinTable =
type === 'product'
? 'product_product_review_review'
: 'seller_seller_review_review'
const [result] = await knex('review')
.avg('review.rating')
.leftJoin(joinTable, `${joinTable}.review_id`, 'review.id')
.where(`${joinTable}.${joinField}`, id)
return result.avg
}
export async function getSellersWithRating(
container: MedusaContainer,
fields: string[]
) {
const knex = container.resolve('__pg_connection__')
const result = await knex
.select(...fields.map((f) => `seller.${f}`))
.avg('review.rating as rating')
.from('product')
.leftJoin(
'seller_seller_review_review',
'seller.id',
'seller_seller_review_review.product_id'
)
.leftJoin('review', 'review.id', 'seller_seller_review_review.review_id')
.groupBy('seller.id')
return result
}
export async function getProductsWithRating(
container: MedusaContainer,
fields: string[]
) {
const knex = container.resolve('__pg_connection__')
const result = await knex
.select(...fields.map((f) => `product.${f}`))
.avg('review.rating as rating')
.from('product')
.leftJoin(
'product_product_review_review',
'product.id',
'product_product_review_review.product_id'
)
.leftJoin('review', 'review.id', 'product_product_review_review.review_id')
.groupBy('product.id')
return result
}

View File

@@ -0,0 +1,27 @@
{
"compilerOptions": {
"lib": ["ES2021"],
"target": "ES2021",
"outDir": "${configDir}/dist",
"esModuleInterop": true,
"declaration": true,
"declarationMap": true,
"noUnusedLocals": true,
"module": "node16",
"moduleResolution": "node16",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true,
"noImplicitReturns": true,
"resolveJsonModule": true,
"forceConsistentCasingInFileNames": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noImplicitThis": true,
"allowJs": true,
"skipLibCheck": true,
"incremental": false
},
"include": ["${configDir}/src"],
"exclude": ["${configDir}/dist", "${configDir}/node_modules"]
}