refactor and restructure pinia store and trpc routers + add members list to account store

This commit is contained in:
Michael Dausmann
2023-04-05 00:28:30 +10:00
parent 10b0d6da3d
commit f2bbe8596a
12 changed files with 214 additions and 119 deletions

View File

@@ -4,15 +4,15 @@
const supabase = useSupabaseAuthClient(); const supabase = useSupabaseAuthClient();
const user = useSupabaseUser(); const user = useSupabaseUser();
const store = useAppStore() const authStore = useAuthStore()
const { activeMembership } = storeToRefs(store); const { activeMembership } = storeToRefs(authStore);
const { $client } = useNuxtApp(); const { $client } = useNuxtApp();
const { data: dbUser } = await $client.userAccount.getDBUser.useQuery(); const { data: dbUser } = await $client.auth.getDBUser.useQuery();
onMounted(async () => { onMounted(async () => {
await store.initUser(); await authStore.initUser();
}); });
async function signout() { async function signout() {
@@ -37,7 +37,7 @@
<!-- Account Switching --> <!-- Account Switching -->
<p v-if="(dbUser?.dbUser?.memberships) && (dbUser.dbUser.memberships.length > 1)"> <p v-if="(dbUser?.dbUser?.memberships) && (dbUser.dbUser.memberships.length > 1)">
<span>Switch Account.. </span> <span>Switch Account.. </span>
<button v-for="membership in dbUser?.dbUser.memberships" @click="store.changeActiveMembership(((membership as unknown) as MembershipWithAccount))"> <!-- This cast is infuriating --> <button v-for="membership in dbUser?.dbUser.memberships" @click="authStore.changeActiveMembership(((membership as unknown) as MembershipWithAccount))"> <!-- This cast is infuriating -->
{{ membership.account.name }} {{ membership.account.name }}
<span v-if="membership.account_id === activeMembership?.account_id">*</span> <span v-if="membership.account_id === activeMembership?.account_id">*</span>
</button> </button>

View File

@@ -45,6 +45,15 @@ export default class UserAccountService {
}); });
} }
async getAccountMembers(account_id: number): Promise<MembershipWithUser[]> {
return prisma_client.membership.findMany({
where: { account_id },
include: {
user: true
}
});
}
async updateAccountStipeCustomerId (account_id: number, stripe_customer_id: string){ async updateAccountStipeCustomerId (account_id: number, stripe_customer_id: string){
return await prisma_client.account.update({ return await prisma_client.account.update({
where: { id: account_id }, where: { id: account_id },

View File

@@ -2,11 +2,20 @@
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { ACCOUNT_ACCESS } from '@prisma/client'; import { ACCOUNT_ACCESS } from '@prisma/client';
const store = useAppStore(); const authStore = useAuthStore();
const { activeMembership } = storeToRefs(store); const { activeMembership } = storeToRefs(authStore);
const accountStore = useAccountStore();
const { activeAccountMembers } = storeToRefs(accountStore)
const config = useRuntimeConfig(); const config = useRuntimeConfig();
const newAccountName = ref(""); const newAccountName = ref("");
watchEffect(async () => {
if (activeMembership.value) {
await accountStore.getActiveAccountMembers();
}
})
function formatDate(date: Date | undefined){ function formatDate(date: Date | undefined){
if(!date){ return ""; } if(!date){ return ""; }
return new Intl.DateTimeFormat('default', {dateStyle: 'long'}).format(date); return new Intl.DateTimeFormat('default', {dateStyle: 'long'}).format(date);
@@ -15,7 +24,7 @@
<template> <template>
<div> <div>
<h3>Account</h3> <h3>Account</h3>
<p>Name: {{ activeMembership?.account.name }} <span v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access !== ACCOUNT_ACCESS.ADMIN)"><input v-model="newAccountName" placeholder="Enter New Name"/><button @click.prevent="store.changeAccountName(newAccountName)">Change Name</button></span></p> <p>Name: {{ activeMembership?.account.name }} <span v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access !== ACCOUNT_ACCESS.ADMIN)"><input v-model="newAccountName" placeholder="Enter New Name"/><button @click.prevent="accountStore.changeAccountName(newAccountName)">Change Name</button></span></p>
<p>Current Period Ends: {{ formatDate(activeMembership?.account.current_period_ends) }}</p> <p>Current Period Ends: {{ formatDate(activeMembership?.account.current_period_ends) }}</p>
<p>Permitted Features: {{ activeMembership?.account.features }}</p> <p>Permitted Features: {{ activeMembership?.account.features }}</p>
<p>Maximum Notes: {{ activeMembership?.account.max_notes }}</p> <p>Maximum Notes: {{ activeMembership?.account.max_notes }}</p>
@@ -23,6 +32,11 @@
<p>Access Level: {{ activeMembership?.access }}</p> <p>Access Level: {{ activeMembership?.access }}</p>
<p>Plan: {{ activeMembership?.account.plan_name }}</p> <p>Plan: {{ activeMembership?.account.plan_name }}</p>
<h4>Members</h4>
<ul>
<li v-for="accountMember in activeAccountMembers">{{ accountMember.user.display_name }}</li>
</ul>
<template v-if="config.public.debugMode"> <template v-if="config.public.debugMode">
<p>******* Debug *******</p> <p>******* Debug *******</p>
<p>Account ID: {{ activeMembership?.account.id }}</p> <p>Account ID: {{ activeMembership?.account.id }}</p>
@@ -32,5 +46,11 @@
<p>Membership Id: {{ activeMembership?.id }}</p> <p>Membership Id: {{ activeMembership?.id }}</p>
<p>User Id: {{ activeMembership?.user_id }}</p> <p>User Id: {{ activeMembership?.user_id }}</p>
</template> </template>
<button @click.prevent="accountStore.changeAccountPlan(2)">Change active Account Plan to 2</button>
<button @click.prevent="accountStore.joinUserToAccount(4)">Join user 4 to active account</button>
<button @click.prevent="accountStore.changeUserAccessWithinAccount(4, 'OWNER')">Change user 4 access within account 5 to OWNER (SHOULD FAIL)</button>
<button @click.prevent="accountStore.changeUserAccessWithinAccount(4, 'ADMIN')">Change user 4 access within account 5 to ADMIN</button>
<button @click.prevent="accountStore.claimOwnershipOfAccount()">Claim Ownership of current account for current user</button>
</div> </div>
</template> </template>

View File

@@ -5,19 +5,21 @@
middleware: ['auth'], middleware: ['auth'],
}); });
const store = useAppStore(); const authStore = useAuthStore();
const { notes } = storeToRefs(store); // ensure the notes list is reactive const { activeMembership } = storeToRefs(authStore);
const notesStore = useNotesStore();
const { notes } = storeToRefs(notesStore); // ensure the notes list is reactive
watchEffect(async () => {
if (activeMembership.value) {
await notesStore.fetchNotesForCurrentUser();
}
})
</script> </script>
<template> <template>
<div> <div>
<h3>Notes Dashboard</h3> <h3>Notes Dashboard</h3>
<p v-for="note in notes">{{ note.note_text }}</p> <p v-for="note in notes">{{ note.note_text }}</p>
<button @click.prevent="store.changeAccountPlan(2)">Change active Account Plan to 2</button>
<button @click.prevent="store.joinUserToAccount(4)">Join user 4 to active account</button>
<button @click.prevent="store.changeUserAccessWithinAccount(4, 'OWNER')">Change user 4 access within account 5 to OWNER (SHOULD FAIL)</button>
<button @click.prevent="store.changeUserAccessWithinAccount(4, 'ADMIN')">Change user 4 access within account 5 to ADMIN</button>
<button @click.prevent="store.claimOwnershipOfAccount()">Claim Ownership of current account for current user</button>
</div> </div>
</template> </template>

View File

@@ -2,8 +2,8 @@
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { ACCOUNT_ACCESS } from '@prisma/client'; import { ACCOUNT_ACCESS } from '@prisma/client';
const store = useAppStore() const authStore = useAuthStore()
const { activeMembership } = storeToRefs(store); const { activeMembership } = storeToRefs(authStore);
</script> </script>
<template> <template>

View File

@@ -4,14 +4,16 @@
*/ */
import { createNuxtApiHandler } from 'trpc-nuxt' import { createNuxtApiHandler } from 'trpc-nuxt'
import { publicProcedure, router } from '~/server/trpc/trpc' import { router } from '~/server/trpc/trpc'
import { createContext } from '~~/server/trpc/context'; import { createContext } from '~~/server/trpc/context';
import { notesRouter } from '~~/server/trpc/routers/notes.router'; import { notesRouter } from '~~/server/trpc/routers/notes.router';
import { userAccountRouter } from '~~/server/trpc/routers/user.account.router'; import { authRouter } from '~~/server/trpc/routers/auth.router';
import { accountRouter } from '~~/server/trpc/routers/account.router';
export const appRouter = router({ export const appRouter = router({
notes: notesRouter, notes: notesRouter,
userAccount: userAccountRouter, auth: authRouter,
account: accountRouter,
}) })
// export only the type definition of the API // export only the type definition of the API

View File

@@ -1,15 +1,9 @@
import UserAccountService from '~~/lib/services/user.account.service'; import UserAccountService from '~~/lib/services/user.account.service';
import { protectedProcedure, router, adminProcedure } from '../trpc' import { router, adminProcedure } from '../trpc'
import { ACCOUNT_ACCESS } from '@prisma/client'; import { ACCOUNT_ACCESS } from '@prisma/client';
import { z } from 'zod'; import { z } from 'zod';
export const userAccountRouter = router({ export const accountRouter = router({
getDBUser: protectedProcedure
.query(({ ctx }) => {
return {
dbUser: ctx.dbUser,
}
}),
changeAccountName: adminProcedure changeAccountName: adminProcedure
.input(z.object({ account_id: z.number(), new_name: z.string() })) .input(z.object({ account_id: z.number(), new_name: z.string() }))
.query(async ({ ctx, input }) => { .query(async ({ ctx, input }) => {
@@ -59,4 +53,14 @@ export const userAccountRouter = router({
membership, membership,
} }
}), }),
getAccountMembers: adminProcedure
.input(z.object({ account_id: z.number() }))
.query(async ({ ctx, input }) => {
const uaService = new UserAccountService();
const memberships = await uaService.getAccountMembers(input.account_id);
return {
memberships,
}
}),
}) })

View File

@@ -0,0 +1,10 @@
import { protectedProcedure, router } from '../trpc'
export const authRouter = router({
getDBUser: protectedProcedure
.query(({ ctx }) => {
return {
dbUser: ctx.dbUser,
}
}),
})

73
stores/account.store.ts Normal file
View File

@@ -0,0 +1,73 @@
import { ACCOUNT_ACCESS } from ".prisma/client"
import { defineStore } from "pinia"
import { MembershipWithUser } from "~~/lib/services/user.account.service"
import { useAuthStore } from './auth.store'
interface State {
activeAccountMembers: MembershipWithUser[]
}
export const useAccountStore = defineStore('account', {
state: (): State => {
return {
activeAccountMembers: [],
}
},
actions: {
async getActiveAccountMembers(){
const authStore = useAuthStore();
if(!authStore.activeMembership) { return; }
const { $client } = useNuxtApp();
const { data: members } = await $client.account.getAccountMembers.useQuery({account_id: authStore.activeMembership.account_id});
if(members.value?.memberships){
this.activeAccountMembers = members.value?.memberships;
}
},
async changeAccountName(new_name: string){
const authStore = useAuthStore();
if(!authStore.activeMembership) { return; }
const { $client } = useNuxtApp();
const { data: account } = await $client.account.changeAccountName.useQuery({account_id: authStore.activeMembership.account_id, new_name});
if(account.value?.account){
authStore.activeMembership.account = account.value.account;
}
},
async changeAccountPlan(plan_id: number){
const authStore = useAuthStore();
if(!authStore.activeMembership) { return; }
const { $client } = useNuxtApp();
const { data: account } = await $client.account.changeAccountPlan.useQuery({account_id: authStore.activeMembership.account_id, plan_id});
if(account.value?.account){
authStore.activeMembership.account = account.value.account;
}
},
async joinUserToAccount(user_id: number){
const authStore = useAuthStore();
if(!authStore.activeMembership) { return; }
const { $client } = useNuxtApp();
const { data: membership } = await $client.account.joinUserToAccount.useQuery({account_id: authStore.activeMembership.account_id, user_id});
if(membership.value?.membership){
authStore.activeMembership = membership.value.membership;
}
},
async changeUserAccessWithinAccount(user_id: number, access: ACCOUNT_ACCESS){
const authStore = useAuthStore();
if(!authStore.activeMembership) { return; }
const { $client } = useNuxtApp();
const { data: membership } = await $client.account.changeUserAccessWithinAccount.useQuery({account_id: authStore.activeMembership.account_id, user_id, access});
if(membership.value?.membership){
authStore.activeMembership = membership.value.membership;
}
},
async claimOwnershipOfAccount(){
const authStore = useAuthStore();
if(!authStore.activeMembership) { return; }
const { $client } = useNuxtApp();
const { data: membership } = await $client.account.claimOwnershipOfAccount.useQuery({account_id: authStore.activeMembership.account_id});
if(membership.value?.membership){
authStore.activeMembership = membership.value.membership;
}
}
}
});

View File

@@ -1,89 +0,0 @@
import { ACCOUNT_ACCESS, Note } from ".prisma/client"
import { defineStore } from "pinia"
import { FullDBUser, MembershipWithAccount } from "~~/lib/services/user.account.service"
interface State {
dbUser?: FullDBUser
activeMembership: MembershipWithAccount | null
notes: Note[]
}
export const useAppStore = defineStore('app', {
state: (): State => {
return {
notes: [],
activeMembership: null
}
},
actions: {
async initUser() {
if(!this.dbUser || !this.activeMembership){
const { $client } = useNuxtApp();
const { dbUser } = await $client.userAccount.getDBUser.query();
if(dbUser){
this.dbUser = dbUser;
if(dbUser.memberships.length > 0){
this.activeMembership = dbUser.memberships[0];
await this.fetchNotesForCurrentUser();
}
}
}
},
async fetchNotesForCurrentUser() {
if(!this.activeMembership) { return; }
const { $client } = useNuxtApp();
const { notes } = await $client.notes.getForCurrentUser.query({account_id: this.activeMembership.account_id});
if(notes){
this.notes = notes;
}
},
async changeActiveMembership(membership: MembershipWithAccount) {
if(membership !== this.activeMembership){
this.activeMembership = membership;
await this.fetchNotesForCurrentUser();
}
},
async changeAccountName(new_name: string){
if(!this.activeMembership) { return; }
const { $client } = useNuxtApp();
const { data: account } = await $client.userAccount.changeAccountName.useQuery({account_id: this.activeMembership.account_id, new_name});
if(account.value?.account){
this.activeMembership.account = account.value.account;
}
},
async changeAccountPlan(plan_id: number){
if(!this.activeMembership) { return; }
const { $client } = useNuxtApp();
const { data: account } = await $client.userAccount.changeAccountPlan.useQuery({account_id: this.activeMembership.account_id, plan_id});
if(account.value?.account){
this.activeMembership.account = account.value.account;
}
},
async joinUserToAccount(user_id: number){
if(!this.activeMembership) { return; }
const { $client } = useNuxtApp();
const { data: membership } = await $client.userAccount.joinUserToAccount.useQuery({account_id: this.activeMembership.account_id, user_id});
if(membership.value?.membership){
this.activeMembership = membership.value.membership;
}
},
async changeUserAccessWithinAccount(user_id: number, access: ACCOUNT_ACCESS){
if(!this.activeMembership) { return; }
const { $client } = useNuxtApp();
const { data: membership } = await $client.userAccount.changeUserAccessWithinAccount.useQuery({account_id: this.activeMembership.account_id, user_id, access});
if(membership.value?.membership){
this.activeMembership = membership.value.membership;
}
},
async claimOwnershipOfAccount(){
if(!this.activeMembership) { return; }
const { $client } = useNuxtApp();
const { data: membership } = await $client.userAccount.claimOwnershipOfAccount.useQuery({account_id: this.activeMembership.account_id});
if(membership.value?.membership){
this.activeMembership = membership.value.membership;
}
}
}
});

35
stores/auth.store.ts Normal file
View File

@@ -0,0 +1,35 @@
import { defineStore } from "pinia"
import { FullDBUser, MembershipWithAccount } from "~~/lib/services/user.account.service"
interface State {
dbUser?: FullDBUser
activeMembership: MembershipWithAccount | null
}
export const useAuthStore = defineStore('auth', {
state: (): State => {
return {
activeMembership: null
}
},
actions: {
async initUser() {
if(!this.dbUser || !this.activeMembership){
const { $client } = useNuxtApp();
const { dbUser } = await $client.auth.getDBUser.query();
if(dbUser){
this.dbUser = dbUser;
if(dbUser.memberships.length > 0){
this.activeMembership = dbUser.memberships[0];
}
}
}
},
async changeActiveMembership(membership: MembershipWithAccount) {
if(membership !== this.activeMembership){
this.activeMembership = membership;
}
},
}
});

29
stores/notes.store.ts Normal file
View File

@@ -0,0 +1,29 @@
import { Note } from ".prisma/client"
import { defineStore } from "pinia"
import { useAuthStore } from './auth.store'
interface State {
notes: Note[]
}
export const useNotesStore = defineStore('notes', {
state: (): State => {
return {
notes: [],
}
},
actions: {
async fetchNotesForCurrentUser() {
const authStore = useAuthStore();
if(!authStore.activeMembership) { return; }
const { $client } = useNuxtApp();
const { notes } = await $client.notes.getForCurrentUser.query({account_id: authStore.activeMembership.account_id});
if(notes){
this.notes = notes;
}
},
}
});