feat: 添加 nCode 组件及相关 API,支持创建、查询、更新和删除操作
This commit is contained in:
13
.vscode/mcp.json
vendored
Normal file
13
.vscode/mcp.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"servers": {
|
||||
"convex": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"convex@latest",
|
||||
"mcp",
|
||||
"start"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
50
convex/_generated/api.d.ts
vendored
50
convex/_generated/api.d.ts
vendored
@@ -11,6 +11,7 @@
|
||||
import type * as github_action from "../github/action.js";
|
||||
import type * as github_starrred from "../github/starrred.js";
|
||||
import type * as http from "../http.js";
|
||||
import type * as nCode from "../nCode.js";
|
||||
|
||||
import type {
|
||||
ApiFromModules,
|
||||
@@ -22,6 +23,7 @@ declare const fullApi: ApiFromModules<{
|
||||
"github/action": typeof github_action;
|
||||
"github/starrred": typeof github_starrred;
|
||||
http: typeof http;
|
||||
nCode: typeof nCode;
|
||||
}>;
|
||||
|
||||
/**
|
||||
@@ -50,4 +52,50 @@ export declare const internal: FilterApi<
|
||||
FunctionReference<any, "internal">
|
||||
>;
|
||||
|
||||
export declare const components: {};
|
||||
export declare const components: {
|
||||
nCode: {
|
||||
action: {
|
||||
create: FunctionReference<
|
||||
"mutation",
|
||||
"internal",
|
||||
{
|
||||
code: string;
|
||||
data: any;
|
||||
description: string;
|
||||
slug?: string;
|
||||
title: string;
|
||||
type: string;
|
||||
userId: string;
|
||||
},
|
||||
any
|
||||
>;
|
||||
deleteBySlug: FunctionReference<
|
||||
"mutation",
|
||||
"internal",
|
||||
{ slug: string },
|
||||
any
|
||||
>;
|
||||
getByCode: FunctionReference<"query", "internal", { code: string }, any>;
|
||||
getBySlug: FunctionReference<"query", "internal", { slug: string }, any>;
|
||||
getList: FunctionReference<
|
||||
"query",
|
||||
"internal",
|
||||
{ type?: string; userId?: string },
|
||||
any
|
||||
>;
|
||||
updateBySlug: FunctionReference<
|
||||
"mutation",
|
||||
"internal",
|
||||
{
|
||||
code?: string;
|
||||
data?: any;
|
||||
description?: string;
|
||||
slug: string;
|
||||
title?: string;
|
||||
type?: string;
|
||||
},
|
||||
any
|
||||
>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { AuthConfig } from "convex/server";
|
||||
|
||||
export default {
|
||||
providers: [
|
||||
{
|
||||
|
||||
50
convex/components/n-code/_generated/api.ts
Normal file
50
convex/components/n-code/_generated/api.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Generated `api` utility.
|
||||
*
|
||||
* THIS CODE IS AUTOMATICALLY GENERATED.
|
||||
*
|
||||
* To regenerate, run `npx convex dev`.
|
||||
* @module
|
||||
*/
|
||||
|
||||
import type * as action from "../action.js";
|
||||
|
||||
import type {
|
||||
ApiFromModules,
|
||||
FilterApi,
|
||||
FunctionReference,
|
||||
} from "convex/server";
|
||||
import { anyApi, componentsGeneric } from "convex/server";
|
||||
|
||||
const fullApi: ApiFromModules<{
|
||||
action: typeof action;
|
||||
}> = anyApi as any;
|
||||
|
||||
/**
|
||||
* A utility for referencing Convex functions in your app's public API.
|
||||
*
|
||||
* Usage:
|
||||
* ```js
|
||||
* const myFunctionReference = api.myModule.myFunction;
|
||||
* ```
|
||||
*/
|
||||
export const api: FilterApi<
|
||||
typeof fullApi,
|
||||
FunctionReference<any, "public">
|
||||
> = anyApi as any;
|
||||
|
||||
/**
|
||||
* A utility for referencing Convex functions in your app's internal API.
|
||||
*
|
||||
* Usage:
|
||||
* ```js
|
||||
* const myFunctionReference = internal.myModule.myFunction;
|
||||
* ```
|
||||
*/
|
||||
export const internal: FilterApi<
|
||||
typeof fullApi,
|
||||
FunctionReference<any, "internal">
|
||||
> = anyApi as any;
|
||||
|
||||
export const components = componentsGeneric() as unknown as {};
|
||||
85
convex/components/n-code/_generated/component.ts
Normal file
85
convex/components/n-code/_generated/component.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Generated `ComponentApi` utility.
|
||||
*
|
||||
* THIS CODE IS AUTOMATICALLY GENERATED.
|
||||
*
|
||||
* To regenerate, run `npx convex dev`.
|
||||
* @module
|
||||
*/
|
||||
|
||||
import type { FunctionReference } from "convex/server";
|
||||
|
||||
/**
|
||||
* A utility for referencing a Convex component's exposed API.
|
||||
*
|
||||
* Useful when expecting a parameter like `components.myComponent`.
|
||||
* Usage:
|
||||
* ```ts
|
||||
* async function myFunction(ctx: QueryCtx, component: ComponentApi) {
|
||||
* return ctx.runQuery(component.someFile.someQuery, { ...args });
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export type ComponentApi<Name extends string | undefined = string | undefined> =
|
||||
{
|
||||
action: {
|
||||
create: FunctionReference<
|
||||
"mutation",
|
||||
"internal",
|
||||
{
|
||||
code: string;
|
||||
data: any;
|
||||
description: string;
|
||||
slug?: string;
|
||||
title: string;
|
||||
type: string;
|
||||
userId: string;
|
||||
},
|
||||
any,
|
||||
Name
|
||||
>;
|
||||
deleteBySlug: FunctionReference<
|
||||
"mutation",
|
||||
"internal",
|
||||
{ slug: string },
|
||||
any,
|
||||
Name
|
||||
>;
|
||||
getByCode: FunctionReference<
|
||||
"query",
|
||||
"internal",
|
||||
{ code: string },
|
||||
any,
|
||||
Name
|
||||
>;
|
||||
getBySlug: FunctionReference<
|
||||
"query",
|
||||
"internal",
|
||||
{ slug: string },
|
||||
any,
|
||||
Name
|
||||
>;
|
||||
getList: FunctionReference<
|
||||
"query",
|
||||
"internal",
|
||||
{ type?: string; userId?: string },
|
||||
any,
|
||||
Name
|
||||
>;
|
||||
updateBySlug: FunctionReference<
|
||||
"mutation",
|
||||
"internal",
|
||||
{
|
||||
code?: string;
|
||||
data?: any;
|
||||
description?: string;
|
||||
slug: string;
|
||||
title?: string;
|
||||
type?: string;
|
||||
},
|
||||
any,
|
||||
Name
|
||||
>;
|
||||
};
|
||||
};
|
||||
60
convex/components/n-code/_generated/dataModel.ts
Normal file
60
convex/components/n-code/_generated/dataModel.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Generated data model types.
|
||||
*
|
||||
* THIS CODE IS AUTOMATICALLY GENERATED.
|
||||
*
|
||||
* To regenerate, run `npx convex dev`.
|
||||
* @module
|
||||
*/
|
||||
|
||||
import type {
|
||||
DataModelFromSchemaDefinition,
|
||||
DocumentByName,
|
||||
TableNamesInDataModel,
|
||||
SystemTableNames,
|
||||
} from "convex/server";
|
||||
import type { GenericId } from "convex/values";
|
||||
import schema from "../schema.js";
|
||||
|
||||
/**
|
||||
* The names of all of your Convex tables.
|
||||
*/
|
||||
export type TableNames = TableNamesInDataModel<DataModel>;
|
||||
|
||||
/**
|
||||
* The type of a document stored in Convex.
|
||||
*
|
||||
* @typeParam TableName - A string literal type of the table name (like "users").
|
||||
*/
|
||||
export type Doc<TableName extends TableNames> = DocumentByName<
|
||||
DataModel,
|
||||
TableName
|
||||
>;
|
||||
|
||||
/**
|
||||
* An identifier for a document in Convex.
|
||||
*
|
||||
* Convex documents are uniquely identified by their `Id`, which is accessible
|
||||
* on the `_id` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids).
|
||||
*
|
||||
* Documents can be loaded using `db.get(tableName, id)` in query and mutation functions.
|
||||
*
|
||||
* IDs are just strings at runtime, but this type can be used to distinguish them from other
|
||||
* strings when type checking.
|
||||
*
|
||||
* @typeParam TableName - A string literal type of the table name (like "users").
|
||||
*/
|
||||
export type Id<TableName extends TableNames | SystemTableNames> =
|
||||
GenericId<TableName>;
|
||||
|
||||
/**
|
||||
* A type describing your Convex data model.
|
||||
*
|
||||
* This type includes information about what tables you have, the type of
|
||||
* documents stored in those tables, and the indexes defined on them.
|
||||
*
|
||||
* This type is used to parameterize methods like `queryGeneric` and
|
||||
* `mutationGeneric` to make them type-safe.
|
||||
*/
|
||||
export type DataModel = DataModelFromSchemaDefinition<typeof schema>;
|
||||
156
convex/components/n-code/_generated/server.ts
Normal file
156
convex/components/n-code/_generated/server.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Generated utilities for implementing server-side Convex query and mutation functions.
|
||||
*
|
||||
* THIS CODE IS AUTOMATICALLY GENERATED.
|
||||
*
|
||||
* To regenerate, run `npx convex dev`.
|
||||
* @module
|
||||
*/
|
||||
|
||||
import type {
|
||||
ActionBuilder,
|
||||
HttpActionBuilder,
|
||||
MutationBuilder,
|
||||
QueryBuilder,
|
||||
GenericActionCtx,
|
||||
GenericMutationCtx,
|
||||
GenericQueryCtx,
|
||||
GenericDatabaseReader,
|
||||
GenericDatabaseWriter,
|
||||
} from "convex/server";
|
||||
import {
|
||||
actionGeneric,
|
||||
httpActionGeneric,
|
||||
queryGeneric,
|
||||
mutationGeneric,
|
||||
internalActionGeneric,
|
||||
internalMutationGeneric,
|
||||
internalQueryGeneric,
|
||||
} from "convex/server";
|
||||
import type { DataModel } from "./dataModel.js";
|
||||
|
||||
/**
|
||||
* Define a query in this Convex app's public API.
|
||||
*
|
||||
* This function will be allowed to read your Convex database and will be accessible from the client.
|
||||
*
|
||||
* @param func - The query function. It receives a {@link QueryCtx} as its first argument.
|
||||
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export const query: QueryBuilder<DataModel, "public"> = queryGeneric;
|
||||
|
||||
/**
|
||||
* Define a query that is only accessible from other Convex functions (but not from the client).
|
||||
*
|
||||
* This function will be allowed to read from your Convex database. It will not be accessible from the client.
|
||||
*
|
||||
* @param func - The query function. It receives a {@link QueryCtx} as its first argument.
|
||||
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export const internalQuery: QueryBuilder<DataModel, "internal"> =
|
||||
internalQueryGeneric;
|
||||
|
||||
/**
|
||||
* Define a mutation in this Convex app's public API.
|
||||
*
|
||||
* This function will be allowed to modify your Convex database and will be accessible from the client.
|
||||
*
|
||||
* @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
|
||||
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export const mutation: MutationBuilder<DataModel, "public"> = mutationGeneric;
|
||||
|
||||
/**
|
||||
* Define a mutation that is only accessible from other Convex functions (but not from the client).
|
||||
*
|
||||
* This function will be allowed to modify your Convex database. It will not be accessible from the client.
|
||||
*
|
||||
* @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
|
||||
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export const internalMutation: MutationBuilder<DataModel, "internal"> =
|
||||
internalMutationGeneric;
|
||||
|
||||
/**
|
||||
* Define an action in this Convex app's public API.
|
||||
*
|
||||
* An action is a function which can execute any JavaScript code, including non-deterministic
|
||||
* code and code with side-effects, like calling third-party services.
|
||||
* They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
|
||||
* They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
|
||||
*
|
||||
* @param func - The action. It receives an {@link ActionCtx} as its first argument.
|
||||
* @returns The wrapped action. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export const action: ActionBuilder<DataModel, "public"> = actionGeneric;
|
||||
|
||||
/**
|
||||
* Define an action that is only accessible from other Convex functions (but not from the client).
|
||||
*
|
||||
* @param func - The function. It receives an {@link ActionCtx} as its first argument.
|
||||
* @returns The wrapped function. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export const internalAction: ActionBuilder<DataModel, "internal"> =
|
||||
internalActionGeneric;
|
||||
|
||||
/**
|
||||
* Define an HTTP action.
|
||||
*
|
||||
* The wrapped function will be used to respond to HTTP requests received
|
||||
* by a Convex deployment if the requests matches the path and method where
|
||||
* this action is routed. Be sure to route your httpAction in `convex/http.js`.
|
||||
*
|
||||
* @param func - The function. It receives an {@link ActionCtx} as its first argument
|
||||
* and a Fetch API `Request` object as its second.
|
||||
* @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up.
|
||||
*/
|
||||
export const httpAction: HttpActionBuilder = httpActionGeneric;
|
||||
|
||||
/**
|
||||
* A set of services for use within Convex query functions.
|
||||
*
|
||||
* The query context is passed as the first argument to any Convex query
|
||||
* function run on the server.
|
||||
*
|
||||
* If you're using code generation, use the `QueryCtx` type in `convex/_generated/server.d.ts` instead.
|
||||
*/
|
||||
export type QueryCtx = GenericQueryCtx<DataModel>;
|
||||
|
||||
/**
|
||||
* A set of services for use within Convex mutation functions.
|
||||
*
|
||||
* The mutation context is passed as the first argument to any Convex mutation
|
||||
* function run on the server.
|
||||
*
|
||||
* If you're using code generation, use the `MutationCtx` type in `convex/_generated/server.d.ts` instead.
|
||||
*/
|
||||
export type MutationCtx = GenericMutationCtx<DataModel>;
|
||||
|
||||
/**
|
||||
* A set of services for use within Convex action functions.
|
||||
*
|
||||
* The action context is passed as the first argument to any Convex action
|
||||
* function run on the server.
|
||||
*/
|
||||
export type ActionCtx = GenericActionCtx<DataModel>;
|
||||
|
||||
/**
|
||||
* An interface to read from the database within Convex query functions.
|
||||
*
|
||||
* The two entry points are {@link DatabaseReader.get}, which fetches a single
|
||||
* document by its {@link Id}, or {@link DatabaseReader.query}, which starts
|
||||
* building a query.
|
||||
*/
|
||||
export type DatabaseReader = GenericDatabaseReader<DataModel>;
|
||||
|
||||
/**
|
||||
* An interface to read from and write to the database within Convex mutation
|
||||
* functions.
|
||||
*
|
||||
* Convex guarantees that all writes within a single mutation are
|
||||
* executed atomically, so you never have to worry about partial writes leaving
|
||||
* your data in an inconsistent state. See [the Convex Guide](https://docs.convex.dev/understanding/convex-fundamentals/functions#atomicity-and-optimistic-concurrency-control)
|
||||
* for the guarantees Convex provides your functions.
|
||||
*/
|
||||
export type DatabaseWriter = GenericDatabaseWriter<DataModel>;
|
||||
139
convex/components/n-code/action.ts
Normal file
139
convex/components/n-code/action.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import { query, mutation } from "./_generated/server.ts";
|
||||
import { v } from "convex/values";
|
||||
import { customAlphabet } from "nanoid";
|
||||
|
||||
const randomId = customAlphabet("0123456789abcdefghijklmnopqrstuvwxyz", 10);
|
||||
/**
|
||||
* 查询 nCode 列表
|
||||
*/
|
||||
export const getList = query({
|
||||
args: {
|
||||
userId: v.optional(v.string()),
|
||||
type: v.optional(v.string()),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
// 优先使用索引过滤
|
||||
if (args.userId) {
|
||||
let results = await ctx.db
|
||||
.query("shortLink")
|
||||
.withIndex("by_userId", (q) => q.eq("userId", args.userId!))
|
||||
.collect();
|
||||
if (args.type) {
|
||||
results = results.filter((item) => item.type === args.type);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
if (args.type) {
|
||||
return await ctx.db
|
||||
.query("shortLink")
|
||||
.withIndex("by_type", (q) => q.eq("type", args.type!))
|
||||
.collect();
|
||||
}
|
||||
return await ctx.db.query("shortLink").collect();
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 根据业务 id 查询单条记录
|
||||
*/
|
||||
export const getBySlug = query({
|
||||
args: { slug: v.string() },
|
||||
handler: async (ctx, args) => {
|
||||
return await ctx.db
|
||||
.query("shortLink")
|
||||
.withIndex("by_slug", (q) => q.eq("slug", args.slug))
|
||||
.first();
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 根据 code 查询单条记录
|
||||
*/
|
||||
export const getByCode = query({
|
||||
args: {
|
||||
code: v.string(),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const result = await ctx.db
|
||||
.query("shortLink")
|
||||
.withIndex("by_code", (q) => q.eq("code", args.code))
|
||||
.first();
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 创建 nCode
|
||||
*/
|
||||
export const create = mutation({
|
||||
args: {
|
||||
slug: v.optional(v.string()),
|
||||
code: v.string(),
|
||||
type: v.string(),
|
||||
data: v.any(),
|
||||
title: v.string(),
|
||||
description: v.string(),
|
||||
userId: v.string(),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const slug = args.slug ?? randomId();
|
||||
// 唯一性检查
|
||||
const existing = await ctx.db
|
||||
.query("shortLink")
|
||||
.withIndex("by_slug", (q) => q.eq("slug", slug))
|
||||
.first();
|
||||
if (existing) {
|
||||
throw new Error(`nCode with slug "${slug}" already exists`);
|
||||
}
|
||||
const result = await ctx.db.insert("shortLink", {
|
||||
version: "1.0", data: {
|
||||
...args.data,
|
||||
permission: {
|
||||
share: 'public',
|
||||
...(args.data?.permission || {})
|
||||
}
|
||||
}, ...args, slug
|
||||
});
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 更新 nCode
|
||||
*/
|
||||
export const updateBySlug = mutation({
|
||||
args: {
|
||||
slug: v.string(),
|
||||
code: v.optional(v.string()),
|
||||
type: v.optional(v.string()),
|
||||
data: v.optional(v.any()),
|
||||
title: v.optional(v.string()),
|
||||
description: v.optional(v.string()),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const { slug, ...rest } = args;
|
||||
const record = await ctx.db
|
||||
.query("shortLink")
|
||||
.withIndex("by_slug", (q) => q.eq("slug", slug))
|
||||
.first();
|
||||
if (!record) throw new Error(`nCode with slug "${slug}" not found`);
|
||||
await ctx.db.patch(record._id, rest);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 删除 nCode
|
||||
*/
|
||||
export const deleteBySlug = mutation({
|
||||
args: {
|
||||
slug: v.string(),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const record = await ctx.db
|
||||
.query("shortLink")
|
||||
.withIndex("by_slug", (q) => q.eq("slug", args.slug))
|
||||
.first();
|
||||
if (!record) throw new Error(`nCode with slug "${args.slug}" not found`);
|
||||
await ctx.db.delete(record._id);
|
||||
},
|
||||
});
|
||||
3
convex/components/n-code/convex.config.ts
Normal file
3
convex/components/n-code/convex.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { defineComponent } from "convex/server";
|
||||
const component = defineComponent("nCode");
|
||||
export default component;
|
||||
50
convex/components/n-code/schema.ts
Normal file
50
convex/components/n-code/schema.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { defineSchema, defineTable } from "convex/server";
|
||||
import { v } from "convex/values";
|
||||
|
||||
export type Permission = {
|
||||
share?: 'public' | 'private' | 'protected';
|
||||
usernames?: string[]; // 仅当 type 为 'protected' 时有效
|
||||
password?: string; // 仅当 type 为 'protected' 时有效
|
||||
'expiration-time'?: string; // ISO 8601 格式的过期时间
|
||||
}
|
||||
|
||||
export default defineSchema({
|
||||
// Other tables here...
|
||||
shortLink: defineTable({
|
||||
// 对外暴露的唯一业务 ID,nanoid 生成
|
||||
slug: v.string(),
|
||||
// 协作码,管理员才能编辑, 6-12 位随机字符串,唯一
|
||||
code: v.string(),
|
||||
// 码的类型,link, agent,默认值为 link
|
||||
type: v.string(),
|
||||
// 执行动作
|
||||
data: v.object({
|
||||
// action: v.string(), // 动作类型,如 "open_url", "run_agent" 等
|
||||
permission: v.optional(v.any()), // 权限设置
|
||||
link: v.optional(v.string()), // 仅当 type 为 'link' 时有效,表示跳转链接
|
||||
}),
|
||||
// 码的标题
|
||||
title: v.string(),
|
||||
// 描述定义
|
||||
description: v.string(),
|
||||
// 标签
|
||||
tags: v.optional(v.array(v.string())),
|
||||
// 绑定用户
|
||||
userId: v.string(),
|
||||
|
||||
version: v.optional(v.string()), // 版本号,默认为 "1.0"
|
||||
|
||||
})
|
||||
.index("by_slug", ["slug"])
|
||||
.index("by_userId", ["userId"])
|
||||
.index("by_code", ["code"])
|
||||
.index("by_type", ["type"]),
|
||||
desc: defineTable({
|
||||
slug: v.string(),
|
||||
// 卡片的正面图片 URL
|
||||
front: v.optional(v.string()),
|
||||
// 卡片的背面图片 URL
|
||||
back: v.optional(v.string()),
|
||||
status: v.optional(v.string()), // 生成状态:active, completed, failed, sold(卖掉了)
|
||||
}).index("by_slug", ["slug"]),
|
||||
});
|
||||
7
convex/convex.config.ts
Normal file
7
convex/convex.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineApp } from "convex/server";
|
||||
import NCode from './components/n-code/convex.config.ts';
|
||||
const app = defineApp();
|
||||
|
||||
app.use(NCode);
|
||||
|
||||
export default app;
|
||||
@@ -8,6 +8,14 @@ export const getList = query({
|
||||
return results;
|
||||
},
|
||||
});
|
||||
export const get = query({
|
||||
args: {
|
||||
id: v.id("github_starred"),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
return await ctx.db.get(args.id);
|
||||
},
|
||||
});
|
||||
|
||||
export const createStarred = mutation({
|
||||
args: {
|
||||
|
||||
85
convex/nCode.ts
Normal file
85
convex/nCode.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* 顶层包装文件 —— 将 nCode Component 的函数暴露为公共 API。
|
||||
*
|
||||
* Convex Component 内部函数对客户端是 internal 级别,
|
||||
* 必须在顶层 app 中用 ctx.runQuery / ctx.runMutation 做代理才能被客户端访问。
|
||||
*
|
||||
* 客户端调用示例:
|
||||
* import { api } from "../convex/_generated/api";
|
||||
* const list = useQuery(api.nCode.getList, { userId: "xxx" });
|
||||
*/
|
||||
import { query, mutation } from "./_generated/server.js";
|
||||
import { components } from "./_generated/api.js";
|
||||
import { v } from "convex/values";
|
||||
|
||||
/** 查询列表,支持按 userId / type 过滤 */
|
||||
export const getList = query({
|
||||
args: {
|
||||
userId: v.optional(v.string()),
|
||||
type: v.optional(v.string()),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
return await ctx.runQuery(components.nCode.action.getList, args);
|
||||
},
|
||||
});
|
||||
|
||||
/** 根据 code 查单条记录 */
|
||||
export const getByCode = query({
|
||||
args: {
|
||||
code: v.string(),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
return await ctx.runQuery(components.nCode.action.getByCode, args);
|
||||
},
|
||||
});
|
||||
|
||||
/** 根据业务 id 查单条记录 */
|
||||
export const getBySlug = query({
|
||||
args: {
|
||||
slug: v.string(),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
return await ctx.runQuery(components.nCode.action.getBySlug, args);
|
||||
},
|
||||
});
|
||||
|
||||
/** 创建 nCode */
|
||||
export const create = mutation({
|
||||
args: {
|
||||
slug: v.optional(v.string()),
|
||||
code: v.string(),
|
||||
type: v.string(),
|
||||
data: v.object({}),
|
||||
title: v.string(),
|
||||
description: v.string(),
|
||||
userId: v.string(),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
return await ctx.runMutation(components.nCode.action.create, args);
|
||||
},
|
||||
});
|
||||
|
||||
/** 更新 nCode */
|
||||
export const updateBySlug = mutation({
|
||||
args: {
|
||||
slug: v.string(),
|
||||
code: v.optional(v.string()),
|
||||
type: v.optional(v.string()),
|
||||
data: v.optional(v.object({})),
|
||||
title: v.optional(v.string()),
|
||||
description: v.optional(v.string()),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
return await ctx.runMutation(components.nCode.action.updateBySlug, args);
|
||||
},
|
||||
});
|
||||
|
||||
/** 删除 nCode */
|
||||
export const deleteBySlug = mutation({
|
||||
args: {
|
||||
slug: v.string(),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
return await ctx.runMutation(components.nCode.action.deleteBySlug, args);
|
||||
},
|
||||
});
|
||||
@@ -27,4 +27,5 @@ export default defineSchema({
|
||||
expiresAt: v.optional(v.string()),
|
||||
token: v.optional(v.string()),
|
||||
}).index("token", ["token"]),
|
||||
|
||||
});
|
||||
@@ -4,7 +4,8 @@
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "bunx convex dev"
|
||||
"dev": "bunx convex dev",
|
||||
"mcp": "npx -y convex@latest mcp start --project-dir ./convex"
|
||||
},
|
||||
"files": [
|
||||
"convex",
|
||||
@@ -23,7 +24,8 @@
|
||||
"dependencies": {
|
||||
"@kevisual/auth": "^2.0.3",
|
||||
"convex": "1.32.0",
|
||||
"jose": "^6.1.3"
|
||||
"jose": "^6.1.3",
|
||||
"nanoid": "^5.1.6"
|
||||
},
|
||||
"exports": {
|
||||
".": "./convex/_generated/api.js"
|
||||
|
||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -17,6 +17,9 @@ importers:
|
||||
jose:
|
||||
specifier: ^6.1.3
|
||||
version: 6.1.3
|
||||
nanoid:
|
||||
specifier: ^5.1.6
|
||||
version: 5.1.6
|
||||
devDependencies:
|
||||
'@kevisual/types':
|
||||
specifier: ^0.0.12
|
||||
@@ -225,6 +228,11 @@ packages:
|
||||
jose@6.1.3:
|
||||
resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==}
|
||||
|
||||
nanoid@5.1.6:
|
||||
resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==}
|
||||
engines: {node: ^18 || >=20}
|
||||
hasBin: true
|
||||
|
||||
prettier@3.8.1:
|
||||
resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -381,6 +389,8 @@ snapshots:
|
||||
|
||||
jose@6.1.3: {}
|
||||
|
||||
nanoid@5.1.6: {}
|
||||
|
||||
prettier@3.8.1: {}
|
||||
|
||||
undici-types@7.18.2: {}
|
||||
|
||||
15
src/convex.ts
Normal file
15
src/convex.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { api } from '../convex/_generated/api.js';
|
||||
import { ConvexClient, AuthTokenFetcher } from "convex/browser";
|
||||
const url = process.env["CONVEX_URL"]
|
||||
const client = new ConvexClient(url!);
|
||||
|
||||
const token = process.env["KEVISUAL_CONVEX_TOKEN"]
|
||||
const authTokenFetcher: AuthTokenFetcher = async ({ forceRefreshToken }: { forceRefreshToken: boolean }) => {
|
||||
console.log("AuthTokenFetcher called, forceRefreshToken:", forceRefreshToken);
|
||||
return token;
|
||||
}
|
||||
client.setAuth(authTokenFetcher, (isAuthenticated) => {
|
||||
console.log("Auth isAuthenticated:", isAuthenticated);
|
||||
});
|
||||
|
||||
export { client, api }
|
||||
19
test/get-list.ts
Normal file
19
test/get-list.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { client, api } from '../src/convex.ts'
|
||||
|
||||
|
||||
// client.query(api.nCode.getList, {}).then((res) => {
|
||||
// console.log('getList', res)
|
||||
// })
|
||||
|
||||
const createNCode = async () => {
|
||||
const res = await client.mutation(api.nCode.create, {
|
||||
code: Math.random().toString(36).substring(2, 8),
|
||||
type: "link",
|
||||
data: {},
|
||||
title: "测试码",
|
||||
description: "这是一个测试码",
|
||||
userId: "test-user-id",
|
||||
})
|
||||
console.log('createNCode', res)
|
||||
}
|
||||
createNCode()
|
||||
@@ -18,6 +18,7 @@
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"test/**/*",
|
||||
"convex/**/*",
|
||||
],
|
||||
}
|
||||
Reference in New Issue
Block a user