@@ -152,6 +152,7 @@ test('should get public user by id', async t => {
|
|||||||
t.not(publicUser, null);
|
t.not(publicUser, null);
|
||||||
t.is(publicUser!.id, user.id);
|
t.is(publicUser!.id, user.id);
|
||||||
t.true(!('password' in publicUser!));
|
t.true(!('password' in publicUser!));
|
||||||
|
t.true(!('email' in publicUser!));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should get public user by email', async t => {
|
test('should get public user by email', async t => {
|
||||||
@@ -164,6 +165,35 @@ test('should get public user by email', async t => {
|
|||||||
t.not(publicUser, null);
|
t.not(publicUser, null);
|
||||||
t.is(publicUser!.id, user.id);
|
t.is(publicUser!.id, user.id);
|
||||||
t.true(!('password' in publicUser!));
|
t.true(!('password' in publicUser!));
|
||||||
|
t.true(!('email' in publicUser!));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should get workspace user by id', async t => {
|
||||||
|
const user = await t.context.user.create({
|
||||||
|
email: 'test@affine.pro',
|
||||||
|
});
|
||||||
|
|
||||||
|
const workspaceUser = await t.context.user.getWorkspaceUser(user.id);
|
||||||
|
|
||||||
|
t.not(workspaceUser, null);
|
||||||
|
t.is(workspaceUser!.id, user.id);
|
||||||
|
t.true(!('password' in workspaceUser!));
|
||||||
|
t.is(workspaceUser!.email, user.email);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should get workspace user by email', async t => {
|
||||||
|
const user = await t.context.user.create({
|
||||||
|
email: 'test@affine.pro',
|
||||||
|
});
|
||||||
|
|
||||||
|
const workspaceUser = await t.context.user.getWorkspaceUserByEmail(
|
||||||
|
user.email
|
||||||
|
);
|
||||||
|
|
||||||
|
t.not(workspaceUser, null);
|
||||||
|
t.is(workspaceUser!.id, user.id);
|
||||||
|
t.true(!('password' in workspaceUser!));
|
||||||
|
t.is(workspaceUser!.email, user.email);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should get user by email', async t => {
|
test('should get user by email', async t => {
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
|
import { randomUUID } from 'node:crypto';
|
||||||
|
|
||||||
import type { TestFn } from 'ava';
|
import type { TestFn } from 'ava';
|
||||||
import ava from 'ava';
|
import ava from 'ava';
|
||||||
|
|
||||||
import { createTestingApp, TestingApp, updateAvatar } from '../utils';
|
import {
|
||||||
|
createTestingApp,
|
||||||
|
getPublicUserById,
|
||||||
|
TestingApp,
|
||||||
|
updateAvatar,
|
||||||
|
} from '../utils';
|
||||||
|
|
||||||
const test = ava as TestFn<{
|
const test = ava as TestFn<{
|
||||||
app: TestingApp;
|
app: TestingApp;
|
||||||
@@ -57,3 +64,43 @@ test('should be able to update user avatar, and invalidate old avatar url', asyn
|
|||||||
const newAvatarRes = await app.GET(new URL(newAvatarUrl).pathname);
|
const newAvatarRes = await app.GET(new URL(newAvatarUrl).pathname);
|
||||||
t.deepEqual(newAvatarRes.body, Buffer.from('new'));
|
t.deepEqual(newAvatarRes.body, Buffer.from('new'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should be able to get public user by id', async t => {
|
||||||
|
const { app } = t.context;
|
||||||
|
|
||||||
|
const u1 = await app.signup();
|
||||||
|
const avatar = Buffer.from('test');
|
||||||
|
await updateAvatar(app, avatar);
|
||||||
|
const u2 = await app.signup();
|
||||||
|
|
||||||
|
// login user can access
|
||||||
|
let user1 = await getPublicUserById(app, u1.id);
|
||||||
|
t.truthy(user1);
|
||||||
|
t.is(user1!.id, u1.id);
|
||||||
|
t.is(user1!.name, u1.name);
|
||||||
|
t.truthy(user1!.avatarUrl);
|
||||||
|
let user2 = await getPublicUserById(app, u2.id);
|
||||||
|
t.deepEqual(user2, {
|
||||||
|
id: u2.id,
|
||||||
|
name: u2.name,
|
||||||
|
avatarUrl: null,
|
||||||
|
});
|
||||||
|
let user3 = await getPublicUserById(app, randomUUID());
|
||||||
|
t.is(user3, null);
|
||||||
|
|
||||||
|
// anonymous user can access
|
||||||
|
await app.logout();
|
||||||
|
user1 = await getPublicUserById(app, u1.id);
|
||||||
|
t.truthy(user1);
|
||||||
|
t.is(user1!.id, u1.id);
|
||||||
|
t.is(user1!.name, u1.name);
|
||||||
|
t.truthy(user1!.avatarUrl);
|
||||||
|
user2 = await getPublicUserById(app, u2.id);
|
||||||
|
t.deepEqual(user2, {
|
||||||
|
id: u2.id,
|
||||||
|
name: u2.name,
|
||||||
|
avatarUrl: null,
|
||||||
|
});
|
||||||
|
user3 = await getPublicUserById(app, randomUUID());
|
||||||
|
t.is(user3, null);
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { PublicUserType } from '../../core/user';
|
||||||
import { TestingApp } from './testing-app';
|
import { TestingApp } from './testing-app';
|
||||||
|
|
||||||
export async function currentUser(app: TestingApp) {
|
export async function currentUser(app: TestingApp) {
|
||||||
@@ -12,6 +13,25 @@ export async function currentUser(app: TestingApp) {
|
|||||||
return res.currentUser;
|
return res.currentUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getPublicUserById(
|
||||||
|
app: TestingApp,
|
||||||
|
id: string
|
||||||
|
): Promise<PublicUserType | null> {
|
||||||
|
const res = await app.gql(
|
||||||
|
`
|
||||||
|
query getPublicUserById($id: String!) {
|
||||||
|
publicUserById(id: $id) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
avatarUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
{ id }
|
||||||
|
);
|
||||||
|
return res.publicUserById;
|
||||||
|
}
|
||||||
|
|
||||||
export async function sendChangeEmail(
|
export async function sendChangeEmail(
|
||||||
app: TestingApp,
|
app: TestingApp,
|
||||||
email: string,
|
email: string,
|
||||||
|
|||||||
@@ -13,4 +13,4 @@ import { UserManagementResolver, UserResolver } from './resolver';
|
|||||||
})
|
})
|
||||||
export class UserModule {}
|
export class UserModule {}
|
||||||
|
|
||||||
export { PublicUserType, UserType } from './types';
|
export { PublicUserType, UserType, WorkspaceUserType } from './types';
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { validators } from '../utils/validators';
|
|||||||
import {
|
import {
|
||||||
DeleteAccount,
|
DeleteAccount,
|
||||||
ManageUserInput,
|
ManageUserInput,
|
||||||
|
PublicUserType,
|
||||||
RemoveAvatar,
|
RemoveAvatar,
|
||||||
UpdateUserInput,
|
UpdateUserInput,
|
||||||
UserOrLimitedUser,
|
UserOrLimitedUser,
|
||||||
@@ -70,6 +71,19 @@ export class UserResolver {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throttle('strict')
|
||||||
|
@Query(() => PublicUserType, {
|
||||||
|
name: 'publicUserById',
|
||||||
|
description: 'Get public user by id',
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
@Public()
|
||||||
|
async getPublicUserById(
|
||||||
|
@Args('id', { type: () => String }) id: string
|
||||||
|
): Promise<PublicUserType | null> {
|
||||||
|
return await this.models.user.getPublicUser(id);
|
||||||
|
}
|
||||||
|
|
||||||
@Mutation(() => UserType, {
|
@Mutation(() => UserType, {
|
||||||
name: 'uploadAvatar',
|
name: 'uploadAvatar',
|
||||||
description: 'Upload user avatar',
|
description: 'Upload user avatar',
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
} from '@nestjs/graphql';
|
} from '@nestjs/graphql';
|
||||||
import type { User } from '@prisma/client';
|
import type { User } from '@prisma/client';
|
||||||
|
|
||||||
import { PublicUser } from '../../models';
|
import { PublicUser, WorkspaceUser } from '../../models';
|
||||||
import { type CurrentUser } from '../auth/session';
|
import { type CurrentUser } from '../auth/session';
|
||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
@@ -51,6 +51,18 @@ export class PublicUserType implements PublicUser {
|
|||||||
@Field()
|
@Field()
|
||||||
name!: string;
|
name!: string;
|
||||||
|
|
||||||
|
@Field(() => String, { nullable: true })
|
||||||
|
avatarUrl!: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class WorkspaceUserType implements WorkspaceUser {
|
||||||
|
@Field()
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
name!: string;
|
||||||
|
|
||||||
@Field()
|
@Field()
|
||||||
email!: string;
|
email!: string;
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export class WorkspaceEvents {
|
|||||||
userId,
|
userId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
}: Events['workspace.members.requestDeclined']) {
|
}: Events['workspace.members.requestDeclined']) {
|
||||||
const user = await this.models.user.getPublicUser(userId);
|
const user = await this.models.user.getWorkspaceUser(userId);
|
||||||
// send decline mail
|
// send decline mail
|
||||||
await this.workspaceService.sendReviewDeclinedEmail(
|
await this.workspaceService.sendReviewDeclinedEmail(
|
||||||
user?.email,
|
user?.email,
|
||||||
@@ -60,8 +60,8 @@ export class WorkspaceEvents {
|
|||||||
to,
|
to,
|
||||||
}: Events['workspace.owner.changed']) {
|
}: Events['workspace.owner.changed']) {
|
||||||
// send ownership transferred mail
|
// send ownership transferred mail
|
||||||
const fromUser = await this.models.user.getPublicUser(from);
|
const fromUser = await this.models.user.getWorkspaceUser(from);
|
||||||
const toUser = await this.models.user.getPublicUser(to);
|
const toUser = await this.models.user.getWorkspaceUser(to);
|
||||||
|
|
||||||
if (fromUser) {
|
if (fromUser) {
|
||||||
await this.workspaceService.sendOwnershipTransferredEmail(
|
await this.workspaceService.sendOwnershipTransferredEmail(
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import {
|
|||||||
DocAction,
|
DocAction,
|
||||||
DocRole,
|
DocRole,
|
||||||
} from '../../permission';
|
} from '../../permission';
|
||||||
import { PublicUserType } from '../../user';
|
import { WorkspaceUserType } from '../../user';
|
||||||
import { WorkspaceType } from '../types';
|
import { WorkspaceType } from '../types';
|
||||||
import {
|
import {
|
||||||
DotToUnderline,
|
DotToUnderline,
|
||||||
@@ -124,8 +124,8 @@ class GrantedDocUserType {
|
|||||||
@Field(() => DocRole, { name: 'role' })
|
@Field(() => DocRole, { name: 'role' })
|
||||||
type!: DocRole;
|
type!: DocRole;
|
||||||
|
|
||||||
@Field(() => PublicUserType)
|
@Field(() => WorkspaceUserType)
|
||||||
user!: PublicUserType;
|
user!: WorkspaceUserType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
@@ -391,16 +391,16 @@ export class DocResolver {
|
|||||||
pagination
|
pagination
|
||||||
);
|
);
|
||||||
|
|
||||||
const publicUsers = await this.models.user.getPublicUsers(
|
const workspaceUsers = await this.models.user.getWorkspaceUsers(
|
||||||
permissions.map(p => p.userId)
|
permissions.map(p => p.userId)
|
||||||
);
|
);
|
||||||
|
|
||||||
const publicUsersMap = new Map(publicUsers.map(pu => [pu.id, pu]));
|
const workspaceUsersMap = new Map(workspaceUsers.map(wu => [wu.id, wu]));
|
||||||
|
|
||||||
return paginate(
|
return paginate(
|
||||||
permissions.map(p => ({
|
permissions.map(p => ({
|
||||||
...p,
|
...p,
|
||||||
user: publicUsersMap.get(p.userId) as PublicUserType,
|
user: workspaceUsersMap.get(p.userId) as WorkspaceUserType,
|
||||||
})),
|
})),
|
||||||
'createdAt',
|
'createdAt',
|
||||||
pagination,
|
pagination,
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export class WorkspaceService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const workspace = await this.getWorkspaceInfo(workspaceId);
|
const workspace = await this.getWorkspaceInfo(workspaceId);
|
||||||
const invitee = await this.models.user.getPublicUser(inviteeUserId);
|
const invitee = await this.models.user.getWorkspaceUser(inviteeUserId);
|
||||||
if (!invitee) {
|
if (!invitee) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Invitee user not found in workspace: ${workspaceId}, userId: ${inviteeUserId}`
|
`Invitee user not found in workspace: ${workspaceId}, userId: ${inviteeUserId}`
|
||||||
@@ -105,10 +105,10 @@ export class WorkspaceService {
|
|||||||
await this.getInviteInfo(inviteId);
|
await this.getInviteInfo(inviteId);
|
||||||
const workspace = await this.getWorkspaceInfo(workspaceId);
|
const workspace = await this.getWorkspaceInfo(workspaceId);
|
||||||
const invitee = inviteeUserId
|
const invitee = inviteeUserId
|
||||||
? await this.models.user.getPublicUser(inviteeUserId)
|
? await this.models.user.getWorkspaceUser(inviteeUserId)
|
||||||
: null;
|
: null;
|
||||||
const inviter = inviterUserId
|
const inviter = inviterUserId
|
||||||
? await this.models.user.getPublicUser(inviterUserId)
|
? await this.models.user.getWorkspaceUser(inviterUserId)
|
||||||
: await this.models.workspaceUser.getOwner(workspaceId);
|
: await this.models.workspaceUser.getOwner(workspaceId);
|
||||||
|
|
||||||
if (!inviter || !invitee) {
|
if (!inviter || !invitee) {
|
||||||
@@ -173,7 +173,7 @@ export class WorkspaceService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const invitee = await this.models.user.getPublicUser(inviteeUserId);
|
const invitee = await this.models.user.getWorkspaceUser(inviteeUserId);
|
||||||
if (!invitee) {
|
if (!invitee) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Invitee user not found for inviteId: ${inviteId}, userId: ${inviteeUserId}`
|
`Invitee user not found for inviteId: ${inviteId}, userId: ${inviteeUserId}`
|
||||||
@@ -220,7 +220,7 @@ export class WorkspaceService {
|
|||||||
userId: string,
|
userId: string,
|
||||||
ws: { id: string; role: WorkspaceRole }
|
ws: { id: string; role: WorkspaceRole }
|
||||||
) {
|
) {
|
||||||
const user = await this.models.user.getPublicUser(userId);
|
const user = await this.models.user.getWorkspaceUser(userId);
|
||||||
if (!user) throw new UserNotFound();
|
if (!user) throw new UserNotFound();
|
||||||
|
|
||||||
const workspace = await this.getWorkspaceInfo(ws.id);
|
const workspace = await this.getWorkspaceInfo(ws.id);
|
||||||
|
|||||||
@@ -533,7 +533,7 @@ export class WorkspaceResolver {
|
|||||||
|
|
||||||
const inviteeId = inviteeUserId || user?.id;
|
const inviteeId = inviteeUserId || user?.id;
|
||||||
if (!inviteeId) throw new UserNotFound();
|
if (!inviteeId) throw new UserNotFound();
|
||||||
const invitee = await this.models.user.getPublicUser(inviteeId);
|
const invitee = await this.models.user.getWorkspaceUser(inviteeId);
|
||||||
|
|
||||||
return { workspace, user: owner, invitee };
|
return { workspace, user: owner, invitee };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { type ConnectedAccount, Prisma, type User } from '@prisma/client';
|
import { type ConnectedAccount, Prisma, type User } from '@prisma/client';
|
||||||
import { pick } from 'lodash-es';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CryptoHelper,
|
CryptoHelper,
|
||||||
@@ -14,11 +13,17 @@ import { WorkspaceRole } from './common';
|
|||||||
import type { Workspace } from './workspace';
|
import type { Workspace } from './workspace';
|
||||||
|
|
||||||
const publicUserSelect = {
|
const publicUserSelect = {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
avatarUrl: true,
|
||||||
|
} satisfies Prisma.UserSelect;
|
||||||
|
const workspaceUserSelect = {
|
||||||
id: true,
|
id: true,
|
||||||
name: true,
|
name: true,
|
||||||
email: true,
|
email: true,
|
||||||
avatarUrl: true,
|
avatarUrl: true,
|
||||||
} satisfies Prisma.UserSelect;
|
} satisfies Prisma.UserSelect;
|
||||||
|
|
||||||
type CreateUserInput = Omit<Prisma.UserCreateInput, 'name'> & { name?: string };
|
type CreateUserInput = Omit<Prisma.UserCreateInput, 'name'> & { name?: string };
|
||||||
type UpdateUserInput = Omit<Partial<Prisma.UserCreateInput>, 'id'>;
|
type UpdateUserInput = Omit<Partial<Prisma.UserCreateInput>, 'id'>;
|
||||||
|
|
||||||
@@ -45,6 +50,7 @@ declare global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type PublicUser = Pick<User, keyof typeof publicUserSelect>;
|
export type PublicUser = Pick<User, keyof typeof publicUserSelect>;
|
||||||
|
export type WorkspaceUser = Pick<User, keyof typeof workspaceUserSelect>;
|
||||||
export type { ConnectedAccount, User };
|
export type { ConnectedAccount, User };
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -83,6 +89,20 @@ export class UserModel extends BaseModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getWorkspaceUser(id: string): Promise<WorkspaceUser | null> {
|
||||||
|
return this.db.user.findUnique({
|
||||||
|
select: workspaceUserSelect,
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getWorkspaceUsers(ids: string[]): Promise<WorkspaceUser[]> {
|
||||||
|
return this.db.user.findMany({
|
||||||
|
select: workspaceUserSelect,
|
||||||
|
where: { id: { in: ids } },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async getUserByEmail(email: string): Promise<User | null> {
|
async getUserByEmail(email: string): Promise<User | null> {
|
||||||
const rows = await this.db.$queryRaw<User[]>`
|
const rows = await this.db.$queryRaw<User[]>`
|
||||||
SELECT id, name, email, password, registered, email_verified as emailVerifiedAt, avatar_url as avatarUrl, registered, created_at as createdAt
|
SELECT id, name, email, password, registered, email_verified as emailVerifiedAt, avatar_url as avatarUrl, registered, created_at as createdAt
|
||||||
@@ -118,7 +138,7 @@ export class UserModel extends BaseModel {
|
|||||||
|
|
||||||
async getPublicUserByEmail(email: string): Promise<PublicUser | null> {
|
async getPublicUserByEmail(email: string): Promise<PublicUser | null> {
|
||||||
const rows = await this.db.$queryRaw<PublicUser[]>`
|
const rows = await this.db.$queryRaw<PublicUser[]>`
|
||||||
SELECT id, name, email, avatar_url as avatarUrl
|
SELECT id, name, avatar_url as avatarUrl
|
||||||
FROM "users"
|
FROM "users"
|
||||||
WHERE lower("email") = lower(${email})
|
WHERE lower("email") = lower(${email})
|
||||||
`;
|
`;
|
||||||
@@ -126,8 +146,14 @@ export class UserModel extends BaseModel {
|
|||||||
return rows[0] ?? null;
|
return rows[0] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
toPublicUser(user: User): PublicUser {
|
async getWorkspaceUserByEmail(email: string): Promise<WorkspaceUser | null> {
|
||||||
return pick(user, Object.keys(publicUserSelect)) as any;
|
const rows = await this.db.$queryRaw<WorkspaceUser[]>`
|
||||||
|
SELECT id, name, email, avatar_url as avatarUrl
|
||||||
|
FROM "users"
|
||||||
|
WHERE lower("email") = lower(${email})
|
||||||
|
`;
|
||||||
|
|
||||||
|
return rows[0] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(data: CreateUserInput) {
|
async create(data: CreateUserInput) {
|
||||||
|
|||||||
@@ -574,7 +574,7 @@ export class SubscriptionService implements OnApplicationBootstrap {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await this.models.user.getPublicUserByEmail(customer.email);
|
const user = await this.models.user.getWorkspaceUserByEmail(customer.email);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
@@ -620,7 +620,7 @@ export class SubscriptionService implements OnApplicationBootstrap {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await this.models.user.getPublicUserByEmail(
|
const user = await this.models.user.getWorkspaceUserByEmail(
|
||||||
invoice.customer_email
|
invoice.customer_email
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -493,7 +493,7 @@ input GrantDocUserRolesInput {
|
|||||||
|
|
||||||
type GrantedDocUserType {
|
type GrantedDocUserType {
|
||||||
role: DocRole!
|
role: DocRole!
|
||||||
user: PublicUserType!
|
user: WorkspaceUserType!
|
||||||
}
|
}
|
||||||
|
|
||||||
type GrantedDocUserTypeEdge {
|
type GrantedDocUserTypeEdge {
|
||||||
@@ -997,7 +997,6 @@ enum PublicDocMode {
|
|||||||
|
|
||||||
type PublicUserType {
|
type PublicUserType {
|
||||||
avatarUrl: String
|
avatarUrl: String
|
||||||
email: String!
|
|
||||||
id: String!
|
id: String!
|
||||||
name: String!
|
name: String!
|
||||||
}
|
}
|
||||||
@@ -1022,6 +1021,9 @@ type Query {
|
|||||||
listCopilotPrompts: [CopilotPromptType!]!
|
listCopilotPrompts: [CopilotPromptType!]!
|
||||||
prices: [SubscriptionPrice!]!
|
prices: [SubscriptionPrice!]!
|
||||||
|
|
||||||
|
"""Get public user by id"""
|
||||||
|
publicUserById(id: String!): PublicUserType
|
||||||
|
|
||||||
"""server config"""
|
"""server config"""
|
||||||
serverConfig: ServerConfigType!
|
serverConfig: ServerConfigType!
|
||||||
|
|
||||||
@@ -1555,6 +1557,13 @@ type WorkspaceType {
|
|||||||
team: Boolean!
|
team: Boolean!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WorkspaceUserType {
|
||||||
|
avatarUrl: String
|
||||||
|
email: String!
|
||||||
|
id: String!
|
||||||
|
name: String!
|
||||||
|
}
|
||||||
|
|
||||||
type WrongSignInCredentialsDataType {
|
type WrongSignInCredentialsDataType {
|
||||||
email: String!
|
email: String!
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user