Campaign REST API
This commit is contained in:
parent
93eaa1e3e4
commit
3ed9ab3dce
|
|
@ -86,6 +86,7 @@ export const campaignTable = table("campaign", {
|
|||
name: text().notNull(),
|
||||
description: text(),
|
||||
owner: int().notNull().references(() => usersTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
joinby: text({ enum: [ 'link', 'invite' ] }).default('invite'),
|
||||
});
|
||||
export const campaignMembersTable = table("campaign_members", {
|
||||
id: int().references(() => campaignTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
|
|
@ -141,7 +142,7 @@ export const characterChoicesRelation = relations(characterChoicesTable, ({ one
|
|||
export const campaignRelation = relations(campaignTable, ({ one, many }) => ({
|
||||
members: many(campaignMembersTable),
|
||||
characters: many(campaignCharactersTable),
|
||||
owner: one(usersTable),
|
||||
owner: one(usersTable, { fields: [campaignTable.owner], references: [usersTable.id], }),
|
||||
}));
|
||||
export const campaignMembersRelation = relations(campaignMembersTable, ({ one }) => ({
|
||||
campaign: one(campaignTable, { fields: [campaignMembersTable.id], references: [campaignTable.id], }),
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
<NavigationMenuViewport class="h-[var(--radix-navigation-menu-viewport-height)] w-full origin-[top_center] flex justify-center overflow-hidden sm:w-[var(--radix-navigation-menu-viewport-width)]" />
|
||||
</div>
|
||||
</NavigationMenuRoot>
|
||||
<NuxtLink :href="{ name: 'character' }" class="flex flex-row gap-2 items-center border-b-2 border-transparent hover:border-accent-blue py-4 select-none" active-class="!text-accent-blue"><span class="px-3 flex-1 truncate">Campagnes</span></NuxtLink>
|
||||
<NuxtLink :href="{ name: 'campaign' }" class="flex flex-row gap-2 items-center border-b-2 border-transparent hover:border-accent-blue py-4 select-none" active-class="!text-accent-blue"><span class="px-3 flex-1 truncate">Campagnes</span></NuxtLink>
|
||||
</div>
|
||||
<div class="flex flex-row gap-16 items-center">
|
||||
<template v-if="!loggedIn">
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
<template></template>
|
||||
|
|
@ -0,0 +1 @@
|
|||
<template></template>
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
guestsGoesTo: '/user/login',
|
||||
});
|
||||
|
||||
const { user, loggedIn } = useUserSession();
|
||||
const { data: campaigns, error, status } = await useFetch(`/api/campaign`);
|
||||
|
||||
function leaveCampaign(id: number)
|
||||
{
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Head>
|
||||
<Title>d[any] - Mes personnages</Title>
|
||||
</Head>
|
||||
<div class="flex flex-col">
|
||||
<div v-if="status === 'pending'" class="flex flex-1 justify-center align-center">
|
||||
<Loading size="large" />
|
||||
</div>
|
||||
<template v-else-if="status === 'success' && loggedIn && user">
|
||||
<div v-if="campaigns && campaigns.length > 0" class="grid p-6 2xl:grid-cols-3 lg:grid-cols-2 grid-cols-1 gap-4 w-full">
|
||||
<div class="flex flex-col w-[360px] border border-light-35 dark:border-dark-35" v-for="campaign of campaigns">
|
||||
<NuxtLink :to="{ name: 'campaign-id', params: { id: campaign.id } }" class="group bg-light-10 dark:bg-dark-10 border-b border-light-35 dark:border-dark-35 p-2 flex flex-col gap-2">
|
||||
<div class="flex flex-row gap-8 ps-4 items-center">
|
||||
<div class="flex flex-1 flex-col gap-2 justify-center">
|
||||
<span class="text-lg font-bold group-hover:text-accent-blue">{{ campaign.name }}</span>
|
||||
<span class="border-b w-full border-light-50 dark:border-dark-50"></span>
|
||||
<div class="flex flex-row flex-1 items-stretch gap-4">
|
||||
<span class="text-sm">{{ campaign.members.length }} joueur{{ campaign.members.length === 1 ? '' : 's' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-full w-[96px] h-[96px] border border-light-50 dark:border-dark-50 bg-light-100 dark:bg-dark-100 !bg-opacity-10"></div>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
<div class="flex justify-around items-center py-2 px-4 gap-4">
|
||||
<NuxtLink :to="{ name: 'character-id-edit', params: { id: campaign.id } }" class="text-sm font-bold cursor-pointer hover:text-accent-blue">Editer</NuxtLink>
|
||||
<span class="w-px h-full bg-light-50 dark:bg-dark-50"></span>
|
||||
<AlertDialogRoot v-if="user.id !== campaign.owner.id">
|
||||
<AlertDialogTrigger>
|
||||
<span class="text-sm font-bold text-light-red dark:text-dark-red">Quitter</span>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogPortal>
|
||||
<AlertDialogOverlay class="bg-light-0 dark:bg-dark-0 opacity-70 fixed inset-0 z-40" />
|
||||
<AlertDialogContent
|
||||
class="data-[state=open]:animate-contentShow fixed top-[50%] left-[50%] max-h-[85vh] w-[90vw] max-w-[800px] translate-x-[-50%] translate-y-[-50%] bg-light-10 dark:bg-dark-10 border border-light-30 dark:border-dark-30 p-6 z-50 text-light-100 dark:text-dark-100">
|
||||
<AlertDialogTitle class="text-3xl font-light relative -top-2">Vous vous appretez à quitter "{{ campaign.name }}". Etes vous sûr ?</AlertDialogTitle>
|
||||
<div class="flex flex-1 justify-end gap-4">
|
||||
<AlertDialogCancel asChild><Button>Non</Button></AlertDialogCancel>
|
||||
<AlertDialogAction asChild><Button @click="() => leaveCampaign(campaign.id)" class="border-light-red dark:border-dark-red hover:border-light-red dark:hover:border-dark-red hover:bg-light-redBack dark:hover:bg-dark-redBack text-light-red dark:text-dark-red focus:shadow-light-red dark:focus:shadow-dark-red">Oui</Button></AlertDialogAction>
|
||||
</div>
|
||||
</AlertDialogContent>
|
||||
</AlertDialogPortal>
|
||||
</AlertDialogRoot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex flex-col gap-2 items-center flex-1">
|
||||
<span class="text-lg font-bold">Vous n'avez pas encore rejoint de campagne</span>
|
||||
<NuxtLink class="inline-flex justify-center items-center outline-none leading-none transition-[box-shadow]
|
||||
text-light-100 dark:text-dark-100 bg-light-20 dark:bg-dark-20 border border-light-40 dark:border-dark-40
|
||||
hover:bg-light-25 dark:hover:bg-dark-25 hover:border-light-50 dark:hover:border-dark-50
|
||||
focus:bg-light-30 dark:focus:bg-dark-30 focus:border-light-50 dark:focus:border-dark-50 focus:shadow-raw focus:shadow-light-50 dark:focus:shadow-dark-50 py-2 px-4" :to="{ name: 'campaign-id-edit', params: { id: 'new' } }">Créer ma campagne</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else>
|
||||
<span>Erreur de chargement</span>
|
||||
<span>{{ error?.message }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import { eq } from 'drizzle-orm';
|
||||
import useDatabase from '~/composables/useDatabase';
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const db = useDatabase();
|
||||
|
||||
const session = await getUserSession(e);
|
||||
|
||||
if(!session || !session.user)
|
||||
{
|
||||
setResponseStatus(e, 401);
|
||||
return;
|
||||
}
|
||||
|
||||
return db.query.campaignTable.findMany({
|
||||
with: {
|
||||
members: { with: { member: { columns: { username: true, id: true } } }, columns: { } },
|
||||
characters: { where: ({ id }) => eq(id, session.user!.id), columns: { character: true } },
|
||||
owner: { columns: { username: true, id: true } }
|
||||
},
|
||||
}).sync();
|
||||
});
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import { z } from 'zod/v4';
|
||||
import useDatabase from '~/composables/useDatabase';
|
||||
import { campaignMembersTable, campaignTable } from '~/db/schema';
|
||||
import { CampaignValidation } from '~/shared/campaign.util';
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const body = await readValidatedBody(e, CampaignValidation.extend({ id: z.unknown(), }).safeParse);
|
||||
if(!body.success)
|
||||
{
|
||||
setResponseStatus(e, 400);
|
||||
return body.error.message;
|
||||
}
|
||||
|
||||
const session = await getUserSession(e);
|
||||
if(!session.user || session.user.state !== 1)
|
||||
{
|
||||
setResponseStatus(e, 401);
|
||||
return;
|
||||
}
|
||||
|
||||
const db = useDatabase();
|
||||
try
|
||||
{
|
||||
const id = db.transaction((tx) => {
|
||||
const id = tx.insert(campaignTable).values({
|
||||
name: body.data.name,
|
||||
description: body.data.description,
|
||||
owner: session.user!.id,
|
||||
joinby: body.data.joinby,
|
||||
}).returning({ id: campaignTable.id }).get().id;
|
||||
|
||||
tx.insert(campaignMembersTable).values({ id, rights: 'dm', user: session.user!.id }).run();
|
||||
|
||||
return id;
|
||||
});
|
||||
|
||||
setResponseStatus(e, 201);
|
||||
return id;
|
||||
}
|
||||
catch(_e)
|
||||
{
|
||||
console.error(_e);
|
||||
|
||||
setResponseStatus(e, 500);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { and, eq } from 'drizzle-orm';
|
||||
import useDatabase from '~/composables/useDatabase';
|
||||
import { campaignTable } from '~/db/schema';
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const id = getRouterParam(e, "id");
|
||||
if(!id) return setResponseStatus(e, 400);
|
||||
|
||||
const session = await getUserSession(e);
|
||||
if(!session || !session.user) return setResponseStatus(e, 401);
|
||||
|
||||
const db = useDatabase();
|
||||
|
||||
try
|
||||
{
|
||||
const deleted = db.delete(campaignTable).where(and(eq(campaignTable.id, parseInt(id, 10)), eq(campaignTable.owner, session.user.id))).returning({ id: campaignTable.id }).get();
|
||||
|
||||
if(deleted) return setResponseStatus(e, 200);
|
||||
else return setResponseStatus(e, 404);
|
||||
}
|
||||
catch(_e)
|
||||
{
|
||||
return setResponseStatus(e, 500);
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import { eq } from 'drizzle-orm';
|
||||
import useDatabase from '~/composables/useDatabase';
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const id = getRouterParam(e, "id");
|
||||
if(!id)
|
||||
{
|
||||
setResponseStatus(e, 400);
|
||||
return;
|
||||
}
|
||||
|
||||
const session = await getUserSession(e);
|
||||
if(!session || !session.user)
|
||||
{
|
||||
setResponseStatus(e, 401);
|
||||
return;
|
||||
}
|
||||
|
||||
const db = useDatabase();
|
||||
const data = db.query.campaignTable.findFirst({
|
||||
with: {
|
||||
members: { with: { member: { columns: { username: true, id: true } } }, columns: { } },
|
||||
characters: { columns: { character: true } },
|
||||
owner: { columns: { username: true, id: true } }
|
||||
},
|
||||
where: ({ id: _id }) => eq(_id, parseInt(id, 10)),
|
||||
}).sync();
|
||||
|
||||
if(data && (data.owner === session.user.id || data.members.find(e => e.member?.id === session.user!.id))) return data;
|
||||
else if(!data) return setResponseStatus(e, 404);
|
||||
else return setResponseStatus(e, 403);
|
||||
});
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import { eq } from 'drizzle-orm';
|
||||
import useDatabase from '~/composables/useDatabase';
|
||||
import { campaignTable } from '~/db/schema';
|
||||
import { CampaignValidation } from '~/shared/campaign.util';
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const params = getRouterParam(e, "id");
|
||||
if(!params)
|
||||
{
|
||||
setResponseStatus(e, 400);
|
||||
return;
|
||||
}
|
||||
const id = parseInt(params, 10);
|
||||
|
||||
const body = await readValidatedBody(e, CampaignValidation.safeParse);
|
||||
if(!body.success)
|
||||
{
|
||||
setResponseStatus(e, 400);
|
||||
return body.error.message;
|
||||
}
|
||||
|
||||
const db = useDatabase();
|
||||
const old = db.select({ id: campaignTable.id, owner: campaignTable.owner }).from(campaignTable).where(eq(campaignTable.id, id)).get();
|
||||
|
||||
if(!old)
|
||||
{
|
||||
setResponseStatus(e, 404);
|
||||
return;
|
||||
}
|
||||
|
||||
const session = await getUserSession(e);
|
||||
if(!session.user || old.owner !== session.user.id || session.user.state !== 1)
|
||||
{
|
||||
setResponseStatus(e, 401);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction((tx) => {
|
||||
tx.update(campaignTable).set({
|
||||
name: body.data.name,
|
||||
description: body.data.description,
|
||||
joinby: body.data.joinby,
|
||||
}).where(eq(campaignTable.id, id)).run();
|
||||
});
|
||||
}
|
||||
catch(_e)
|
||||
{
|
||||
setResponseStatus(e, 500);
|
||||
return;
|
||||
}
|
||||
|
||||
setResponseStatus(e, 200);
|
||||
return;
|
||||
});
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import { and, eq, sql } from "drizzle-orm";
|
||||
import { campaignMembersTable, campaignTable } from "~/db/schema";
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const _id = getRouterParam(e, "id");
|
||||
if(!_id)
|
||||
{
|
||||
setResponseStatus(e, 400);
|
||||
return;
|
||||
}
|
||||
const id = parseInt(_id, 10);
|
||||
|
||||
const session = await getUserSession(e);
|
||||
if(!session || !session.user)
|
||||
{
|
||||
setResponseStatus(e, 401);
|
||||
return;
|
||||
}
|
||||
|
||||
const db = useDatabase();
|
||||
try
|
||||
{
|
||||
const campaign = db.select({ owner: campaignTable.owner }).from(campaignTable).where(and(eq(campaignTable.id, sql.placeholder('id')))).get({ id: id });
|
||||
if(campaign && campaign.owner === session.user.id) return setResponseStatus(e, 403);
|
||||
|
||||
const members = db.select({ id: campaignMembersTable.user }).from(campaignMembersTable).where(and(eq(campaignMembersTable.id, sql.placeholder('id')), eq(campaignMembersTable.user, sql.placeholder('user')))).get({ id: id, user: session.user.id });
|
||||
if(members) return setResponseStatus(e, 403);
|
||||
|
||||
db.insert(campaignMembersTable).values({ id, rights: 'player', user: session.user!.id });
|
||||
|
||||
return setResponseStatus(e, 200);
|
||||
}
|
||||
catch(_e)
|
||||
{
|
||||
return setResponseStatus(e, 500);
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { and, eq, sql } from "drizzle-orm";
|
||||
import { campaignMembersTable, campaignTable } from "~/db/schema";
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const id = getRouterParam(e, "id");
|
||||
if(!id)
|
||||
{
|
||||
setResponseStatus(e, 400);
|
||||
return;
|
||||
}
|
||||
|
||||
const session = await getUserSession(e);
|
||||
if(!session || !session.user)
|
||||
{
|
||||
setResponseStatus(e, 401);
|
||||
return;
|
||||
}
|
||||
|
||||
const db = useDatabase();
|
||||
try
|
||||
{
|
||||
const campaign = db.select({ owner: campaignTable.owner }).from(campaignTable).where(and(eq(campaignTable.id, sql.placeholder('id')))).get({ id: parseInt(id, 10) });
|
||||
|
||||
if(campaign && campaign.owner === session.user.id) return setResponseStatus(e, 403);
|
||||
|
||||
const deleted = db.delete(campaignMembersTable).where(and(eq(campaignMembersTable.id, parseInt(id, 10)), eq(campaignMembersTable.user, session.user.id))).returning({ id: campaignMembersTable.id }).get();
|
||||
|
||||
if(deleted) return setResponseStatus(e, 200);
|
||||
else return setResponseStatus(e, 404);
|
||||
}
|
||||
catch(_e)
|
||||
{
|
||||
return setResponseStatus(e, 500);
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import { z } from "zod/v4";
|
||||
|
||||
export const CampaignValidation = z.object({
|
||||
id: z.number(),
|
||||
name: z.string().nonempty(),
|
||||
description: z.string(),
|
||||
joinby: z.enum([ 'link', 'invite' ]),
|
||||
});
|
||||
Loading…
Reference in New Issue