kaneo (minimalist kanban) fork to experiment adding a tangled integration github.com/usekaneo/kaneo
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at cd7cada2f86b4e866a15b4323bb8d6d7ab5bba8b 226 lines 5.2 kB view raw
1import { and, eq, gt } from "drizzle-orm"; 2import db from "../database"; 3import { invitationTable, userTable, workspaceTable } from "../database/schema"; 4 5type RegistrationCheckResult = { 6 allowed: boolean; 7 reason: string; 8 invitation?: { 9 id: string; 10 email: string; 11 workspaceId: string; 12 workspaceName: string; 13 inviterName: string; 14 expiresAt: Date; 15 status: string; 16 }; 17}; 18 19export async function checkRegistrationAllowed( 20 email?: string, 21 invitationId?: string, 22): Promise<RegistrationCheckResult> { 23 const isRegistrationDisabled = process.env.DISABLE_REGISTRATION === "true"; 24 25 if (!isRegistrationDisabled) { 26 return { 27 allowed: true, 28 reason: "Registration is enabled", 29 }; 30 } 31 32 if (!invitationId && !email) { 33 return { 34 allowed: false, 35 reason: 36 "Registration is currently disabled. Please contact your administrator for an invitation.", 37 }; 38 } 39 40 const invitation = await findValidInvitation(email, invitationId); 41 42 if (!invitation) { 43 return { 44 allowed: false, 45 reason: 46 "Registration is currently disabled. You need a valid invitation to create an account.", 47 }; 48 } 49 50 return { 51 allowed: true, 52 reason: "Valid invitation found", 53 invitation, 54 }; 55} 56 57async function findValidInvitation( 58 email?: string, 59 invitationId?: string, 60): Promise<RegistrationCheckResult["invitation"] | null> { 61 const now = new Date(); 62 63 const conditions = [ 64 eq(invitationTable.status, "pending"), 65 gt(invitationTable.expiresAt, now), 66 ]; 67 68 if (invitationId) { 69 conditions.push(eq(invitationTable.id, invitationId)); 70 } 71 72 if (email) { 73 conditions.push(eq(invitationTable.email, email.toLowerCase())); 74 } 75 76 if (!invitationId && !email) { 77 return null; 78 } 79 80 const result = await db 81 .select({ 82 id: invitationTable.id, 83 email: invitationTable.email, 84 workspaceId: invitationTable.workspaceId, 85 workspaceName: workspaceTable.name, 86 inviterName: userTable.name, 87 expiresAt: invitationTable.expiresAt, 88 status: invitationTable.status, 89 }) 90 .from(invitationTable) 91 .innerJoin( 92 workspaceTable, 93 eq(invitationTable.workspaceId, workspaceTable.id), 94 ) 95 .innerJoin(userTable, eq(invitationTable.inviterId, userTable.id)) 96 .where(and(...conditions)) 97 .limit(1); 98 99 const row = result[0]; 100 if (!row) { 101 return null; 102 } 103 104 return row; 105} 106 107type InvitationDetails = { 108 id: string; 109 email: string; 110 workspaceName: string; 111 inviterName: string; 112 expiresAt: Date; 113 status: string; 114 expired: boolean; 115}; 116 117type InvitationDetailsResult = { 118 valid: boolean; 119 invitation?: InvitationDetails; 120 error?: string; 121}; 122 123export async function getInvitationDetails( 124 invitationId: string, 125): Promise<InvitationDetailsResult> { 126 const now = new Date(); 127 128 const result = await db 129 .select({ 130 id: invitationTable.id, 131 email: invitationTable.email, 132 workspaceName: workspaceTable.name, 133 inviterName: userTable.name, 134 expiresAt: invitationTable.expiresAt, 135 status: invitationTable.status, 136 }) 137 .from(invitationTable) 138 .innerJoin( 139 workspaceTable, 140 eq(invitationTable.workspaceId, workspaceTable.id), 141 ) 142 .innerJoin(userTable, eq(invitationTable.inviterId, userTable.id)) 143 .where(eq(invitationTable.id, invitationId)) 144 .limit(1); 145 146 const row = result[0]; 147 if (!row) { 148 return { 149 valid: false, 150 error: "Invitation not found", 151 }; 152 } 153 154 const expired = row.expiresAt < now; 155 const isAccepted = row.status === "accepted"; 156 const isCanceled = row.status === "canceled"; 157 158 const baseInvitation: InvitationDetails = { 159 id: row.id, 160 email: row.email, 161 workspaceName: row.workspaceName, 162 inviterName: row.inviterName, 163 expiresAt: row.expiresAt, 164 status: row.status, 165 expired, 166 }; 167 168 if (isAccepted) { 169 return { 170 valid: false, 171 error: "This invitation has already been accepted", 172 }; 173 } 174 175 if (isCanceled) { 176 return { 177 valid: false, 178 error: "This invitation has been canceled", 179 }; 180 } 181 182 if (expired) { 183 return { 184 valid: false, 185 invitation: baseInvitation, 186 error: "This invitation has expired", 187 }; 188 } 189 190 return { 191 valid: true, 192 invitation: baseInvitation, 193 }; 194} 195 196export async function getUserPendingInvitations(userEmail: string) { 197 const now = new Date(); 198 199 const result = await db 200 .select({ 201 id: invitationTable.id, 202 email: invitationTable.email, 203 workspaceId: invitationTable.workspaceId, 204 workspaceName: workspaceTable.name, 205 inviterName: userTable.name, 206 expiresAt: invitationTable.expiresAt, 207 createdAt: invitationTable.createdAt, 208 status: invitationTable.status, 209 }) 210 .from(invitationTable) 211 .innerJoin( 212 workspaceTable, 213 eq(invitationTable.workspaceId, workspaceTable.id), 214 ) 215 .innerJoin(userTable, eq(invitationTable.inviterId, userTable.id)) 216 .where( 217 and( 218 eq(invitationTable.email, userEmail.toLowerCase()), 219 eq(invitationTable.status, "pending"), 220 gt(invitationTable.expiresAt, now), 221 ), 222 ) 223 .orderBy(invitationTable.createdAt); 224 225 return result; 226}