refactor service layer
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { MembershipWithAccount } from '~~/lib/services/user.account.service';
|
|
||||||
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();
|
||||||
|
|||||||
@@ -1,41 +1,8 @@
|
|||||||
import { ACCOUNT_ACCESS, User, Membership, Account, Plan } from '@prisma/client';
|
import { ACCOUNT_ACCESS } from '@prisma/client';
|
||||||
import prisma_client from '~~/prisma/prisma.client';
|
import prisma_client from '~~/prisma/prisma.client';
|
||||||
import { UtilService } from './util.service';
|
import { AccountWithMembers, MembershipWithAccount, MembershipWithUser } from './service.types';
|
||||||
const config = useRuntimeConfig();
|
|
||||||
|
|
||||||
|
|
||||||
export type MembershipWithAccount = (Membership & {account: Account});
|
|
||||||
export type FullDBUser = (User & { memberships: MembershipWithAccount[]; });
|
|
||||||
export type MembershipWithUser = (Membership & { user: User});
|
|
||||||
export type AccountWithMembers = (Account & {members: MembershipWithUser[]});
|
|
||||||
export default class UserAccountService {
|
|
||||||
async getUserBySupabaseId(supabase_uid: string): Promise<FullDBUser | null> {
|
|
||||||
return prisma_client.user.findFirst({
|
|
||||||
where: { supabase_uid },
|
|
||||||
include: { memberships: {include: {
|
|
||||||
account: true
|
|
||||||
}}}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getFullUserBySupabaseId(supabase_uid: string): Promise<FullDBUser | null> {
|
|
||||||
return prisma_client.user.findFirst({
|
|
||||||
where: { supabase_uid },
|
|
||||||
include: { memberships: {include: {
|
|
||||||
account: true
|
|
||||||
}}}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getUserById(user_id: number): Promise<FullDBUser | null> {
|
|
||||||
return prisma_client.user.findFirstOrThrow({
|
|
||||||
where: { id: user_id },
|
|
||||||
include: { memberships: {include: {
|
|
||||||
account: true
|
|
||||||
}}}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
export default class AccountService {
|
||||||
async getAccountById(account_id: number): Promise<AccountWithMembers> {
|
async getAccountById(account_id: number): Promise<AccountWithMembers> {
|
||||||
return prisma_client.account.findFirstOrThrow({
|
return prisma_client.account.findFirstOrThrow({
|
||||||
where: { id: account_id },
|
where: { id: account_id },
|
||||||
@@ -99,40 +66,6 @@ export default class UserAccountService {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async createUser( supabase_uid: string, display_name: string, email: string ): Promise<FullDBUser | null> {
|
|
||||||
const trialPlan = await prisma_client.plan.findFirstOrThrow({ where: { name: config.initialPlanName}});
|
|
||||||
return prisma_client.user.create({
|
|
||||||
data:{
|
|
||||||
supabase_uid: supabase_uid,
|
|
||||||
display_name: display_name,
|
|
||||||
email: email,
|
|
||||||
memberships: {
|
|
||||||
create: {
|
|
||||||
account: {
|
|
||||||
create: {
|
|
||||||
name: display_name,
|
|
||||||
current_period_ends: UtilService.addMonths(new Date(), config.initialPlanActiveMonths),
|
|
||||||
plan_id: trialPlan.id,
|
|
||||||
features: trialPlan.features,
|
|
||||||
max_notes: trialPlan.max_notes,
|
|
||||||
max_members: trialPlan.max_members,
|
|
||||||
plan_name: trialPlan.name,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
access: ACCOUNT_ACCESS.OWNER
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
include: { memberships: {include: {
|
|
||||||
account: true
|
|
||||||
}}}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteUser(user_id: number) {
|
|
||||||
return prisma_client.user.delete({ where: { id: user_id } });
|
|
||||||
}
|
|
||||||
|
|
||||||
async joinUserToAccount(user_id: number, account_id: number): Promise<MembershipWithAccount> {
|
async joinUserToAccount(user_id: number, account_id: number): Promise<MembershipWithAccount> {
|
||||||
const account = await prisma_client.account.findUnique({
|
const account = await prisma_client.account.findUnique({
|
||||||
where: {
|
where: {
|
||||||
60
lib/services/auth.service.ts
Normal file
60
lib/services/auth.service.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { ACCOUNT_ACCESS } from '@prisma/client';
|
||||||
|
import prisma_client from '~~/prisma/prisma.client';
|
||||||
|
import { FullDBUser } from './service.types';
|
||||||
|
import { UtilService } from './util.service';
|
||||||
|
|
||||||
|
const config = useRuntimeConfig();
|
||||||
|
|
||||||
|
export default class AuthService {
|
||||||
|
async getFullUserBySupabaseId(supabase_uid: string): Promise<FullDBUser | null> {
|
||||||
|
return prisma_client.user.findFirst({
|
||||||
|
where: { supabase_uid },
|
||||||
|
include: { memberships: {include: {
|
||||||
|
account: true
|
||||||
|
}}}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUserById(user_id: number): Promise<FullDBUser | null> {
|
||||||
|
return prisma_client.user.findFirstOrThrow({
|
||||||
|
where: { id: user_id },
|
||||||
|
include: { memberships: {include: {
|
||||||
|
account: true
|
||||||
|
}}}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async createUser( supabase_uid: string, display_name: string, email: string ): Promise<FullDBUser | null> {
|
||||||
|
const trialPlan = await prisma_client.plan.findFirstOrThrow({ where: { name: config.initialPlanName}});
|
||||||
|
return prisma_client.user.create({
|
||||||
|
data:{
|
||||||
|
supabase_uid: supabase_uid,
|
||||||
|
display_name: display_name,
|
||||||
|
email: email,
|
||||||
|
memberships: {
|
||||||
|
create: {
|
||||||
|
account: {
|
||||||
|
create: {
|
||||||
|
name: display_name,
|
||||||
|
current_period_ends: UtilService.addMonths(new Date(), config.initialPlanActiveMonths),
|
||||||
|
plan_id: trialPlan.id,
|
||||||
|
features: trialPlan.features,
|
||||||
|
max_notes: trialPlan.max_notes,
|
||||||
|
max_members: trialPlan.max_members,
|
||||||
|
plan_name: trialPlan.name,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
access: ACCOUNT_ACCESS.OWNER
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
include: { memberships: {include: {
|
||||||
|
account: true
|
||||||
|
}}}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteUser(user_id: number) {
|
||||||
|
return prisma_client.user.delete({ where: { id: user_id } });
|
||||||
|
}
|
||||||
|
}
|
||||||
5
lib/services/service.types.ts
Normal file
5
lib/services/service.types.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { User, Membership, Account } from '@prisma/client';
|
||||||
|
export type MembershipWithAccount = (Membership & {account: Account});
|
||||||
|
export type FullDBUser = (User & { memberships: MembershipWithAccount[]; });
|
||||||
|
export type MembershipWithUser = (Membership & { user: User});
|
||||||
|
export type AccountWithMembers = (Account & {members: MembershipWithUser[]});
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ACCOUNT_ACCESS } from '@prisma/client';
|
import { ACCOUNT_ACCESS } from '@prisma/client';
|
||||||
import Stripe from 'stripe';
|
import Stripe from 'stripe';
|
||||||
import UserAccountService, { AccountWithMembers } from '~~/lib/services/user.account.service';
|
import AccountService from '~~/lib/services/account.service';
|
||||||
|
import { AccountWithMembers } from '~~/lib/services/service.types';
|
||||||
|
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const stripe = new Stripe(config.stripeSecretKey, { apiVersion: '2022-11-15' });
|
const stripe = new Stripe(config.stripeSecretKey, { apiVersion: '2022-11-15' });
|
||||||
@@ -11,8 +12,8 @@ export default defineEventHandler(async (event) => {
|
|||||||
account_id = +account_id
|
account_id = +account_id
|
||||||
console.log(`session.post.ts recieved price_id:${price_id}, account_id:${account_id}`);
|
console.log(`session.post.ts recieved price_id:${price_id}, account_id:${account_id}`);
|
||||||
|
|
||||||
const userService = new UserAccountService();
|
const accountService = new AccountService();
|
||||||
const account: AccountWithMembers = await userService.getAccountById(account_id);
|
const account: AccountWithMembers = await accountService.getAccountById(account_id);
|
||||||
let customer_id: string
|
let customer_id: string
|
||||||
if(!account.stripe_customer_id){
|
if(!account.stripe_customer_id){
|
||||||
// need to pre-emptively create a Stripe user for this account so we know who they are when the webhook comes back
|
// need to pre-emptively create a Stripe user for this account so we know who they are when the webhook comes back
|
||||||
@@ -20,7 +21,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
console.log(`Creating account with name ${account.name} and email ${owner?.user.email}`);
|
console.log(`Creating account with name ${account.name} and email ${owner?.user.email}`);
|
||||||
const customer = await stripe.customers.create({ name: account.name, email: owner?.user.email });
|
const customer = await stripe.customers.create({ name: account.name, email: owner?.user.email });
|
||||||
customer_id = customer.id;
|
customer_id = customer.id;
|
||||||
userService.updateAccountStipeCustomerId(account_id, customer.id);
|
accountService.updateAccountStipeCustomerId(account_id, customer.id);
|
||||||
} else {
|
} else {
|
||||||
customer_id = account.stripe_customer_id;
|
customer_id = account.stripe_customer_id;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Stripe from 'stripe';
|
import Stripe from 'stripe';
|
||||||
import UserAccountService from '~~/lib/services/user.account.service';
|
import AccountService from '~~/lib/services/account.service';
|
||||||
|
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const stripe = new Stripe(config.stripeSecretKey, { apiVersion: '2022-11-15' });
|
const stripe = new Stripe(config.stripeSecretKey, { apiVersion: '2022-11-15' });
|
||||||
@@ -36,13 +36,13 @@ export default defineEventHandler(async (event) => {
|
|||||||
throw createError({ statusCode: 400, statusMessage: `Error validating Webhook Event` });
|
throw createError({ statusCode: 400, statusMessage: `Error validating Webhook Event` });
|
||||||
}
|
}
|
||||||
|
|
||||||
const userService = new UserAccountService();
|
const accountService = new AccountService();
|
||||||
|
|
||||||
let current_period_ends: Date = new Date(subscription.current_period_end * 1000);
|
let current_period_ends: Date = new Date(subscription.current_period_end * 1000);
|
||||||
current_period_ends.setDate(current_period_ends.getDate() + config.subscriptionGraceDays);
|
current_period_ends.setDate(current_period_ends.getDate() + config.subscriptionGraceDays);
|
||||||
|
|
||||||
console.log(`updating stripe sub details subscription.current_period_end:${subscription.current_period_end}, subscription.id:${subscription.id}, stripe_product_id:${stripe_product_id}`);
|
console.log(`updating stripe sub details subscription.current_period_end:${subscription.current_period_end}, subscription.id:${subscription.id}, stripe_product_id:${stripe_product_id}`);
|
||||||
userService.updateStripeSubscriptionDetailsForAccount(subscription.customer.toString(), subscription.id, current_period_ends, stripe_product_id);
|
accountService.updateStripeSubscriptionDetailsForAccount(subscription.customer.toString(), subscription.id, current_period_ends, stripe_product_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return `handled ${stripeEvent.type}.`;
|
return `handled ${stripeEvent.type}.`;
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import { inferAsyncReturnType, TRPCError } from '@trpc/server'
|
|||||||
import { H3Event } from 'h3';
|
import { H3Event } from 'h3';
|
||||||
import { serverSupabaseUser } from '#supabase/server'
|
import { serverSupabaseUser } from '#supabase/server'
|
||||||
import { User } from '@supabase/supabase-js';
|
import { User } from '@supabase/supabase-js';
|
||||||
import UserAccountService, { FullDBUser } from '~~/lib/services/user.account.service';
|
import { FullDBUser } from '~~/lib/services/service.types';
|
||||||
|
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;
|
||||||
@@ -12,11 +13,11 @@ export async function createContext(event: H3Event){
|
|||||||
user = await serverSupabaseUser(event);
|
user = await serverSupabaseUser(event);
|
||||||
}
|
}
|
||||||
if (!dbUser && user) {
|
if (!dbUser && user) {
|
||||||
const userService = new UserAccountService();
|
const authService = new AuthService();
|
||||||
dbUser = await userService.getFullUserBySupabaseId(user.id);
|
dbUser = await authService.getFullUserBySupabaseId(user.id);
|
||||||
|
|
||||||
if (!dbUser && user) {
|
if (!dbUser && user) {
|
||||||
dbUser = await userService.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`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import UserAccountService from '~~/lib/services/user.account.service';
|
|
||||||
import { 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';
|
||||||
|
import AccountService from '~~/lib/services/account.service';
|
||||||
|
import { MembershipWithAccount } from '~~/lib/services/service.types';
|
||||||
|
|
||||||
export const accountRouter = router({
|
export const accountRouter = router({
|
||||||
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 }) => {
|
||||||
const uaService = new UserAccountService();
|
const accountService = new AccountService();
|
||||||
const account = await uaService.changeAccountName(input.account_id, input.new_name);
|
const account = await accountService.changeAccountName(input.account_id, input.new_name);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
account,
|
account,
|
||||||
@@ -17,8 +18,8 @@ export const accountRouter = router({
|
|||||||
changeAccountPlan: adminProcedure
|
changeAccountPlan: adminProcedure
|
||||||
.input(z.object({ account_id: z.number(), plan_id: z.number() }))
|
.input(z.object({ account_id: z.number(), plan_id: z.number() }))
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const uaService = new UserAccountService();
|
const accountService = new AccountService();
|
||||||
const account = await uaService.changeAccountPlan(input.account_id, input.plan_id);
|
const account = await accountService.changeAccountPlan(input.account_id, input.plan_id);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
account,
|
account,
|
||||||
@@ -27,8 +28,8 @@ export const accountRouter = router({
|
|||||||
joinUserToAccount: adminProcedure
|
joinUserToAccount: adminProcedure
|
||||||
.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 }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const uaService = new UserAccountService();
|
const accountService = new AccountService();
|
||||||
const membership = (ctx.dbUser?.id)?await uaService.joinUserToAccount(input.user_id, input.account_id):null;
|
const membership: MembershipWithAccount| null = (ctx.dbUser?.id)?await accountService.joinUserToAccount(input.user_id, input.account_id):null;
|
||||||
return {
|
return {
|
||||||
membership,
|
membership,
|
||||||
}
|
}
|
||||||
@@ -36,8 +37,8 @@ export const accountRouter = router({
|
|||||||
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(), account_id: z.number(), access: z.enum([ACCOUNT_ACCESS.ADMIN, ACCOUNT_ACCESS.OWNER, ACCOUNT_ACCESS.READ_ONLY, ACCOUNT_ACCESS.READ_WRITE]) }))
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const uaService = new UserAccountService();
|
const accountService = new AccountService();
|
||||||
const membership = await uaService.changeUserAccessWithinAccount(input.user_id, input.account_id, input.access);
|
const membership = await accountService.changeUserAccessWithinAccount(input.user_id, input.account_id, input.access);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
membership,
|
membership,
|
||||||
@@ -46,8 +47,8 @@ export const accountRouter = router({
|
|||||||
claimOwnershipOfAccount: adminProcedure
|
claimOwnershipOfAccount: adminProcedure
|
||||||
.input(z.object({ account_id: z.number() }))
|
.input(z.object({ account_id: z.number() }))
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const uaService = new UserAccountService();
|
const accountService = new AccountService();
|
||||||
const membership = await uaService.claimOwnershipOfAccount(ctx.dbUser.id, input.account_id);
|
const membership = await accountService.claimOwnershipOfAccount(ctx.dbUser.id, input.account_id);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
membership,
|
membership,
|
||||||
@@ -56,8 +57,8 @@ export const accountRouter = router({
|
|||||||
getAccountMembers: adminProcedure
|
getAccountMembers: adminProcedure
|
||||||
.input(z.object({ account_id: z.number() }))
|
.input(z.object({ account_id: z.number() }))
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const uaService = new UserAccountService();
|
const accountService = new AccountService();
|
||||||
const memberships = await uaService.getAccountMembers(input.account_id);
|
const memberships = await accountService.getAccountMembers(input.account_id);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
memberships,
|
memberships,
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ const isAdminForInputAccountId = t.middleware(({ next, rawInput, ctx }) => {
|
|||||||
if (!result.success) throw new TRPCError({ code: 'BAD_REQUEST' });
|
if (!result.success) throw new TRPCError({ code: 'BAD_REQUEST' });
|
||||||
const { account_id } = result.data;
|
const { account_id } = result.data;
|
||||||
const test_membership = ctx.dbUser.memberships.find(membership => membership.account_id == account_id);
|
const test_membership = ctx.dbUser.memberships.find(membership => membership.account_id == account_id);
|
||||||
console.log(`isAdminForInputAccountId test_membership?.access:${test_membership?.access}`);
|
|
||||||
if(!test_membership || (test_membership?.access !== ACCOUNT_ACCESS.ADMIN && test_membership?.access !== ACCOUNT_ACCESS.OWNER)) {
|
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,6 +1,6 @@
|
|||||||
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/user.account.service"
|
import { MembershipWithUser } from "~~/lib/services/service.types";
|
||||||
|
|
||||||
import { useAuthStore } from './auth.store'
|
import { useAuthStore } from './auth.store'
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { defineStore } from "pinia"
|
import { defineStore } from "pinia"
|
||||||
import { FullDBUser, MembershipWithAccount } from "~~/lib/services/user.account.service"
|
import { FullDBUser, MembershipWithAccount } from "~~/lib/services/service.types";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
dbUser?: FullDBUser
|
dbUser?: FullDBUser
|
||||||
|
|||||||
Reference in New Issue
Block a user