refactor(task-tool): split task.ts into per-action modules
Extract CRUD actions into dedicated modules: - task-action-create.ts, task-action-get.ts - task-action-list.ts, task-action-update.ts, task-action-delete.ts - task-id-validator.ts: ID validation logic
This commit is contained in:
46
src/tools/task/task-action-create.ts
Normal file
46
src/tools/task/task-action-create.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { join } from "path"
|
||||||
|
import type { OhMyOpenCodeConfig } from "../../config/schema"
|
||||||
|
import type { TaskObject } from "./types"
|
||||||
|
import { TaskCreateInputSchema, TaskObjectSchema } from "./types"
|
||||||
|
import {
|
||||||
|
acquireLock,
|
||||||
|
generateTaskId,
|
||||||
|
getTaskDir,
|
||||||
|
writeJsonAtomic,
|
||||||
|
} from "../../features/claude-tasks/storage"
|
||||||
|
|
||||||
|
export async function handleCreate(
|
||||||
|
args: Record<string, unknown>,
|
||||||
|
config: Partial<OhMyOpenCodeConfig>,
|
||||||
|
context: { sessionID: string }
|
||||||
|
): Promise<string> {
|
||||||
|
const validatedArgs = TaskCreateInputSchema.parse(args)
|
||||||
|
const taskDir = getTaskDir(config)
|
||||||
|
const lock = acquireLock(taskDir)
|
||||||
|
|
||||||
|
if (!lock.acquired) {
|
||||||
|
return JSON.stringify({ error: "task_lock_unavailable" })
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const taskId = generateTaskId()
|
||||||
|
const task: TaskObject = {
|
||||||
|
id: taskId,
|
||||||
|
subject: validatedArgs.subject,
|
||||||
|
description: validatedArgs.description ?? "",
|
||||||
|
status: "pending",
|
||||||
|
blocks: validatedArgs.blocks ?? [],
|
||||||
|
blockedBy: validatedArgs.blockedBy ?? [],
|
||||||
|
repoURL: validatedArgs.repoURL,
|
||||||
|
parentID: validatedArgs.parentID,
|
||||||
|
threadID: context.sessionID,
|
||||||
|
}
|
||||||
|
|
||||||
|
const validatedTask = TaskObjectSchema.parse(task)
|
||||||
|
writeJsonAtomic(join(taskDir, `${taskId}.json`), validatedTask)
|
||||||
|
|
||||||
|
return JSON.stringify({ task: validatedTask })
|
||||||
|
} finally {
|
||||||
|
lock.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/tools/task/task-action-delete.ts
Normal file
36
src/tools/task/task-action-delete.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { existsSync, unlinkSync } from "fs"
|
||||||
|
import { join } from "path"
|
||||||
|
import type { OhMyOpenCodeConfig } from "../../config/schema"
|
||||||
|
import { TaskDeleteInputSchema } from "./types"
|
||||||
|
import { acquireLock, getTaskDir } from "../../features/claude-tasks/storage"
|
||||||
|
import { parseTaskId } from "./task-id-validator"
|
||||||
|
|
||||||
|
export async function handleDelete(
|
||||||
|
args: Record<string, unknown>,
|
||||||
|
config: Partial<OhMyOpenCodeConfig>
|
||||||
|
): Promise<string> {
|
||||||
|
const validatedArgs = TaskDeleteInputSchema.parse(args)
|
||||||
|
const taskId = parseTaskId(validatedArgs.id)
|
||||||
|
if (!taskId) {
|
||||||
|
return JSON.stringify({ error: "invalid_task_id" })
|
||||||
|
}
|
||||||
|
const taskDir = getTaskDir(config)
|
||||||
|
const lock = acquireLock(taskDir)
|
||||||
|
|
||||||
|
if (!lock.acquired) {
|
||||||
|
return JSON.stringify({ error: "task_lock_unavailable" })
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const taskPath = join(taskDir, `${taskId}.json`)
|
||||||
|
|
||||||
|
if (!existsSync(taskPath)) {
|
||||||
|
return JSON.stringify({ error: "task_not_found" })
|
||||||
|
}
|
||||||
|
|
||||||
|
unlinkSync(taskPath)
|
||||||
|
return JSON.stringify({ success: true })
|
||||||
|
} finally {
|
||||||
|
lock.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/tools/task/task-action-get.ts
Normal file
21
src/tools/task/task-action-get.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { join } from "path"
|
||||||
|
import type { OhMyOpenCodeConfig } from "../../config/schema"
|
||||||
|
import { TaskGetInputSchema, TaskObjectSchema } from "./types"
|
||||||
|
import { getTaskDir, readJsonSafe } from "../../features/claude-tasks/storage"
|
||||||
|
import { parseTaskId } from "./task-id-validator"
|
||||||
|
|
||||||
|
export async function handleGet(
|
||||||
|
args: Record<string, unknown>,
|
||||||
|
config: Partial<OhMyOpenCodeConfig>
|
||||||
|
): Promise<string> {
|
||||||
|
const validatedArgs = TaskGetInputSchema.parse(args)
|
||||||
|
const taskId = parseTaskId(validatedArgs.id)
|
||||||
|
if (!taskId) {
|
||||||
|
return JSON.stringify({ error: "invalid_task_id" })
|
||||||
|
}
|
||||||
|
const taskDir = getTaskDir(config)
|
||||||
|
const taskPath = join(taskDir, `${taskId}.json`)
|
||||||
|
|
||||||
|
const task = readJsonSafe(taskPath, TaskObjectSchema)
|
||||||
|
return JSON.stringify({ task: task ?? null })
|
||||||
|
}
|
||||||
60
src/tools/task/task-action-list.ts
Normal file
60
src/tools/task/task-action-list.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { existsSync } from "fs"
|
||||||
|
import { join } from "path"
|
||||||
|
import type { OhMyOpenCodeConfig } from "../../config/schema"
|
||||||
|
import type { TaskObject } from "./types"
|
||||||
|
import { TaskListInputSchema, TaskObjectSchema } from "./types"
|
||||||
|
import { getTaskDir, listTaskFiles, readJsonSafe } from "../../features/claude-tasks/storage"
|
||||||
|
|
||||||
|
export async function handleList(
|
||||||
|
args: Record<string, unknown>,
|
||||||
|
config: Partial<OhMyOpenCodeConfig>
|
||||||
|
): Promise<string> {
|
||||||
|
const validatedArgs = TaskListInputSchema.parse(args)
|
||||||
|
const taskDir = getTaskDir(config)
|
||||||
|
|
||||||
|
if (!existsSync(taskDir)) {
|
||||||
|
return JSON.stringify({ tasks: [] })
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = listTaskFiles(config)
|
||||||
|
if (files.length === 0) {
|
||||||
|
return JSON.stringify({ tasks: [] })
|
||||||
|
}
|
||||||
|
|
||||||
|
const allTasks: TaskObject[] = []
|
||||||
|
for (const fileId of files) {
|
||||||
|
const task = readJsonSafe(join(taskDir, `${fileId}.json`), TaskObjectSchema)
|
||||||
|
if (task) {
|
||||||
|
allTasks.push(task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let tasks = allTasks.filter((task) => task.status !== "completed")
|
||||||
|
|
||||||
|
if (validatedArgs.status) {
|
||||||
|
tasks = tasks.filter((task) => task.status === validatedArgs.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validatedArgs.parentID) {
|
||||||
|
tasks = tasks.filter((task) => task.parentID === validatedArgs.parentID)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ready = args["ready"] === true
|
||||||
|
if (ready) {
|
||||||
|
tasks = tasks.filter((task) => {
|
||||||
|
if (task.blockedBy.length === 0) return true
|
||||||
|
return task.blockedBy.every((depId) => {
|
||||||
|
const depTask = allTasks.find((t) => t.id === depId)
|
||||||
|
return depTask?.status === "completed"
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const limitRaw = args["limit"]
|
||||||
|
const limit = typeof limitRaw === "number" ? limitRaw : undefined
|
||||||
|
if (limit !== undefined && limit > 0) {
|
||||||
|
tasks = tasks.slice(0, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify({ tasks })
|
||||||
|
}
|
||||||
57
src/tools/task/task-action-update.ts
Normal file
57
src/tools/task/task-action-update.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { join } from "path"
|
||||||
|
import type { OhMyOpenCodeConfig } from "../../config/schema"
|
||||||
|
import { TaskUpdateInputSchema, TaskObjectSchema } from "./types"
|
||||||
|
import { acquireLock, getTaskDir, readJsonSafe, writeJsonAtomic } from "../../features/claude-tasks/storage"
|
||||||
|
import { parseTaskId } from "./task-id-validator"
|
||||||
|
|
||||||
|
export async function handleUpdate(
|
||||||
|
args: Record<string, unknown>,
|
||||||
|
config: Partial<OhMyOpenCodeConfig>
|
||||||
|
): Promise<string> {
|
||||||
|
const validatedArgs = TaskUpdateInputSchema.parse(args)
|
||||||
|
const taskId = parseTaskId(validatedArgs.id)
|
||||||
|
if (!taskId) {
|
||||||
|
return JSON.stringify({ error: "invalid_task_id" })
|
||||||
|
}
|
||||||
|
const taskDir = getTaskDir(config)
|
||||||
|
const lock = acquireLock(taskDir)
|
||||||
|
|
||||||
|
if (!lock.acquired) {
|
||||||
|
return JSON.stringify({ error: "task_lock_unavailable" })
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const taskPath = join(taskDir, `${taskId}.json`)
|
||||||
|
const task = readJsonSafe(taskPath, TaskObjectSchema)
|
||||||
|
|
||||||
|
if (!task) {
|
||||||
|
return JSON.stringify({ error: "task_not_found" })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validatedArgs.subject !== undefined) {
|
||||||
|
task.subject = validatedArgs.subject
|
||||||
|
}
|
||||||
|
if (validatedArgs.description !== undefined) {
|
||||||
|
task.description = validatedArgs.description
|
||||||
|
}
|
||||||
|
if (validatedArgs.status !== undefined) {
|
||||||
|
task.status = validatedArgs.status
|
||||||
|
}
|
||||||
|
if (validatedArgs.addBlockedBy !== undefined) {
|
||||||
|
task.blockedBy = [...task.blockedBy, ...validatedArgs.addBlockedBy]
|
||||||
|
}
|
||||||
|
if (validatedArgs.repoURL !== undefined) {
|
||||||
|
task.repoURL = validatedArgs.repoURL
|
||||||
|
}
|
||||||
|
if (validatedArgs.parentID !== undefined) {
|
||||||
|
task.parentID = validatedArgs.parentID
|
||||||
|
}
|
||||||
|
|
||||||
|
const validatedTask = TaskObjectSchema.parse(task)
|
||||||
|
writeJsonAtomic(taskPath, validatedTask)
|
||||||
|
|
||||||
|
return JSON.stringify({ task: validatedTask })
|
||||||
|
} finally {
|
||||||
|
lock.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/tools/task/task-id-validator.ts
Normal file
6
src/tools/task/task-id-validator.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const TASK_ID_PATTERN = /^T-[A-Za-z0-9-]+$/
|
||||||
|
|
||||||
|
export function parseTaskId(id: string): string | null {
|
||||||
|
if (!TASK_ID_PATTERN.test(id)) return null
|
||||||
|
return id
|
||||||
|
}
|
||||||
@@ -1,38 +1,10 @@
|
|||||||
import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool"
|
import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool"
|
||||||
import { existsSync, readdirSync, unlinkSync } from "fs"
|
|
||||||
import { join } from "path"
|
|
||||||
import type { OhMyOpenCodeConfig } from "../../config/schema"
|
import type { OhMyOpenCodeConfig } from "../../config/schema"
|
||||||
import type {
|
import { handleCreate } from "./task-action-create"
|
||||||
TaskObject,
|
import { handleDelete } from "./task-action-delete"
|
||||||
TaskCreateInput,
|
import { handleGet } from "./task-action-get"
|
||||||
TaskListInput,
|
import { handleList } from "./task-action-list"
|
||||||
TaskGetInput,
|
import { handleUpdate } from "./task-action-update"
|
||||||
TaskUpdateInput,
|
|
||||||
TaskDeleteInput,
|
|
||||||
} from "./types"
|
|
||||||
import {
|
|
||||||
TaskObjectSchema,
|
|
||||||
TaskCreateInputSchema,
|
|
||||||
TaskListInputSchema,
|
|
||||||
TaskGetInputSchema,
|
|
||||||
TaskUpdateInputSchema,
|
|
||||||
TaskDeleteInputSchema,
|
|
||||||
} from "./types"
|
|
||||||
import {
|
|
||||||
getTaskDir,
|
|
||||||
readJsonSafe,
|
|
||||||
writeJsonAtomic,
|
|
||||||
acquireLock,
|
|
||||||
generateTaskId,
|
|
||||||
listTaskFiles,
|
|
||||||
} from "../../features/claude-tasks/storage"
|
|
||||||
|
|
||||||
const TASK_ID_PATTERN = /^T-[A-Za-z0-9-]+$/
|
|
||||||
|
|
||||||
function parseTaskId(id: string): string | null {
|
|
||||||
if (!TASK_ID_PATTERN.test(id)) return null
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createTask(config: Partial<OhMyOpenCodeConfig>): ToolDefinition {
|
export function createTask(config: Partial<OhMyOpenCodeConfig>): ToolDefinition {
|
||||||
return tool({
|
return tool({
|
||||||
@@ -66,9 +38,7 @@ All actions return JSON strings.`,
|
|||||||
limit: tool.schema.number().optional().describe("Maximum number of tasks to return"),
|
limit: tool.schema.number().optional().describe("Maximum number of tasks to return"),
|
||||||
},
|
},
|
||||||
execute: async (args, context) => {
|
execute: async (args, context) => {
|
||||||
const action = args.action as "create" | "list" | "get" | "update" | "delete"
|
switch (args.action) {
|
||||||
|
|
||||||
switch (action) {
|
|
||||||
case "create":
|
case "create":
|
||||||
return handleCreate(args, config, context)
|
return handleCreate(args, config, context)
|
||||||
case "list":
|
case "list":
|
||||||
@@ -85,201 +55,3 @@ All actions return JSON strings.`,
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCreate(
|
|
||||||
args: Record<string, unknown>,
|
|
||||||
config: Partial<OhMyOpenCodeConfig>,
|
|
||||||
context: { sessionID: string }
|
|
||||||
): Promise<string> {
|
|
||||||
const validatedArgs = TaskCreateInputSchema.parse(args)
|
|
||||||
const taskDir = getTaskDir(config)
|
|
||||||
const lock = acquireLock(taskDir)
|
|
||||||
|
|
||||||
if (!lock.acquired) {
|
|
||||||
return JSON.stringify({ error: "task_lock_unavailable" })
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const taskId = generateTaskId()
|
|
||||||
const task: TaskObject = {
|
|
||||||
id: taskId,
|
|
||||||
subject: validatedArgs.subject,
|
|
||||||
description: validatedArgs.description ?? "",
|
|
||||||
status: "pending",
|
|
||||||
blocks: validatedArgs.blocks ?? [],
|
|
||||||
blockedBy: validatedArgs.blockedBy ?? [],
|
|
||||||
repoURL: validatedArgs.repoURL,
|
|
||||||
parentID: validatedArgs.parentID,
|
|
||||||
threadID: context.sessionID,
|
|
||||||
}
|
|
||||||
|
|
||||||
const validatedTask = TaskObjectSchema.parse(task)
|
|
||||||
writeJsonAtomic(join(taskDir, `${taskId}.json`), validatedTask)
|
|
||||||
|
|
||||||
return JSON.stringify({ task: validatedTask })
|
|
||||||
} finally {
|
|
||||||
lock.release()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleList(
|
|
||||||
args: Record<string, unknown>,
|
|
||||||
config: Partial<OhMyOpenCodeConfig>
|
|
||||||
): Promise<string> {
|
|
||||||
const validatedArgs = TaskListInputSchema.parse(args)
|
|
||||||
const taskDir = getTaskDir(config)
|
|
||||||
|
|
||||||
if (!existsSync(taskDir)) {
|
|
||||||
return JSON.stringify({ tasks: [] })
|
|
||||||
}
|
|
||||||
|
|
||||||
const files = listTaskFiles(config)
|
|
||||||
if (files.length === 0) {
|
|
||||||
return JSON.stringify({ tasks: [] })
|
|
||||||
}
|
|
||||||
|
|
||||||
const allTasks: TaskObject[] = []
|
|
||||||
for (const fileId of files) {
|
|
||||||
const task = readJsonSafe(join(taskDir, `${fileId}.json`), TaskObjectSchema)
|
|
||||||
if (task) {
|
|
||||||
allTasks.push(task)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter out completed tasks by default
|
|
||||||
let tasks = allTasks.filter((task) => task.status !== "completed")
|
|
||||||
|
|
||||||
// Apply status filter if provided
|
|
||||||
if (validatedArgs.status) {
|
|
||||||
tasks = tasks.filter((task) => task.status === validatedArgs.status)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply parentID filter if provided
|
|
||||||
if (validatedArgs.parentID) {
|
|
||||||
tasks = tasks.filter((task) => task.parentID === validatedArgs.parentID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply ready filter if requested
|
|
||||||
if (args.ready) {
|
|
||||||
tasks = tasks.filter((task) => {
|
|
||||||
if (task.blockedBy.length === 0) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// All blocking tasks must be completed
|
|
||||||
return task.blockedBy.every((depId: string) => {
|
|
||||||
const depTask = allTasks.find((t) => t.id === depId)
|
|
||||||
return depTask?.status === "completed"
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply limit if provided
|
|
||||||
const limit = args.limit as number | undefined
|
|
||||||
if (limit !== undefined && limit > 0) {
|
|
||||||
tasks = tasks.slice(0, limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.stringify({ tasks })
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleGet(
|
|
||||||
args: Record<string, unknown>,
|
|
||||||
config: Partial<OhMyOpenCodeConfig>
|
|
||||||
): Promise<string> {
|
|
||||||
const validatedArgs = TaskGetInputSchema.parse(args)
|
|
||||||
const taskId = parseTaskId(validatedArgs.id)
|
|
||||||
if (!taskId) {
|
|
||||||
return JSON.stringify({ error: "invalid_task_id" })
|
|
||||||
}
|
|
||||||
const taskDir = getTaskDir(config)
|
|
||||||
const taskPath = join(taskDir, `${taskId}.json`)
|
|
||||||
|
|
||||||
const task = readJsonSafe(taskPath, TaskObjectSchema)
|
|
||||||
|
|
||||||
return JSON.stringify({ task: task ?? null })
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleUpdate(
|
|
||||||
args: Record<string, unknown>,
|
|
||||||
config: Partial<OhMyOpenCodeConfig>
|
|
||||||
): Promise<string> {
|
|
||||||
const validatedArgs = TaskUpdateInputSchema.parse(args)
|
|
||||||
const taskId = parseTaskId(validatedArgs.id)
|
|
||||||
if (!taskId) {
|
|
||||||
return JSON.stringify({ error: "invalid_task_id" })
|
|
||||||
}
|
|
||||||
const taskDir = getTaskDir(config)
|
|
||||||
const lock = acquireLock(taskDir)
|
|
||||||
|
|
||||||
if (!lock.acquired) {
|
|
||||||
return JSON.stringify({ error: "task_lock_unavailable" })
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const taskPath = join(taskDir, `${taskId}.json`)
|
|
||||||
const task = readJsonSafe(taskPath, TaskObjectSchema)
|
|
||||||
|
|
||||||
if (!task) {
|
|
||||||
return JSON.stringify({ error: "task_not_found" })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update fields if provided
|
|
||||||
if (validatedArgs.subject !== undefined) {
|
|
||||||
task.subject = validatedArgs.subject
|
|
||||||
}
|
|
||||||
if (validatedArgs.description !== undefined) {
|
|
||||||
task.description = validatedArgs.description
|
|
||||||
}
|
|
||||||
if (validatedArgs.status !== undefined) {
|
|
||||||
task.status = validatedArgs.status
|
|
||||||
}
|
|
||||||
if (validatedArgs.addBlockedBy !== undefined) {
|
|
||||||
task.blockedBy = [...task.blockedBy, ...validatedArgs.addBlockedBy]
|
|
||||||
}
|
|
||||||
if (validatedArgs.repoURL !== undefined) {
|
|
||||||
task.repoURL = validatedArgs.repoURL
|
|
||||||
}
|
|
||||||
if (validatedArgs.parentID !== undefined) {
|
|
||||||
task.parentID = validatedArgs.parentID
|
|
||||||
}
|
|
||||||
|
|
||||||
const validatedTask = TaskObjectSchema.parse(task)
|
|
||||||
writeJsonAtomic(taskPath, validatedTask)
|
|
||||||
|
|
||||||
return JSON.stringify({ task: validatedTask })
|
|
||||||
} finally {
|
|
||||||
lock.release()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleDelete(
|
|
||||||
args: Record<string, unknown>,
|
|
||||||
config: Partial<OhMyOpenCodeConfig>
|
|
||||||
): Promise<string> {
|
|
||||||
const validatedArgs = TaskDeleteInputSchema.parse(args)
|
|
||||||
const taskId = parseTaskId(validatedArgs.id)
|
|
||||||
if (!taskId) {
|
|
||||||
return JSON.stringify({ error: "invalid_task_id" })
|
|
||||||
}
|
|
||||||
const taskDir = getTaskDir(config)
|
|
||||||
const lock = acquireLock(taskDir)
|
|
||||||
|
|
||||||
if (!lock.acquired) {
|
|
||||||
return JSON.stringify({ error: "task_lock_unavailable" })
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const taskPath = join(taskDir, `${taskId}.json`)
|
|
||||||
|
|
||||||
if (!existsSync(taskPath)) {
|
|
||||||
return JSON.stringify({ error: "task_not_found" })
|
|
||||||
}
|
|
||||||
|
|
||||||
unlinkSync(taskPath)
|
|
||||||
|
|
||||||
return JSON.stringify({ success: true })
|
|
||||||
} finally {
|
|
||||||
lock.release()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user