mirror of https://github.com/buster-so/buster.git
create basic user to organization db
This commit is contained in:
parent
74993ca556
commit
2e73d97ffc
|
@ -7,6 +7,8 @@ const app = new Hono()
|
|||
// Apply authentication globally to ALL routes in this router
|
||||
.use('*', requireAuth)
|
||||
.get('/', (c) => {
|
||||
const _user = c.get('busterUser');
|
||||
|
||||
// Stub data for user listing (only accessible to authenticated users)
|
||||
const stubUsers = [
|
||||
{ id: '1', name: 'John Doe', email: 'john@example.com', role: 'admin' },
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
export * from './user';
|
||||
export * from './users-to-organizations';
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
import { getUserOrganizationId } from '../organizations/organizations';
|
||||
import { getUserToOrganization } from './users-to-organizations';
|
||||
|
||||
// Mock the organizations module
|
||||
vi.mock('../organizations/organizations', () => ({
|
||||
getUserOrganizationId: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock the database connection
|
||||
vi.mock('../../connection', () => ({
|
||||
db: {
|
||||
select: vi.fn(() => ({
|
||||
from: vi.fn(() => ({
|
||||
innerJoin: vi.fn(() => ({
|
||||
where: vi.fn(() => Promise.resolve([])),
|
||||
})),
|
||||
})),
|
||||
})),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('getUserToOrganization', () => {
|
||||
const mockGetUserOrganizationId = vi.mocked(getUserOrganizationId);
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test('should throw error when user is not found in any organization', async () => {
|
||||
// Arrange
|
||||
mockGetUserOrganizationId.mockResolvedValue(null);
|
||||
|
||||
// Act & Assert
|
||||
await expect(
|
||||
getUserToOrganization({ userId: '123e4567-e89b-12d3-a456-426614174000' })
|
||||
).rejects.toThrow('User not found in any organization');
|
||||
});
|
||||
|
||||
test('should return users in the same organization without filters', async () => {
|
||||
// Arrange
|
||||
const mockUsers = [
|
||||
{
|
||||
id: '123e4567-e89b-12d3-a456-426614174000',
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
avatarUrl: null,
|
||||
role: 'querier',
|
||||
status: 'active',
|
||||
},
|
||||
{
|
||||
id: '123e4567-e89b-12d3-a456-426614174001',
|
||||
name: 'Jane Smith',
|
||||
email: 'jane@example.com',
|
||||
avatarUrl: 'https://example.com/avatar.jpg',
|
||||
role: 'workspace_admin',
|
||||
status: 'active',
|
||||
},
|
||||
];
|
||||
|
||||
mockGetUserOrganizationId.mockResolvedValue({
|
||||
organizationId: 'org-123',
|
||||
role: 'querier',
|
||||
});
|
||||
|
||||
const { db } = await import('../../connection');
|
||||
const mockDb = vi.mocked(db);
|
||||
|
||||
mockDb.select.mockReturnValue({
|
||||
from: vi.fn().mockReturnValue({
|
||||
innerJoin: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockResolvedValue(mockUsers),
|
||||
}),
|
||||
}),
|
||||
} as unknown as ReturnType<typeof db.select>);
|
||||
|
||||
// Act
|
||||
const result = await getUserToOrganization({
|
||||
userId: '123e4567-e89b-12d3-a456-426614174000',
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(mockUsers);
|
||||
expect(mockGetUserOrganizationId).toHaveBeenCalledWith('123e4567-e89b-12d3-a456-426614174000');
|
||||
});
|
||||
|
||||
test('should apply userName filter when provided', async () => {
|
||||
// Arrange
|
||||
const mockUsers = [
|
||||
{
|
||||
id: '123e4567-e89b-12d3-a456-426614174000',
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
avatarUrl: null,
|
||||
role: 'querier',
|
||||
status: 'active',
|
||||
},
|
||||
];
|
||||
|
||||
mockGetUserOrganizationId.mockResolvedValue({
|
||||
organizationId: 'org-123',
|
||||
role: 'querier',
|
||||
});
|
||||
|
||||
const { db } = await import('../../connection');
|
||||
const mockDb = vi.mocked(db);
|
||||
|
||||
mockDb.select.mockReturnValue({
|
||||
from: vi.fn().mockReturnValue({
|
||||
innerJoin: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockResolvedValue(mockUsers),
|
||||
}),
|
||||
}),
|
||||
} as unknown as ReturnType<typeof db.select>);
|
||||
|
||||
// Act
|
||||
const result = await getUserToOrganization({
|
||||
userId: '123e4567-e89b-12d3-a456-426614174000',
|
||||
filters: { userName: 'John' },
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(mockUsers);
|
||||
expect(mockGetUserOrganizationId).toHaveBeenCalledWith('123e4567-e89b-12d3-a456-426614174000');
|
||||
});
|
||||
|
||||
test('should validate input with invalid UUID', async () => {
|
||||
// Act & Assert
|
||||
await expect(getUserToOrganization({ userId: 'invalid-uuid' })).rejects.toThrow(
|
||||
'User ID must be a valid UUID'
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,90 @@
|
|||
import { and, eq, isNull, like } from 'drizzle-orm';
|
||||
import { z } from 'zod';
|
||||
import { db } from '../../connection';
|
||||
import { users, usersToOrganizations } from '../../schema';
|
||||
import { getUserOrganizationId } from '../organizations/organizations';
|
||||
|
||||
// Input schema for type safety
|
||||
const GetUserToOrganizationInputSchema = z.object({
|
||||
userId: z.string().uuid('User ID must be a valid UUID'),
|
||||
filters: z
|
||||
.object({
|
||||
userName: z.string().optional(),
|
||||
email: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type GetUserToOrganizationInput = z.infer<typeof GetUserToOrganizationInputSchema>;
|
||||
|
||||
// Output schema for type safety
|
||||
export const UserOutputSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
name: z.string().nullable(),
|
||||
email: z.string(),
|
||||
avatarUrl: z.string().nullable(),
|
||||
role: z.string(),
|
||||
status: z.string(),
|
||||
});
|
||||
|
||||
export type UserWithRole = z.infer<typeof UserOutputSchema>;
|
||||
|
||||
export const getUserToOrganization = async ({
|
||||
userId,
|
||||
filters,
|
||||
}: GetUserToOrganizationInput): Promise<UserWithRole[]> => {
|
||||
// Validate input
|
||||
const validated = GetUserToOrganizationInputSchema.parse({ userId, filters });
|
||||
|
||||
try {
|
||||
// First, get the user's organization ID using the existing function
|
||||
const userOrg = await getUserOrganizationId(validated.userId);
|
||||
|
||||
if (!userOrg) {
|
||||
throw new Error('User not found in any organization');
|
||||
}
|
||||
|
||||
const organizationId = userOrg.organizationId;
|
||||
|
||||
// Build the where conditions for the join
|
||||
const joinConditions = and(
|
||||
eq(users.id, usersToOrganizations.userId),
|
||||
eq(usersToOrganizations.organizationId, organizationId),
|
||||
isNull(usersToOrganizations.deletedAt)
|
||||
);
|
||||
|
||||
// Build the where conditions for filtering
|
||||
const conditions = [];
|
||||
if (validated.filters?.userName) {
|
||||
conditions.push(like(users.name, `%${validated.filters.userName}%`));
|
||||
}
|
||||
if (validated.filters?.email) {
|
||||
conditions.push(like(users.email, `%${validated.filters.email}%`));
|
||||
}
|
||||
|
||||
const baseQuery = db
|
||||
.select({
|
||||
id: users.id,
|
||||
name: users.name,
|
||||
email: users.email,
|
||||
avatarUrl: users.avatarUrl,
|
||||
role: usersToOrganizations.role,
|
||||
status: usersToOrganizations.status,
|
||||
})
|
||||
.from(users)
|
||||
.innerJoin(usersToOrganizations, joinConditions);
|
||||
|
||||
const results =
|
||||
conditions.length > 0
|
||||
? await baseQuery.where(conditions.length === 1 ? conditions[0] : and(...conditions))
|
||||
: await baseQuery;
|
||||
|
||||
// Validate and return results
|
||||
return results.map((user) => UserOutputSchema.parse(user));
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
throw new Error(`Invalid input: ${error.errors.map((e) => e.message).join(', ')}`);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue