Skip to content

Commit

Permalink
feat: project can be create with a team, and huge performance update
Browse files Browse the repository at this point in the history
  • Loading branch information
HugoRCD committed Mar 28, 2024
1 parent 4e2c5f7 commit 5d0603c
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 82 deletions.
90 changes: 49 additions & 41 deletions apps/app/components/project/Create.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
<script setup lang="ts">
import type { CreateProjectInput, Team } from "@shelve/types";
const { refresh } = defineProps({
refresh: {
type: Function,
required: true,
},
})
const projectCreateInput = ref({
const projectCreateInput = ref<CreateProjectInput>({
name: "",
description: "",
avatar: "",
Expand All @@ -14,7 +16,6 @@ const projectCreateInput = ref({
homepage: "",
})
const createModal = ref(false)
const isOpen = ref(false)
const { status: createStatus, error: createError, execute } = useFetch("/api/project", {
Expand All @@ -29,41 +30,27 @@ async function createProject() {
if (createError.value) toast.error("An error occurred")
else {
toast.success("Your project has been created")
createModal.value = false
isOpen.value = false
await refresh()
}
}
const teamMembers = ref([
{
id: 1,
name: "Benjamin Canac",
avatar: "https://avatars.githubusercontent.com/u/739984?v=4",
},
{
id: 2,
name: "Anthony Gore",
avatar: "https://avatars.githubusercontent.com/u/904724?v=4",
},
{
id: 3,
name: "Sébastien Marroufi",
avatar: "https://avatars.githubusercontent.com/u/7547335?v=4",
},
])
function addTeamMember() {
teamMembers.value.push({
id: teamMembers.value.length + 1,
name: "New member",
avatar: "",
})
function addTeam(team: Team) {
projectCreateInput.value.team = team
}
function removeTeamMember(index: number) {
teamMembers.value.splice(index, 1)
function removeTeam() {
delete projectCreateInput.value.team
}
const {
fetchTeams,
loading
} = useTeams();
fetchTeams()
const teams = useUserTeams();
function importProject() {
const input = document.createElement("input")
input.type = "file"
Expand Down Expand Up @@ -124,19 +111,40 @@ function importProject() {
Add team members to your project
</p>
</div>
<UAvatarGroup size="sm" :max="6">
<div class="flex size-8 cursor-pointer items-center justify-center rounded-full border border-dashed border-gray-400" @click="addTeamMember">
+
<div>
<USkeleton v-if="loading" class="h-8" />
<div v-else>
<div v-if="projectCreateInput.team" class="flex items-center justify-between">
<div class="flex flex-col gap-2">
<h3 class="text-xs font-semibold text-neutral-600 dark:text-neutral-400">
{{ projectCreateInput.team.name }}
</h3>
<TeamMembers :team-id="projectCreateInput.team.id" :members="projectCreateInput.team.members" display />
</div>
<UButton
variant="soft"
color="red"
class="text-xs"
label="Unlink"
icon="i-lucide-unlink"
@click="removeTeam"
/>
</div>
<div v-else class="flex flex-col gap-4">
<div v-if="teams.length !== 0" class="flex flex-col gap-4">
<div v-for="team in teams" :key="team.id" class="divide-y divide-gray-100 dark:divide-gray-800">
<ProjectTeamAssign :team="team" :project-id="0" is-emit @add-team="addTeam" />
</div>
</div>
<div v-else class="flex flex-col items-center justify-center gap-2">
<p class="text-pretty text-xs text-neutral-500 dark:text-neutral-400">
You don't have any teams yet
</p>
<TeamCreate>Create one</TeamCreate>
</div>
</div>
</div>
<UAvatar
v-for="(member, index) in teamMembers"
:key="member.id"
:src="member.avatar"
:alt="member.name"
size="sm"
@click="removeTeamMember(index)"
/>
</UAvatarGroup>
</div>
</div>
<UDivider class="my-4" />
<div class="flex flex-col gap-4">
Expand Down
39 changes: 24 additions & 15 deletions apps/app/components/project/TeamAssign.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import type { Team } from "@shelve/types";
import type { PropType } from "vue";
const { team, projectId } = defineProps({
const { team, projectId, isEmit } = defineProps({
team: {
type: Object as PropType<Team>,
required: true,
Expand All @@ -11,30 +11,39 @@ const { team, projectId } = defineProps({
type: Number,
required: true,
},
isEmit: {
type: Boolean,
default: false,
},
});
const loading = ref(false);
const refresh = inject("refresh") as Function;
const emit = defineEmits(["addTeam"]);
async function addTeamToProject(teamId: number) {
loading.value = true;
try {
await $fetch(`/api/project/${projectId}/team/${teamId}`, {
method: "POST",
});
toast.success("Team added to project");
await refresh();
} catch (error) {
toast.error("An error occurred");
if (isEmit) {
emit("addTeam", team);
} else {
loading.value = true;
try {
await $fetch(`/api/project/${projectId}/team/${teamId}`, {
method: "POST",
});
toast.success("Team added to project");
const refresh = inject("refresh") as Function;
await refresh();
} catch (error) {
toast.error("An error occurred");
}
loading.value = false;
}
loading.value = false;
}
</script>

<template>
<div :key="team.id" class="flex items-center justify-between rounded-lg bg-neutral-100 px-4 py-2 dark:bg-neutral-900">
<div class="flex items-center gap-4">
<h3 class="text-sm font-semibold">
<div :key="team.id" class="flex items-center justify-between">
<div class="flex flex-col gap-2">
<h3 class="text-xs font-semibold text-neutral-600 dark:text-neutral-400">
{{ team.name }}
</h3>
<TeamMembers :members="team.members" :team-id="team.id" display />
Expand Down
2 changes: 1 addition & 1 deletion apps/app/server/api/project/[id]/index.put.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default eventHandler(async (event: H3Event) => {
const projectUpdateInput = await readBody(event);
delete projectUpdateInput.variables;
delete projectUpdateInput.team;
await updateProject(projectUpdateInput, parseInt(id));
await updateProject(projectUpdateInput, parseInt(id), user.id);
return {
statusCode: 200,
message: "Project updated",
Expand Down
85 changes: 63 additions & 22 deletions apps/app/server/app/projectService.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,44 @@
import type { ProjectCreateInput, ProjectUpdateInput } from "@shelve/types";
import type { CreateProjectInput, ProjectUpdateInput } from "@shelve/types";
import prisma from "~/server/database/client";

export async function createProject(project: ProjectCreateInput, userId: number) {
return prisma.project.create({
data: {
...project,
ownerId: userId,
users: {
connect: {
id: userId,
export async function createProject(project: CreateProjectInput, userId: number) {
const projectAlreadyExists = await isProjectAlreadyExists(project.name, userId);
if (projectAlreadyExists) throw new Error('Project already exists');
if (project.team) {
return prisma.project.create({
data: {
...project,
ownerId: userId,
users: {
connect: {
id: userId,
}
},
team: {
connect: {
id: project.team.id,
}
}
}
}
});
});
} else {
return prisma.project.create({
data: {
...project,
ownerId: userId,
users: {
connect: {
id: userId,
}
}
}
});
}
}

export async function updateProject(project: ProjectUpdateInput, projectId: number) {
export async function updateProject(project: ProjectUpdateInput, projectId: number, userId: number) {
await removeCachedUserProjects(userId.toString());
await removeCachedProjectById(projectId.toString());
return prisma.project.update({
where: {
id: projectId,
Expand All @@ -24,7 +47,7 @@ export async function updateProject(project: ProjectUpdateInput, projectId: numb
});
}

export async function getProjectById(id: number) {
export const getProjectById = cachedFunction(async (id: number) => {
return prisma.project.findUnique({
where: {
id,
Expand All @@ -48,7 +71,11 @@ export async function getProjectById(id: number) {
}
}
});
}
}, {
maxAge: 60 * 60,
name: 'getProjectById',
getKey: (id: number) => `projectId:${id}`,
});

export async function addTeamToProject(projectId: number, teamId: number) {
return prisma.project.update({
Expand Down Expand Up @@ -79,16 +106,12 @@ export async function removeTeamFromProject(projectId: number, teamId: number) {
}
});
}

export async function getProjectsByUserId(userId: number) {
export const getProjectsByUserId = cachedFunction(async (userId: number) => {
const [projects, teams] = await Promise.all([
prisma.project.findMany({
where: {
ownerId: userId,
},
cacheStrategy: {
ttl: 60,
}
}),
prisma.team.findMany({
where: {
Expand All @@ -100,21 +123,39 @@ export async function getProjectsByUserId(userId: number) {
},
include: {
projects: true,
},
cacheStrategy: {
ttl: 60,
}
})
]);
const teamProjects = teams.map(team => team.projects);
return [...projects, ...teamProjects].flat().filter((project, index, self) => self.findIndex(p => p.id === project.id) === index);
}, {
maxAge: 60 * 60,
name: 'getProjectsByUserId',
getKey: (userId: number) => `userId:${userId}`,
});

async function isProjectAlreadyExists(name: string, userId: number) {
const project = await prisma.project.findFirst({
where: {
name: {
equals: name,
mode: 'insensitive',
},
ownerId: userId,
}
});
return !!project;
}

async function removeCachedUserProjects(userId: string) {
return await useStorage('cache').removeItem(`nitro:functions:getProjectsByUserId:userId:${userId}.json`);
}
async function removeCachedProjectById(id: string) {
return await useStorage('cache').removeItem(`nitro:functions:getProjectById:projectId:${id}.json`);
}

export async function deleteProject(id: number, userId: number) {
await removeCachedProjectById(id.toString);
return prisma.project.delete({
where: {
id,
Expand Down
2 changes: 2 additions & 0 deletions apps/app/server/database/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ model Project {
users User[]
teamId Int?
team Team? @relation(fields: [teamId], references: [id])
@@index([name], name: "project_name_index")
}

model Variables {
Expand Down
7 changes: 4 additions & 3 deletions packages/shelve-types/src/Project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ export type Project = {
variables?: Variable[];
};

export type ProjectCreateInput = {
export type CreateProjectInput = {
id?: number;
name: string;
description?: string;
avatar?: string;
description: string;
avatar: string;
ownerId: number;
team?: Team;
};

export type ProjectUpdateInput = {
Expand Down

0 comments on commit 5d0603c

Please sign in to comment.