mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-09 23:48:36 +01:00
add members | phase 1
This commit is contained in:
@@ -4,12 +4,18 @@ const projects = useFetch<TProject[]>('/api/project/list', {
|
||||
key: 'projectslist', ...signHeaders()
|
||||
});
|
||||
|
||||
|
||||
|
||||
export function useProjectsList() {
|
||||
return { ...projects, projects: projects.data }
|
||||
}
|
||||
|
||||
const guestProjects = useFetch<TProject[]>('/api/project/list_guest', {
|
||||
key: 'guestProjectslist', ...signHeaders()
|
||||
});
|
||||
|
||||
export function useGuestProjectsList() {
|
||||
return { ...guestProjects, guestProjects: guestProjects.data }
|
||||
}
|
||||
|
||||
|
||||
const activeProjectId = useFetch<string>(`/api/user/active_project`, {
|
||||
key: 'activeProjectId', ...signHeaders(),
|
||||
@@ -28,7 +34,10 @@ export function useActiveProject() {
|
||||
if (!projects.data.value) return;
|
||||
if (!activeProjectId.data.value) return;
|
||||
const target = projects.data.value.find(e => e._id.toString() == activeProjectId.data.value);
|
||||
return target;
|
||||
if (target) return target;
|
||||
if (!guestProjects.data.value) return;
|
||||
const guestTarget = guestProjects.data.value.find(e => e._id.toString() == activeProjectId.data.value);
|
||||
return guestTarget;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ const sections: Section[] = [
|
||||
title: 'General',
|
||||
entries: [
|
||||
{ label: 'Projects', icon: 'far fa-table-layout', to: '/project_selector' },
|
||||
{ label: 'Members', icon: 'far fa-users', to: '/members' },
|
||||
{ label: 'Admin', icon: 'fas fa-cat', adminOnly: true, to: '/admin' },
|
||||
]
|
||||
},
|
||||
|
||||
83
dashboard/pages/members.vue
Normal file
83
dashboard/pages/members.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
definePageMeta({ layout: 'dashboard' });
|
||||
|
||||
const activeProject = useActiveProject();
|
||||
|
||||
const columns = [
|
||||
{ key: 'me', label: '' },
|
||||
{ key: 'email', label: 'Email' },
|
||||
{ key: 'name', label: 'Name' },
|
||||
{ key: 'role', label: 'Role' },
|
||||
// { key: 'pending', label: 'Pending' },
|
||||
]
|
||||
|
||||
const { data: members, refresh: refreshMembers } = useFetch('/api/project/members/list', signHeaders());
|
||||
|
||||
const showAddMember = ref<boolean>(false);
|
||||
|
||||
const addMemberEmail = ref<string>("");
|
||||
|
||||
async function addMember() {
|
||||
|
||||
if (addMemberEmail.value.length === 0) return;
|
||||
|
||||
try {
|
||||
|
||||
showAddMember.value = false;
|
||||
|
||||
await $fetch('/api/project/members/add', {
|
||||
method: 'POST',
|
||||
...signHeaders({ 'Content-Type': 'application/json' }),
|
||||
body: JSON.stringify({ email: addMemberEmail.value }),
|
||||
onResponseError({ request, response, options }) {
|
||||
alert(response.statusText);
|
||||
}
|
||||
});
|
||||
|
||||
addMemberEmail.value = '';
|
||||
|
||||
refreshMembers();
|
||||
|
||||
} catch (ex: any) { }
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
|
||||
<div class="home w-full h-full px-10 lg:px-6 overflow-y-auto pb-[12rem] md:pb-0 py-2">
|
||||
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
|
||||
<div @click="showAddMember = !showAddMember;"
|
||||
class="flex items-center gap-2 bg-menu w-fit px-3 py-2 rounded-lg hover:bg-menu/80 cursor-pointer">
|
||||
<i class="fas fa-plus"></i>
|
||||
<div> Add member </div>
|
||||
</div>
|
||||
|
||||
<div v-if="showAddMember" class="flex gap-4 items-center">
|
||||
<input v-model="addMemberEmail" class="focus:outline-none bg-menu px-4 py-1 rounded-lg" type="text"
|
||||
placeholder="user email">
|
||||
<div @click="addMember" class="bg-menu w-fit py-1 px-4 rounded-lg hover:bg-menu/80 cursor-pointer">
|
||||
Add
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<UTable :rows="members || []" :columns="columns">
|
||||
<template #me-data="e">
|
||||
<i v-if="e.row.me" class="far fa-user"></i>
|
||||
<i v-if="!e.row.me"></i>
|
||||
</template>
|
||||
</UTable>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
@@ -3,6 +3,7 @@
|
||||
definePageMeta({ layout: 'dashboard' });
|
||||
|
||||
const { projects, refresh } = useProjectsList();
|
||||
const { guestProjects } = useGuestProjectsList();
|
||||
const { pid } = useActiveProjectId();
|
||||
|
||||
const { data: maxProjects } = useFetch("/api/user/max_projects", signHeaders());
|
||||
@@ -69,7 +70,7 @@ async function deleteAccount() {
|
||||
<div class="flex gap-4 items-center">
|
||||
<div class="text-text font-bold text-[1.5rem]"> Projects </div>
|
||||
<div class="text-text-sub/90 text-[1rem] font-semibold lato">
|
||||
{{ projects?.length ?? '-' }} / {{maxProjects || 3}}
|
||||
{{ projects?.length ?? '-' }} / {{ maxProjects || 3 }}
|
||||
</div>
|
||||
</div>
|
||||
<NuxtLink v-if="(projects?.length || 0) < (maxProjects || 3)" to="/project_creation"
|
||||
@@ -101,9 +102,18 @@ async function deleteAccount() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-for="e of guestProjects">
|
||||
<DashboardProjectSelectionCard class="outline outline-[2px] outline-yellow-200"
|
||||
@click="onProjectClick(e._id.toString())" :title="e.name" :active="pid == e._id.toString()"
|
||||
:subtitle="pid == e._id.toString() ? 'ATTIVO' : ''" :chip="''">
|
||||
</DashboardProjectSelectionCard>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="px-10">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { AuthContext } from "./middleware/01-authorization";
|
||||
import { ProjectModel } from "~/../shared/schema/ProjectSchema";
|
||||
import { LITLYX_PROJECT_ID } from '@data/LITLYX'
|
||||
import { hasAccessToProject } from "./utils/hasAccessToProject";
|
||||
|
||||
export async function getUserProjectFromId(project_id: string, user: AuthContext | undefined) {
|
||||
if (project_id == LITLYX_PROJECT_ID) {
|
||||
@@ -8,7 +9,10 @@ export async function getUserProjectFromId(project_id: string, user: AuthContext
|
||||
return project;
|
||||
} else {
|
||||
if (!user?.logged) return;
|
||||
const project = await ProjectModel.findOne({ _id: project_id, owner: user.id });
|
||||
const project = await ProjectModel.findById(project_id);
|
||||
if (!project) return;
|
||||
const hasAccess = await hasAccessToProject(user.id, project_id, project);
|
||||
if (!hasAccess) return;
|
||||
return project;
|
||||
}
|
||||
|
||||
|
||||
25
dashboard/server/api/project/list_guest.ts
Normal file
25
dashboard/server/api/project/list_guest.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { ProjectModel, TProject } from "@schema/ProjectSchema";
|
||||
import { TTeamMember, TeamMemberModel } from "@schema/TeamMemberSchema";
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
|
||||
const userData = getRequestUser(event);
|
||||
if (!userData?.logged) return [];
|
||||
|
||||
|
||||
const members = await TeamMemberModel.find({
|
||||
user_id: userData.id
|
||||
});
|
||||
|
||||
const projects: TProject[] = [];
|
||||
|
||||
for (const member of members) {
|
||||
const project = await ProjectModel.findById(member.project_id);
|
||||
if (!project) continue;
|
||||
projects.push(project.toJSON());
|
||||
}
|
||||
|
||||
return projects;
|
||||
|
||||
|
||||
});
|
||||
38
dashboard/server/api/project/members/add.post.ts
Normal file
38
dashboard/server/api/project/members/add.post.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { ProjectModel } from "@schema/ProjectSchema";
|
||||
import { TeamMemberModel } from "@schema/TeamMemberSchema";
|
||||
import { UserModel } from "@schema/UserSchema";
|
||||
import { UserSettingsModel } from "@schema/UserSettings";
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
|
||||
const userData = getRequestUser(event);
|
||||
if (!userData?.logged) return setResponseStatus(event, 400, 'NotLogged');
|
||||
|
||||
const currentActiveProject = await UserSettingsModel.findOne({ user_id: userData.id });
|
||||
if (!currentActiveProject) return setResponseStatus(event, 400, 'You need to select a project');
|
||||
|
||||
const project_id = currentActiveProject.active_project_id;
|
||||
|
||||
const project = await ProjectModel.findById(project_id);
|
||||
if (!project) return setResponseStatus(event, 400, 'Project not found');
|
||||
|
||||
if (project.owner.toString() != userData.id) {
|
||||
return setResponseStatus(event, 400, 'You are not the owner');
|
||||
}
|
||||
|
||||
const { email } = await readBody(event);
|
||||
|
||||
const targetUser = await UserModel.findOne({ email });
|
||||
if (!targetUser) return setResponseStatus(event, 400, 'No user with this email');
|
||||
|
||||
|
||||
await TeamMemberModel.create({
|
||||
project_id,
|
||||
user_id: targetUser.id,
|
||||
pending: true,
|
||||
role: 'GUEST'
|
||||
});
|
||||
|
||||
return { ok: true };
|
||||
|
||||
});
|
||||
49
dashboard/server/api/project/members/list.ts
Normal file
49
dashboard/server/api/project/members/list.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { ProjectModel } from "@schema/ProjectSchema";
|
||||
import { TeamMemberModel } from "@schema/TeamMemberSchema";
|
||||
import { UserModel } from "@schema/UserSchema";
|
||||
import { UserSettingsModel } from "@schema/UserSettings";
|
||||
import StripeService from '~/server/services/StripeService';
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
|
||||
const userData = getRequestUser(event);
|
||||
if (!userData?.logged) return setResponseStatus(event, 400, 'NotLogged');
|
||||
|
||||
const currentActiveProject = await UserSettingsModel.findOne({ user_id: userData.id });
|
||||
if (!currentActiveProject) return setResponseStatus(event, 400, 'You need to select a project');
|
||||
|
||||
const project_id = currentActiveProject.active_project_id;
|
||||
|
||||
const project = await ProjectModel.findById(project_id);
|
||||
if (!project) return setResponseStatus(event, 400, 'Project not found');
|
||||
|
||||
const owner = await UserModel.findById(project.owner);
|
||||
if (!owner) return setResponseStatus(event, 400, 'No owner');
|
||||
|
||||
const members = await TeamMemberModel.find({ project_id });
|
||||
|
||||
const result: { email: string, name: string, role: string, pending: boolean, me: boolean }[] = [];
|
||||
|
||||
result.push({
|
||||
email: owner.email,
|
||||
name: owner.name,
|
||||
role: 'OWNER',
|
||||
pending: false,
|
||||
me: userData.id === owner.id
|
||||
})
|
||||
|
||||
for (const member of members) {
|
||||
const userMember = await UserModel.findById(member.user_id);
|
||||
if (!userMember) continue;
|
||||
result.push({
|
||||
email: userMember.email,
|
||||
name: userMember.name,
|
||||
role: member.role,
|
||||
pending: member.pending,
|
||||
me: userData.id === userMember.id
|
||||
})
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
});
|
||||
@@ -2,6 +2,7 @@
|
||||
import { ProjectModel } from "@schema/ProjectSchema";
|
||||
|
||||
import { UserSettingsModel } from "@schema/UserSettings";
|
||||
import { hasAccessToProject } from "~/server/utils/hasAccessToProject";
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
|
||||
@@ -12,7 +13,7 @@ export default defineEventHandler(async event => {
|
||||
|
||||
const { project_id } = getQuery(event);
|
||||
|
||||
const hasAccess = await ProjectModel.exists({ owner: userData.id, _id: project_id });
|
||||
const hasAccess = await hasAccessToProject(userData.id, project_id as string);
|
||||
|
||||
if (!hasAccess) return setResponseStatus(event, 400, 'No access to project');
|
||||
|
||||
|
||||
11
dashboard/server/utils/hasAccessToProject.ts
Normal file
11
dashboard/server/utils/hasAccessToProject.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { ProjectModel, TProject } from "@schema/ProjectSchema";
|
||||
import { TeamMemberModel } from "@schema/TeamMemberSchema";
|
||||
|
||||
export async function hasAccessToProject(user_id: string, project_id: string, project?: TProject) {
|
||||
const targetProject = project || await ProjectModel.findById(project_id, { owner: true });
|
||||
if (!targetProject) return [false, 'NONE'];
|
||||
if (targetProject.owner.toString() === user_id) return [true, 'OWNER'];
|
||||
const members = await TeamMemberModel.find({ project_id });
|
||||
if (members.map(e => e.user_id.toString()).includes(user_id)) return [true, 'GUEST'];
|
||||
return [false, 'NONE'];
|
||||
}
|
||||
Reference in New Issue
Block a user