@@ -1701,3 +1701,275 @@ export async function deleteOrganization(
17011701
17021702 return ! ! deletedOrganization ;
17031703}
1704+
1705+ /**
1706+ * Add or update a user's membership in an organization (only admins and owners can do this)
1707+ *
1708+ * @param db Database instance
1709+ * @param organizationIdOrHandle Organization ID or handle
1710+ * @param targetUserId User ID to add/update membership for
1711+ * @param role Role to assign (member, admin, owner)
1712+ * @param adminUserId User ID of the admin/owner making the change
1713+ * @returns The created or updated membership record, or null if permission denied
1714+ */
1715+ export async function addOrUpdateMembership (
1716+ db : ReturnType < typeof createDatabase > ,
1717+ organizationIdOrHandle : string ,
1718+ targetUserId : string ,
1719+ role : OrganizationRoleType ,
1720+ adminUserId : string
1721+ ) : Promise < MembershipRow | null > {
1722+ // First, verify the admin user has permission (admin or owner)
1723+ const [ adminMembership ] = await db
1724+ . select ( )
1725+ . from ( memberships )
1726+ . innerJoin ( organizations , eq ( memberships . organizationId , organizations . id ) )
1727+ . where (
1728+ and (
1729+ eq ( memberships . userId , adminUserId ) ,
1730+ getOrganizationCondition ( organizationIdOrHandle ) ,
1731+ inArray ( memberships . role , [
1732+ OrganizationRole . ADMIN ,
1733+ OrganizationRole . OWNER ,
1734+ ] )
1735+ )
1736+ )
1737+ . limit ( 1 ) ;
1738+
1739+ if ( ! adminMembership ) {
1740+ return null ; // Admin user doesn't have permission
1741+ }
1742+
1743+ // Additional check: only owners can assign the owner role
1744+ if (
1745+ role === OrganizationRole . OWNER &&
1746+ adminMembership . memberships . role !== OrganizationRole . OWNER
1747+ ) {
1748+ return null ; // Only owners can assign owner role
1749+ }
1750+
1751+ const organizationId = adminMembership . organizations . id ;
1752+ const now = new Date ( ) ;
1753+
1754+ // CRITICAL: Prevent changing the role of the main owner in their personal organization
1755+ // Check if the target user is the main owner of their personal organization
1756+ const [ targetUser ] = await db
1757+ . select ( )
1758+ . from ( users )
1759+ . where ( eq ( users . id , targetUserId ) )
1760+ . limit ( 1 ) ;
1761+
1762+ if ( targetUser && targetUser . organizationId === organizationId ) {
1763+ // This is the user's personal organization - their role cannot be changed
1764+ return null ; // Cannot change role of main owner in their personal organization
1765+ }
1766+
1767+ // Check if the target user is already a member
1768+ const [ existingMembership ] = await db
1769+ . select ( )
1770+ . from ( memberships )
1771+ . where (
1772+ and (
1773+ eq ( memberships . userId , targetUserId ) ,
1774+ eq ( memberships . organizationId , organizationId )
1775+ )
1776+ )
1777+ . limit ( 1 ) ;
1778+
1779+ if ( existingMembership ) {
1780+ // Update existing membership
1781+ const [ updatedMembership ] = await db
1782+ . update ( memberships )
1783+ . set ( {
1784+ role,
1785+ updatedAt : now ,
1786+ } )
1787+ . where (
1788+ and (
1789+ eq ( memberships . userId , targetUserId ) ,
1790+ eq ( memberships . organizationId , organizationId )
1791+ )
1792+ )
1793+ . returning ( ) ;
1794+
1795+ return updatedMembership ;
1796+ } else {
1797+ // Create new membership
1798+ const newMembership : MembershipInsert = {
1799+ userId : targetUserId ,
1800+ organizationId,
1801+ role,
1802+ createdAt : now ,
1803+ updatedAt : now ,
1804+ } ;
1805+
1806+ const [ createdMembership ] = await db
1807+ . insert ( memberships )
1808+ . values ( newMembership )
1809+ . returning ( ) ;
1810+
1811+ return createdMembership ;
1812+ }
1813+ }
1814+
1815+ /**
1816+ * Delete a user's membership from an organization (only admins and owners can do this)
1817+ *
1818+ * @param db Database instance
1819+ * @param organizationIdOrHandle Organization ID or handle
1820+ * @param targetUserId User ID to remove from the organization
1821+ * @param adminUserId User ID of the admin/owner making the change
1822+ * @returns True if membership was deleted, false if permission denied or not found
1823+ */
1824+ export async function deleteMembership (
1825+ db : ReturnType < typeof createDatabase > ,
1826+ organizationIdOrHandle : string ,
1827+ targetUserId : string ,
1828+ adminUserId : string
1829+ ) : Promise < boolean > {
1830+ // First, verify the admin user has permission (admin or owner)
1831+ const [ adminMembership ] = await db
1832+ . select ( )
1833+ . from ( memberships )
1834+ . innerJoin ( organizations , eq ( memberships . organizationId , organizations . id ) )
1835+ . where (
1836+ and (
1837+ eq ( memberships . userId , adminUserId ) ,
1838+ getOrganizationCondition ( organizationIdOrHandle ) ,
1839+ inArray ( memberships . role , [
1840+ OrganizationRole . ADMIN ,
1841+ OrganizationRole . OWNER ,
1842+ ] )
1843+ )
1844+ )
1845+ . limit ( 1 ) ;
1846+
1847+ if ( ! adminMembership ) {
1848+ return false ; // Admin user doesn't have permission
1849+ }
1850+
1851+ // Get the target user's membership to check their role
1852+ const [ targetMembership ] = await db
1853+ . select ( )
1854+ . from ( memberships )
1855+ . where (
1856+ and (
1857+ eq ( memberships . userId , targetUserId ) ,
1858+ eq ( memberships . organizationId , adminMembership . organizations . id )
1859+ )
1860+ )
1861+ . limit ( 1 ) ;
1862+
1863+ if ( ! targetMembership ) {
1864+ return false ; // Target user is not a member
1865+ }
1866+
1867+ // Additional check: only owners can delete other owners
1868+ if (
1869+ targetMembership . role === OrganizationRole . OWNER &&
1870+ adminMembership . memberships . role !== OrganizationRole . OWNER
1871+ ) {
1872+ return false ; // Only owners can delete other owners
1873+ }
1874+
1875+ // Prevent users from deleting themselves
1876+ if ( targetUserId === adminUserId ) {
1877+ return false ; // Users cannot delete their own membership
1878+ }
1879+
1880+ // CRITICAL: Prevent removing the main owner from their personal organization
1881+ // Check if the target user is the main owner of their personal organization
1882+ const [ targetUser ] = await db
1883+ . select ( )
1884+ . from ( users )
1885+ . where ( eq ( users . id , targetUserId ) )
1886+ . limit ( 1 ) ;
1887+
1888+ if (
1889+ targetUser &&
1890+ targetUser . organizationId === adminMembership . organizations . id
1891+ ) {
1892+ // This is the user's personal organization - they cannot be removed as the main owner
1893+ return false ; // Cannot remove main owner from their personal organization
1894+ }
1895+
1896+ // Delete the membership
1897+ const [ deletedMembership ] = await db
1898+ . delete ( memberships )
1899+ . where (
1900+ and (
1901+ eq ( memberships . userId , targetUserId ) ,
1902+ eq ( memberships . organizationId , adminMembership . organizations . id )
1903+ )
1904+ )
1905+ . returning ( { id : memberships . userId } ) ;
1906+
1907+ return ! ! deletedMembership ;
1908+ }
1909+
1910+ /**
1911+ * List all memberships for an organization
1912+ *
1913+ * @param db Database instance
1914+ * @param organizationIdOrHandle Organization ID or handle
1915+ * @returns Array of membership records
1916+ */
1917+ export async function getOrganizationMemberships (
1918+ db : ReturnType < typeof createDatabase > ,
1919+ organizationIdOrHandle : string
1920+ ) {
1921+ return await db
1922+ . select ( {
1923+ userId : memberships . userId ,
1924+ organizationId : memberships . organizationId ,
1925+ role : memberships . role ,
1926+ createdAt : memberships . createdAt ,
1927+ updatedAt : memberships . updatedAt ,
1928+ } )
1929+ . from ( memberships )
1930+ . innerJoin ( organizations , eq ( memberships . organizationId , organizations . id ) )
1931+ . where ( getOrganizationCondition ( organizationIdOrHandle ) )
1932+ . orderBy ( memberships . createdAt ) ;
1933+ }
1934+
1935+ /**
1936+ * List all memberships for an organization with user information
1937+ *
1938+ * @param db Database instance
1939+ * @param organizationIdOrHandle Organization ID or handle
1940+ * @returns Array of membership records with user details
1941+ */
1942+ export async function getOrganizationMembershipsWithUsers (
1943+ db : ReturnType < typeof createDatabase > ,
1944+ organizationIdOrHandle : string
1945+ ) {
1946+ const results = await db
1947+ . select ( {
1948+ userId : memberships . userId ,
1949+ organizationId : memberships . organizationId ,
1950+ role : memberships . role ,
1951+ createdAt : memberships . createdAt ,
1952+ updatedAt : memberships . updatedAt ,
1953+ user : {
1954+ id : users . id ,
1955+ name : users . name ,
1956+ email : users . email ,
1957+ avatarUrl : users . avatarUrl ,
1958+ } ,
1959+ } )
1960+ . from ( memberships )
1961+ . innerJoin ( organizations , eq ( memberships . organizationId , organizations . id ) )
1962+ . innerJoin ( users , eq ( memberships . userId , users . id ) )
1963+ . where ( getOrganizationCondition ( organizationIdOrHandle ) )
1964+ . orderBy ( memberships . createdAt ) ;
1965+
1966+ // Convert null values to undefined to match TypeScript interface
1967+ return results . map ( ( result ) => ( {
1968+ ...result ,
1969+ user : {
1970+ ...result . user ,
1971+ email : result . user . email ?? undefined ,
1972+ avatarUrl : result . user . avatarUrl ?? undefined ,
1973+ } ,
1974+ } ) ) ;
1975+ }
0 commit comments