mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00:00
refactor(infra): directory structure (#4615)
This commit is contained in:
72
packages/backend/server/src/utils/__tests__/doc.spec.ts
Normal file
72
packages/backend/server/src/utils/__tests__/doc.spec.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import test from 'ava';
|
||||
|
||||
import { DocID, DocVariant } from '../doc';
|
||||
|
||||
test('can parse', t => {
|
||||
// workspace only
|
||||
let id = new DocID('ws');
|
||||
|
||||
t.is(id.workspace, 'ws');
|
||||
t.assert(id.isWorkspace);
|
||||
|
||||
// full id
|
||||
id = new DocID('ws:space:sub');
|
||||
|
||||
t.is(id.workspace, 'ws');
|
||||
t.is(id.variant, DocVariant.Space);
|
||||
t.is(id.guid, 'sub');
|
||||
|
||||
// variant only
|
||||
id = new DocID('space:sub', 'ws');
|
||||
|
||||
t.is(id.workspace, 'ws');
|
||||
t.is(id.variant, DocVariant.Space);
|
||||
t.is(id.guid, 'sub');
|
||||
|
||||
// sub id only
|
||||
id = new DocID('sub', 'ws');
|
||||
|
||||
t.is(id.workspace, 'ws');
|
||||
t.is(id.variant, DocVariant.Unknown);
|
||||
t.is(id.guid, 'sub');
|
||||
});
|
||||
|
||||
test('fail', t => {
|
||||
t.throws(() => new DocID('a:b:c:d'), {
|
||||
message: 'Invalid format of Doc ID: a:b:c:d',
|
||||
});
|
||||
t.throws(() => new DocID(':space:sub'), { message: 'Workspace is required' });
|
||||
t.throws(() => new DocID('space:sub'), { message: 'Workspace is required' });
|
||||
t.throws(() => new DocID('ws:any:sub'), {
|
||||
message: 'Invalid ID variant: any',
|
||||
});
|
||||
t.throws(() => new DocID('ws:space:'), {
|
||||
message: 'ID is required for non-workspace doc',
|
||||
});
|
||||
t.throws(() => new DocID('ws::space'), {
|
||||
message: 'Variant is required for non-workspace doc',
|
||||
});
|
||||
});
|
||||
|
||||
test('fix', t => {
|
||||
let id = new DocID('ws');
|
||||
// can't fix because the doc variant is [Workspace]
|
||||
id.fixWorkspace('ws2');
|
||||
t.is(id.workspace, 'ws');
|
||||
t.is(id.toString(), 'ws');
|
||||
|
||||
id = new DocID('ws:space:sub');
|
||||
|
||||
id.fixWorkspace('ws2');
|
||||
|
||||
t.is(id.workspace, 'ws2');
|
||||
t.is(id.toString(), 'ws2:space:sub');
|
||||
|
||||
id = new DocID('space:sub', 'ws');
|
||||
t.is(id.workspace, 'ws');
|
||||
t.is(id.toString(), 'ws:space:sub');
|
||||
|
||||
id = new DocID('ws2:space:sub', 'ws');
|
||||
t.is(id.workspace, 'ws');
|
||||
t.is(id.toString(), 'ws:space:sub');
|
||||
});
|
||||
115
packages/backend/server/src/utils/doc.ts
Normal file
115
packages/backend/server/src/utils/doc.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
export enum DocVariant {
|
||||
Workspace = 'workspace',
|
||||
Space = 'space',
|
||||
Settings = 'settings',
|
||||
Unknown = 'unknown',
|
||||
}
|
||||
|
||||
registerEnumType(DocVariant, {
|
||||
name: 'DocVariant',
|
||||
});
|
||||
|
||||
export class DocID {
|
||||
raw: string;
|
||||
workspace: string;
|
||||
variant: DocVariant;
|
||||
private sub: string | null;
|
||||
|
||||
static parse(raw: string): DocID | null {
|
||||
try {
|
||||
return new DocID(raw);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* pure guid for workspace and subdoc without any prefix
|
||||
*/
|
||||
get guid(): string {
|
||||
return this.variant === DocVariant.Workspace
|
||||
? this.workspace
|
||||
: // sub is always truthy when variant is not workspace
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
this.sub!;
|
||||
}
|
||||
|
||||
get full(): string {
|
||||
return this.variant === DocVariant.Workspace
|
||||
? this.workspace
|
||||
: `${this.workspace}:${this.variant}:${this.sub}`;
|
||||
}
|
||||
|
||||
get isWorkspace(): boolean {
|
||||
return this.variant === DocVariant.Workspace;
|
||||
}
|
||||
|
||||
constructor(raw: string, workspaceId?: string) {
|
||||
if (!raw.length) {
|
||||
throw new Error('Invalid Empty Doc ID');
|
||||
}
|
||||
|
||||
let parts = raw.split(':');
|
||||
|
||||
if (parts.length > 3) {
|
||||
throw new Error(`Invalid format of Doc ID: ${raw}`);
|
||||
} else if (parts.length === 2) {
|
||||
// `${variant}:${guid}`
|
||||
if (!workspaceId) {
|
||||
throw new Error('Workspace is required');
|
||||
}
|
||||
|
||||
parts.unshift(workspaceId);
|
||||
} else if (parts.length === 1) {
|
||||
// ${ws} or ${pageId}
|
||||
if (workspaceId && parts[0] !== workspaceId) {
|
||||
parts = [workspaceId, DocVariant.Unknown, parts[0]];
|
||||
} else {
|
||||
// parts:[ws] equals [workspaceId]
|
||||
}
|
||||
}
|
||||
|
||||
let workspace = parts.at(0);
|
||||
|
||||
// fix for `${non-workspaceId}:${variant}:${guid}`
|
||||
if (workspaceId) {
|
||||
workspace = workspaceId;
|
||||
}
|
||||
|
||||
const variant = parts.at(1);
|
||||
const docId = parts.at(2);
|
||||
|
||||
if (!workspace) {
|
||||
throw new Error('Workspace is required');
|
||||
}
|
||||
|
||||
if (variant) {
|
||||
if (!Object.values(DocVariant).includes(variant as any)) {
|
||||
throw new Error(`Invalid ID variant: ${variant}`);
|
||||
}
|
||||
|
||||
if (!docId) {
|
||||
throw new Error('ID is required for non-workspace doc');
|
||||
}
|
||||
} else if (docId) {
|
||||
throw new Error('Variant is required for non-workspace doc');
|
||||
}
|
||||
|
||||
this.raw = raw;
|
||||
this.workspace = workspace;
|
||||
this.variant = (variant as DocVariant | undefined) ?? DocVariant.Workspace;
|
||||
this.sub = docId || null;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.full;
|
||||
}
|
||||
|
||||
fixWorkspace(workspaceId: string) {
|
||||
if (!this.isWorkspace && this.workspace !== workspaceId) {
|
||||
this.workspace = workspaceId;
|
||||
}
|
||||
}
|
||||
}
|
||||
77
packages/backend/server/src/utils/nestjs.ts
Normal file
77
packages/backend/server/src/utils/nestjs.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import type { ArgumentsHost, ExecutionContext } from '@nestjs/common';
|
||||
import type { GqlContextType } from '@nestjs/graphql';
|
||||
import { GqlArgumentsHost, GqlExecutionContext } from '@nestjs/graphql';
|
||||
import type { Request, Response } from 'express';
|
||||
|
||||
export function getRequestResponseFromContext(context: ExecutionContext) {
|
||||
switch (context.getType<GqlContextType>()) {
|
||||
case 'graphql': {
|
||||
const gqlContext = GqlExecutionContext.create(context).getContext<{
|
||||
req: Request;
|
||||
}>();
|
||||
return {
|
||||
req: gqlContext.req,
|
||||
res: gqlContext.req.res,
|
||||
};
|
||||
}
|
||||
case 'http': {
|
||||
const http = context.switchToHttp();
|
||||
return {
|
||||
req: http.getRequest<Request>(),
|
||||
res: http.getResponse<Response>(),
|
||||
};
|
||||
}
|
||||
case 'ws': {
|
||||
const ws = context.switchToWs();
|
||||
const req = ws.getClient().handshake;
|
||||
|
||||
const cookies = req?.headers?.cookie;
|
||||
// patch cookies to match auth guard logic
|
||||
if (typeof cookies === 'string') {
|
||||
req.cookies = cookies
|
||||
.split(';')
|
||||
.map(v => v.split('='))
|
||||
.reduce(
|
||||
(acc, v) => {
|
||||
acc[decodeURIComponent(v[0].trim())] = decodeURIComponent(
|
||||
v[1].trim()
|
||||
);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>
|
||||
);
|
||||
}
|
||||
|
||||
return { req };
|
||||
}
|
||||
default:
|
||||
throw new Error('Unknown context type for getting request and response');
|
||||
}
|
||||
}
|
||||
|
||||
export function getRequestResponseFromHost(host: ArgumentsHost) {
|
||||
switch (host.getType<GqlContextType>()) {
|
||||
case 'graphql': {
|
||||
const gqlContext = GqlArgumentsHost.create(host).getContext<{
|
||||
req: Request;
|
||||
}>();
|
||||
return {
|
||||
req: gqlContext.req,
|
||||
res: gqlContext.req.res,
|
||||
};
|
||||
}
|
||||
case 'http': {
|
||||
const http = host.switchToHttp();
|
||||
return {
|
||||
req: http.getRequest<Request>(),
|
||||
res: http.getResponse<Response>(),
|
||||
};
|
||||
}
|
||||
default:
|
||||
throw new Error('Unknown host type for getting request and response');
|
||||
}
|
||||
}
|
||||
|
||||
export function getRequestFromHost(host: ArgumentsHost) {
|
||||
return getRequestResponseFromHost(host).req;
|
||||
}
|
||||
42
packages/backend/server/src/utils/types.ts
Normal file
42
packages/backend/server/src/utils/types.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
export type DeepPartial<T> = T extends Array<infer U>
|
||||
? DeepPartial<U>[]
|
||||
: T extends ReadonlyArray<infer U>
|
||||
? ReadonlyArray<DeepPartial<U>>
|
||||
: T extends object
|
||||
? {
|
||||
[K in keyof T]?: DeepPartial<T[K]>;
|
||||
}
|
||||
: T;
|
||||
|
||||
type Join<Prefix, Suffixes> = Prefix extends string | number
|
||||
? Suffixes extends string | number
|
||||
? Prefix extends ''
|
||||
? Suffixes
|
||||
: `${Prefix}.${Suffixes}`
|
||||
: never
|
||||
: never;
|
||||
|
||||
export type PrimitiveType =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| symbol
|
||||
| null
|
||||
| undefined;
|
||||
|
||||
export type LeafPaths<
|
||||
T,
|
||||
Path extends string = '',
|
||||
MaxDepth extends string = '...',
|
||||
Depth extends string = '',
|
||||
> = Depth extends MaxDepth
|
||||
? never
|
||||
: T extends Record<string | number, any>
|
||||
? {
|
||||
[K in keyof T]-?: K extends string | number
|
||||
? T[K] extends PrimitiveType
|
||||
? K
|
||||
: Join<K, LeafPaths<T[K], Path, MaxDepth, `${Depth}.`>>
|
||||
: never;
|
||||
}[keyof T]
|
||||
: never;
|
||||
Reference in New Issue
Block a user