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