Files
fullstack-fuware/src/service/house.api.ts

531 lines
14 KiB
TypeScript

import { prisma } from '@/db';
import { HouseWhereInput } from '@/generated/prisma/models';
import { DB_TABLE, LOG_ACTION, NOTIFICATION_TYPE } from '@/types/enum';
import { auth } from '@lib/auth';
import { parseError } from '@lib/errors';
import { authMiddleware } from '@lib/middleware';
import { createServerFn } from '@tanstack/react-start';
import { getRequestHeaders } from '@tanstack/react-start/server';
import {
actionInvitationSchema,
baseHouse,
houseCreateBESchema,
houseEditBESchema,
houseForSelectSchema,
houseListSchema,
invitationCreateBESchema,
removeMemberSchema,
} from './house.schema';
import { createAuditLog, createNotification } from './repository';
export const getAllHouse = createServerFn({ method: 'GET' })
.middleware([authMiddleware])
.validator(houseListSchema)
.handler(async ({ data }) => {
try {
const { page, limit, keyword } = data;
const skip = (page - 1) * limit;
const where: HouseWhereInput = {
OR: [
{
name: {
contains: keyword,
mode: 'insensitive',
},
},
],
};
const [list, total]: [HouseWithMembers[], number] = await Promise.all([
await prisma.house.findMany({
where,
orderBy: { createdAt: 'desc' },
include: {
members: {
select: {
role: true,
user: {
select: {
id: true,
name: true,
email: true,
image: true,
},
},
},
},
},
take: limit,
skip,
}),
await prisma.house.count({ where }),
]);
const totalPage = Math.ceil(+total / limit);
return {
result: list,
pagination: {
currentPage: page,
totalPage,
totalItem: total,
},
};
} catch (error) {
console.error(error);
const { message, code } = parseError(error);
throw { message, code };
}
});
export const getCurrentUserHouses = createServerFn({ method: 'GET' })
.middleware([authMiddleware])
.handler(async ({ context: { user } }) => {
try {
const houses = await prisma.house.findMany({
where: { members: { some: { userId: user.id } } },
orderBy: { createdAt: 'asc' },
include: {
_count: {
select: {
members: true,
},
},
members: {
select: {
role: true,
user: {
select: {
id: true,
name: true,
email: true,
image: true,
},
},
},
},
},
});
return houses;
} catch (error) {
console.error(error);
const { message, code } = parseError(error);
throw { message, code };
}
});
export const getHouseForSelect = createServerFn({ method: 'GET' })
.middleware([authMiddleware])
.validator(houseForSelectSchema)
.handler(async ({ data }) => {
try {
const result = await prisma.house.findMany({
where: {
OR: [
{
name: {
contains: data.keyword,
mode: 'insensitive',
},
},
],
},
select: {
id: true,
name: true,
color: true,
},
orderBy: { createdAt: 'desc' },
take: 5,
});
return result;
} catch (error) {
console.error(error);
const { message, code } = parseError(error);
throw { message, code };
}
});
export const createHouse = createServerFn({ method: 'POST' })
.middleware([authMiddleware])
.validator(houseCreateBESchema)
.handler(async ({ data, context: { user } }) => {
try {
const result = await auth.api.createOrganization({
body: data,
});
if (!result) throw Error('Failed to create house');
await createAuditLog({
action: LOG_ACTION.CREATE,
tableName: DB_TABLE.ORGANIZATION,
recordId: result.id,
oldValue: '',
newValue: JSON.stringify(result),
userId: user.id,
});
return result;
} catch (error) {
console.error(error);
const { message, code } = parseError(error);
throw { message, code };
}
});
export const updateHouse = createServerFn({ method: 'POST' })
.middleware([authMiddleware])
.validator(houseEditBESchema)
.handler(async ({ data, context: { user } }) => {
try {
const currentHouse = await prisma.house.findUnique({
where: { id: data.id },
});
if (!currentHouse) throw Error('House not found');
const { id, slug, name, color } = data;
const result = await prisma.house.update({
where: { id },
data: {
name,
slug,
color,
},
});
await createAuditLog({
action: LOG_ACTION.UPDATE,
tableName: DB_TABLE.ORGANIZATION,
recordId: id,
oldValue: JSON.stringify(currentHouse),
newValue: JSON.stringify(result),
userId: user.id,
});
return result;
} catch (error) {
console.error(error);
const { message, code } = parseError(error);
throw { message, code };
}
});
export const deleteHouse = createServerFn({ method: 'POST' })
.middleware([authMiddleware])
.validator(baseHouse)
.handler(async ({ data, context: { user } }) => {
try {
const currentHouse = await prisma.house.findUnique({
where: { id: data.id },
});
if (!currentHouse) throw Error('House not found');
const result = await Promise.all([
prisma.house.delete({
where: { id: data.id },
}),
prisma.member.deleteMany({
where: { organizationId: data.id },
}),
prisma.invitation.deleteMany({
where: { organizationId: data.id },
}),
]);
await createAuditLog({
action: LOG_ACTION.DELETE,
tableName: DB_TABLE.ORGANIZATION,
recordId: result[0]?.id,
oldValue: JSON.stringify(currentHouse),
newValue: '',
userId: user.id,
});
return result;
} catch (error) {
console.error(error);
const { message, code } = parseError(error);
throw { message, code };
}
});
export const deleteUserHouse = createServerFn({ method: 'POST' })
.middleware([authMiddleware])
.validator(baseHouse)
.handler(async ({ data, context: { user } }) => {
try {
const currentHouse = await prisma.house.findUnique({
where: { id: data.id },
});
if (!currentHouse) throw Error('House not found');
const headers = getRequestHeaders();
const result = await auth.api.deleteOrganization({
body: {
organizationId: data.id, // required
},
headers,
});
if (result) {
await createAuditLog({
action: LOG_ACTION.DELETE,
tableName: DB_TABLE.ORGANIZATION,
recordId: result?.id,
oldValue: JSON.stringify(currentHouse),
newValue: '',
userId: user.id,
});
}
return result;
} catch (error) {
console.error(error);
const { message, code } = parseError(error);
throw { message, code };
}
});
export const invitationMember = createServerFn({ method: 'POST' })
.middleware([authMiddleware])
.validator(invitationCreateBESchema)
.handler(async ({ data, context: { user } }) => {
try {
const headers = getRequestHeaders();
const body = {
email: data.email,
role: data.role,
organizationId: data.houseId,
};
const cuser = await prisma.user.findUnique({
where: { email: data.email },
});
const chouse = await prisma.house.findUnique({
where: { id: data.houseId },
});
const result = await auth.api.createInvitation({
body,
headers,
});
if (result && cuser && chouse) {
await createNotification({
type: NOTIFICATION_TYPE.INVITATION,
userId: cuser.id,
title: 'INVITATION_HOUSE',
message: 'INVITATION_HOUSE',
link: result.id,
metadata: JSON.stringify({
house: chouse,
}),
readAt: null,
});
await createAuditLog({
action: LOG_ACTION.CREATE,
tableName: DB_TABLE.INVITATION,
recordId: result.id,
oldValue: '',
newValue: JSON.stringify(body),
userId: user.id,
});
}
return result;
} catch (error) {
console.error(error);
const { message, code } = parseError(error);
throw { message, code };
}
});
export const cancelInvitation = createServerFn({ method: 'POST' })
.middleware([authMiddleware])
.validator(baseHouse)
.handler(async ({ data }) => {
try {
const headers = getRequestHeaders();
const result = await auth.api.cancelInvitation({
body: {
invitationId: data.id, // required
},
headers,
});
if (result) {
const notification = await prisma.notification.findFirst({
where: { link: data.id },
});
if (notification) {
await prisma.notification.update({
where: { id: notification.id },
data: { link: null },
});
}
}
return result;
} catch (error) {
console.error(error);
const { message, code } = parseError(error);
throw { message, code };
}
});
export const acceptInvitation = createServerFn({ method: 'POST' })
.middleware([authMiddleware])
.validator(actionInvitationSchema)
.handler(async ({ data, context: { user } }) => {
try {
const result = await prisma.invitation.update({
where: { id: data.id },
data: { status: 'accepted' },
});
if (result) {
const notify = await prisma.notification.update({
where: { id: data.notificationId },
data: { link: null },
});
const member = await auth.api.addMember({
body: {
userId: notify.userId,
organizationId: result.organizationId,
role: (result.role as 'admin' | 'owner' | 'member') || 'member',
},
});
await createAuditLog({
action: LOG_ACTION.UPDATE,
tableName: DB_TABLE.INVITATION,
recordId: result.id,
oldValue: JSON.stringify(result),
newValue: 'Accept Invitation / Đồng ý mời',
userId: user.id,
});
if (member) {
await createAuditLog({
action: LOG_ACTION.CREATE,
tableName: DB_TABLE.MEMBER,
recordId: member.id,
oldValue: '',
newValue: JSON.stringify(member),
userId: user.id,
});
}
}
return result;
} catch (error) {
console.error(error);
const { message, code } = parseError(error);
throw { message, code };
}
});
export const rejectInvitation = createServerFn({ method: 'POST' })
.middleware([authMiddleware])
.validator(actionInvitationSchema)
.handler(async ({ data }) => {
try {
const headers = getRequestHeaders();
const result = await auth.api.rejectInvitation({
body: {
invitationId: data.id,
},
headers,
});
if (result) {
await prisma.notification.update({
where: { id: data.notificationId },
data: { link: null },
});
}
return result;
} catch (error) {
console.error(error);
const { message, code } = parseError(error);
throw { message, code };
}
});
export const leaveHouse = createServerFn({ method: 'POST' })
.middleware([authMiddleware])
.validator(baseHouse)
.handler(async ({ data }) => {
try {
const headers = getRequestHeaders();
const result = await auth.api.leaveOrganization({
body: {
organizationId: data.id,
},
headers,
});
if (result) {
await createAuditLog({
action: LOG_ACTION.UPDATE,
tableName: DB_TABLE.ORGANIZATION,
recordId: result.id,
oldValue: JSON.stringify(result),
newValue: `${result.user.name} đã rời khỏi nhà (left the house)`,
userId: result.user.id,
});
}
return result;
} catch (error) {
console.error(error);
const { message, code } = parseError(error);
throw { message, code };
}
});
export const removeMember = createServerFn({ method: 'POST' })
.middleware([authMiddleware])
.validator(removeMemberSchema)
.handler(async ({ data, context: { user } }) => {
try {
const headers = getRequestHeaders();
const result = await auth.api.removeMember({
body: {
memberIdOrEmail: data.memberId,
organizationId: data.houseId,
},
headers,
});
if (result) {
const oldMember = await prisma.user.findUnique({
where: { id: result.member.userId },
});
await createAuditLog({
action: LOG_ACTION.UPDATE,
tableName: DB_TABLE.MEMBER,
recordId: result.member.id,
oldValue: JSON.stringify(result),
newValue: `${oldMember?.name} đã bị "mời" rời khỏi nhà (was "asked" to leave the house.)`,
userId: user.id,
});
}
return result;
} catch (error) {
console.error(error);
const { message, code } = parseError(error);
throw { message, code };
}
});