mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
feat(server): auth server (#2773)
This commit is contained in:
@@ -17,6 +17,7 @@ config:
|
||||
UUID: string
|
||||
ID: string
|
||||
JSON: any
|
||||
Upload: File
|
||||
overwrite: true
|
||||
schema: ../../apps/server/src/schema.gql
|
||||
documents: ./src/**/*.gql
|
||||
|
||||
@@ -129,10 +129,26 @@ module.exports = {
|
||||
.map(field => field.name.value)
|
||||
.join(',');
|
||||
nameLocationMap.set(exportedName, location);
|
||||
const containsFile = doc.definitions.some(def => {
|
||||
const { variableDefinitions } = def;
|
||||
if (variableDefinitions) {
|
||||
return variableDefinitions.some(variableDefinition => {
|
||||
if (
|
||||
variableDefinition?.type?.type?.name?.value === 'Upload'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
defs.push(`export const ${exportedName} = {
|
||||
id: '${exportedName}' as const,
|
||||
operationName: '${doc.operationName}',
|
||||
definitionName: '${doc.defName}',
|
||||
containsFile: ${containsFile},
|
||||
query: \`
|
||||
${print(doc)}${importing || ''}\`,
|
||||
}
|
||||
@@ -159,6 +175,7 @@ ${print(doc)}${importing || ''}\``);
|
||||
operationName: string
|
||||
definitionName: string
|
||||
query: string
|
||||
containsFile?: boolean
|
||||
}
|
||||
`,
|
||||
...defs,
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
"lodash-es": "^4.17.21",
|
||||
"prettier": "^2.8.8"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "gql-gen"
|
||||
},
|
||||
"dependencies": {
|
||||
"graphql": "^16.6.0"
|
||||
}
|
||||
|
||||
@@ -102,7 +102,8 @@ describe('GraphQL fetcher', () => {
|
||||
)
|
||||
);
|
||||
|
||||
await expect(gql({ query, variables: {} })).rejects.toMatchInlineSnapshot(`
|
||||
await expect(gql({ query, variables: void 0 })).rejects
|
||||
.toMatchInlineSnapshot(`
|
||||
[
|
||||
[GraphQLError: error],
|
||||
]
|
||||
|
||||
@@ -7,10 +7,12 @@ import type { Mutations, Queries } from './schema';
|
||||
|
||||
export type NotArray<T> = T extends Array<unknown> ? never : T;
|
||||
|
||||
type _QueryVariables<Q extends GraphQLQuery> = Extract<
|
||||
Queries | Mutations,
|
||||
{ name: Q['id'] }
|
||||
>['variables'];
|
||||
export type _QueryVariables<Q extends GraphQLQuery> =
|
||||
Q['id'] extends Queries['name']
|
||||
? Extract<Queries, { name: Q['id'] }>['variables']
|
||||
: Q['id'] extends Mutations['name']
|
||||
? Extract<Mutations, { name: Q['id'] }>['variables']
|
||||
: undefined;
|
||||
|
||||
export type QueryVariables<Q extends GraphQLQuery> = _QueryVariables<Q> extends
|
||||
| never
|
||||
@@ -65,13 +67,6 @@ export type RequestOptions<Q extends GraphQLQuery> = QueryVariablesOption<Q> & {
|
||||
* parameter passed to `fetch` function
|
||||
*/
|
||||
context?: AllowedRequestContext;
|
||||
/**
|
||||
* files need to be uploaded
|
||||
*
|
||||
* When provided, the request body will be turned to multiparts to satisfy
|
||||
* file uploading scene.
|
||||
*/
|
||||
files?: File[];
|
||||
/**
|
||||
* Whether keep null or undefined value in variables.
|
||||
*
|
||||
@@ -105,27 +100,43 @@ function filterEmptyValue(vars: any) {
|
||||
return newVars;
|
||||
}
|
||||
|
||||
export function appendFormData(body: RequestBody, files: File[]) {
|
||||
export function transformToForm(body: RequestBody) {
|
||||
const form = new FormData();
|
||||
const gqlBody: {
|
||||
name?: string;
|
||||
query: string;
|
||||
variables: any;
|
||||
map: any;
|
||||
} = {
|
||||
query: body.query,
|
||||
variables: body.variables,
|
||||
map: {},
|
||||
};
|
||||
|
||||
if (body.operationName) {
|
||||
form.append('operationName', body.operationName);
|
||||
gqlBody.name = body.operationName;
|
||||
}
|
||||
form.append('query', body.query);
|
||||
form.append('variables', JSON.stringify(body.variables));
|
||||
files.forEach(file => {
|
||||
form.append(file.name, file);
|
||||
});
|
||||
|
||||
body.form = form;
|
||||
if (body.variables) {
|
||||
let i = 0;
|
||||
Object.entries(body.variables).forEach(([key, value]) => {
|
||||
if (value instanceof File) {
|
||||
gqlBody.map['0'] = [`variables.${key}`];
|
||||
form.append(`${i}`, value);
|
||||
i++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
form.append('operations', JSON.stringify(gqlBody));
|
||||
return form;
|
||||
}
|
||||
|
||||
function formatRequestBody<Q extends GraphQLQuery>({
|
||||
query,
|
||||
variables,
|
||||
keepNilVariables,
|
||||
files,
|
||||
}: QueryOptions<Q>): RequestBody {
|
||||
}: QueryOptions<Q>): RequestBody | FormData {
|
||||
const body: RequestBody = {
|
||||
query: query.query,
|
||||
variables:
|
||||
@@ -136,10 +147,9 @@ function formatRequestBody<Q extends GraphQLQuery>({
|
||||
body.operationName = query.operationName;
|
||||
}
|
||||
|
||||
if (files?.length) {
|
||||
appendFormData(body, files);
|
||||
if (query.containsFile) {
|
||||
return transformToForm(body);
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
@@ -157,7 +167,7 @@ export const gqlFetcherFactory = (endpoint: string) => {
|
||||
'x-operation-name': options.query.operationName,
|
||||
'x-definition-name': options.query.definitionName,
|
||||
},
|
||||
body: body.form ?? JSON.stringify(body),
|
||||
body: body instanceof FormData ? body : JSON.stringify(body),
|
||||
})
|
||||
).then(async res => {
|
||||
if (res.headers.get('content-type') === 'application/json') {
|
||||
|
||||
@@ -2,6 +2,6 @@ mutation createWorkspace {
|
||||
createWorkspace {
|
||||
id
|
||||
public
|
||||
created_at
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,18 +4,36 @@ export interface GraphQLQuery {
|
||||
operationName: string;
|
||||
definitionName: string;
|
||||
query: string;
|
||||
containsFile?: boolean;
|
||||
}
|
||||
|
||||
export const createWorkspaceMutation = {
|
||||
id: 'createWorkspaceMutation' as const,
|
||||
operationName: 'createWorkspace',
|
||||
definitionName: 'createWorkspace',
|
||||
containsFile: false,
|
||||
query: `
|
||||
mutation createWorkspace {
|
||||
createWorkspace {
|
||||
id
|
||||
public
|
||||
created_at
|
||||
createdAt
|
||||
}
|
||||
}`,
|
||||
};
|
||||
|
||||
export const uploadAvatarMutation = {
|
||||
id: 'uploadAvatarMutation' as const,
|
||||
operationName: 'uploadAvatar',
|
||||
definitionName: 'uploadAvatar',
|
||||
containsFile: true,
|
||||
query: `
|
||||
mutation uploadAvatar($id: String!, $avatar: Upload!) {
|
||||
uploadAvatar(id: $id, avatar: $avatar) {
|
||||
id
|
||||
name
|
||||
avatarUrl
|
||||
email
|
||||
}
|
||||
}`,
|
||||
};
|
||||
@@ -24,13 +42,13 @@ export const workspaceByIdQuery = {
|
||||
id: 'workspaceByIdQuery' as const,
|
||||
operationName: 'workspaceById',
|
||||
definitionName: 'workspace',
|
||||
containsFile: false,
|
||||
query: `
|
||||
query workspaceById($id: String!) {
|
||||
workspace(id: $id) {
|
||||
id
|
||||
type
|
||||
public
|
||||
created_at
|
||||
createdAt
|
||||
}
|
||||
}`,
|
||||
};
|
||||
|
||||
8
packages/graphql/src/graphql/upload-avatar.gql
Normal file
8
packages/graphql/src/graphql/upload-avatar.gql
Normal file
@@ -0,0 +1,8 @@
|
||||
mutation uploadAvatar($id: String!, $avatar: Upload!) {
|
||||
uploadAvatar(id: $id, avatar: $avatar) {
|
||||
id
|
||||
name
|
||||
avatarUrl
|
||||
email
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
query workspaceById($id: String!) {
|
||||
workspace(id: $id) {
|
||||
id
|
||||
type
|
||||
public
|
||||
created_at
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,23 +10,40 @@ export type MakeOptional<T, K extends keyof T> = Omit<T, K> & {
|
||||
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & {
|
||||
[SubKey in K]: Maybe<T[SubKey]>;
|
||||
};
|
||||
export type MakeEmpty<
|
||||
T extends { [key: string]: unknown },
|
||||
K extends keyof T
|
||||
> = { [_ in K]?: never };
|
||||
export type Incremental<T> =
|
||||
| T
|
||||
| {
|
||||
[P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never;
|
||||
};
|
||||
/** All built-in and custom scalars, mapped to their actual values */
|
||||
export interface Scalars {
|
||||
ID: string;
|
||||
String: string;
|
||||
Boolean: boolean;
|
||||
Int: number;
|
||||
Float: number;
|
||||
ID: { input: string; output: string };
|
||||
String: { input: string; output: string };
|
||||
Boolean: { input: boolean; output: boolean };
|
||||
Int: { input: number; output: number };
|
||||
Float: { input: number; output: number };
|
||||
/** A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date-time format. */
|
||||
DateTime: string;
|
||||
DateTime: { input: string; output: string };
|
||||
/** The `Upload` scalar type represents a file upload. */
|
||||
Upload: { input: File; output: File };
|
||||
}
|
||||
|
||||
/** Workspace type */
|
||||
export enum WorkspaceType {
|
||||
/** Normal workspace */
|
||||
Normal = 'Normal',
|
||||
/** Private workspace */
|
||||
Private = 'Private',
|
||||
/** User permission in workspace */
|
||||
export enum Permission {
|
||||
Admin = 'Admin',
|
||||
Owner = 'Owner',
|
||||
Read = 'Read',
|
||||
Write = 'Write',
|
||||
}
|
||||
|
||||
export interface UpdateWorkspaceInput {
|
||||
id: Scalars['ID']['input'];
|
||||
/** is Public workspace */
|
||||
public: InputMaybe<Scalars['Boolean']['input']>;
|
||||
}
|
||||
|
||||
export type CreateWorkspaceMutationVariables = Exact<{ [key: string]: never }>;
|
||||
@@ -34,25 +51,40 @@ export type CreateWorkspaceMutationVariables = Exact<{ [key: string]: never }>;
|
||||
export type CreateWorkspaceMutation = {
|
||||
__typename?: 'Mutation';
|
||||
createWorkspace: {
|
||||
__typename?: 'Workspace';
|
||||
__typename?: 'WorkspaceType';
|
||||
id: string;
|
||||
public: boolean;
|
||||
created_at: string;
|
||||
createdAt: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type UploadAvatarMutationVariables = Exact<{
|
||||
id: Scalars['String']['input'];
|
||||
avatar: Scalars['Upload']['input'];
|
||||
}>;
|
||||
|
||||
export type UploadAvatarMutation = {
|
||||
__typename?: 'Mutation';
|
||||
uploadAvatar: {
|
||||
__typename?: 'UserType';
|
||||
id: string;
|
||||
name: string;
|
||||
avatarUrl: string | null;
|
||||
email: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type WorkspaceByIdQueryVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
id: Scalars['String']['input'];
|
||||
}>;
|
||||
|
||||
export type WorkspaceByIdQuery = {
|
||||
__typename?: 'Query';
|
||||
workspace: {
|
||||
__typename?: 'Workspace';
|
||||
__typename?: 'WorkspaceType';
|
||||
id: string;
|
||||
type: WorkspaceType;
|
||||
public: boolean;
|
||||
created_at: string;
|
||||
createdAt: string;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -62,8 +94,14 @@ export type Queries = {
|
||||
response: WorkspaceByIdQuery;
|
||||
};
|
||||
|
||||
export type Mutations = {
|
||||
name: 'createWorkspaceMutation';
|
||||
variables: CreateWorkspaceMutationVariables;
|
||||
response: CreateWorkspaceMutation;
|
||||
};
|
||||
export type Mutations =
|
||||
| {
|
||||
name: 'createWorkspaceMutation';
|
||||
variables: CreateWorkspaceMutationVariables;
|
||||
response: CreateWorkspaceMutation;
|
||||
}
|
||||
| {
|
||||
name: 'uploadAvatarMutation';
|
||||
variables: UploadAvatarMutationVariables;
|
||||
response: UploadAvatarMutation;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user