massive state refactor, mostly fixes #3
This commit is contained in:
@@ -1,18 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { MembershipWithAccount } from '~~/lib/services/service.types';
|
|
||||||
|
|
||||||
const supabase = useSupabaseAuthClient();
|
const supabase = useSupabaseAuthClient();
|
||||||
const user = useSupabaseUser();
|
const user = useSupabaseUser();
|
||||||
const authStore = useAuthStore()
|
const accountStore = useAccountStore()
|
||||||
const { activeMembership } = storeToRefs(authStore);
|
const { dbUser, activeAccountId } = storeToRefs(accountStore);
|
||||||
|
|
||||||
const { $client } = useNuxtApp();
|
|
||||||
|
|
||||||
const { data: dbUser } = await $client.auth.getDBUser.useQuery();
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await authStore.initUser();
|
await accountStore.init()
|
||||||
});
|
});
|
||||||
|
|
||||||
async function signout() {
|
async function signout() {
|
||||||
@@ -35,12 +30,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Account Switching -->
|
<!-- Account Switching -->
|
||||||
<p v-if="(dbUser?.dbUser?.memberships) && (dbUser.dbUser.memberships.length > 1)">
|
<p v-if="dbUser?.memberships && dbUser?.memberships.length > 1">
|
||||||
<span>Switch Account.. </span>
|
<span>Switch Account.. </span>
|
||||||
<button :disabled="membership.pending" v-for="membership in dbUser?.dbUser.memberships" @click="authStore.changeActiveMembership(((membership as unknown) as MembershipWithAccount))"> <!-- This cast is infuriating -->
|
<button :disabled="membership.pending" v-for="membership in dbUser?.memberships" @click="accountStore.changeActiveAccount(membership.account_id)">
|
||||||
{{ membership.account.name }}
|
{{ membership.account.name }}
|
||||||
<span v-if="membership.pending">(pending)</span>
|
<span v-if="membership.pending">(pending)</span>
|
||||||
<span v-if="membership.account_id === activeMembership?.account_id">*</span>
|
<span v-if="membership.account_id === activeAccountId">*</span>
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
<hr>
|
<hr>
|
||||||
|
|||||||
@@ -2,24 +2,16 @@
|
|||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { ACCOUNT_ACCESS } from '@prisma/client';
|
import { ACCOUNT_ACCESS } from '@prisma/client';
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
|
||||||
const { activeMembership } = storeToRefs(authStore);
|
|
||||||
const accountStore = useAccountStore();
|
const accountStore = useAccountStore();
|
||||||
const { activeAccountMembers } = storeToRefs(accountStore)
|
const { activeMembership, activeAccountMembers } = storeToRefs(accountStore)
|
||||||
|
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const newAccountName = ref("");
|
const newAccountName = ref("");
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await authStore.initUser();
|
await accountStore.init();
|
||||||
});
|
});
|
||||||
|
|
||||||
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);
|
||||||
@@ -50,8 +42,8 @@
|
|||||||
[{{ accountMember.access }}]
|
[{{ accountMember.access }}]
|
||||||
<span v-if="accountMember.pending">(pending)</span>
|
<span v-if="accountMember.pending">(pending)</span>
|
||||||
<span v-if="accountMember.pending && activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN)"><button @click.prevent="accountStore.acceptPendingMembership(accountMember.id)">Accept Pending Membership</button></span>
|
<span v-if="accountMember.pending && activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN)"><button @click.prevent="accountStore.acceptPendingMembership(accountMember.id)">Accept Pending Membership</button></span>
|
||||||
<span v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN) && accountMember.access === ACCOUNT_ACCESS.READ_ONLY"><button @click.prevent="accountStore.changeUserAccessWithinAccount(accountMember.user.id, ACCOUNT_ACCESS.READ_WRITE)">Promote to Read/Write</button></span>
|
<span v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN) && accountMember.access === ACCOUNT_ACCESS.READ_ONLY && !accountMember.pending"><button @click.prevent="accountStore.changeUserAccessWithinAccount(accountMember.user.id, ACCOUNT_ACCESS.READ_WRITE)">Promote to Read/Write</button></span>
|
||||||
<span v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN) && accountMember.access === ACCOUNT_ACCESS.READ_WRITE"><button @click.prevent="accountStore.changeUserAccessWithinAccount(accountMember.user.id, ACCOUNT_ACCESS.ADMIN)">Promote to Admin</button></span>
|
<span v-if="activeMembership && (activeMembership.access === ACCOUNT_ACCESS.OWNER || activeMembership.access === ACCOUNT_ACCESS.ADMIN) && accountMember.access === ACCOUNT_ACCESS.READ_WRITE && !accountMember.pending"><button @click.prevent="accountStore.changeUserAccessWithinAccount(accountMember.user.id, ACCOUNT_ACCESS.ADMIN)">Promote to Admin</button></span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|||||||
@@ -5,21 +5,12 @@
|
|||||||
middleware: ['auth'],
|
middleware: ['auth'],
|
||||||
});
|
});
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
|
||||||
const { activeMembership } = storeToRefs(authStore);
|
|
||||||
|
|
||||||
const notesStore = useNotesStore();
|
const notesStore = useNotesStore();
|
||||||
const { notes } = storeToRefs(notesStore); // ensure the notes list is reactive
|
const { notes } = storeToRefs(notesStore); // ensure the notes list is reactive
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await authStore.initUser();
|
await notesStore.fetchNotesForCurrentUser();
|
||||||
});
|
});
|
||||||
|
|
||||||
watchEffect(async () => {
|
|
||||||
if (activeMembership.value) {
|
|
||||||
await notesStore.fetchNotesForCurrentUser();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -4,8 +4,9 @@
|
|||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const {join_password} : {join_password?: string} = route.params;
|
const {join_password} : {join_password?: string} = route.params;
|
||||||
|
|
||||||
const { $client } = useNuxtApp();
|
const accountStore = useAccountStore();
|
||||||
|
|
||||||
|
const { $client } = useNuxtApp();
|
||||||
// this could probably be an elegant destructure here but I lost patience
|
// this could probably be an elegant destructure here but I lost patience
|
||||||
let account: AccountWithMembers | undefined;
|
let account: AccountWithMembers | undefined;
|
||||||
if(join_password){
|
if(join_password){
|
||||||
@@ -16,8 +17,8 @@
|
|||||||
const { data: dbUser } = await $client.auth.getDBUser.useQuery();
|
const { data: dbUser } = await $client.auth.getDBUser.useQuery();
|
||||||
|
|
||||||
async function doJoin(){
|
async function doJoin(){
|
||||||
if(dbUser.value?.dbUser && account){
|
if(account){
|
||||||
await $client.account.joinUserToAccountPending.useQuery({account_id: account.id, user_id: dbUser.value.dbUser.id});
|
await accountStore.joinUserToAccountPending(account.id);
|
||||||
} else {
|
} else {
|
||||||
console.log(`Unable to Join`)
|
console.log(`Unable to Join`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import AuthService from '~~/lib/services/auth.service';
|
|||||||
export async function createContext(event: H3Event){
|
export async function createContext(event: H3Event){
|
||||||
let user: User | null = null;
|
let user: User | null = null;
|
||||||
let dbUser: FullDBUser | null = null;
|
let dbUser: FullDBUser | null = null;
|
||||||
|
let activeAccountId: number | null = null;
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
user = await serverSupabaseUser(event);
|
user = await serverSupabaseUser(event);
|
||||||
@@ -20,11 +21,24 @@ export async function createContext(event: H3Event){
|
|||||||
dbUser = await authService.createUser(user.id, user.user_metadata.full_name?user.user_metadata.full_name:"no name supplied", user.email?user.email:"no@email.supplied" );
|
dbUser = await authService.createUser(user.id, user.user_metadata.full_name?user.user_metadata.full_name:"no name supplied", user.email?user.email:"no@email.supplied" );
|
||||||
console.log(`\n Created DB User \n ${JSON.stringify(dbUser)}\n`);
|
console.log(`\n Created DB User \n ${JSON.stringify(dbUser)}\n`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(dbUser){
|
||||||
|
const preferredAccountId = getCookie(event, 'preferred-active-account-id')
|
||||||
|
if(preferredAccountId && dbUser?.memberships.find(m => m.account_id === +preferredAccountId && !m.pending)){
|
||||||
|
activeAccountId = +preferredAccountId
|
||||||
|
} else {
|
||||||
|
const defaultActive = dbUser.memberships[0].account_id.toString();
|
||||||
|
setCookie(event, 'preferred-active-account-id', defaultActive, {expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365 * 10)});
|
||||||
|
activeAccountId = +defaultActive;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user,
|
user, // the Supabase User
|
||||||
dbUser,
|
dbUser, // the corresponding Database User
|
||||||
|
activeAccountId, // the account ID that is active for the user
|
||||||
|
event, // required to enable setCookie in accountRouter
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,93 +1,96 @@
|
|||||||
import { router, adminProcedure, publicProcedure } from '../trpc'
|
import { router, adminProcedure, publicProcedure, protectedProcedure } from '../trpc'
|
||||||
import { ACCOUNT_ACCESS } from '@prisma/client';
|
import { ACCOUNT_ACCESS } from '@prisma/client';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import AccountService from '~~/lib/services/account.service';
|
import AccountService from '~~/lib/services/account.service';
|
||||||
import { MembershipWithAccount } from '~~/lib/services/service.types';
|
import { MembershipWithAccount } from '~~/lib/services/service.types';
|
||||||
|
|
||||||
|
/*
|
||||||
|
Note on proliferation of Bang syntax... adminProcedure throws if either the ctx.dbUser or the ctx.activeAccountId is not available but the compiler can't figure that out so bang quiesces the null warning
|
||||||
|
*/
|
||||||
export const accountRouter = router({
|
export const accountRouter = router({
|
||||||
|
getDBUser: protectedProcedure
|
||||||
|
.query(({ ctx }) => {
|
||||||
|
return {
|
||||||
|
dbUser: ctx.dbUser,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
getActiveAccountId: protectedProcedure
|
||||||
|
.query(({ ctx }) => {
|
||||||
|
return {
|
||||||
|
activeAccountId: ctx.activeAccountId,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
changeActiveAccount: adminProcedure
|
||||||
|
.input(z.object({ account_id: z.number() }))
|
||||||
|
.mutation(async ({ ctx, input }) => {
|
||||||
|
ctx.activeAccountId = input.account_id;
|
||||||
|
setCookie(ctx.event, 'preferred-active-account-id', input.account_id.toString(), {expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365 * 10)});
|
||||||
|
}),
|
||||||
changeAccountName: adminProcedure
|
changeAccountName: adminProcedure
|
||||||
.input(z.object({ account_id: z.number(), new_name: z.string() }))
|
.input(z.object({ new_name: z.string() }))
|
||||||
.query(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const accountService = new AccountService();
|
const accountService = new AccountService();
|
||||||
const account = await accountService.changeAccountName(input.account_id, input.new_name);
|
const account = await accountService.changeAccountName(ctx.activeAccountId!, input.new_name);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
account,
|
account,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
rotateJoinPassword: adminProcedure
|
rotateJoinPassword: adminProcedure
|
||||||
.input(z.object({ account_id: z.number() }))
|
.mutation(async ({ ctx }) => {
|
||||||
.query(async ({ ctx, input }) => {
|
|
||||||
const accountService = new AccountService();
|
const accountService = new AccountService();
|
||||||
const account = await accountService.rotateJoinPassword(input.account_id);
|
const account = await accountService.rotateJoinPassword(ctx.activeAccountId!);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
account,
|
account,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
getAccountByJoinPassword: publicProcedure
|
getAccountByJoinPassword: publicProcedure
|
||||||
.input(z.object({ join_password: z.string() }))
|
.input(z.object({ join_password: z.string() }))
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ input }) => {
|
||||||
const accountService = new AccountService();
|
const accountService = new AccountService();
|
||||||
const account = await accountService.getAccountByJoinPassword(input.join_password);
|
const account = await accountService.getAccountByJoinPassword(input.join_password);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
account,
|
account,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
joinUserToAccount: adminProcedure
|
joinUserToAccountPending: publicProcedure // this uses a passed account id rather than using the active account because user is usually active on their personal or some other account when they attempt to join a new account
|
||||||
.input(z.object({ account_id: z.number(), user_id: z.number() }))
|
.input(z.object({ account_id: z.number(), user_id: z.number() }))
|
||||||
.query(async ({ ctx, input }) => {
|
.mutation(async ({ input }) => {
|
||||||
const accountService = new AccountService();
|
const accountService = new AccountService();
|
||||||
const membership: MembershipWithAccount| null = (ctx.dbUser?.id)?await accountService.joinUserToAccount(input.user_id, input.account_id, false):null;
|
const membership: MembershipWithAccount = await accountService.joinUserToAccount(input.user_id, input.account_id, true);
|
||||||
return {
|
|
||||||
membership,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
joinUserToAccountPending: publicProcedure
|
|
||||||
.input(z.object({ account_id: z.number(), user_id: z.number() }))
|
|
||||||
.query(async ({ ctx, input }) => {
|
|
||||||
const accountService = new AccountService();
|
|
||||||
const membership: MembershipWithAccount| null = (ctx.dbUser?.id)?await accountService.joinUserToAccount(input.user_id, input.account_id, true):null;
|
|
||||||
return {
|
return {
|
||||||
membership,
|
membership,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
acceptPendingMembership: adminProcedure
|
acceptPendingMembership: adminProcedure
|
||||||
.input(z.object({ account_id: z.number(), membership_id: z.number() }))
|
.input(z.object({ membership_id: z.number() }))
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const accountService = new AccountService();
|
const accountService = new AccountService();
|
||||||
const membership: MembershipWithAccount| null = (ctx.dbUser?.id)?await accountService.acceptPendingMembership(input.account_id, input.membership_id):null;
|
const membership: MembershipWithAccount = await accountService.acceptPendingMembership(ctx.activeAccountId!, input.membership_id);
|
||||||
return {
|
return {
|
||||||
membership,
|
membership,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
changeUserAccessWithinAccount: adminProcedure
|
changeUserAccessWithinAccount: adminProcedure
|
||||||
.input(z.object({ user_id: z.number(), account_id: z.number(), access: z.enum([ACCOUNT_ACCESS.ADMIN, ACCOUNT_ACCESS.OWNER, ACCOUNT_ACCESS.READ_ONLY, ACCOUNT_ACCESS.READ_WRITE]) }))
|
.input(z.object({ user_id: z.number(), access: z.enum([ACCOUNT_ACCESS.ADMIN, ACCOUNT_ACCESS.OWNER, ACCOUNT_ACCESS.READ_ONLY, ACCOUNT_ACCESS.READ_WRITE]) }))
|
||||||
.query(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const accountService = new AccountService();
|
const accountService = new AccountService();
|
||||||
const membership = await accountService.changeUserAccessWithinAccount(input.user_id, input.account_id, input.access);
|
const membership = await accountService.changeUserAccessWithinAccount(input.user_id, ctx.activeAccountId!, input.access);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
membership,
|
membership,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
claimOwnershipOfAccount: adminProcedure
|
claimOwnershipOfAccount: adminProcedure
|
||||||
.input(z.object({ account_id: z.number() }))
|
.mutation(async ({ ctx }) => {
|
||||||
.query(async ({ ctx, input }) => {
|
|
||||||
const accountService = new AccountService();
|
const accountService = new AccountService();
|
||||||
const membership = await accountService.claimOwnershipOfAccount(ctx.dbUser!.id, input.account_id); // adminProcedure errors if ctx.dbUser is null so bang is ok here
|
const membership = await accountService.claimOwnershipOfAccount(ctx.dbUser!.id, ctx.activeAccountId!);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
membership,
|
membership,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
getAccountMembers: adminProcedure
|
getAccountMembers: adminProcedure
|
||||||
.input(z.object({ account_id: z.number() }))
|
.query(async ({ ctx }) => {
|
||||||
.query(async ({ ctx, input }) => {
|
|
||||||
const accountService = new AccountService();
|
const accountService = new AccountService();
|
||||||
const memberships = await accountService.getAccountMembers(input.account_id);
|
const memberships = await accountService.getAccountMembers(ctx.activeAccountId!);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
memberships,
|
memberships,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,9 @@ import { z } from 'zod';
|
|||||||
|
|
||||||
export const notesRouter = router({
|
export const notesRouter = router({
|
||||||
getForCurrentUser: protectedProcedure
|
getForCurrentUser: protectedProcedure
|
||||||
.input(z.object({ account_id: z.number() }))
|
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const notesService = new NotesService();
|
const notesService = new NotesService();
|
||||||
const notes = await notesService.getNotesForAccountId(input.account_id);
|
const notes = (ctx.activeAccountId)?await notesService.getNotesForAccountId(ctx.activeAccountId):[];
|
||||||
return {
|
return {
|
||||||
notes,
|
notes,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
*/
|
*/
|
||||||
import { initTRPC, TRPCError } from '@trpc/server'
|
import { initTRPC, TRPCError } from '@trpc/server'
|
||||||
import { Context } from './context';
|
import { Context } from './context';
|
||||||
import { z } from 'zod';
|
|
||||||
import { ACCOUNT_ACCESS } from '@prisma/client';
|
import { ACCOUNT_ACCESS } from '@prisma/client';
|
||||||
import superjson from 'superjson';
|
import superjson from 'superjson';
|
||||||
|
|
||||||
@@ -32,14 +31,11 @@ const isAuthed = t.middleware(({ next, ctx }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const isAdminForInputAccountId = t.middleware(({ next, rawInput, ctx }) => {
|
const isAdminForInputAccountId = t.middleware(({ next, rawInput, ctx }) => {
|
||||||
if (!ctx.dbUser) {
|
if (!ctx.dbUser || !ctx.activeAccountId) {
|
||||||
throw new TRPCError({ code: 'UNAUTHORIZED' });
|
throw new TRPCError({ code: 'UNAUTHORIZED' });
|
||||||
}
|
}
|
||||||
const result = z.object({ account_id: z.number() }).safeParse(rawInput);
|
const activeMembership = ctx.dbUser.memberships.find(membership => membership.account_id == ctx.activeAccountId);
|
||||||
if (!result.success) throw new TRPCError({ code: 'BAD_REQUEST' });
|
if(!activeMembership || (activeMembership?.access !== ACCOUNT_ACCESS.ADMIN && activeMembership?.access !== ACCOUNT_ACCESS.OWNER)) {
|
||||||
const { account_id } = result.data;
|
|
||||||
const test_membership = ctx.dbUser.memberships.find(membership => membership.account_id == account_id);
|
|
||||||
if(!test_membership || (test_membership?.access !== ACCOUNT_ACCESS.ADMIN && test_membership?.access !== ACCOUNT_ACCESS.OWNER)) {
|
|
||||||
throw new TRPCError({ code: 'UNAUTHORIZED' });
|
throw new TRPCError({ code: 'UNAUTHORIZED' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,43 +1,87 @@
|
|||||||
import { ACCOUNT_ACCESS } from ".prisma/client"
|
import { ACCOUNT_ACCESS } from ".prisma/client"
|
||||||
import { defineStore } from "pinia"
|
import { defineStore } from "pinia"
|
||||||
import { MembershipWithUser } from "~~/lib/services/service.types";
|
import { FullDBUser, MembershipWithUser } from "~~/lib/services/service.types";
|
||||||
|
|
||||||
import { useAuthStore } from './auth.store'
|
/*
|
||||||
|
This store manages User and Account state including the ActiveAccount
|
||||||
|
It is used in the Account administration page and the header due to it's account switching features.
|
||||||
|
|
||||||
|
Note) Other pages don't need this state as the dbUser and activeAccount are available on the TRPC Context
|
||||||
|
so that other routers can use them to filter results to the active user and account transparently.
|
||||||
|
|
||||||
|
[State Map]
|
||||||
|
|
||||||
|
(supabase) user -> dbUser <-- this.dbUser
|
||||||
|
|
|
||||||
|
|
|
||||||
|
membership-----membership------membership* <-- this.dbUser.memberships (*this.activeMembership)
|
||||||
|
| | |
|
||||||
|
account account acccount** <--**(id=this.activeAccountId)
|
||||||
|
|
|
||||||
|
membership-----membership------membership <-- this.activeAccountMembers
|
||||||
|
| | |
|
||||||
|
account account acccount*
|
||||||
|
*/
|
||||||
interface State {
|
interface State {
|
||||||
|
dbUser: FullDBUser | null,
|
||||||
|
activeAccountId: number | null,
|
||||||
activeAccountMembers: MembershipWithUser[]
|
activeAccountMembers: MembershipWithUser[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAccountStore = defineStore('account', {
|
export const useAccountStore = defineStore('account', {
|
||||||
state: (): State => {
|
state: (): State => {
|
||||||
return {
|
return {
|
||||||
|
dbUser: null,
|
||||||
|
activeAccountId: null,
|
||||||
activeAccountMembers: [],
|
activeAccountMembers: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getters: {
|
||||||
|
activeMembership: (state) => state.dbUser?.memberships.find(m => m.account_id === state.activeAccountId)
|
||||||
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async getActiveAccountMembers(){
|
async init(){
|
||||||
const authStore = useAuthStore();
|
|
||||||
if(!authStore.activeMembership) { return; }
|
|
||||||
const { $client } = useNuxtApp();
|
const { $client } = useNuxtApp();
|
||||||
const { data: members } = await $client.account.getAccountMembers.useQuery({account_id: authStore.activeMembership.account_id});
|
if(!this.dbUser){
|
||||||
if(members.value?.memberships){
|
const { dbUser } = await $client.auth.getDBUser.query();
|
||||||
this.activeAccountMembers = members.value?.memberships;
|
if(dbUser){
|
||||||
|
this.dbUser = dbUser;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!this.activeAccountId){
|
||||||
|
const { activeAccountId } = await $client.account.getActiveAccountId.query();
|
||||||
|
if(activeAccountId){
|
||||||
|
this.activeAccountId = activeAccountId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(this.activeAccountMembers.length == 0){
|
||||||
|
await this.getActiveAccountMembers();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async changeAccountName(new_name: string){
|
async getActiveAccountMembers(){
|
||||||
const authStore = useAuthStore();
|
|
||||||
if(!authStore.activeMembership) { return; }
|
|
||||||
const { $client } = useNuxtApp();
|
const { $client } = useNuxtApp();
|
||||||
const { data: account } = await $client.account.changeAccountName.useQuery({account_id: authStore.activeMembership.account_id, new_name});
|
const { data: memberships } = await $client.account.getAccountMembers.useQuery();
|
||||||
if(account.value?.account){
|
if(memberships.value?.memberships){
|
||||||
authStore.activeMembership.account = account.value.account;
|
this.activeAccountMembers = memberships.value?.memberships;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async changeActiveAccount(account_id: number){
|
||||||
|
const { $client } = useNuxtApp();
|
||||||
|
this.activeAccountId = account_id;
|
||||||
|
await $client.account.changeActiveAccount.mutate({account_id}); // sets active account on context for other routers and sets the preference in a cookie
|
||||||
|
await this.getActiveAccountMembers(); // these relate to the active account and need to ber re-fetched
|
||||||
|
},
|
||||||
|
async changeAccountName(new_name: string){
|
||||||
|
if(!this.activeMembership){ return; }
|
||||||
|
const { $client } = useNuxtApp();
|
||||||
|
const { account } = await $client.account.changeAccountName.mutate({ new_name });
|
||||||
|
if(account){
|
||||||
|
this.activeMembership.account.name = account.name;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async acceptPendingMembership(membership_id: number){
|
async acceptPendingMembership(membership_id: number){
|
||||||
const authStore = useAuthStore();
|
|
||||||
if(!authStore.activeMembership) { return; }
|
|
||||||
const { $client } = useNuxtApp();
|
const { $client } = useNuxtApp();
|
||||||
const { data: membership } = await $client.account.acceptPendingMembership.useQuery({account_id: authStore.activeMembership.account_id, membership_id});
|
const { data: membership } = await $client.account.acceptPendingMembership.useQuery({ membership_id });
|
||||||
|
|
||||||
if(membership.value && membership.value.membership?.pending === false){
|
if(membership.value && membership.value.membership?.pending === false){
|
||||||
for(const m of this.activeAccountMembers){
|
for(const m of this.activeAccountMembers){
|
||||||
@@ -48,46 +92,42 @@ export const useAccountStore = defineStore('account', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async rotateJoinPassword(){
|
async rotateJoinPassword(){
|
||||||
const authStore = useAuthStore();
|
|
||||||
if(!authStore.activeMembership) { return; }
|
|
||||||
const { $client } = useNuxtApp();
|
const { $client } = useNuxtApp();
|
||||||
const { data: account } = await $client.account.rotateJoinPassword.useQuery({account_id: authStore.activeMembership.account_id});
|
const { account } = await $client.account.rotateJoinPassword.mutate();
|
||||||
if(account.value?.account){
|
if(account && this.activeMembership){
|
||||||
authStore.activeMembership.account = account.value.account;
|
this.activeMembership.account = account;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async joinUserToAccount(user_id: number){
|
async joinUserToAccountPending(account_id: number){
|
||||||
const authStore = useAuthStore();
|
if(!this.dbUser) { return; }
|
||||||
if(!authStore.activeMembership) { return; }
|
|
||||||
const { $client } = useNuxtApp();
|
const { $client } = useNuxtApp();
|
||||||
const { data: membership } = await $client.account.joinUserToAccount.useQuery({account_id: authStore.activeMembership.account_id, user_id});
|
const { membership } = await $client.account.joinUserToAccountPending.mutate({account_id, user_id: this.dbUser.id});
|
||||||
if(membership.value?.membership){
|
if(membership && this.activeMembership){
|
||||||
authStore.activeMembership = membership.value.membership;
|
this.dbUser?.memberships.push(membership);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async changeUserAccessWithinAccount(user_id: number, access: ACCOUNT_ACCESS){
|
async changeUserAccessWithinAccount(user_id: number, access: ACCOUNT_ACCESS){
|
||||||
const authStore = useAuthStore();
|
|
||||||
if(!authStore.activeMembership) { return; }
|
|
||||||
const { $client } = useNuxtApp();
|
const { $client } = useNuxtApp();
|
||||||
const { data: membership } = await $client.account.changeUserAccessWithinAccount.useQuery({account_id: authStore.activeMembership.account_id, user_id, access});
|
const { membership } = await $client.account.changeUserAccessWithinAccount.mutate({ user_id, access });
|
||||||
if(membership.value?.membership){
|
if(membership){
|
||||||
for(const m of this.activeAccountMembers){
|
for(const m of this.activeAccountMembers){
|
||||||
if(m.id === membership.value?.membership.id){
|
if(m.id === membership.id){
|
||||||
m.access = membership.value?.membership.access;
|
m.access = membership.access;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async claimOwnershipOfAccount(){
|
async claimOwnershipOfAccount(){
|
||||||
const authStore = useAuthStore();
|
|
||||||
if(!authStore.activeMembership) { return; }
|
|
||||||
const { $client } = useNuxtApp();
|
const { $client } = useNuxtApp();
|
||||||
const { data: membership } = await $client.account.claimOwnershipOfAccount.useQuery({account_id: authStore.activeMembership.account_id});
|
const { membership } = await $client.account.claimOwnershipOfAccount.mutate();
|
||||||
if(membership.value?.membership){
|
if(membership){
|
||||||
authStore.activeMembership.access = membership.value.membership.access;
|
if(this.activeMembership){
|
||||||
|
this.activeMembership.access = membership.access;
|
||||||
|
}
|
||||||
|
|
||||||
for(const m of this.activeAccountMembers){
|
for(const m of this.activeAccountMembers){
|
||||||
if(m.id === membership.value?.membership.id){
|
if(m.id === membership.id){
|
||||||
m.access = membership.value?.membership.access;
|
m.access = membership.access;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
import { defineStore } from "pinia"
|
|
||||||
import { FullDBUser, MembershipWithAccount } from "~~/lib/services/service.types";
|
|
||||||
|
|
||||||
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;
|
|
||||||
const activeMemberships = dbUser.memberships.filter(m => !m.pending)
|
|
||||||
if(activeMemberships.length > 0){
|
|
||||||
this.activeMembership = activeMemberships[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async changeActiveMembership(membership: MembershipWithAccount) {
|
|
||||||
if(membership !== this.activeMembership){
|
|
||||||
this.activeMembership = membership;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
import { Note } from ".prisma/client"
|
import { Note } from ".prisma/client"
|
||||||
import { defineStore } from "pinia"
|
import { defineStore } from "pinia"
|
||||||
|
|
||||||
import { useAuthStore } from './auth.store'
|
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
notes: Note[]
|
notes: Note[]
|
||||||
}
|
}
|
||||||
@@ -15,12 +13,8 @@ export const useNotesStore = defineStore('notes', {
|
|||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async fetchNotesForCurrentUser() {
|
async fetchNotesForCurrentUser() {
|
||||||
const authStore = useAuthStore();
|
|
||||||
|
|
||||||
if(!authStore.activeMembership) { return; }
|
|
||||||
|
|
||||||
const { $client } = useNuxtApp();
|
const { $client } = useNuxtApp();
|
||||||
const { notes } = await $client.notes.getForCurrentUser.query({account_id: authStore.activeMembership.account_id});
|
const { notes } = await $client.notes.getForCurrentUser.query();
|
||||||
if(notes){
|
if(notes){
|
||||||
this.notes = notes;
|
this.notes = notes;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user