feat(db): mesh data model — meshes, members, invites, audit log
- pgSchema "mesh" with 4 tables isolating the peer mesh domain - Enums: visibility, transport, tier, role - audit_log is metadata-only (E2E encryption enforced at broker/client) - Cascade on mesh delete, soft-delete via archivedAt/revokedAt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
332
packages/api/src/modules/admin/users/queries.ts
Normal file
332
packages/api/src/modules/admin/users/queries.ts
Normal file
@@ -0,0 +1,332 @@
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import {
|
||||
and,
|
||||
asc,
|
||||
between,
|
||||
count,
|
||||
eq,
|
||||
getOrderByFromSort,
|
||||
ilike,
|
||||
inArray,
|
||||
or,
|
||||
} from "@turbostarter/db";
|
||||
import {
|
||||
account,
|
||||
customer,
|
||||
invitation,
|
||||
member,
|
||||
organization,
|
||||
user,
|
||||
} from "@turbostarter/db/schema";
|
||||
import { db } from "@turbostarter/db/server";
|
||||
|
||||
import type {
|
||||
GetUserAccountsInput,
|
||||
GetUserInvitationsInput,
|
||||
GetUserMembershipsInput,
|
||||
GetUserPlansInput,
|
||||
GetUsersInput,
|
||||
} from "../../../schema";
|
||||
|
||||
export const getUsersCount = async () =>
|
||||
db
|
||||
.select({ count: count() })
|
||||
.from(user)
|
||||
.then((res) => res[0]?.count ?? 0);
|
||||
|
||||
export const getUsers = async (input: GetUsersInput) => {
|
||||
const offset = (input.page - 1) * input.perPage;
|
||||
|
||||
const where = and(
|
||||
input.q
|
||||
? or(ilike(user.name, `%${input.q}%`), ilike(user.email, `%${input.q}%`))
|
||||
: undefined,
|
||||
input.role ? inArray(user.role, input.role) : undefined,
|
||||
input.twoFactorEnabled
|
||||
? inArray(user.twoFactorEnabled, input.twoFactorEnabled)
|
||||
: undefined,
|
||||
input.banned ? inArray(user.banned, input.banned) : undefined,
|
||||
input.createdAt
|
||||
? between(
|
||||
user.createdAt,
|
||||
dayjs(input.createdAt[0]).startOf("day").toDate(),
|
||||
dayjs(input.createdAt[1]).endOf("day").toDate(),
|
||||
)
|
||||
: undefined,
|
||||
);
|
||||
|
||||
const orderBy = input.sort
|
||||
? getOrderByFromSort({ sort: input.sort, defaultSchema: user })
|
||||
: [asc(user.name)];
|
||||
|
||||
return db.transaction(async (tx) => {
|
||||
const data = await tx
|
||||
.select()
|
||||
.from(user)
|
||||
.where(where)
|
||||
.limit(input.perPage)
|
||||
.offset(offset)
|
||||
.orderBy(...orderBy);
|
||||
|
||||
const total = await tx
|
||||
.select({
|
||||
count: count(),
|
||||
})
|
||||
.from(user)
|
||||
.where(where)
|
||||
.execute()
|
||||
.then((res) => res[0]?.count ?? 0);
|
||||
|
||||
return {
|
||||
data,
|
||||
total,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const getUserAccounts = async ({
|
||||
userId,
|
||||
...input
|
||||
}: GetUserAccountsInput & { userId: string }) => {
|
||||
const offset = (input.page - 1) * input.perPage;
|
||||
|
||||
const where = and(
|
||||
input.providerId
|
||||
? inArray(account.providerId, input.providerId)
|
||||
: undefined,
|
||||
input.createdAt
|
||||
? between(
|
||||
account.createdAt,
|
||||
dayjs(input.createdAt[0]).startOf("day").toDate(),
|
||||
dayjs(input.createdAt[1]).endOf("day").toDate(),
|
||||
)
|
||||
: undefined,
|
||||
input.updatedAt
|
||||
? between(
|
||||
account.updatedAt,
|
||||
dayjs(input.updatedAt[0]).startOf("day").toDate(),
|
||||
dayjs(input.updatedAt[1]).endOf("day").toDate(),
|
||||
)
|
||||
: undefined,
|
||||
eq(account.userId, userId),
|
||||
);
|
||||
|
||||
const orderBy = input.sort
|
||||
? getOrderByFromSort({ sort: input.sort, defaultSchema: account })
|
||||
: [asc(account.providerId)];
|
||||
|
||||
return db.transaction(async (tx) => {
|
||||
const data = await tx
|
||||
.select()
|
||||
.from(account)
|
||||
.where(where)
|
||||
.limit(input.perPage)
|
||||
.offset(offset)
|
||||
.orderBy(...orderBy);
|
||||
|
||||
const total = await tx
|
||||
.select({
|
||||
count: count(),
|
||||
})
|
||||
.from(account)
|
||||
.where(where)
|
||||
.execute()
|
||||
.then((res) => res[0]?.count ?? 0);
|
||||
|
||||
return {
|
||||
data,
|
||||
total,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const getUserPlans = async ({
|
||||
userId,
|
||||
...input
|
||||
}: GetUserPlansInput & { userId: string }) => {
|
||||
const offset = (input.page - 1) * input.perPage;
|
||||
|
||||
const where = and(
|
||||
input.plan ? inArray(customer.plan, input.plan) : undefined,
|
||||
input.status ? inArray(customer.status, input.status) : undefined,
|
||||
input.createdAt
|
||||
? between(
|
||||
customer.createdAt,
|
||||
dayjs(input.createdAt[0]).startOf("day").toDate(),
|
||||
dayjs(input.createdAt[1]).endOf("day").toDate(),
|
||||
)
|
||||
: undefined,
|
||||
eq(customer.userId, userId),
|
||||
);
|
||||
|
||||
const orderBy = input.sort
|
||||
? getOrderByFromSort({ sort: input.sort, defaultSchema: customer })
|
||||
: [asc(customer.plan)];
|
||||
|
||||
return db.transaction(async (tx) => {
|
||||
const data = await tx
|
||||
.select({
|
||||
id: customer.id,
|
||||
userId: customer.userId,
|
||||
customerId: customer.customerId,
|
||||
plan: customer.plan,
|
||||
status: customer.status,
|
||||
credits: customer.credits,
|
||||
createdAt: customer.createdAt,
|
||||
updatedAt: customer.updatedAt,
|
||||
user: {
|
||||
name: user.name,
|
||||
},
|
||||
})
|
||||
.from(customer)
|
||||
.leftJoin(user, eq(customer.userId, user.id))
|
||||
.where(where)
|
||||
.limit(input.perPage)
|
||||
.offset(offset)
|
||||
.orderBy(...orderBy);
|
||||
|
||||
const total = await tx
|
||||
.select({ count: count() })
|
||||
.from(customer)
|
||||
.where(where)
|
||||
.execute()
|
||||
.then((res) => res[0]?.count ?? 0);
|
||||
|
||||
return {
|
||||
data,
|
||||
total,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const getUserMemberships = async ({
|
||||
userId,
|
||||
...input
|
||||
}: GetUserMembershipsInput & { userId: string }) => {
|
||||
const offset = (input.page - 1) * input.perPage;
|
||||
|
||||
const where = and(
|
||||
input.role ? inArray(member.role, input.role) : undefined,
|
||||
input.createdAt
|
||||
? between(
|
||||
member.createdAt,
|
||||
dayjs(input.createdAt[0]).startOf("day").toDate(),
|
||||
dayjs(input.createdAt[1]).endOf("day").toDate(),
|
||||
)
|
||||
: undefined,
|
||||
eq(member.userId, userId),
|
||||
);
|
||||
|
||||
const orderBy = input.sort
|
||||
? getOrderByFromSort({ sort: input.sort, defaultSchema: member })
|
||||
: [asc(organization.name)];
|
||||
|
||||
return db.transaction(async (tx) => {
|
||||
const data = await tx
|
||||
.select({
|
||||
id: member.id,
|
||||
organizationId: member.organizationId,
|
||||
role: member.role,
|
||||
createdAt: member.createdAt,
|
||||
userId: member.userId,
|
||||
organization: {
|
||||
id: organization.id,
|
||||
name: organization.name,
|
||||
slug: organization.slug,
|
||||
logo: organization.logo,
|
||||
},
|
||||
user: {
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
image: user.image,
|
||||
},
|
||||
})
|
||||
.from(member)
|
||||
.leftJoin(organization, eq(member.organizationId, organization.id))
|
||||
.leftJoin(user, eq(member.userId, user.id))
|
||||
.where(where)
|
||||
.limit(input.perPage)
|
||||
.offset(offset)
|
||||
.orderBy(...orderBy);
|
||||
|
||||
const total = await tx
|
||||
.select({
|
||||
count: count(),
|
||||
})
|
||||
.from(member)
|
||||
.leftJoin(organization, eq(member.organizationId, organization.id))
|
||||
.where(where)
|
||||
.execute()
|
||||
.then((res) => res[0]?.count ?? 0);
|
||||
|
||||
return {
|
||||
data,
|
||||
total,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const getUserInvitations = async ({
|
||||
userId,
|
||||
...input
|
||||
}: GetUserInvitationsInput & { userId: string }) => {
|
||||
const offset = (input.page - 1) * input.perPage;
|
||||
|
||||
const where = and(
|
||||
input.status ? inArray(invitation.status, input.status) : undefined,
|
||||
input.role ? inArray(invitation.role, input.role) : undefined,
|
||||
input.expiresAt
|
||||
? between(
|
||||
invitation.expiresAt,
|
||||
dayjs(input.expiresAt[0]).startOf("day").toDate(),
|
||||
dayjs(input.expiresAt[1]).endOf("day").toDate(),
|
||||
)
|
||||
: undefined,
|
||||
eq(user.id, userId),
|
||||
);
|
||||
|
||||
const orderBy = input.sort
|
||||
? getOrderByFromSort({ sort: input.sort, defaultSchema: invitation })
|
||||
: [asc(organization.name)];
|
||||
|
||||
return db.transaction(async (tx) => {
|
||||
const data = await tx
|
||||
.select({
|
||||
id: invitation.id,
|
||||
email: invitation.email,
|
||||
role: invitation.role,
|
||||
status: invitation.status,
|
||||
expiresAt: invitation.expiresAt,
|
||||
inviterId: invitation.inviterId,
|
||||
organizationId: invitation.organizationId,
|
||||
organization: {
|
||||
id: organization.id,
|
||||
name: organization.name,
|
||||
logo: organization.logo,
|
||||
},
|
||||
})
|
||||
.from(invitation)
|
||||
.leftJoin(organization, eq(invitation.organizationId, organization.id))
|
||||
.leftJoin(user, eq(invitation.email, user.email))
|
||||
.where(where)
|
||||
.limit(input.perPage)
|
||||
.offset(offset)
|
||||
.orderBy(...orderBy);
|
||||
|
||||
const total = await tx
|
||||
.select({ count: count() })
|
||||
.from(invitation)
|
||||
.leftJoin(organization, eq(invitation.organizationId, organization.id))
|
||||
.leftJoin(user, eq(invitation.email, user.email))
|
||||
.where(where)
|
||||
.execute()
|
||||
.then((res) => res[0]?.count ?? 0);
|
||||
|
||||
return {
|
||||
data,
|
||||
total,
|
||||
};
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user