Refactor: Remove unused Convex HTTP routes and schema definitions

- Deleted `http.ts` which contained custom HTTP routes for authentication and JWKS.
- Removed `schema.ts` that defined database schemas for `github_starred`, `users`, and `sessions`.
- Updated `package.json` to remove the Convex dependency and updated other dependencies.
- Deleted SQL script `clear.sql` for removing constraints from tables.
- Removed `update.sh` script for updating dependencies.
- Deleted demo application routes and models in `app-demo` directory.
- Cleaned up unused modules and imports across various files.
- Removed Redis and common utility scripts that were not in use.
- Deleted test scripts related to user and bucket management.
This commit is contained in:
2026-02-07 00:16:00 +08:00
parent 32f4e9c40f
commit d62a75842f
31 changed files with 10 additions and 1484 deletions

View File

@@ -1,49 +0,0 @@
/* eslint-disable */
/**
* Generated `api` utility.
*
* THIS CODE IS AUTOMATICALLY GENERATED.
*
* To regenerate, run `npx convex dev`.
* @module
*/
import type * as http from "../http.js";
import type {
ApiFromModules,
FilterApi,
FunctionReference,
} from "convex/server";
declare const fullApi: ApiFromModules<{
http: typeof http;
}>;
/**
* A utility for referencing Convex functions in your app's public API.
*
* Usage:
* ```js
* const myFunctionReference = api.myModule.myFunction;
* ```
*/
export declare const api: FilterApi<
typeof fullApi,
FunctionReference<any, "public">
>;
/**
* A utility for referencing Convex functions in your app's internal API.
*
* Usage:
* ```js
* const myFunctionReference = internal.myModule.myFunction;
* ```
*/
export declare const internal: FilterApi<
typeof fullApi,
FunctionReference<any, "internal">
>;
export declare const components: {};

View File

@@ -1,23 +0,0 @@
/* eslint-disable */
/**
* Generated `api` utility.
*
* THIS CODE IS AUTOMATICALLY GENERATED.
*
* To regenerate, run `npx convex dev`.
* @module
*/
import { anyApi, componentsGeneric } from "convex/server";
/**
* A utility for referencing Convex functions in your app's API.
*
* Usage:
* ```js
* const myFunctionReference = api.myModule.myFunction;
* ```
*/
export const api = anyApi;
export const internal = anyApi;
export const components = componentsGeneric();

View File

@@ -1,60 +0,0 @@
/* 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>;

View File

@@ -1,143 +0,0 @@
/* 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 {
ActionBuilder,
HttpActionBuilder,
MutationBuilder,
QueryBuilder,
GenericActionCtx,
GenericMutationCtx,
GenericQueryCtx,
GenericDatabaseReader,
GenericDatabaseWriter,
} 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 declare const query: QueryBuilder<DataModel, "public">;
/**
* 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 declare const internalQuery: QueryBuilder<DataModel, "internal">;
/**
* 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 declare const mutation: MutationBuilder<DataModel, "public">;
/**
* 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 declare const internalMutation: MutationBuilder<DataModel, "internal">;
/**
* 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 declare const action: ActionBuilder<DataModel, "public">;
/**
* 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 declare const internalAction: ActionBuilder<DataModel, "internal">;
/**
* 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 declare const httpAction: HttpActionBuilder;
/**
* 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.
*
* This differs from the {@link MutationCtx} because all of the services are
* read-only.
*/
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.
*/
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>;

View File

@@ -1,93 +0,0 @@
/* 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 {
actionGeneric,
httpActionGeneric,
queryGeneric,
mutationGeneric,
internalActionGeneric,
internalMutationGeneric,
internalQueryGeneric,
} from "convex/server";
/**
* 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 = 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 = 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 = 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 = 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 = 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 = 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 = httpActionGeneric;

View File

@@ -1,13 +0,0 @@
import { AuthConfig } from "convex/server";
export default {
providers: [
{
type: "customJwt",
applicationID: "convex-app",
issuer: "https://convex.kevisual.cn",
jwks: "https://api-convex.kevisual.cn/root/convex/jwks.json",
algorithm: "RS256",
},
],
};

View File

@@ -1,7 +0,0 @@
import { defineApp } from "convex/server";
const app = defineApp();
// import myComponent from "../component/convex.config.ts";
// app.use(myComponent);
export default app;

View File

@@ -1,39 +0,0 @@
import { httpRouter } from "convex/server";
import { httpAction } from "./_generated/server";
const http = httpRouter();
http.route({
path: "/auth",
method: "POST",
handler: httpAction(async (ctx, request) => {
// 处理请求并返回响应
return new Response(JSON.stringify({ message: "Hello from custom endpoint!" }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}),
})
// https://api-convex.kevisual.cn/root/convex/jwks.json
http.route({
path: '/root/convex/jwks.json',
method: 'GET',
handler: httpAction(async (ctx, request) => {
// 返回 JWKS 数据
const jwks = {
"keys": [
{
"kty": "RSA",
"n": "km4cjJJOMFkl2G5qWMuFmWwF7rmeqRYzYdR8SddKeeMW0e9yIf5pv2Mfwv0aMJUpb-_j3j9M7whx_SEGc_2jx1vxCu1AlYURhnnLTWdsR-ZRPr2LK9UstMrgpWV425R2RliqXTDTYlSxUUlD9nPue_tqbfwN2aM9MCarm67xK_ZCcKRlW9o9L2-9UMfzRA7uiy4VQtOemP0PTXp-E9RxNiMPOQXIRls9wTW_EkDT3dGy7JCZhj7_qib3T8k9m84SwU7wI2R_3IH4DcHSMAn1BRRMXZ1_wPhbP39laNtdJgbDjGCqUccGhLUaoo2WGkZ52eb7NPqamp0K1Dh2jwTIJQ",
"e": "AQAB",
"kid": "kid-key-1"
}
]
};
return new Response(JSON.stringify(jwks), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}),
})
export default http;

View File

@@ -1,30 +0,0 @@
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
// Other tables here...
github_starred: defineTable({
author: v.string(),
auto: v.string(),
description: v.string(),
last_updated: v.string(),
link: v.string(),
repo_id: v.float64(),
stars: v.float64(),
summary: v.string(),
title: v.string(),
}),
users: defineTable({
userId: v.string(), // 外部系统的用户 ID
name: v.string(),
createdAt: v.string(),
lastLoginAt: v.optional(v.string()),
}).index("userId", ["userId"]),
sessions: defineTable({
userId: v.id("users"),
createdAt: v.string(),
expiresAt: v.optional(v.string()),
token: v.optional(v.string()),
}).index("token", ["token"]),
});

View File

@@ -37,7 +37,6 @@
"studio": "npx drizzle-kit studio", "studio": "npx drizzle-kit studio",
"drizzle:migrate": "npx drizzle-kit migrate", "drizzle:migrate": "npx drizzle-kit migrate",
"drizzle:push": "npx drizzle-kit push", "drizzle:push": "npx drizzle-kit push",
"convex": "bunx convex dev",
"pub": "envision pack -p -u -c" "pub": "envision pack -p -u -c"
}, },
"keywords": [], "keywords": [],
@@ -54,26 +53,23 @@
"@types/busboy": "^1.5.4", "@types/busboy": "^1.5.4",
"@types/send": "^1.2.1", "@types/send": "^1.2.1",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"bullmq": "^5.67.2", "bullmq": "^5.67.3",
"busboy": "^1.6.0", "busboy": "^1.6.0",
"commander": "^14.0.3",
"drizzle-kit": "^0.31.8", "drizzle-kit": "^0.31.8",
"drizzle-orm": "^0.45.1", "drizzle-orm": "^0.45.1",
"drizzle-zod": "^0.8.3", "drizzle-zod": "^0.8.3",
"eventemitter3": "^5.0.4", "eventemitter3": "^5.0.4",
"ioredis": "^5.9.2", "ioredis": "^5.9.2",
"minio": "^8.0.6",
"pg": "^8.18.0", "pg": "^8.18.0",
"pm2": "^6.0.14", "pm2": "^6.0.14",
"send": "^1.2.1", "send": "^1.2.1",
"sequelize": "^6.37.7", "sequelize": "^6.37.7",
"ws": "npm:@kevisual/ws", "ws": "npm:@kevisual/ws",
"xml2js": "^0.6.2", "xml2js": "^0.6.2"
"zod-to-json-schema": "^3.25.1"
}, },
"devDependencies": { "devDependencies": {
"@aws-sdk/client-s3": "^3.981.0", "@aws-sdk/client-s3": "^3.984.0",
"@kevisual/api": "^0.0.44", "@kevisual/api": "^0.0.47",
"@kevisual/code-center-module": "0.0.24", "@kevisual/code-center-module": "0.0.24",
"@kevisual/context": "^0.0.4", "@kevisual/context": "^0.0.4",
"@kevisual/file-listener": "^0.0.2", "@kevisual/file-listener": "^0.0.2",
@@ -88,16 +84,14 @@
"@types/bun": "^1.3.8", "@types/bun": "^1.3.8",
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "^4.2.2",
"@types/jsonwebtoken": "^9.0.10", "@types/jsonwebtoken": "^9.0.10",
"@types/node": "^25.2.0", "@types/node": "^25.2.1",
"@types/pg": "^8.16.0", "@types/pg": "^8.16.0",
"@types/semver": "^7.7.1", "@types/semver": "^7.7.1",
"@types/xml2js": "^0.4.14", "@types/xml2js": "^0.4.14",
"archiver": "^7.0.1", "archiver": "^7.0.1",
"better-sqlite3": "^12.6.2",
"convex": "^1.31.7",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"dayjs": "^1.11.19", "dayjs": "^1.11.19",
"dotenv": "^17.2.3", "dotenv": "^17.2.4",
"es-toolkit": "^1.44.0", "es-toolkit": "^1.44.0",
"ioredis": "^5.9.2", "ioredis": "^5.9.2",
"jsonwebtoken": "^9.0.3", "jsonwebtoken": "^9.0.3",
@@ -107,7 +101,7 @@
"p-queue": "^9.1.0", "p-queue": "^9.1.0",
"pg": "^8.18.0", "pg": "^8.18.0",
"pm2": "^6.0.14", "pm2": "^6.0.14",
"semver": "^7.7.3", "semver": "^7.7.4",
"sequelize": "^6.37.7", "sequelize": "^6.37.7",
"zod": "^4.3.6" "zod": "^4.3.6"
}, },
@@ -115,11 +109,5 @@
"inflight": "latest", "inflight": "latest",
"picomatch": "^4.0.2" "picomatch": "^4.0.2"
}, },
"pnpm": {
"onlyBuiltDependencies": [
"esbuild",
"better-sqlite3"
]
},
"packageManager": "pnpm@10.28.2" "packageManager": "pnpm@10.28.2"
} }

View File

@@ -1,155 +0,0 @@
-- =====================================================
-- 删除 cf_orgs 表中所有带数字的重复唯一约束
-- 保留 cf_orgs_username_key不带数字的
-- =====================================================
DO $$
DECLARE
constraint_record RECORD;
BEGIN
FOR constraint_record IN
SELECT conname
FROM pg_constraint
WHERE conrelid = 'cf_orgs'::regclass
AND conname LIKE 'cf_orgs_username_key%'
AND conname != 'cf_orgs_username_key'
AND conname ~ '[0-9]' -- 包含数字的
ORDER BY conname
LOOP
EXECUTE format('ALTER TABLE cf_orgs DROP CONSTRAINT IF EXISTS %I', constraint_record.conname);
RAISE NOTICE '已删除约束: %', constraint_record.conname;
END LOOP;
END $$;
-- =====================================================
-- 删除 cf_user 表中所有带数字的重复唯一约束
-- 保留 cf_user_username_key不带数字的
-- =====================================================
DO $$
DECLARE
constraint_record RECORD;
BEGIN
FOR constraint_record IN
SELECT conname
FROM pg_constraint
WHERE conrelid = 'cf_user'::regclass
AND conname LIKE 'cf_user_username_key%'
AND conname != 'cf_user_username_key'
AND conname ~ '[0-9]' -- 包含数字的
ORDER BY conname
LOOP
EXECUTE format('ALTER TABLE cf_user DROP CONSTRAINT IF EXISTS %I', constraint_record.conname);
RAISE NOTICE '已删除约束: %', constraint_record.conname;
END LOOP;
END $$;
-- =====================================================
-- 验证删除结果
-- =====================================================
-- 查看 cf_orgs 剩余的 username 约束
SELECT 'cf_orgs 表剩余的 username 约束:' AS info;
SELECT conname
FROM pg_constraint
WHERE conrelid = 'cf_orgs'::regclass
AND conname LIKE 'cf_orgs_username_key%'
ORDER BY conname;
-- 查看 cf_user 剩余的 username 约束
SELECT 'cf_user 表剩余的 username 约束:' AS info;
SELECT conname
FROM pg_constraint
WHERE conrelid = 'cf_user'::regclass
AND conname LIKE 'cf_user_username_key%'
ORDER BY conname;
-- =====================================================
-- 删除 kv_app_domain 表中所有带数字的重复唯一约束
-- 保留 kv_app_domain_domain_key不带数字的
-- =====================================================
DO $$
DECLARE
constraint_record RECORD;
BEGIN
FOR constraint_record IN
SELECT conname
FROM pg_constraint
WHERE conrelid = 'kv_app_domain'::regclass
AND conname LIKE 'kv_app_domain_domain_key%'
AND conname != 'kv_app_domain_domain_key'
AND conname ~ '[0-9]' -- 包含数字的
ORDER BY conname
LOOP
EXECUTE format('ALTER TABLE kv_app_domain DROP CONSTRAINT IF EXISTS %I', constraint_record.conname);
RAISE NOTICE '已删除约束: %', constraint_record.conname;
END LOOP;
END $$;
-- =====================================================
-- 删除 prompts 表中所有带数字的重复唯一约束
-- 保留 prompts_key_key不带数字的
-- =====================================================
DO $$
DECLARE
constraint_record RECORD;
BEGIN
FOR constraint_record IN
SELECT conname
FROM pg_constraint
WHERE conrelid = 'prompts'::regclass
AND conname LIKE 'prompts_key_key%'
AND conname != 'prompts_key_key'
AND conname ~ '[0-9]' -- 包含数字的
ORDER BY conname
LOOP
EXECUTE format('ALTER TABLE prompts DROP CONSTRAINT IF EXISTS %I', constraint_record.conname);
RAISE NOTICE '已删除约束: %', constraint_record.conname;
END LOOP;
END $$;
-- =====================================================
-- 删除 apps_trades 表中所有带数字的重复唯一约束
-- 保留 apps_trades_out_trade_no_key不带数字的
-- =====================================================
DO $$
DECLARE
constraint_record RECORD;
BEGIN
FOR constraint_record IN
SELECT conname
FROM pg_constraint
WHERE conrelid = 'apps_trades'::regclass
AND conname LIKE 'apps_trades_out_trade_no_key%'
AND conname != 'apps_trades_out_trade_no_key'
AND conname ~ '[0-9]' -- 包含数字的
ORDER BY conname
LOOP
EXECUTE format('ALTER TABLE apps_trades DROP CONSTRAINT IF EXISTS %I', constraint_record.conname);
RAISE NOTICE '已删除约束: %', constraint_record.conname;
END LOOP;
END $$;
-- =====================================================
-- 删除 ai_agent 表中所有带数字的重复唯一约束
-- 保留 ai_agent_key_key不带数字的
-- =====================================================
DO $$
DECLARE
constraint_record RECORD;
BEGIN
FOR constraint_record IN
SELECT conname
FROM pg_constraint
WHERE conrelid = 'ai_agent'::regclass
AND conname LIKE 'ai_agent_key_key%'
AND conname != 'ai_agent_key_key'
AND conname ~ '[0-9]' -- 包含数字的
ORDER BY conname
LOOP
EXECUTE format('ALTER TABLE ai_agent DROP CONSTRAINT IF EXISTS %I', constraint_record.conname);
RAISE NOTICE '已删除约束: %', constraint_record.conname;
END LOOP;
END $$;

View File

@@ -1,7 +0,0 @@
#!/bin/bash
pnpm i -g npm-check-updates
ncu -u
pnpm install
# /home/ubuntu/.nvm/versions/node/v22.14.0/bin/ncu -u

View File

@@ -1,119 +0,0 @@
import { Op } from 'sequelize';
import { AppDemoModel } from './models/index.ts';
import { app } from '@/app.ts';
app
.route({
path: 'app-demo',
key: 'list',
middleware: ['auth'],
})
.define(async (ctx) => {
const tokenUser = ctx.state.tokenUser;
const { page = 1, pageSize = 20, search, sort = 'DESC' } = ctx.query;
const searchWhere = search
? {
[Op.or]: [{ title: { [Op.like]: `%${search}%` } }, { summary: { [Op.like]: `%${search}%` } }],
}
: {};
const { rows: appDemo, count } = await AppDemoModel.findAndCountAll({
where: {
uid: tokenUser.id,
...searchWhere,
},
offset: (page - 1) * pageSize,
limit: pageSize,
order: [['updatedAt', sort]],
});
ctx.body = {
list: appDemo,
pagination: {
page,
current: page,
pageSize,
total: count,
},
};
})
.addTo(app);
app
.route({
path: 'app-demo',
key: 'update',
middleware: ['auth'],
})
.define(async (ctx) => {
const tokenUser = ctx.state.tokenUser;
const { id, data, updatedAt: _clear, createdAt: _clear2, ...rest } = ctx.query.data;
let appDemo: AppDemoModel;
let isNew = false;
if (id) {
const appDemo = await AppDemoModel.findByPk(id);
if (appDemo.uid !== tokenUser.uid) {
ctx.throw(403, 'No permission');
}
} else {
appDemo = await AppDemoModel.create({
data: data,
...rest,
uid: tokenUser.uid,
});
isNew = true;
}
if (!appDemo) {
ctx.throw(404, 'AppDemo not found');
}
if (!isNew) {
appDemo = await appDemo.update({
data: { ...appDemo.data, ...data },
...rest,
});
}
ctx.body = appDemo;
})
.addTo(app);
app
.route({
path: 'app-demo',
key: 'delete',
middleware: ['auth'],
})
.define(async (ctx) => {
const tokenUser = ctx.state.tokenUser;
const { id, force = false } = ctx.query.data || {};
if (!id) {
ctx.throw(400, 'id is required');
}
const appDemo = await AppDemoModel.findByPk(id);
if (appDemo.uid !== tokenUser.id) {
ctx.throw(403, 'No permission');
}
await appDemo.destroy({ force });
ctx.body = appDemo;
})
.addTo(app);
app
.route({
path: 'app-demo',
key: 'get',
middleware: ['auth'],
})
.define(async (ctx) => {
const tokenUser = ctx.state.tokenUser;
const { id } = ctx.query.data || {};
if (!id) {
ctx.throw(400, 'id is required');
}
const appDemo = await AppDemoModel.findByPk(id);
if (appDemo.uid !== tokenUser.id) {
ctx.throw(403, 'No permission');
}
ctx.body = appDemo;
})
.addTo(app);

View File

@@ -1,71 +0,0 @@
import { sequelize } from '@/modules/sequelize.ts';
import { DataTypes, Model } from 'sequelize';
export interface AppDemoData {
[key: string]: any;
}
export type AppDemo = Partial<InstanceType<typeof AppDemoModel>>;
export class AppDemoModel extends Model {
declare id: string;
declare title: string;
declare description: string;
declare summary: string;
declare data: AppDemoData;
declare tags: string[];
declare version: string;
declare uid: string;
declare createdAt: Date;
declare updatedAt: Date;
}
AppDemoModel.init(
{
id: {
type: DataTypes.UUID,
primaryKey: true,
defaultValue: DataTypes.UUIDV4,
},
title: {
type: DataTypes.TEXT,
defaultValue: '',
},
description: {
type: DataTypes.TEXT,
defaultValue: '',
},
summary: {
type: DataTypes.TEXT,
defaultValue: '',
},
tags: {
type: DataTypes.JSONB,
defaultValue: [],
},
version: {
type: DataTypes.INTEGER,
defaultValue: 0,
},
data: {
type: DataTypes.JSONB,
defaultValue: {},
},
uid: {
type: DataTypes.UUID,
allowNull: false,
},
},
{
sequelize,
tableName: 'kv_app_demo',
paranoid: true,
},
);
// AppDemoModel.sync({ alter: true, logging: false }).catch((e) => {
// console.error('AppDemoModel sync', e);
// });

View File

@@ -1,3 +1 @@
export { sequelize } from './sequelize.ts'; export { sequelize } from './sequelize.ts';
export * from './minio.ts'

View File

@@ -1,13 +0,0 @@
import { Client, } from 'minio';
import { useConfig } from '@kevisual/use-config';
const config = useConfig();
const minioConfig = {
endPoint: config.MINIO_ENDPOINT || 'localhost',
// @ts-ignore
port: parseInt(config.MINIO_PORT || '9000'),
useSSL: config.MINIO_USE_SSL === 'true',
accessKey: config.MINIO_ACCESS_KEY,
secretKey: config.MINIO_SECRET_KEY,
};
export const minioClient = new Client(minioConfig);

View File

@@ -1,14 +0,0 @@
import { program, Command } from 'commander';
// import { useContextKey } from '@kevisual/context';
// import * as redisLib from './modules/redis.ts';
// import * as sequelizeLib from './modules/sequelize.ts';
// import * as minioLib from './modules/minio.ts';
// export const redis = useContextKey('redis', () => redisLib.redis);
// export const minioClient = useContextKey('minioClient', () => minioLib.minioClient);
// export const sequelize = useContextKey('sequelize', () => sequelizeLib.sequelize);
export { program, Command };
program.description('code-center的一部分工具');
program.version('1.0.0', '-v, --version');

View File

@@ -1,5 +1,4 @@
import { app } from '@/app.ts'; import { AppModel } from '../module/index.ts';
import { AppModel, AppListModel } from '../module/index.ts';
export const mvAppFromUserAToUserB = async (userA: string, userB: string) => { export const mvAppFromUserAToUserB = async (userA: string, userB: string) => {
const appList = await AppModel.findAll({ const appList = await AppModel.findAll({
where: { where: {

View File

@@ -80,8 +80,4 @@ AppDomainModel.init(
tableName: 'kv_app_domain', tableName: 'kv_app_domain',
paranoid: true, paranoid: true,
}, },
); );
// AppDomainModel.sync({ alter: true, logging: false }).catch((e) => {
// console.error('AppDomainModel sync', e);
// });

View File

@@ -1,327 +0,0 @@
import { useContextKey } from '@kevisual/context';
import { nanoid, customAlphabet } from 'nanoid';
import { DataTypes, Model, ModelAttributes } from 'sequelize';
import type { Sequelize } from 'sequelize';
export const random = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
export type Mark = Partial<InstanceType<typeof MarkModel>>;
export type MarkData = {
md?: string; // markdown
mdList?: string[]; // markdown list
type?: string; // 类型 markdown | json | html | image | video | audio | code | link | file
data?: any;
key?: string; // 文件的名称, 唯一
push?: boolean; // 是否推送到elasticsearch
pushTime?: Date; // 推送时间
summary?: string; // 摘要
nodes?: MarkDataNode[]; // 节点
[key: string]: any;
};
export type MarkFile = {
id: string;
name: string;
url: string;
size: number;
type: 'self' | 'data' | 'generate'; // generate为生成文件
query: string; // 'data.nodes[id].content';
hash: string;
fileKey: string; // 文件的名称, 唯一
};
export type MarkDataNode = {
id?: string;
[key: string]: any;
};
export type MarkConfig = {
[key: string]: any;
};
export type MarkAuth = {
[key: string]: any;
};
/**
* 隐秘内容
* auth
* config
*
*/
export class MarkModel extends Model {
declare id: string;
declare title: string; // 标题可以ai生成
declare description: string; // 描述可以ai生成
declare cover: string; // 封面可以ai生成
declare thumbnail: string; // 缩略图
declare key: string; // 文件路径
declare markType: string; // markdown | json | html | image | video | audio | code | link | file
declare link: string; // 访问链接
declare tags: string[]; // 标签
declare summary: string; // 摘要, description的简化版
declare data: MarkData; // 数据
declare uid: string; // 操作用户的id
declare puid: string; // 父级用户的id, 真实用户
declare config: MarkConfig; // mark属于一定不会暴露的内容。
declare fileList: MarkFile[]; // 文件管理
declare uname: string; // 用户的名称, 或者着别名
declare markedAt: Date; // 标记时间
declare createdAt: Date;
declare updatedAt: Date;
declare version: number;
/**
* 加锁更新data中的node的节点通过node的id
* @param param0
*/
static async updateJsonNode(id: string, node: MarkDataNode, opts?: { operate?: 'update' | 'delete'; Model?: any; sequelize?: Sequelize }) {
const sequelize = opts?.sequelize || (await useContextKey('sequelize'));
const transaction = await sequelize.transaction(); // 开启事务
const operate = opts.operate || 'update';
const isUpdate = operate === 'update';
const Model = opts.Model || MarkModel;
try {
// 1. 获取当前的 JSONB 字段值(加锁)
const mark = await Model.findByPk(id, {
transaction,
lock: transaction.LOCK.UPDATE, // 加锁,防止其他事务同时修改
});
if (!mark) {
throw new Error('Mark not found');
}
// 2. 修改特定的数组元素
const data = mark.data as MarkData;
const items = data.nodes;
if (!node.id) {
node.id = random(12);
}
// 找到要更新的元素
const itemIndex = items.findIndex((item) => item.id === node.id);
if (itemIndex === -1) {
isUpdate && items.push(node);
} else {
if (isUpdate) {
items[itemIndex] = node;
} else {
items.splice(itemIndex, 1);
}
}
const version = Number(mark.version) + 1;
// 4. 更新 JSONB 字段
const result = await mark.update(
{
data: {
...data,
nodes: items,
},
version,
},
{ transaction },
);
await transaction.commit();
return result;
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async updateJsonNodes(id: string, nodes: { node: MarkDataNode; operate?: 'update' | 'delete' }[], opts?: { Model?: any; sequelize?: Sequelize }) {
const sequelize = opts?.sequelize || (await useContextKey('sequelize'));
const transaction = await sequelize.transaction(); // 开启事务
const Model = opts?.Model || MarkModel;
try {
const mark = await Model.findByPk(id, {
transaction,
lock: transaction.LOCK.UPDATE, // 加锁,防止其他事务同时修改
});
if (!mark) {
throw new Error('Mark not found');
}
const data = mark.data as MarkData;
const _nodes = data.nodes || [];
// 过滤不在nodes中的节点
const blankNodes = nodes.filter((node) => !_nodes.find((n) => n.id === node.node.id)).map((node) => node.node);
// 更新或删除节点
const newNodes = _nodes
.map((node) => {
const nodeOperate = nodes.find((n) => n.node.id === node.id);
if (nodeOperate) {
if (nodeOperate.operate === 'delete') {
return null;
}
return nodeOperate.node;
}
return node;
})
.filter((node) => node !== null);
const version = Number(mark.version) + 1;
const result = await mark.update(
{
data: {
...data,
nodes: [...blankNodes, ...newNodes],
},
version,
},
{ transaction },
);
await transaction.commit();
return result;
} catch (error) {
await transaction.rollback();
throw error;
}
}
static async updateData(id: string, data: MarkData, opts: { Model?: any; sequelize?: Sequelize }) {
const sequelize = opts.sequelize || (await useContextKey('sequelize'));
const transaction = await sequelize.transaction(); // 开启事务
const Model = opts.Model || MarkModel;
const mark = await Model.findByPk(id, {
transaction,
lock: transaction.LOCK.UPDATE, // 加锁,防止其他事务同时修改
});
if (!mark) {
throw new Error('Mark not found');
}
const version = Number(mark.version) + 1;
const result = await mark.update(
{
...mark.data,
...data,
data: {
...mark.data,
...data,
},
version,
},
{ transaction },
);
await transaction.commit();
return result;
}
static async createNew(data: any, opts: { Model?: any; sequelize?: Sequelize }) {
const sequelize = opts.sequelize || (await useContextKey('sequelize'));
const transaction = await sequelize.transaction(); // 开启事务
const Model = opts.Model || MarkModel;
const result = await Model.create({ ...data, version: 1 }, { transaction });
await transaction.commit();
return result;
}
}
export type MarkInitOpts<T = any> = {
tableName: string;
sequelize?: Sequelize;
callInit?: (attribute: ModelAttributes) => ModelAttributes;
Model?: T extends typeof MarkModel ? T : typeof MarkModel;
};
export type Opts = {
sync?: boolean;
alter?: boolean;
logging?: boolean | ((...args: any) => any);
force?: boolean;
};
export const MarkMInit = async <T = any>(opts: MarkInitOpts<T>, sync?: Opts) => {
const sequelize = await useContextKey('sequelize');
opts.sequelize = opts.sequelize || sequelize;
const { callInit, Model, ...optsRest } = opts;
const modelAttribute = {
id: {
type: DataTypes.UUID,
primaryKey: true,
defaultValue: DataTypes.UUIDV4,
comment: 'id',
},
title: {
type: DataTypes.TEXT,
defaultValue: '',
},
key: {
type: DataTypes.TEXT, // 对应的minio的文件路径
defaultValue: '',
},
markType: {
type: DataTypes.TEXT,
defaultValue: 'md', // markdown | json | html | image | video | audio | code | link | file
comment: '类型',
},
description: {
type: DataTypes.TEXT,
defaultValue: '',
},
cover: {
type: DataTypes.TEXT,
defaultValue: '',
comment: '封面',
},
thumbnail: {
type: DataTypes.TEXT,
defaultValue: '',
comment: '缩略图',
},
link: {
type: DataTypes.TEXT,
defaultValue: '',
comment: '链接',
},
tags: {
type: DataTypes.JSONB,
defaultValue: [],
},
summary: {
type: DataTypes.TEXT,
defaultValue: '',
comment: '摘要',
},
config: {
type: DataTypes.JSONB,
defaultValue: {},
},
data: {
type: DataTypes.JSONB,
defaultValue: {},
},
fileList: {
type: DataTypes.JSONB,
defaultValue: [],
},
uname: {
type: DataTypes.STRING,
defaultValue: '',
comment: '用户的名称, 更新后的用户的名称',
},
version: {
type: DataTypes.INTEGER, // 更新刷新版本,多人协作
defaultValue: 1,
},
markedAt: {
type: DataTypes.DATE,
allowNull: true,
comment: '标记时间',
},
uid: {
type: DataTypes.UUID,
allowNull: true,
},
puid: {
type: DataTypes.UUID,
allowNull: true,
},
};
const InitModel = Model || MarkModel;
InitModel.init(callInit ? callInit(modelAttribute) : modelAttribute, {
sequelize,
paranoid: true,
...optsRest,
});
if (sync && sync.sync) {
const { sync: _, ...rest } = sync;
MarkModel.sync({ alter: true, logging: false, ...rest }).catch((e) => {
console.error('MarkModel sync', e);
});
}
};
export const markModelInit = MarkMInit;
export const syncMarkModel = async (sync?: Opts, tableName = 'micro_mark') => {
const sequelize = await useContextKey('sequelize');
await MarkMInit({ sequelize, tableName }, sync);
};

View File

@@ -1,5 +0,0 @@
export * from '@kevisual/code-center-module/src/mark/mark-model.ts';
import { markModelInit, MarkModel, syncMarkModel } from '@kevisual/code-center-module/src/mark/mark-model.ts';
export { markModelInit, MarkModel };
syncMarkModel({ sync: true, alter: true, logging: false });

View File

@@ -1,4 +1,4 @@
import { app, sequelize } from '@/app.ts'; import { app } from '@/app.ts';
import { Org } from '@/models/org.ts'; import { Org } from '@/models/org.ts';
import { User } from '@/models/user.ts'; import { User } from '@/models/user.ts';
import { Op } from 'sequelize'; import { Op } from 'sequelize';

View File

@@ -1,50 +0,0 @@
import { program, Command } from '../program.ts';
import { initUser, logger, close } from './common.ts';
const usrCommand = new Command('user').description('用户相关操作');
program.addCommand(usrCommand);
const changePwd = new Command('pwd')
.description('修改用户密码')
.option('-u, --username <username>', '用户名')
.option('-p, --password <password>', '新密码')
.action(async (opts) => {
const username = opts.username;
const password = opts.password;
if (!username) {
logger.error('用户名不能为空');
close();
return;
}
const { User } = await initUser();
const newPassword = password || 'kevisual';
logger.info(`用户名: ${username}`);
logger.info(`新密码: ${newPassword}`);
const user = await User.findOne({ where: { username: username }, logging: false });
if (!user) {
logger.error('用户不存在');
return;
}
const newP = await user.createPassword(newPassword);
logger.info('新密码加密成功', '新密码: ', newPassword);
close();
});
usrCommand.addCommand(changePwd);
const list = new Command('list').description('列出所有用户').action(async () => {
console.log('列出所有用户 start');
const { User } = await initUser();
console.log('列出所有用户');
const users = await User.findAll({ limit: 10, order: [['createdAt', 'DESC']] });
if (users.length === 0) {
logger.info('没有用户');
return;
}
users.forEach((user) => {
console.log(`用户名: ${user.username}`);
});
console.log(`用户数量: ${users.length}`);
await close();
});
usrCommand.addCommand(list);

View File

@@ -1,3 +0,0 @@
import * as redisLib from '../modules/redis.ts';
import { useContextKey, useContext } from '@kevisual/context';
export const redis = useContextKey('redis', () => redisLib.redis);

View File

@@ -1,34 +0,0 @@
import { config } from '../modules/config.ts';
import { sequelize } from '../modules/sequelize.ts';
export { program, Command } from '../program.ts';
import { User, UserInit, OrgInit, Org, UserSecretInit, UserSecret } from '../auth/index.ts';
import { Logger } from '@kevisual/logger';
export const close = async () => {
process.exit(0);
};
export { sequelize };
export const logger = new Logger({
level: (config?.LOG_LEVEL || 'info') as any,
showTime: true,
});
export const initUser = async () => {
console.log('init user');
await UserInit(sequelize, undefined, {
alter: true,
logging: false,
});
await OrgInit(sequelize, undefined, {
alter: true,
logging: false,
});
await UserSecretInit(sequelize, undefined, {
alter: true,
logging: false,
});
return {
User: User,
Org: Org,
UserSecret: UserSecret,
};
};

View File

@@ -1,12 +0,0 @@
import { AppListModel, AppModel } from '../routes/app-manager/module/index.ts';
import { program, Command, close } from './common.ts';
const app = program.command('app');
const appList = new Command('list').action(async () => {
const list = await AppListModel.findAll();
console.log(list.map((item) => item.toJSON()));
close();
});
app.addCommand(appList);

View File

@@ -1,22 +0,0 @@
import { Client } from 'minio'
export async function createBucket(client: Client, bucketName: string) {
const exists = await client.bucketExists(bucketName)
if (!exists) {
await client.makeBucket(bucketName, '')
console.log(`Bucket "${bucketName}" created successfully.`)
} else {
console.log(`Bucket "${bucketName}" already exists.`)
}
}
const minioClient = new Client({
endPoint: 'minio.kevisual.cn',
port: 9000,
useSSL: false,
accessKey: 'admin',
secretKey: 'xiongxiao',
})
createBucket(minioClient, 'nocodb').catch((err) => {
console.error('Error creating bucket:', err)
})

View File

@@ -1,62 +0,0 @@
import { sequelize } from '../modules/sequelize.ts';
import { initUser } from '../scripts/common.ts';
import '../scripts/common-redis.ts';
import { useContextKey } from '@kevisual/context';
export const main = async () => {
const models = await initUser();
const username = 'root';
const orgname = 'admin';
const user = await models.User.findOne({ where: { username } });
const org = await models.User.findOne({ where: { username: orgname } });
console.log('user.id', user?.id);
console.log('org.id', org?.id);
// const userSecret1 = await models.UserSecret.createSecret(user?.id!);
// userSecret1.title = 'root secret';
// await userSecret1.save();
// await models.UserSecret.destroy({
// where: {
// orgId: '16a496d4-8cd6-4e02-b403-c2adc006a53d',
// },
// });
const userSecret2 = await models.UserSecret.createSecret(user?.id!, org?.id!);
userSecret2.title = 'root org secret';
await userSecret2.save();
const secretList = await models.UserSecret.findAll();
for (const secret of secretList) {
console.log(`\nSecret ID: ${secret.id}, User ID: ${secret.userId}, Org ID: ${secret.orgId}, Token: ${secret.token}, Expired Time: ${secret.expiredTime}`);
}
process.exit(0);
};
main();
export const dropTable = async () => {
await sequelize.query('DROP TABLE IF EXISTS "cf_user_secrets"');
console.log('UserSecret table dropped');
process.exit(0);
};
// dropTable()
const token1 = 'sk_tvwzgp5lky8iupawh0encvd52vji4o8argvd2x668gn15q83xpgo8fe10ny7wfsq';
const orgToken2 = 'sk_x37p8iifh6k18c3f121w49nmfy1sbjqpyol9fcsz0lmc5dz493wrfwvtxc4gi9od';
export const main2 = async () => {
const redis = useContextKey('redis');
if (!redis) {
console.error('Redis is not initialized');
return;
}
const models = await initUser();
const UserSecret = models.UserSecret;
const v = await models.UserSecret.verifyToken(token1);
console.log('verifyToken', v);
process.exit(0);
};
// main2();

View File

@@ -1,46 +0,0 @@
import { useContextKey } from '@kevisual/context';
import { sequelize } from '../modules/sequelize.ts';
import { MarkModel, syncMarkModel } from '../routes/mark/model.ts';
export const sequelize2 = useContextKey('sequelize', () => sequelize);
const main = async () => {
// 把所有markmodel的表的source字段的类型改为jsonb
// const marks = await MarkModel.findAll();
// const mark = marks[0];
// for (const mark of marks) {
// if (mark.source) {
// try {
// await MarkModel.update({ source: {} }, { where: { id: mark.id } });
// } catch (e) {
// console.error('update source error:', e);
// }
// }
// }
console.log('update source success');
// await MarkModel.sync({ alter: true, logging: true }).catch((e) => {
// console.error('MarkModel.sync error:', e);
// });
await syncMarkModel({ alter: true, logging: true, sync: true });
};
main();
const sql = `ALTER TABLE "micro_mark" ALTER COLUMN "source" DROP NOT NULL;ALTER TABLE "micro_mark" ALTER COLUMN "source" SET DEFAULT '{}';ALTER TABLE "micro_mark" ALTER COLUMN "source" TYPE JSONB ; COMMENT ON COLUMN "micro_mark"."source" IS '需要的数据的来源,作为一个备注使用。';`;
// sequelize
/**
* 失败
*/
const runSql = async () => {
sequelize
.query(sql)
.then(() => {
console.log('update source success');
})
.catch((e) => {
console.error('update source error:', e);
});
};
// runSql();

View File

@@ -1,48 +0,0 @@
import { sequelize } from '../modules/sequelize.ts';
import { User, UserInit, UserServices, Org, OrgInit } from '../auth/index.ts';
// User.sync({ alter: true, logging: true }).then(() => {
// console.log('sync user done');
// });
// class UserChange extends User {
// static async syncUser() {
// await UserChange.sync({ alter: true, logging: false });
// console.log('sync user done');
// }
// }
export const main = async () => {
await UserInit(sequelize, null, {
alter: true,
logging: false,
});
await OrgInit(sequelize, null, {
alter: true,
logging: false,
});
const user = await User.findAll({});
for (const u of user) {
console.log(u.username, u.type);
}
};
main();
export const changeRootPassword = async () => {
await OrgInit(sequelize, null, {
alter: true,
logging: false,
});
await UserInit(sequelize, null, {
alter: true,
logging: false,
});
const user = await User.findOne({ where: { username: 'root' } });
if (user) {
await user.createPassword('');
await user.save();
console.log('change root password done');
process.exit(0);
}
};
// changeRootPassword();

View File

@@ -1,10 +0,0 @@
import { sequelize } from '../modules/sequelize.ts';
console.log('sequelize');
// 获取所有表名
const [tables] = await sequelize.query(
"SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';"
);
console.log('tables', tables);