update
This commit is contained in:
@@ -26,12 +26,15 @@
|
||||
"devDependencies": {
|
||||
"@kevisual/dts": "^0.0.3",
|
||||
"@kevisual/types": "^0.0.10",
|
||||
"@kevisual/use-config": "^1.0.19",
|
||||
"@types/bun": "^1.3.2",
|
||||
"@kevisual/use-config": "^1.0.21",
|
||||
"@types/bun": "^1.3.3",
|
||||
"@types/node": "^24.10.1",
|
||||
"nocodb-sdk": "^0.265.1"
|
||||
},
|
||||
"exports": {
|
||||
".": "./dist/app.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"form-data": "^4.0.5"
|
||||
}
|
||||
}
|
||||
59
pnpm-lock.yaml
generated
59
pnpm-lock.yaml
generated
@@ -7,6 +7,10 @@ settings:
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
form-data:
|
||||
specifier: ^4.0.5
|
||||
version: 4.0.5
|
||||
devDependencies:
|
||||
'@kevisual/dts':
|
||||
specifier: ^0.0.3
|
||||
@@ -15,11 +19,11 @@ importers:
|
||||
specifier: ^0.0.10
|
||||
version: 0.0.10
|
||||
'@kevisual/use-config':
|
||||
specifier: ^1.0.19
|
||||
version: 1.0.19(dotenv@16.6.1)
|
||||
specifier: ^1.0.21
|
||||
version: 1.0.21(dotenv@16.6.1)
|
||||
'@types/bun':
|
||||
specifier: ^1.3.2
|
||||
version: 1.3.2(@types/react@19.2.6)
|
||||
specifier: ^1.3.3
|
||||
version: 1.3.3
|
||||
'@types/node':
|
||||
specifier: ^24.10.1
|
||||
version: 24.10.1
|
||||
@@ -62,10 +66,10 @@ packages:
|
||||
'@kevisual/types@0.0.10':
|
||||
resolution: {integrity: sha512-Q73uzzjk9UidumnmCvOpgzqDDvQxsblz22bIFuoiioUFJWwaparx8bpd8ArRyFojicYL1YJoFDzDZ9j9NN8grA==}
|
||||
|
||||
'@kevisual/use-config@1.0.19':
|
||||
resolution: {integrity: sha512-Q1IH4eMqUe5w6Bq8etoqOSls9FPIy0xwwD3wHf26EsQLZadhccI9qkDuFzP/rFWDa57mwFPEfwbGE5UlqWOCkw==}
|
||||
'@kevisual/use-config@1.0.21':
|
||||
resolution: {integrity: sha512-czgy4+tBDBJI6QTnKh2PCwswET6ZpZ4ZqBE/SPkkOivEtlrcPzLs5elwMLZ3goD1XMD4VB3yjumb5WuW/8H8MA==}
|
||||
peerDependencies:
|
||||
dotenv: ^16.4.7
|
||||
dotenv: ^17
|
||||
|
||||
'@rollup/plugin-commonjs@28.0.9':
|
||||
resolution: {integrity: sha512-PIR4/OHZ79romx0BVVll/PkwWpJ7e5lsqFa3gFfcrFPWwLXLV39JVUzQV9RKjWerE7B845Hqjj9VYlQeieZ2dA==}
|
||||
@@ -217,8 +221,8 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@types/bun@1.3.2':
|
||||
resolution: {integrity: sha512-t15P7k5UIgHKkxwnMNkJbWlh/617rkDGEdSsDbu+qNHTaz9SKf7aC8fiIlUdD5RPpH6GEkP0cK7WlvmrEBRtWg==}
|
||||
'@types/bun@1.3.3':
|
||||
resolution: {integrity: sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g==}
|
||||
|
||||
'@types/estree@1.0.8':
|
||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||
@@ -226,9 +230,6 @@ packages:
|
||||
'@types/node@24.10.1':
|
||||
resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==}
|
||||
|
||||
'@types/react@19.2.6':
|
||||
resolution: {integrity: sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==}
|
||||
|
||||
'@types/resolve@1.20.2':
|
||||
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
|
||||
|
||||
@@ -238,10 +239,8 @@ packages:
|
||||
axios@1.11.0:
|
||||
resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==}
|
||||
|
||||
bun-types@1.3.2:
|
||||
resolution: {integrity: sha512-i/Gln4tbzKNuxP70OWhJRZz1MRfvqExowP7U6JKoI8cntFrtxg7RJK3jvz7wQW54UuvNC8tbKHHri5fy74FVqg==}
|
||||
peerDependencies:
|
||||
'@types/react': ^19
|
||||
bun-types@1.3.3:
|
||||
resolution: {integrity: sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ==}
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||
@@ -257,9 +256,6 @@ packages:
|
||||
commondir@1.0.1:
|
||||
resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
|
||||
|
||||
csstype@3.2.3:
|
||||
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
||||
|
||||
dayjs@1.11.13:
|
||||
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
|
||||
|
||||
@@ -319,8 +315,8 @@ packages:
|
||||
debug:
|
||||
optional: true
|
||||
|
||||
form-data@4.0.4:
|
||||
resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==}
|
||||
form-data@4.0.5:
|
||||
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
fsevents@2.3.3:
|
||||
@@ -492,7 +488,7 @@ snapshots:
|
||||
|
||||
'@kevisual/types@0.0.10': {}
|
||||
|
||||
'@kevisual/use-config@1.0.19(dotenv@16.6.1)':
|
||||
'@kevisual/use-config@1.0.21(dotenv@16.6.1)':
|
||||
dependencies:
|
||||
'@kevisual/load': 0.0.6
|
||||
dotenv: 16.6.1
|
||||
@@ -602,11 +598,9 @@ snapshots:
|
||||
'@rollup/rollup-win32-x64-msvc@4.53.3':
|
||||
optional: true
|
||||
|
||||
'@types/bun@1.3.2(@types/react@19.2.6)':
|
||||
'@types/bun@1.3.3':
|
||||
dependencies:
|
||||
bun-types: 1.3.2(@types/react@19.2.6)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
bun-types: 1.3.3
|
||||
|
||||
'@types/estree@1.0.8': {}
|
||||
|
||||
@@ -614,10 +608,6 @@ snapshots:
|
||||
dependencies:
|
||||
undici-types: 7.16.0
|
||||
|
||||
'@types/react@19.2.6':
|
||||
dependencies:
|
||||
csstype: 3.2.3
|
||||
|
||||
'@types/resolve@1.20.2': {}
|
||||
|
||||
asynckit@0.4.0: {}
|
||||
@@ -625,15 +615,14 @@ snapshots:
|
||||
axios@1.11.0:
|
||||
dependencies:
|
||||
follow-redirects: 1.15.11
|
||||
form-data: 4.0.4
|
||||
form-data: 4.0.5
|
||||
proxy-from-env: 1.1.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
bun-types@1.3.2(@types/react@19.2.6):
|
||||
bun-types@1.3.3:
|
||||
dependencies:
|
||||
'@types/node': 24.10.1
|
||||
'@types/react': 19.2.6
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
dependencies:
|
||||
@@ -655,8 +644,6 @@ snapshots:
|
||||
|
||||
commondir@1.0.1: {}
|
||||
|
||||
csstype@3.2.3: {}
|
||||
|
||||
dayjs@1.11.13: {}
|
||||
|
||||
deepmerge@4.3.1: {}
|
||||
@@ -696,7 +683,7 @@ snapshots:
|
||||
|
||||
follow-redirects@1.15.11: {}
|
||||
|
||||
form-data@4.0.4:
|
||||
form-data@4.0.5:
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
|
||||
BIN
public/mole.png
Normal file
BIN
public/mole.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 233 KiB |
22
src/api.ts
22
src/api.ts
@@ -13,6 +13,9 @@ type MakeRequestOptions = {
|
||||
data?: Record<string, any>;
|
||||
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
||||
json?: boolean;
|
||||
// body 优先级别高于 data
|
||||
isFromData?: boolean;
|
||||
body?: any;
|
||||
};
|
||||
export class Query {
|
||||
baseURL: string;
|
||||
@@ -24,6 +27,7 @@ export class Query {
|
||||
makeRequest(endpoint: string, options: MakeRequestOptions) {
|
||||
const url = new URL(endpoint, this.baseURL);
|
||||
const isJson = options.json ?? true;
|
||||
const bodyIsFormData = options.isFromData || false;
|
||||
if (options.params) {
|
||||
Object.entries(options.params).forEach(([key, value]) => {
|
||||
url.searchParams.append(key, String(value));
|
||||
@@ -33,12 +37,19 @@ export class Query {
|
||||
const headers: HeadersInit = {
|
||||
'xc-token': `${this.token}`,
|
||||
};
|
||||
headers['Content-Type'] = 'application/json';
|
||||
|
||||
// 如果 body 不是 FormData,才设置 Content-Type
|
||||
if (!bodyIsFormData) {
|
||||
headers['Content-Type'] = 'application/json';
|
||||
}
|
||||
|
||||
const fetchOptions: FetchOptions = {
|
||||
method,
|
||||
headers,
|
||||
};
|
||||
if (options.data) {
|
||||
if (options.body) {
|
||||
fetchOptions.body = options.body;
|
||||
} else if (options.data) {
|
||||
fetchOptions.body = JSON.stringify(options.data);
|
||||
}
|
||||
return fetch(url.href, fetchOptions).then(async (response) => {
|
||||
@@ -47,6 +58,13 @@ export class Query {
|
||||
}
|
||||
if (isJson) {
|
||||
const result = await response.json();
|
||||
const isArray = Array.isArray(result);
|
||||
if (isArray) {
|
||||
return {
|
||||
code: 200,
|
||||
list: result,
|
||||
} as ResponseList;
|
||||
}
|
||||
result.code = 200;
|
||||
return result;
|
||||
}
|
||||
|
||||
79
src/data/upload.ts
Normal file
79
src/data/upload.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
|
||||
import { Query } from '../api.ts';
|
||||
export class Upload {
|
||||
private query: Query;
|
||||
|
||||
constructor(query: Query) {
|
||||
this.query = query;
|
||||
}
|
||||
/**
|
||||
* 创建上传, 上传后,自动返回的数据是列表
|
||||
* @param opts
|
||||
* @returns
|
||||
*/
|
||||
public async createUpload(opts: UploadOpts): Promise<{
|
||||
code: number;
|
||||
list: UploadItem[];
|
||||
}> {
|
||||
const formData = new FormData();
|
||||
const { path, mimeType, file, size, title, url } = opts;
|
||||
if (url) {
|
||||
formData.append('url', url);
|
||||
}
|
||||
if (file) {
|
||||
formData.append('file', file, title);
|
||||
}
|
||||
if (path) {
|
||||
formData.append('path', path);
|
||||
}
|
||||
if (mimeType) {
|
||||
formData.append('mimetype', mimeType);
|
||||
}
|
||||
if (size) {
|
||||
formData.append('size', size.toString());
|
||||
}
|
||||
if (title) {
|
||||
formData.append('title', title);
|
||||
}
|
||||
|
||||
|
||||
return await this.query.makeRequest('/api/v2/storage/upload', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
isFromData: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
type UploadOpts = {
|
||||
path?: string;
|
||||
mimeType?: string;
|
||||
file: any;
|
||||
size?: number;
|
||||
title: string;
|
||||
url?: string; // 可选,互联网资源链接
|
||||
}
|
||||
type UploadItem = {
|
||||
/**
|
||||
* nocodb 资源路径
|
||||
*/
|
||||
path: string;
|
||||
/**
|
||||
* 文件标题
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* 文件大小,单位字节
|
||||
*/
|
||||
size: number;
|
||||
/**
|
||||
* 文件 MIME 类型
|
||||
*/
|
||||
mimetype: string;
|
||||
width: number;
|
||||
height: number;
|
||||
/**
|
||||
* 签名后的完整访问路径
|
||||
*/
|
||||
signedPath: string;
|
||||
}
|
||||
@@ -3,3 +3,4 @@ export * from './api.ts';
|
||||
export * from './main.ts';
|
||||
export * from './meta/index.ts';
|
||||
|
||||
export * from './data/upload.ts';
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Query } from './api.ts';
|
||||
import { Meta } from './meta/index.ts';
|
||||
import { Record } from './record.ts';
|
||||
import { Upload } from './data/upload.ts';
|
||||
export type NocoApiOptions = {
|
||||
table?: string;
|
||||
token?: string;
|
||||
@@ -11,6 +12,7 @@ export class NocoApi {
|
||||
query: Query;
|
||||
record: Record;
|
||||
meta: Meta;
|
||||
upload: Upload;
|
||||
|
||||
constructor(options?: NocoApiOptions) {
|
||||
const table = options?.table;
|
||||
@@ -19,6 +21,7 @@ export class NocoApi {
|
||||
this.query = new Query({ baseURL, token });
|
||||
this.record = new Record(this.query, table);
|
||||
this.meta = new Meta({ query: this.query });
|
||||
this.upload = new Upload(this.query);
|
||||
}
|
||||
/**
|
||||
*
|
||||
|
||||
58
src/utils/mime-type.ts
Normal file
58
src/utils/mime-type.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
export const extname = (str: string): string => {
|
||||
const match = str.match(/\.[^\.]+$/);
|
||||
return match ? match[0] : '';
|
||||
}
|
||||
/**
|
||||
* 根据文件扩展名获取 MIME 类型
|
||||
* @param fileName - 文件名
|
||||
* @returns MIME 类型
|
||||
*/
|
||||
export function getMimeType(fileName: string): string {
|
||||
const ext = extname(fileName).toLowerCase();
|
||||
const mimeTypes: Record<string, string> = {
|
||||
// 图片
|
||||
'.png': 'image/png',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.gif': 'image/gif',
|
||||
'.webp': 'image/webp',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.bmp': 'image/bmp',
|
||||
'.ico': 'image/x-icon',
|
||||
|
||||
// 文档
|
||||
'.pdf': 'application/pdf',
|
||||
'.doc': 'application/msword',
|
||||
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'.xls': 'application/vnd.ms-excel',
|
||||
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'.ppt': 'application/vnd.ms-powerpoint',
|
||||
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'.txt': 'text/plain',
|
||||
'.csv': 'text/csv',
|
||||
|
||||
// 音视频
|
||||
'.mp3': 'audio/mpeg',
|
||||
'.mp4': 'video/mp4',
|
||||
'.wav': 'audio/wav',
|
||||
'.avi': 'video/x-msvideo',
|
||||
'.mov': 'video/quicktime',
|
||||
|
||||
// 压缩文件
|
||||
'.zip': 'application/zip',
|
||||
'.rar': 'application/x-rar-compressed',
|
||||
'.7z': 'application/x-7z-compressed',
|
||||
'.tar': 'application/x-tar',
|
||||
'.gz': 'application/gzip',
|
||||
|
||||
// 代码文件
|
||||
'.json': 'application/json',
|
||||
'.xml': 'application/xml',
|
||||
'.html': 'text/html',
|
||||
'.css': 'text/css',
|
||||
'.js': 'text/javascript',
|
||||
'.ts': 'text/typescript',
|
||||
};
|
||||
|
||||
return mimeTypes[ext] || 'application/octet-stream';
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import { NocoApi } from './../src/main.ts';
|
||||
import { useConfig } from '@kevisual/use-config'
|
||||
export const config = useConfig()
|
||||
|
||||
import util from 'node:util';
|
||||
// # 签到表
|
||||
const table = 'mcby44q8zrayvn9'
|
||||
// const table = 'mcby44q8zrayvn9'
|
||||
// 本地
|
||||
const table = 'mi89er1m951pb3g'
|
||||
export const nocoApi = new NocoApi({
|
||||
baseURL: config.NOCODB_URL || 'http://localhost:8080',
|
||||
token: config.NOCODB_API_KEY || '',
|
||||
@@ -11,4 +13,5 @@ export const nocoApi = new NocoApi({
|
||||
});
|
||||
|
||||
|
||||
// console.log('nocoApi', await nocoApi.record.list())
|
||||
// const list = await nocoApi.record.list()
|
||||
// console.log(util.inspect(list, { depth: null, colors: true }))
|
||||
|
||||
21
test/upload.ts
Normal file
21
test/upload.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { nocoApi } from './common';
|
||||
import util from 'node:util';
|
||||
const filePath = path.resolve(process.cwd(), 'public/mole.png');
|
||||
const absolutePath = path.resolve(process.cwd(), filePath);
|
||||
const fileBuffer = fs.readFileSync(absolutePath);
|
||||
const blob = new Blob([fileBuffer], { type: 'image/png' });
|
||||
|
||||
const res = await nocoApi.upload.createUpload({
|
||||
file: blob,
|
||||
title: 'mole3.png',
|
||||
})
|
||||
console.log('上传结果:');
|
||||
console.log(util.inspect(res, { depth: null }));
|
||||
// const update = await nocoApi.record.update({
|
||||
// Id: 4,
|
||||
// Picture: res.list
|
||||
// })
|
||||
|
||||
// console.log(update)
|
||||
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"extends": "@kevisual/types/json/backend.json",
|
||||
"compilerOptions": {
|
||||
"module": "NodeNext",
|
||||
"target": "esnext",
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
|
||||
Reference in New Issue
Block a user