fix: fix the name consistency
Some checks failed
Deploy skills-market-server / deploy (push) Has been cancelled
Some checks failed
Deploy skills-market-server / deploy (push) Has been cancelled
This commit is contained in:
parent
de65426d64
commit
b5634494b7
134
server.js
134
server.js
@ -158,6 +158,11 @@ function normalizeTagList(tags) {
|
||||
return out
|
||||
}
|
||||
|
||||
/** Slug rules must match publish — DB `name` is always lowercased and sanitized. */
|
||||
function normalizeResourceNameParam(raw) {
|
||||
return String(raw || '').toLowerCase().replace(/[^a-z0-9-]/g, '-')
|
||||
}
|
||||
|
||||
const ROOT_MODE_TAGS = ['clarify', 'cowork', 'create', 'video', 'code', 'like']
|
||||
|
||||
function getAllowedModeTagsFromUser(user) {
|
||||
@ -194,6 +199,43 @@ function canUserAccessTaggedResource(user, resourceTags) {
|
||||
return tags.some((tag) => allowedTags.includes(tag))
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制 frontmatter 里 `name:` 与路由/库里的规范 id 一致(避免模型写成英文标题导致与 Mongo.name 不一致)。
|
||||
*/
|
||||
function normalizeAgentMarkdownContent(content, canonicalName) {
|
||||
if (typeof content !== 'string' || !canonicalName) return content
|
||||
const fmMatch = content.match(/^---\s*\r?\n([\s\S]*?)\r?\n---/)
|
||||
if (!fmMatch) {
|
||||
return `---\nname: ${canonicalName}\n---\n\n${content}`
|
||||
}
|
||||
const lines = fmMatch[1].split(/\r?\n/)
|
||||
let replaced = false
|
||||
const out = lines.map((line) => {
|
||||
if (!replaced && /^name:\s*/.test(line)) {
|
||||
replaced = true
|
||||
return `name: ${canonicalName}`
|
||||
}
|
||||
return line
|
||||
})
|
||||
if (!replaced) {
|
||||
out.unshift(`name: ${canonicalName}`)
|
||||
}
|
||||
return `---\n${out.join('\n')}\n---` + content.slice(fmMatch[0].length)
|
||||
}
|
||||
|
||||
/** SKILL.md frontmatter `name:` 与路由/库 id 对齐(多文件技能包内只改这一条路径)。 */
|
||||
function normalizeSkillMdFilesName(files, canonicalName) {
|
||||
if (!Array.isArray(files)) return files
|
||||
return files.map((f) => {
|
||||
if (!f || typeof f.content !== 'string') return f
|
||||
const p = f.path || ''
|
||||
if (p === 'SKILL.md' || p.endsWith('/SKILL.md')) {
|
||||
return { ...f, content: normalizeAgentMarkdownContent(f.content, canonicalName) }
|
||||
}
|
||||
return f
|
||||
})
|
||||
}
|
||||
|
||||
function extractDescription(files) {
|
||||
const skillFile = files.find(f => f.path === 'SKILL.md' || f.path.endsWith('SKILL.md'))
|
||||
if (!skillFile) return ''
|
||||
@ -327,8 +369,9 @@ app.get('/api/skills', async (req, res) => {
|
||||
|
||||
app.get('/api/skills/:name', async (req, res) => {
|
||||
try {
|
||||
const nameKey = normalizeResourceNameParam(req.params.name)
|
||||
const skill = await skillsCollection.findOne({
|
||||
name: req.params.name,
|
||||
name: nameKey,
|
||||
is_public: PUBLIC_VISIBILITY_FILTER
|
||||
})
|
||||
|
||||
@ -359,8 +402,9 @@ app.get('/api/skills/:name', async (req, res) => {
|
||||
|
||||
app.get('/api/skills/:name/download', async (req, res) => {
|
||||
try {
|
||||
const nameKey = normalizeResourceNameParam(req.params.name)
|
||||
const skill = await skillsCollection.findOne({
|
||||
name: req.params.name,
|
||||
name: nameKey,
|
||||
is_public: PUBLIC_VISIBILITY_FILTER
|
||||
})
|
||||
|
||||
@ -392,8 +436,9 @@ app.post('/api/skills/:name/execute', requireAuth(async (req, res) => {
|
||||
return res.status(400).json({ success: false, error: 'scriptPath is required' })
|
||||
}
|
||||
|
||||
const nameKey = normalizeResourceNameParam(req.params.name)
|
||||
const skill = await skillsCollection.findOne({
|
||||
name: req.params.name,
|
||||
name: nameKey,
|
||||
is_public: PUBLIC_VISIBILITY_FILTER
|
||||
})
|
||||
|
||||
@ -413,7 +458,7 @@ app.post('/api/skills/:name/execute', requireAuth(async (req, res) => {
|
||||
const fs = require('fs')
|
||||
const { execSync } = require('child_process')
|
||||
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), `skill-${req.params.name}-`))
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), `skill-${nameKey}-`))
|
||||
const tempScript = path.join(tempDir, path.basename(scriptPath))
|
||||
fs.writeFileSync(tempScript, script.content, 'utf-8')
|
||||
|
||||
@ -460,8 +505,9 @@ app.post('/api/skills/:name/execute', requireAuth(async (req, res) => {
|
||||
app.get('/api/skills/:name/files/*', async (req, res) => {
|
||||
try {
|
||||
const filePath = req.params[0] // 捕获通配符部分
|
||||
const nameKey = normalizeResourceNameParam(req.params.name)
|
||||
const skill = await skillsCollection.findOne({
|
||||
name: req.params.name,
|
||||
name: nameKey,
|
||||
is_public: PUBLIC_VISIBILITY_FILTER
|
||||
})
|
||||
|
||||
@ -492,7 +538,8 @@ app.post('/api/skills/:name/lock', async (req, res) => {
|
||||
if (!authRoutes.hasPermission(req.user, 'canViewSkillsPage')) {
|
||||
return res.status(403).json({ success: false, error: '无权编辑技能' })
|
||||
}
|
||||
const skill = await skillsCollection.findOne({ name: req.params.name })
|
||||
const nameKey = normalizeResourceNameParam(req.params.name)
|
||||
const skill = await skillsCollection.findOne({ name: nameKey })
|
||||
if (!skill) {
|
||||
return res.status(404).json({ success: false, error: 'Skill not found' })
|
||||
}
|
||||
@ -517,7 +564,8 @@ app.delete('/api/skills/:name/lock', async (req, res) => {
|
||||
if (!authRoutes.hasPermission(req.user, 'canViewSkillsPage')) {
|
||||
return res.status(403).json({ success: false, error: '无权编辑技能' })
|
||||
}
|
||||
const skill = await skillsCollection.findOne({ name: req.params.name })
|
||||
const nameKey = normalizeResourceNameParam(req.params.name)
|
||||
const skill = await skillsCollection.findOne({ name: nameKey })
|
||||
if (!skill) {
|
||||
return res.status(404).json({ success: false, error: 'Skill not found' })
|
||||
}
|
||||
@ -590,8 +638,9 @@ app.patch('/api/skills/:name/tags', async (req, res) => {
|
||||
const allowedModeTags = getAllowedModeTagsFromUser(req.user)
|
||||
const nextTags = normalizeTagList(req.body?.tags).filter((tag) => allowedModeTags.includes(tag))
|
||||
const now = new Date()
|
||||
const nameKey = normalizeResourceNameParam(req.params.name)
|
||||
const result = await skillsCollection.findOneAndUpdate(
|
||||
{ name: req.params.name },
|
||||
{ name: nameKey },
|
||||
{
|
||||
$set: {
|
||||
tags: nextTags,
|
||||
@ -644,8 +693,7 @@ app.post('/api/skills/:name/publish', async (req, res) => {
|
||||
return res.status(400).json({ success: false, error: 'No files provided' })
|
||||
}
|
||||
|
||||
const skillName = req.params.name.toLowerCase().replace(/[^a-z0-9-]/g, '-')
|
||||
const skillDescription = description || extractDescription(files)
|
||||
const skillName = normalizeResourceNameParam(req.params.name)
|
||||
const now = new Date()
|
||||
|
||||
const existingSkill = await skillsCollection.findOne({ name: skillName })
|
||||
@ -684,6 +732,8 @@ app.post('/api/skills/:name/publish', async (req, res) => {
|
||||
const nextFiles = useReplaceAll
|
||||
? files
|
||||
: mergeSkillFilesByPath(existingSkill.files || [], files)
|
||||
const nextFilesNorm = normalizeSkillMdFilesName(nextFiles, skillName)
|
||||
const skillDescription = description || extractDescription(nextFilesNorm)
|
||||
|
||||
const versionEntry = {
|
||||
version: (existingSkill.versions?.length || 0) + 1,
|
||||
@ -696,7 +746,7 @@ app.post('/api/skills/:name/publish', async (req, res) => {
|
||||
|
||||
const updateData = {
|
||||
$set: {
|
||||
files: nextFiles,
|
||||
files: nextFilesNorm,
|
||||
description: skillDescription,
|
||||
updated_at: now,
|
||||
updated_by: userId,
|
||||
@ -718,19 +768,21 @@ app.post('/api/skills/:name/publish', async (req, res) => {
|
||||
version: versionEntry.version
|
||||
})
|
||||
} else {
|
||||
const filesNorm = normalizeSkillMdFilesName(files, skillName)
|
||||
const skillDescription = description || extractDescription(filesNorm)
|
||||
const newSkill = {
|
||||
name: skillName,
|
||||
description: skillDescription,
|
||||
owner: userId,
|
||||
owner_nickname: userNickname,
|
||||
files,
|
||||
files: filesNorm,
|
||||
downloads: 0,
|
||||
is_public: true,
|
||||
tags: normalizedTags,
|
||||
versions: [{
|
||||
version: 1,
|
||||
description: 'Initial version',
|
||||
files,
|
||||
files: filesNorm,
|
||||
created_at: now,
|
||||
created_by: userId,
|
||||
created_by_nickname: userNickname
|
||||
@ -767,8 +819,9 @@ app.post('/api/skills/:name/publish', async (req, res) => {
|
||||
|
||||
app.get('/api/skills/:name/versions', async (req, res) => {
|
||||
try {
|
||||
const nameKey = normalizeResourceNameParam(req.params.name)
|
||||
const skill = await skillsCollection.findOne(
|
||||
{ name: req.params.name },
|
||||
{ name: nameKey },
|
||||
{ projection: { versions: 1 } }
|
||||
)
|
||||
|
||||
@ -793,9 +846,9 @@ app.get('/api/skills/:name/versions', async (req, res) => {
|
||||
app.get('/api/skills/:name/versions/:version', async (req, res) => {
|
||||
try {
|
||||
const versionNum = parseInt(req.params.version)
|
||||
|
||||
const nameKey = normalizeResourceNameParam(req.params.name)
|
||||
const skill = await skillsCollection.findOne(
|
||||
{ name: req.params.name },
|
||||
{ name: nameKey },
|
||||
{ projection: { versions: 1 } }
|
||||
)
|
||||
|
||||
@ -827,7 +880,8 @@ app.get('/api/skills/:name/versions/:version', async (req, res) => {
|
||||
|
||||
app.delete('/api/skills/:name', requireAdmin(async (req, res) => {
|
||||
try {
|
||||
const skill = await skillsCollection.findOne({ name: req.params.name })
|
||||
const nameKey = normalizeResourceNameParam(req.params.name)
|
||||
const skill = await skillsCollection.findOne({ name: nameKey })
|
||||
|
||||
if (!skill) {
|
||||
return res.status(404).json({ success: false, error: 'Skill not found' })
|
||||
@ -946,7 +1000,8 @@ app.get('/api/agents/mine', async (req, res, next) => {
|
||||
|
||||
app.get('/api/agents/:name', async (req, res) => {
|
||||
try {
|
||||
const agent = await agentsCollection.findOne({ name: req.params.name, is_public: PUBLIC_VISIBILITY_FILTER })
|
||||
const nameKey = normalizeResourceNameParam(req.params.name)
|
||||
const agent = await agentsCollection.findOne({ name: nameKey, is_public: PUBLIC_VISIBILITY_FILTER })
|
||||
if (!agent || !canUserAccessTaggedResource(req.user, agent.tags)) {
|
||||
return res.status(404).json({ success: false, error: 'Agent not found' })
|
||||
}
|
||||
@ -969,7 +1024,8 @@ app.post('/api/agents/:name/publish', async (req, res) => {
|
||||
const normalizedTags = normalizeTagList(tags).filter((tag) => allowedModeTags.includes(tag))
|
||||
if (!content) return res.status(400).json({ success: false, error: 'No content provided' })
|
||||
|
||||
const agentName = req.params.name.toLowerCase().replace(/[^a-z0-9-]/g, '-')
|
||||
const agentName = normalizeResourceNameParam(req.params.name)
|
||||
const contentNormalized = normalizeAgentMarkdownContent(content, agentName)
|
||||
const now = new Date()
|
||||
const existing = await agentsCollection.findOne({ name: agentName })
|
||||
|
||||
@ -994,12 +1050,18 @@ app.post('/api/agents/:name/publish', async (req, res) => {
|
||||
remote_updated_at: existing.updated_at
|
||||
})
|
||||
}
|
||||
const versionEntry = { version: (existing.versions?.length || 0) + 1, content: existing.content, created_at: now, created_by: userId, created_by_nickname: userNickname }
|
||||
const versionEntry = {
|
||||
version: (existing.versions?.length || 0) + 1,
|
||||
content: existing.content,
|
||||
created_at: now,
|
||||
created_by: userId,
|
||||
created_by_nickname: userNickname
|
||||
}
|
||||
await agentsCollection.updateOne(
|
||||
{ _id: existing._id },
|
||||
{
|
||||
$set: {
|
||||
content,
|
||||
content: contentNormalized,
|
||||
description: description || existing.description,
|
||||
updated_at: now,
|
||||
updated_by: userId,
|
||||
@ -1016,10 +1078,18 @@ app.post('/api/agents/:name/publish', async (req, res) => {
|
||||
description: description || '',
|
||||
owner: userId,
|
||||
owner_nickname: userNickname,
|
||||
content,
|
||||
content: contentNormalized,
|
||||
is_public: true,
|
||||
tags: normalizedTags,
|
||||
versions: [{ version: 1, content, created_at: now, created_by: userId, created_by_nickname: userNickname }],
|
||||
versions: [
|
||||
{
|
||||
version: 1,
|
||||
content: contentNormalized,
|
||||
created_at: now,
|
||||
created_by: userId,
|
||||
created_by_nickname: userNickname
|
||||
}
|
||||
],
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
created_by: userId,
|
||||
@ -1045,8 +1115,9 @@ app.patch('/api/agents/:name/tags', async (req, res) => {
|
||||
const allowedModeTags = getAllowedModeTagsFromUser(req.user)
|
||||
const nextTags = normalizeTagList(req.body?.tags).filter((tag) => allowedModeTags.includes(tag))
|
||||
const now = new Date()
|
||||
const nameKey = normalizeResourceNameParam(req.params.name)
|
||||
const result = await agentsCollection.findOneAndUpdate(
|
||||
{ name: req.params.name },
|
||||
{ name: nameKey },
|
||||
{
|
||||
$set: {
|
||||
tags: nextTags,
|
||||
@ -1069,7 +1140,8 @@ app.patch('/api/agents/:name/tags', async (req, res) => {
|
||||
|
||||
app.delete('/api/agents/:name', requireAdmin(async (req, res) => {
|
||||
try {
|
||||
const agent = await agentsCollection.findOne({ name: req.params.name })
|
||||
const nameKey = normalizeResourceNameParam(req.params.name)
|
||||
const agent = await agentsCollection.findOne({ name: nameKey })
|
||||
if (!agent) return res.status(404).json({ success: false, error: 'Agent not found' })
|
||||
await agentsCollection.deleteOne({ _id: agent._id })
|
||||
res.json({ success: true })
|
||||
@ -1084,7 +1156,8 @@ app.post('/api/agents/:name/lock', async (req, res, next) => {
|
||||
if (!authRoutes.hasPermission(req.user, 'canViewAgentsPage')) {
|
||||
return res.status(403).json({ success: false, error: '无权编辑智能体' })
|
||||
}
|
||||
const agent = await agentsCollection.findOne({ name: req.params.name })
|
||||
const nameKey = normalizeResourceNameParam(req.params.name)
|
||||
const agent = await agentsCollection.findOne({ name: nameKey })
|
||||
if (!agent) return res.status(404).json({ success: false, error: 'Agent not found' })
|
||||
const activeLock = getActiveLock(agent)
|
||||
if (activeLock && !sameUserId(activeLock.userId, req.user.id)) {
|
||||
@ -1104,7 +1177,8 @@ app.delete('/api/agents/:name/lock', async (req, res, next) => {
|
||||
if (!authRoutes.hasPermission(req.user, 'canViewAgentsPage')) {
|
||||
return res.status(403).json({ success: false, error: '无权编辑智能体' })
|
||||
}
|
||||
const agent = await agentsCollection.findOne({ name: req.params.name })
|
||||
const nameKey = normalizeResourceNameParam(req.params.name)
|
||||
const agent = await agentsCollection.findOne({ name: nameKey })
|
||||
if (!agent) return res.status(404).json({ success: false, error: 'Agent not found' })
|
||||
const activeLock = getActiveLock(agent)
|
||||
const isAdmin = req.user.role === 'admin'
|
||||
@ -1121,7 +1195,8 @@ app.delete('/api/agents/:name/lock', async (req, res, next) => {
|
||||
|
||||
app.get('/api/agents/:name/versions', async (req, res) => {
|
||||
try {
|
||||
const agent = await agentsCollection.findOne({ name: req.params.name }, { projection: { versions: 1 } })
|
||||
const nameKey = normalizeResourceNameParam(req.params.name)
|
||||
const agent = await agentsCollection.findOne({ name: nameKey }, { projection: { versions: 1 } })
|
||||
if (!agent) return res.status(404).json({ success: false, error: 'Agent not found' })
|
||||
const versions = (agent.versions || []).map((v) => ({ version: v.version, created_at: v.created_at, created_by: v.created_by, created_by_nickname: v.created_by_nickname }))
|
||||
res.json({ success: true, versions })
|
||||
@ -1132,7 +1207,8 @@ app.get('/api/agents/:name/versions', async (req, res) => {
|
||||
|
||||
app.get('/api/agents/:name/versions/:version', async (req, res) => {
|
||||
try {
|
||||
const agent = await agentsCollection.findOne({ name: req.params.name }, { projection: { versions: 1 } })
|
||||
const nameKey = normalizeResourceNameParam(req.params.name)
|
||||
const agent = await agentsCollection.findOne({ name: nameKey }, { projection: { versions: 1 } })
|
||||
if (!agent) return res.status(404).json({ success: false, error: 'Agent not found' })
|
||||
const versionNum = parseInt(req.params.version, 10)
|
||||
const version = (agent.versions || []).find((v) => v.version === versionNum)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user