diff --git a/frontend/components/common/project/CreateDialog.tsx b/frontend/components/common/project/CreateDialog.tsx
index c930649..02579a4 100644
--- a/frontend/components/common/project/CreateDialog.tsx
+++ b/frontend/components/common/project/CreateDialog.tsx
@@ -180,8 +180,10 @@ export function CreateDialog({
project_tags: tags.length > 0 ? tags : undefined,
start_time: formData.startTime.toISOString(),
end_time: formData.endTime.toISOString(),
+ trust_level_limit_type: formData.trustLevelLimitType,
minimum_trust_level: formData.minimumTrustLevel,
allow_same_ip: formData.allowSameIP,
+ score_limit_type: formData.scoreLimitType,
risk_level: formData.riskLevel,
distribution_type: formData.distributionType,
topic_id: formData.topicId,
@@ -214,11 +216,14 @@ export function CreateDialog({
tags: tags,
start_time: formData.startTime.toISOString(),
end_time: formData.endTime.toISOString(),
+ trust_level_limit_type: formData.trustLevelLimitType,
minimum_trust_level: formData.minimumTrustLevel,
allow_same_ip: formData.allowSameIP,
+ score_limit_type: formData.scoreLimitType,
risk_level: formData.riskLevel,
distribution_type: formData.distributionType,
total_items: items.length,
+ price: formData.price || '0',
created_at: new Date().toISOString(),
};
diff --git a/frontend/components/common/project/EditDialog.tsx b/frontend/components/common/project/EditDialog.tsx
index b6576fc..eeb55ad 100644
--- a/frontend/components/common/project/EditDialog.tsx
+++ b/frontend/components/common/project/EditDialog.tsx
@@ -156,8 +156,10 @@ export function EditDialog({
project_tags: tags.length > 0 ? tags : undefined,
start_time: formData.startTime.toISOString(),
end_time: formData.endTime.toISOString(),
+ trust_level_limit_type: formData.trustLevelLimitType,
minimum_trust_level: formData.minimumTrustLevel,
allow_same_ip: formData.allowSameIP,
+ score_limit_type: formData.scoreLimitType,
risk_level: formData.riskLevel,
price: formData.price || '0',
// 只有非抽奖项目才允许更新项目内容
@@ -184,9 +186,12 @@ export function EditDialog({
tags: tags,
start_time: formData.startTime.toISOString(),
end_time: formData.endTime.toISOString(),
+ trust_level_limit_type: formData.trustLevelLimitType,
minimum_trust_level: formData.minimumTrustLevel,
allow_same_ip: formData.allowSameIP,
+ score_limit_type: formData.scoreLimitType,
risk_level: formData.riskLevel,
+ price: formData.price || '0',
total_items: project.distribution_type === DistributionType.LOTTERY ?
project.total_items :
project.total_items + newItems.length,
diff --git a/frontend/components/common/project/ProjectBasicForm.tsx b/frontend/components/common/project/ProjectBasicForm.tsx
index 5bca538..784f0e8 100644
--- a/frontend/components/common/project/ProjectBasicForm.tsx
+++ b/frontend/components/common/project/ProjectBasicForm.tsx
@@ -19,9 +19,9 @@ import {TagSelector} from '@/components/ui/tag-selector';
import {DateTimePicker} from '@/components/ui/DateTimePicker';
import MarkdownEditor from '@/components/common/markdown/Editor';
import {HelpCircle, Wallet} from 'lucide-react';
-import {FORM_LIMITS, TRUST_LEVEL_OPTIONS} from '@/components/common/project';
+import {FORM_LIMITS, REQUIREMENT_LIMIT_TYPE_OPTIONS, TRUST_LEVEL_OPTIONS} from '@/components/common/project';
import {TrustLevel} from '@/lib/services/core/types';
-import {DistributionType} from '@/lib/services/project/types';
+import {DistributionType, RequirementLimitType} from '@/lib/services/project/types';
import {ProjectFormData} from '@/hooks/use-project-form';
import services from '@/lib/services';
@@ -153,27 +153,50 @@ export function ProjectBasicForm({
-
-
+
+
+
+
+
+
+
+ {formData.trustLevelLimitType === RequirementLimitType.MINIMUM ?
+ '仅允许达到该等级及以上的用户领取' :
+ '仅允许达到该等级及以下的用户领取'}
+
-
+
@@ -182,25 +205,52 @@ export function ProjectBasicForm({
- 注:此功能已于 2025/07/22 更新
低于此分数的用户无法领取项目内容!
+
+ 注:你可以设置最低分或最高分。
+
+ 最低分适合提高门槛,最高分适合更偏向新人。
+
-
{
- const userInput = parseInt(e.target.value) || 0;
- const clampedInput = Math.max(0, Math.min(100, userInput));
- const riskLevel = 100 - clampedInput;
- updateField('riskLevel', riskLevel);
- }}
- placeholder="输入0-100的用户分数"
- />
+
+
+
+ {
+ const userInput = parseInt(e.target.value) || 0;
+ const clampedInput = Math.max(0, Math.min(100, userInput));
+ const riskLevel = 100 - clampedInput;
+ updateField('riskLevel', riskLevel);
+ }}
+ placeholder="输入 0-100 的用户分数"
+ />
+
+
+ {formData.scoreLimitType === RequirementLimitType.MINIMUM ?
+ '仅允许达到该分数及以上的用户领取' :
+ '仅允许达到该分数及以下的用户领取'}
+
diff --git a/frontend/components/common/project/ProjectCard.tsx b/frontend/components/common/project/ProjectCard.tsx
index 5f77683..40111b3 100644
--- a/frontend/components/common/project/ProjectCard.tsx
+++ b/frontend/components/common/project/ProjectCard.tsx
@@ -3,12 +3,13 @@
import {Button} from '@/components/ui/button';
import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from '@/components/ui/tooltip';
import {MotionEffect} from '@/components/animate-ui/effects/motion-effect';
-import {CURRENCY_LABEL, DISTRIBUTION_MODE_NAMES} from '@/components/common/project';
+import {CURRENCY_LABEL, DISTRIBUTION_MODE_NAMES, getRequirementLimitTypeLabel} from '@/components/common/project';
import {BadgeCheck, Boxes, Coins, Gauge, Pencil, ShieldCheck, Trash2, Waypoints} from 'lucide-react';
import {formatDateTimeWithSeconds} from '@/lib/utils';
import {ProjectListItem} from '@/lib/services/project/types';
const TRUST_LEVEL_SHORT_LABELS: Partial> = {
+ 0: 'TL0',
1: 'TL1',
2: 'TL2',
3: 'TL3',
@@ -63,11 +64,12 @@ export function ProjectCard({
const modeText = DISTRIBUTION_MODE_NAMES[project.distribution_type];
const description = project.description?.trim();
const trustText = TRUST_LEVEL_SHORT_LABELS[project.minimum_trust_level];
- const riskText = `${String(project.risk_level).padStart(2, '0')}`;
- const showRisk = project.risk_level > 0;
+ const riskThreshold = String(100 - project.risk_level).padStart(2, '0');
const showPrice = priceNum > 0;
const showIpLimit = !project.allow_same_ip;
const showTrustLevel = Boolean(trustText);
+ const trustPrefix = getRequirementLimitTypeLabel(project.trust_level_limit_type);
+ const scorePrefix = getRequirementLimitTypeLabel(project.score_limit_type);
return (
@@ -154,12 +156,10 @@ export function ProjectCard({
{itemText}
- {showRisk && (
-
-
- {riskText}
-
- )}
+
+
+ {scorePrefix}{riskThreshold}
+
{showPrice && (
@@ -178,7 +178,7 @@ export function ProjectCard({
{showTrustLevel && (
- {trustText}
+ {trustPrefix}{trustText}
)}
{showIpLimit && (
diff --git a/frontend/components/common/project/constants.ts b/frontend/components/common/project/constants.ts
index bddf530..d65de84 100644
--- a/frontend/components/common/project/constants.ts
+++ b/frontend/components/common/project/constants.ts
@@ -1,4 +1,5 @@
import {TrustLevel} from '@/lib/services/core/types';
+import {RequirementLimitType} from '@/lib/services/project/types';
/**
* 表单验证常量
@@ -63,6 +64,14 @@ export const TRUST_LEVEL_OPTIONS = [
{value: TrustLevel.LEADER, label: '领导者'},
];
+export const REQUIREMENT_LIMIT_TYPE_OPTIONS = [
+ {value: RequirementLimitType.MINIMUM, label: '最低'},
+ {value: RequirementLimitType.MAXIMUM, label: '最高'},
+];
+
+export const getRequirementLimitTypeLabel = (value: RequirementLimitType): string =>
+ REQUIREMENT_LIMIT_TYPE_OPTIONS.find((option) => option.value === value)?.label || '最低';
+
/**
* 信任等级配置 - 用于卡片样式和显示
*/
diff --git a/frontend/components/common/receive/ReceiveContent.tsx b/frontend/components/common/receive/ReceiveContent.tsx
index 4d7edc9..68a903f 100644
--- a/frontend/components/common/receive/ReceiveContent.tsx
+++ b/frontend/components/common/receive/ReceiveContent.tsx
@@ -9,14 +9,14 @@ import {Button} from '@/components/ui/button';
import {Badge} from '@/components/ui/badge';
import {Avatar, AvatarFallback, AvatarImage} from '@/components/ui/avatar';
import {AvatarGroup, AvatarGroupTooltip} from '@/components/animate-ui/components/animate/avatar-group';
-import {CURRENCY_LABEL, DISTRIBUTION_MODE_NAMES, TRUST_LEVEL_OPTIONS} from '@/components/common/project';
+import {CURRENCY_LABEL, DISTRIBUTION_MODE_NAMES, getRequirementLimitTypeLabel, TRUST_LEVEL_OPTIONS} from '@/components/common/project';
import {ArrowLeftIcon, Copy, Gift, Clock, AlertCircle, Package, Coins, Loader2, CalendarRange, Hash} from 'lucide-react';
import ContentRender from '@/components/common/markdown/ContentRender';
import {ReportButton} from '@/components/common/receive/ReportButton';
import {ReceiveVerify, ReceiveVerifyRef} from '@/components/common/receive/ReceiveVerify';
import services from '@/lib/services';
import {BasicUserInfo} from '@/lib/services/core';
-import {GetProjectResponseData} from '@/lib/services/project';
+import {GetProjectResponseData, RequirementLimitType} from '@/lib/services/project';
import {formatDate, formatDateTimeWithSeconds, copyToClipboard} from '@/lib/utils';
import {motion} from 'motion/react';
import {Separator} from '@/components/ui/separator';
@@ -91,11 +91,30 @@ const ReceiveButton = ({
);
}
- if (!user || user.trust_level < project.minimum_trust_level) {
+ const trustLevelBlocked = !user ||
+ (project.trust_level_limit_type === RequirementLimitType.MINIMUM ?
+ user.trust_level < project.minimum_trust_level :
+ user.trust_level > project.minimum_trust_level);
+ const scoreThreshold = 100 - project.risk_level;
+ const scoreBlocked = !user ||
+ (project.score_limit_type === RequirementLimitType.MINIMUM ?
+ user.score < scoreThreshold :
+ user.score > scoreThreshold);
+
+ if (trustLevelBlocked) {
return (
+ );
+ }
+
+ if (scoreBlocked) {
+ return (
+
);
}
@@ -396,6 +415,9 @@ export function ReceiveContent({data}: ReceiveContentProps) {
const trustLevelConfig = TRUST_LEVEL_OPTIONS.find((option) => option.value === currentProject.minimum_trust_level);
const distributionModeName = DISTRIBUTION_MODE_NAMES[currentProject.distribution_type] || '分发项目';
+ const scoreThreshold = 100 - currentProject.risk_level;
+ const trustLabel = `${getRequirementLimitTypeLabel(currentProject.trust_level_limit_type)}${trustLevelConfig?.label || '无限制'}`;
+ const scoreLabel = `${getRequirementLimitTypeLabel(currentProject.score_limit_type)} ${scoreThreshold}`;
const startTime = new Date(currentProject.start_time);
const endTime = new Date(currentProject.end_time);
const receivedCount = Math.max(currentProject.total_items - currentProject.available_items_count, 0);
@@ -424,11 +446,11 @@ export function ReceiveContent({data}: ReceiveContentProps) {
},
{
label: '社区分数',
- value: `${100 - currentProject.risk_level}`,
+ value: scoreLabel,
},
{
label: '信任等级',
- value: trustLevelConfig?.label || '无限制',
+ value: trustLabel,
},
{
label: '消耗 LDC',
diff --git a/frontend/hooks/use-project-form.ts b/frontend/hooks/use-project-form.ts
index 073afb3..7069180 100644
--- a/frontend/hooks/use-project-form.ts
+++ b/frontend/hooks/use-project-form.ts
@@ -2,7 +2,7 @@
import {useState, useCallback} from 'react';
import {TrustLevel} from '@/lib/services/core/types';
-import {DistributionType, ProjectListItem} from '@/lib/services/project/types';
+import {DistributionType, ProjectListItem, RequirementLimitType} from '@/lib/services/project/types';
import {DEFAULT_FORM_VALUES} from '@/components/common/project';
export interface ProjectFormData {
@@ -10,8 +10,10 @@ export interface ProjectFormData {
description: string;
startTime: Date;
endTime: Date;
+ trustLevelLimitType: RequirementLimitType;
minimumTrustLevel: TrustLevel;
allowSameIP: boolean;
+ scoreLimitType: RequirementLimitType;
riskLevel: number;
distributionType: DistributionType;
topicId?: number;
@@ -35,8 +37,10 @@ export function useProjectForm(options: UseProjectFormOptions) {
description: project.description || '',
startTime: new Date(project.start_time),
endTime: new Date(project.end_time),
+ trustLevelLimitType: project.trust_level_limit_type ?? RequirementLimitType.MINIMUM,
minimumTrustLevel: project.minimum_trust_level,
allowSameIP: project.allow_same_ip,
+ scoreLimitType: project.score_limit_type ?? RequirementLimitType.MINIMUM,
riskLevel: project.risk_level,
distributionType: project.distribution_type,
price: project.price ?? '0',
@@ -48,8 +52,10 @@ export function useProjectForm(options: UseProjectFormOptions) {
description: '',
startTime: new Date(),
endTime: new Date(Date.now() + DEFAULT_FORM_VALUES.TIME_OFFSET_24H),
+ trustLevelLimitType: RequirementLimitType.MINIMUM,
minimumTrustLevel: TrustLevel.BASIC_USER,
allowSameIP: false,
+ scoreLimitType: RequirementLimitType.MINIMUM,
riskLevel: DEFAULT_FORM_VALUES.RISK_LEVEL,
distributionType: DistributionType.ONE_FOR_EACH,
price: '0',
diff --git a/frontend/lib/services/project/types.ts b/frontend/lib/services/project/types.ts
index c17cf99..a9ee6ca 100644
--- a/frontend/lib/services/project/types.ts
+++ b/frontend/lib/services/project/types.ts
@@ -13,6 +13,17 @@ export enum DistributionType {
INVITE = 2,
}
+/**
+ * 项目限制类型
+ */
+/* eslint-disable no-unused-vars */
+export enum RequirementLimitType {
+ /** 最低 */
+ MINIMUM = 0,
+ /** 最高 */
+ MAXIMUM = 1,
+}
+
/**
* 项目基础信息
*/
@@ -31,10 +42,14 @@ export interface Project {
start_time: string;
/** 结束时间 */
end_time: string;
+ /** 社区等级限制类型 */
+ trust_level_limit_type: RequirementLimitType;
/** 最低信任等级 */
minimum_trust_level: TrustLevel;
/** 是否允许同一IP */
allow_same_ip: boolean;
+ /** 社区分数限制类型 */
+ score_limit_type: RequirementLimitType;
/** 风险等级 */
risk_level: number;
/** 领取单价,字符串形式的两位小数。"0" 或空表示免费 */
@@ -61,10 +76,14 @@ export interface CreateProjectRequest {
start_time: string;
/** 结束时间 */
end_time: string;
+ /** 社区等级限制类型 */
+ trust_level_limit_type?: RequirementLimitType;
/** 最低信任等级 */
minimum_trust_level: TrustLevel;
/** 是否允许同一IP */
allow_same_ip?: boolean;
+ /** 社区分数限制类型 */
+ score_limit_type?: RequirementLimitType;
/** 风险等级 */
risk_level?: number;
/** 分发类型 */
@@ -91,10 +110,14 @@ export interface UpdateProjectRequest {
start_time: string;
/** 结束时间 */
end_time: string;
+ /** 社区等级限制类型 */
+ trust_level_limit_type?: RequirementLimitType;
/** 最低信任等级 */
minimum_trust_level: TrustLevel;
/** 是否允许同一IP */
allow_same_ip?: boolean;
+ /** 社区分数限制类型 */
+ score_limit_type?: RequirementLimitType;
/** 风险等级 */
risk_level?: number;
/** 追加的项目物品列表 */
@@ -284,10 +307,14 @@ export interface ProjectListItem {
start_time: string;
/** 结束时间 */
end_time: string;
+ /** 社区等级限制类型 */
+ trust_level_limit_type: RequirementLimitType;
/** 最低信任等级 */
minimum_trust_level: TrustLevel;
/** 是否允许同一IP */
allow_same_ip: boolean;
+ /** 社区分数限制类型 */
+ score_limit_type: RequirementLimitType;
/** 风险等级 */
risk_level: number;
/** 领取单价,字符串,两位小数 */
diff --git a/internal/apps/project/constants.go b/internal/apps/project/constants.go
index 05d2a0d..7679f26 100644
--- a/internal/apps/project/constants.go
+++ b/internal/apps/project/constants.go
@@ -38,6 +38,13 @@ const (
DistributionTypeInvite
)
+type RequirementLimitType int8
+
+const (
+ RequirementLimitTypeMinimum RequirementLimitType = iota
+ RequirementLimitTypeMaximum
+)
+
type ProjectStatus uint8
const (
diff --git a/internal/apps/project/err.go b/internal/apps/project/err.go
index e948856..78d991b 100644
--- a/internal/apps/project/err.go
+++ b/internal/apps/project/err.go
@@ -30,8 +30,10 @@ const (
TimeTooEarly = "未到开启时间"
TimeTooLate = "已经结束"
TrustLevelNotMatch = "社区等级未达标,需要信任等级 %d"
+ TrustLevelTooHigh = "社区等级过高,仅限信任等级 %d 及以下"
UnknownError = "未知异常"
ScoreNotEnough = "社区分数未达标,需要分数 %d"
+ ScoreTooHigh = "社区分数过高,仅限分数 %d 及以下"
SameIPReceived = "已有相同IP领取"
NoStock = "无库存"
NotFound = "项目不存在"
diff --git a/internal/apps/project/models.go b/internal/apps/project/models.go
index d4f730d..91a9d30 100644
--- a/internal/apps/project/models.go
+++ b/internal/apps/project/models.go
@@ -47,25 +47,27 @@ import (
)
type Project struct {
- ID string `json:"id" gorm:"primaryKey;size:64"`
- Name string `json:"name" gorm:"size:32"`
- Description string `json:"description" gorm:"size:1024"`
- DistributionType DistributionType `json:"distribution_type"`
- TotalItems int64 `json:"total_items"`
- StartTime time.Time `json:"start_time"`
- EndTime time.Time `json:"end_time" gorm:"index:idx_projects_end_completed_trust_risk,priority:1"`
- MinimumTrustLevel oauth.TrustLevel `json:"minimum_trust_level" gorm:"index:idx_projects_end_completed_trust_risk,priority:4"`
- AllowSameIP bool `json:"allow_same_ip"`
- RiskLevel int8 `json:"risk_level" gorm:"index:idx_projects_end_completed_trust_risk,priority:5"`
- CreatorID uint64 `json:"creator_id" gorm:"index"`
- IsCompleted bool `json:"is_completed" gorm:"index:idx_projects_end_completed_trust_risk,priority:2"`
- Status ProjectStatus `json:"status" gorm:"default:0;index;index:idx_projects_end_completed_trust_risk,priority:3"`
- ReportCount uint8 `json:"report_count" gorm:"default:0"`
- HideFromExplore bool `json:"hide_from_explore" gorm:"default:false"`
- Price decimal.Decimal `json:"price" gorm:"type:decimal(10,2);default:0;not null"`
- Creator oauth.User `json:"-" gorm:"foreignKey:CreatorID"`
- CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
- UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
+ ID string `json:"id" gorm:"primaryKey;size:64"`
+ Name string `json:"name" gorm:"size:32"`
+ Description string `json:"description" gorm:"size:1024"`
+ DistributionType DistributionType `json:"distribution_type"`
+ TotalItems int64 `json:"total_items"`
+ StartTime time.Time `json:"start_time"`
+ EndTime time.Time `json:"end_time" gorm:"index:idx_projects_end_completed_trust_risk,priority:1"`
+ TrustLevelLimitType RequirementLimitType `json:"trust_level_limit_type" gorm:"default:0;index:idx_projects_end_completed_trust_risk,priority:4"`
+ MinimumTrustLevel oauth.TrustLevel `json:"minimum_trust_level" gorm:"index:idx_projects_end_completed_trust_risk,priority:5"`
+ AllowSameIP bool `json:"allow_same_ip"`
+ ScoreLimitType RequirementLimitType `json:"score_limit_type" gorm:"default:0;index:idx_projects_end_completed_trust_risk,priority:6"`
+ RiskLevel int8 `json:"risk_level" gorm:"index:idx_projects_end_completed_trust_risk,priority:7"`
+ CreatorID uint64 `json:"creator_id" gorm:"index"`
+ IsCompleted bool `json:"is_completed" gorm:"index:idx_projects_end_completed_trust_risk,priority:2"`
+ Status ProjectStatus `json:"status" gorm:"default:0;index;index:idx_projects_end_completed_trust_risk,priority:3"`
+ ReportCount uint8 `json:"report_count" gorm:"default:0"`
+ HideFromExplore bool `json:"hide_from_explore" gorm:"default:false"`
+ Price decimal.Decimal `json:"price" gorm:"type:decimal(10,2);default:0;not null"`
+ Creator oauth.User `json:"-" gorm:"foreignKey:CreatorID"`
+ CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
+ UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}
// IsPaid 是否为付费项目
@@ -415,12 +417,31 @@ func (p *Project) HasStock(ctx context.Context) (bool, error) {
return stock > 0, nil
}
+func (p *Project) ScoreThreshold() int {
+ return oauth.BaseUserScore - int(p.RiskLevel)
+}
+
func (p *Project) ValidateRequirement(user *oauth.User) error {
- if user.TrustLevel < p.MinimumTrustLevel {
- return fmt.Errorf(TrustLevelNotMatch, p.MinimumTrustLevel)
+ switch p.TrustLevelLimitType {
+ case RequirementLimitTypeMaximum:
+ if user.TrustLevel > p.MinimumTrustLevel {
+ return fmt.Errorf(TrustLevelTooHigh, p.MinimumTrustLevel)
+ }
+ default:
+ if user.TrustLevel < p.MinimumTrustLevel {
+ return fmt.Errorf(TrustLevelNotMatch, p.MinimumTrustLevel)
+ }
}
- if user.RiskLevel() > p.RiskLevel {
- return fmt.Errorf(ScoreNotEnough, 100-int(p.RiskLevel))
+
+ switch p.ScoreLimitType {
+ case RequirementLimitTypeMaximum:
+ if user.RiskLevel() < p.RiskLevel {
+ return fmt.Errorf(ScoreTooHigh, p.ScoreThreshold())
+ }
+ default:
+ if user.RiskLevel() > p.RiskLevel {
+ return fmt.Errorf(ScoreNotEnough, p.ScoreThreshold())
+ }
}
return nil
}
diff --git a/internal/apps/project/routers.go b/internal/apps/project/routers.go
index c10f217..b1b9550 100644
--- a/internal/apps/project/routers.go
+++ b/internal/apps/project/routers.go
@@ -46,16 +46,18 @@ type ProjectResponse struct {
}
type ProjectRequest struct {
- Name string `json:"name" binding:"required,min=1,max=32"`
- Description string `json:"description" binding:"max=1024"`
- ProjectTags []string `json:"project_tags" binding:"dive,min=1,max=16"`
- StartTime time.Time `json:"start_time" binding:"required"`
- EndTime time.Time `json:"end_time" binding:"required,gtfield=StartTime"`
- MinimumTrustLevel oauth.TrustLevel `json:"minimum_trust_level" binding:"oneof=0 1 2 3 4"`
- AllowSameIP bool `json:"allow_same_ip"`
- RiskLevel int8 `json:"risk_level" binding:"min=0,max=100"`
- HideFromExplore bool `json:"hide_from_explore"`
- Price decimal.Decimal `json:"price"`
+ Name string `json:"name" binding:"required,min=1,max=32"`
+ Description string `json:"description" binding:"max=1024"`
+ ProjectTags []string `json:"project_tags" binding:"dive,min=1,max=16"`
+ StartTime time.Time `json:"start_time" binding:"required"`
+ EndTime time.Time `json:"end_time" binding:"required,gtfield=StartTime"`
+ TrustLevelLimitType RequirementLimitType `json:"trust_level_limit_type" binding:"oneof=0 1"`
+ MinimumTrustLevel oauth.TrustLevel `json:"minimum_trust_level" binding:"oneof=0 1 2 3 4"`
+ AllowSameIP bool `json:"allow_same_ip"`
+ ScoreLimitType RequirementLimitType `json:"score_limit_type" binding:"oneof=0 1"`
+ RiskLevel int8 `json:"risk_level" binding:"min=0,max=100"`
+ HideFromExplore bool `json:"hide_from_explore"`
+ Price decimal.Decimal `json:"price"`
}
type GetProjectResponseData struct {
Project `json:",inline"` // 内嵌所有 Project 字段
@@ -89,9 +91,11 @@ func GetProject(c *gin.Context) {
}
return
}
- if err := project.ValidateRequirement(currentUser); err != nil {
- c.JSON(http.StatusForbidden, ProjectResponse{ErrorMsg: err.Error()})
- return
+ if project.CreatorID != currentUser.ID {
+ if err := project.ValidateRequirement(currentUser); err != nil {
+ c.JSON(http.StatusForbidden, ProjectResponse{ErrorMsg: err.Error()})
+ return
+ }
}
tags, err := project.GetTags(db.DB(c.Request.Context()))
@@ -177,20 +181,22 @@ func CreateProject(c *gin.Context) {
// init project
project := Project{
- ID: uuid.NewString(),
- Name: req.Name,
- Description: req.Description,
- DistributionType: req.DistributionType,
- TotalItems: int64(len(req.ProjectItems)),
- StartTime: req.StartTime,
- EndTime: req.EndTime,
- MinimumTrustLevel: req.MinimumTrustLevel,
- AllowSameIP: req.AllowSameIP,
- RiskLevel: req.RiskLevel,
- CreatorID: currentUser.ID,
- IsCompleted: false,
- HideFromExplore: req.HideFromExplore,
- Price: req.Price,
+ ID: uuid.NewString(),
+ Name: req.Name,
+ Description: req.Description,
+ DistributionType: req.DistributionType,
+ TotalItems: int64(len(req.ProjectItems)),
+ StartTime: req.StartTime,
+ EndTime: req.EndTime,
+ TrustLevelLimitType: req.TrustLevelLimitType,
+ MinimumTrustLevel: req.MinimumTrustLevel,
+ AllowSameIP: req.AllowSameIP,
+ ScoreLimitType: req.ScoreLimitType,
+ RiskLevel: req.RiskLevel,
+ CreatorID: currentUser.ID,
+ IsCompleted: false,
+ HideFromExplore: req.HideFromExplore,
+ Price: req.Price,
}
// create project
@@ -257,8 +263,10 @@ func UpdateProject(c *gin.Context) {
project.Description = req.Description
project.StartTime = req.StartTime
project.EndTime = req.EndTime
+ project.TrustLevelLimitType = req.TrustLevelLimitType
project.MinimumTrustLevel = req.MinimumTrustLevel
project.AllowSameIP = req.AllowSameIP
+ project.ScoreLimitType = req.ScoreLimitType
project.RiskLevel = req.RiskLevel
project.HideFromExplore = req.HideFromExplore
project.Price = req.Price
@@ -630,20 +638,22 @@ type ListProjectsRequest struct {
}
type ListProjectsResponseDataResult struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Description string `json:"description"`
- DistributionType DistributionType `json:"distribution_type"`
- TotalItems int64 `json:"total_items"`
- StartTime time.Time `json:"start_time"`
- EndTime time.Time `json:"end_time"`
- MinimumTrustLevel oauth.TrustLevel `json:"minimum_trust_level"`
- AllowSameIP bool `json:"allow_same_ip"`
- RiskLevel int8 `json:"risk_level"`
- HideFromExplore bool `json:"hide_from_explore"`
- Price decimal.Decimal `json:"price"`
- Tags utils.StringArray `json:"tags"`
- CreatedAt time.Time `json:"created_at"`
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Description string `json:"description"`
+ DistributionType DistributionType `json:"distribution_type"`
+ TotalItems int64 `json:"total_items"`
+ StartTime time.Time `json:"start_time"`
+ EndTime time.Time `json:"end_time"`
+ TrustLevelLimitType RequirementLimitType `json:"trust_level_limit_type"`
+ MinimumTrustLevel oauth.TrustLevel `json:"minimum_trust_level"`
+ AllowSameIP bool `json:"allow_same_ip"`
+ ScoreLimitType RequirementLimitType `json:"score_limit_type"`
+ RiskLevel int8 `json:"risk_level"`
+ HideFromExplore bool `json:"hide_from_explore"`
+ Price decimal.Decimal `json:"price"`
+ Tags utils.StringArray `json:"tags"`
+ CreatedAt time.Time `json:"created_at"`
}
type ListProjectsResponseData struct {
diff --git a/internal/apps/project/utils.go b/internal/apps/project/utils.go
index 319cb15..3eb2851 100644
--- a/internal/apps/project/utils.go
+++ b/internal/apps/project/utils.go
@@ -71,17 +71,33 @@ func ListProjectsWithTags(ctx context.Context, offset, limit int, tags []string,
getTotalCountSql := `SELECT COUNT(DISTINCT p.id) as total
FROM projects p
LEFT JOIN project_tags pt ON p.id = pt.project_id
- WHERE p.end_time > ? AND p.is_completed = false AND p.status = ? AND p.minimum_trust_level <= ? AND p.risk_level >= ? AND p.hide_from_explore = false AND NOT EXISTS ( SELECT 1 FROM project_items pi WHERE pi.project_id = p.id AND pi.receiver_id = ?)`
+ WHERE p.end_time > ? AND p.is_completed = false AND p.status = ?
+ AND ((p.trust_level_limit_type = ? AND p.minimum_trust_level <= ?) OR (p.trust_level_limit_type = ? AND p.minimum_trust_level >= ?))
+ AND ((p.score_limit_type = ? AND p.risk_level >= ?) OR (p.score_limit_type = ? AND p.risk_level <= ?))
+ AND p.hide_from_explore = false
+ AND NOT EXISTS (SELECT 1 FROM project_items pi WHERE pi.project_id = p.id AND pi.receiver_id = ?)`
getProjectWithTagsSql := `SELECT
p.id,p.name,p.description,p.distribution_type,p.total_items,
- p.start_time,p.end_time,p.minimum_trust_level,p.allow_same_ip,p.risk_level,p.price,p.created_at,
+ p.start_time,p.end_time,p.trust_level_limit_type,p.minimum_trust_level,p.allow_same_ip,p.score_limit_type,p.risk_level,p.price,p.created_at,
IF(COUNT(pt.tag) = 0, NULL, JSON_ARRAYAGG(pt.tag)) AS tags
FROM projects p
LEFT JOIN project_tags pt ON p.id = pt.project_id
- WHERE p.end_time > ? AND p.is_completed = false AND p.status = ? AND p.minimum_trust_level <= ? AND p.risk_level >= ? AND p.hide_from_explore = false AND NOT EXISTS ( SELECT 1 FROM project_items pi WHERE pi.project_id = p.id AND pi.receiver_id = ?)`
-
- var parameters = []interface{}{now, ProjectStatusNormal, currentUser.TrustLevel, currentUser.RiskLevel(), currentUser.ID}
+ WHERE p.end_time > ? AND p.is_completed = false AND p.status = ?
+ AND ((p.trust_level_limit_type = ? AND p.minimum_trust_level <= ?) OR (p.trust_level_limit_type = ? AND p.minimum_trust_level >= ?))
+ AND ((p.score_limit_type = ? AND p.risk_level >= ?) OR (p.score_limit_type = ? AND p.risk_level <= ?))
+ AND p.hide_from_explore = false
+ AND NOT EXISTS (SELECT 1 FROM project_items pi WHERE pi.project_id = p.id AND pi.receiver_id = ?)`
+
+ var parameters = []interface{}{
+ now,
+ ProjectStatusNormal,
+ RequirementLimitTypeMinimum, currentUser.TrustLevel,
+ RequirementLimitTypeMaximum, currentUser.TrustLevel,
+ RequirementLimitTypeMinimum, currentUser.RiskLevel(),
+ RequirementLimitTypeMaximum, currentUser.RiskLevel(),
+ currentUser.ID,
+ }
if len(tags) > 0 {
getTotalCountSql += ` AND pt.tag IN (?)`
getProjectWithTagsSql += ` AND pt.tag IN (?)`
@@ -126,7 +142,7 @@ func ListMyProjectsWithTags(ctx context.Context, creatorID uint64, offset, limit
getMyProjectWithTagsSql := `SELECT
p.id,p.name,p.description,p.distribution_type,p.total_items,
- p.start_time,p.end_time,p.minimum_trust_level,p.allow_same_ip,p.risk_level,p.hide_from_explore,p.price,p.created_at,
+ p.start_time,p.end_time,p.trust_level_limit_type,p.minimum_trust_level,p.allow_same_ip,p.score_limit_type,p.risk_level,p.hide_from_explore,p.price,p.created_at,
IF(COUNT(pt.tag) = 0, NULL, JSON_ARRAYAGG(pt.tag)) AS tags
FROM projects p
LEFT JOIN project_tags pt ON p.id = pt.project_id