misc: initial project favorite in grid view

This commit is contained in:
Sheen Capadngan
2024-06-28 17:40:34 +08:00
parent 7cdc47cd3a
commit 11d9166684
8 changed files with 272 additions and 47 deletions

View File

@@ -0,0 +1,19 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasColumn(TableName.OrgMembership, "projectFavorites"))) {
await knex.schema.alterTable(TableName.OrgMembership, (tb) => {
tb.specificType("projectFavorites", "text[]");
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.OrgMembership, "projectFavorites")) {
await knex.schema.alterTable(TableName.OrgMembership, (t) => {
t.dropColumn("projectFavorites");
});
}
}

View File

@@ -16,7 +16,8 @@ export const OrgMembershipsSchema = z.object({
updatedAt: z.date(),
userId: z.string().uuid().nullable().optional(),
orgId: z.string().uuid(),
roleId: z.string().uuid().nullable().optional()
roleId: z.string().uuid().nullable().optional(),
projectFavorites: z.string().array().nullable().optional()
});
export type TOrgMemberships = z.infer<typeof OrgMembershipsSchema>;

View File

@@ -415,8 +415,10 @@ export const registerRoutes = async (
userAliasDAL,
orgMembershipDAL,
tokenService,
smtpService
smtpService,
projectMembershipDAL
});
const loginService = authLoginServiceFactory({ userDAL, smtpService, tokenService, orgDAL, tokenDAL: authTokenDAL });
const passwordService = authPaswordServiceFactory({
tokenService,

View File

@@ -3,7 +3,7 @@ import { z } from "zod";
import { UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { logger } from "@app/lib/logger";
import { authRateLimit, readLimit } from "@app/server/config/rateLimiter";
import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -90,4 +90,48 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
return res.redirect(`${appCfg.SITE_URL}/login`);
}
});
server.route({
method: "GET",
url: "/me/project-favorites",
config: {
rateLimit: readLimit
},
schema: {
querystring: z.object({
orgId: z.string().trim()
}),
response: {
200: z.object({
projectFavorites: z.string().array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
return server.services.user.getUserProjectFavorites(req.permission.id, req.query.orgId);
}
});
server.route({
method: "PUT",
url: "/me/project-favorites",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
orgId: z.string().trim(),
projectFavorites: z.string().array()
})
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
return server.services.user.updateUserProjectFavorites(
req.permission.id,
req.body.orgId,
req.body.projectFavorites
);
}
});
};

View File

@@ -8,6 +8,7 @@ import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
import { AuthMethod } from "../auth/auth-type";
import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal";
import { TUserDALFactory } from "./user-dal";
type TUserServiceFactoryDep = {
@@ -26,8 +27,9 @@ type TUserServiceFactoryDep = {
| "delete"
>;
userAliasDAL: Pick<TUserAliasDALFactory, "find" | "insertMany">;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find" | "insertMany">;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find" | "insertMany" | "findOne" | "updateById">;
tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser" | "validateTokenForUser">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find">;
smtpService: Pick<TSmtpService, "sendMail">;
};
@@ -37,6 +39,7 @@ export const userServiceFactory = ({
userDAL,
userAliasDAL,
orgMembershipDAL,
projectMembershipDAL,
tokenService,
smtpService
}: TUserServiceFactoryDep) => {
@@ -247,6 +250,49 @@ export const userServiceFactory = ({
return privateKey;
};
const getUserProjectFavorites = async (userId: string, orgId: string) => {
const orgMembership = await orgMembershipDAL.findOne({
userId,
orgId
});
if (!orgMembership) {
throw new BadRequestError({
message: "User does not belong in the organization."
});
}
return { projectFavorites: orgMembership.projectFavorites || [] };
};
const updateUserProjectFavorites = async (userId: string, orgId: string, projectIds: string[]) => {
const orgMembership = await orgMembershipDAL.findOne({
userId,
orgId
});
if (!orgMembership) {
throw new BadRequestError({
message: "User does not belong in the organization."
});
}
const memberProjectFavorites = (
await projectMembershipDAL.find({
userId,
$in: {
projectId: projectIds
}
})
).map((projectMembership) => projectMembership.projectId);
return (
await orgMembershipDAL.updateById(orgMembership.id, {
projectFavorites: memberProjectFavorites
})
).projectFavorites;
};
return {
sendEmailVerificationCode,
verifyEmailVerificationCode,
@@ -258,6 +304,8 @@ export const userServiceFactory = ({
createUserAction,
getUserAction,
unlockUser,
getUserPrivateKey
getUserPrivateKey,
getUserProjectFavorites,
updateUserProjectFavorites
};
};