mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 19:21:10 +02:00
make auth working
This commit is contained in:
parent
7a1f1e9aef
commit
25824ed0ac
73 changed files with 9093 additions and 3877 deletions
|
|
@ -1,6 +1,9 @@
|
|||
import { supabase } from '../utils/supabase';
|
||||
/**
|
||||
* Space Service - CRUD operations via Backend API
|
||||
*/
|
||||
import { spaceApi, type Space as ApiSpace, type SpaceMember as ApiSpaceMember } from './api';
|
||||
|
||||
// Type definitions for spaces and members
|
||||
// Re-export types with backwards-compatible naming (snake_case for mobile)
|
||||
export type Space = {
|
||||
id: string;
|
||||
name: string;
|
||||
|
|
@ -24,190 +27,140 @@ export type SpaceMember = {
|
|||
updated_at: string;
|
||||
};
|
||||
|
||||
// Get all spaces for a user (both owned and member of)
|
||||
// Helper to convert API response to local format
|
||||
function toLocalSpace(space: ApiSpace): Space {
|
||||
return {
|
||||
id: space.id,
|
||||
name: space.name,
|
||||
description: space.description,
|
||||
owner_id: space.ownerId,
|
||||
created_at: space.createdAt,
|
||||
updated_at: space.updatedAt,
|
||||
is_archived: space.isArchived,
|
||||
};
|
||||
}
|
||||
|
||||
function toLocalSpaceMember(member: ApiSpaceMember): SpaceMember {
|
||||
return {
|
||||
id: member.id,
|
||||
space_id: member.spaceId,
|
||||
user_id: member.userId,
|
||||
role: member.role,
|
||||
invitation_status: member.invitationStatus,
|
||||
invited_by: member.invitedBy,
|
||||
invited_at: member.invitedAt,
|
||||
joined_at: member.joinedAt,
|
||||
created_at: member.createdAt,
|
||||
updated_at: member.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all spaces for a user (both owned and member of)
|
||||
*/
|
||||
export async function getUserSpaces(userId: string): Promise<Space[]> {
|
||||
try {
|
||||
const { data: memberData, error: memberError } = await supabase
|
||||
.from('space_members')
|
||||
.select(`
|
||||
space_id,
|
||||
role,
|
||||
invitation_status
|
||||
`)
|
||||
.eq('user_id', userId)
|
||||
.eq('invitation_status', 'accepted');
|
||||
|
||||
if (memberError) {
|
||||
console.error('Error fetching user space memberships:', memberError);
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!memberData || memberData.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get space IDs the user is a member of
|
||||
const spaceIds = memberData.map(m => m.space_id);
|
||||
|
||||
// Fetch the actual space data
|
||||
const { data: spaces, error: spacesError } = await supabase
|
||||
.from('spaces')
|
||||
.select('*')
|
||||
.in('id', spaceIds)
|
||||
.eq('is_archived', false)
|
||||
.order('created_at', { ascending: false });
|
||||
|
||||
if (spacesError) {
|
||||
console.error('Error fetching spaces:', spacesError);
|
||||
return [];
|
||||
}
|
||||
|
||||
return spaces as Space[];
|
||||
const spaces = await spaceApi.getUserSpaces();
|
||||
return spaces.map(toLocalSpace);
|
||||
} catch (error) {
|
||||
console.error('Error in getUserSpaces:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Get spaces the user owns
|
||||
/**
|
||||
* Get spaces the user owns
|
||||
*/
|
||||
export async function getOwnedSpaces(userId: string): Promise<Space[]> {
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('spaces')
|
||||
.select('*')
|
||||
.eq('owner_id', userId)
|
||||
.eq('is_archived', false)
|
||||
.order('created_at', { ascending: false });
|
||||
|
||||
if (error) {
|
||||
console.error('Error fetching owned spaces:', error);
|
||||
return [];
|
||||
}
|
||||
|
||||
return data as Space[];
|
||||
const spaces = await spaceApi.getOwnedSpaces();
|
||||
return spaces.map(toLocalSpace);
|
||||
} catch (error) {
|
||||
console.error('Error in getOwnedSpaces:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Get a single space by ID
|
||||
/**
|
||||
* Get a single space by ID
|
||||
*/
|
||||
export async function getSpace(spaceId: string): Promise<Space | null> {
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('spaces')
|
||||
.select('*')
|
||||
.eq('id', spaceId)
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
console.error('Error fetching space:', error);
|
||||
const space = await spaceApi.getSpace(spaceId);
|
||||
if (!space) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return data as Space;
|
||||
return toLocalSpace(space);
|
||||
} catch (error) {
|
||||
console.error('Error in getSpace:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new space
|
||||
/**
|
||||
* Create a new space
|
||||
*/
|
||||
export async function createSpace(
|
||||
userId: string,
|
||||
name: string,
|
||||
description?: string
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('spaces')
|
||||
.insert({
|
||||
name,
|
||||
description,
|
||||
owner_id: userId
|
||||
})
|
||||
.select('id')
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
console.error('Error creating space:', error);
|
||||
return null;
|
||||
}
|
||||
|
||||
return data.id;
|
||||
const space = await spaceApi.createSpace(name, description);
|
||||
return space?.id || null;
|
||||
} catch (error) {
|
||||
console.error('Error in createSpace:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Update a space
|
||||
/**
|
||||
* Update a space
|
||||
*/
|
||||
export async function updateSpace(
|
||||
spaceId: string,
|
||||
updates: { name?: string; description?: string; is_archived?: boolean }
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const { error } = await supabase
|
||||
.from('spaces')
|
||||
.update(updates)
|
||||
.eq('id', spaceId);
|
||||
|
||||
if (error) {
|
||||
console.error('Error updating space:', error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return await spaceApi.updateSpace(spaceId, {
|
||||
name: updates.name,
|
||||
description: updates.description,
|
||||
isArchived: updates.is_archived,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in updateSpace:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete a space
|
||||
/**
|
||||
* Delete a space
|
||||
*/
|
||||
export async function deleteSpace(spaceId: string): Promise<boolean> {
|
||||
try {
|
||||
// Delete the space (members will be cascade deleted due to foreign key constraint)
|
||||
const { error } = await supabase
|
||||
.from('spaces')
|
||||
.delete()
|
||||
.eq('id', spaceId);
|
||||
|
||||
if (error) {
|
||||
console.error('Error deleting space:', error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return await spaceApi.deleteSpace(spaceId);
|
||||
} catch (error) {
|
||||
console.error('Error in deleteSpace:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get members of a space
|
||||
/**
|
||||
* Get members of a space
|
||||
*/
|
||||
export async function getSpaceMembers(spaceId: string): Promise<SpaceMember[]> {
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('space_members')
|
||||
.select('*')
|
||||
.eq('space_id', spaceId)
|
||||
.order('role', { ascending: true })
|
||||
.order('joined_at', { ascending: false });
|
||||
|
||||
if (error) {
|
||||
console.error('Error fetching space members:', error);
|
||||
return [];
|
||||
}
|
||||
|
||||
return data as SpaceMember[];
|
||||
const members = await spaceApi.getSpaceMembers(spaceId);
|
||||
return members.map(toLocalSpaceMember);
|
||||
} catch (error) {
|
||||
console.error('Error in getSpaceMembers:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Add a member to a space
|
||||
/**
|
||||
* Add a member to a space (invite)
|
||||
*/
|
||||
export async function inviteUserToSpace(
|
||||
spaceId: string,
|
||||
userId: string,
|
||||
|
|
@ -215,231 +168,86 @@ export async function inviteUserToSpace(
|
|||
role: 'admin' | 'member' | 'viewer' = 'member'
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
// Check if user is already a member
|
||||
const { data: existingMember, error: checkError } = await supabase
|
||||
.from('space_members')
|
||||
.select('id, invitation_status')
|
||||
.eq('space_id', spaceId)
|
||||
.eq('user_id', userId)
|
||||
.maybeSingle();
|
||||
|
||||
if (checkError) {
|
||||
console.error('Error checking existing membership:', checkError);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If already a member with accepted status, just return true
|
||||
if (existingMember && existingMember.invitation_status === 'accepted') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If there's a pending or declined invitation, update it
|
||||
if (existingMember) {
|
||||
const { error: updateError } = await supabase
|
||||
.from('space_members')
|
||||
.update({
|
||||
role,
|
||||
invitation_status: 'pending',
|
||||
invited_by: invitedByUserId,
|
||||
invited_at: new Date().toISOString()
|
||||
})
|
||||
.eq('id', existingMember.id);
|
||||
|
||||
if (updateError) {
|
||||
console.error('Error updating invitation:', updateError);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, create a new invitation
|
||||
const { error: insertError } = await supabase
|
||||
.from('space_members')
|
||||
.insert({
|
||||
space_id: spaceId,
|
||||
user_id: userId,
|
||||
role,
|
||||
invited_by: invitedByUserId,
|
||||
invitation_status: 'pending'
|
||||
});
|
||||
|
||||
if (insertError) {
|
||||
console.error('Error inviting user to space:', insertError);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return await spaceApi.inviteUser(spaceId, userId, role);
|
||||
} catch (error) {
|
||||
console.error('Error in inviteUserToSpace:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Accept or decline a space invitation
|
||||
/**
|
||||
* Accept or decline a space invitation
|
||||
*/
|
||||
export async function respondToInvitation(
|
||||
spaceId: string,
|
||||
userId: string,
|
||||
status: 'accepted' | 'declined'
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const updates: any = {
|
||||
invitation_status: status
|
||||
};
|
||||
|
||||
// If accepting, set the joined_at timestamp
|
||||
if (status === 'accepted') {
|
||||
updates.joined_at = new Date().toISOString();
|
||||
}
|
||||
|
||||
const { error } = await supabase
|
||||
.from('space_members')
|
||||
.update(updates)
|
||||
.eq('space_id', spaceId)
|
||||
.eq('user_id', userId);
|
||||
|
||||
if (error) {
|
||||
console.error('Error responding to invitation:', error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return await spaceApi.respondToInvitation(spaceId, status);
|
||||
} catch (error) {
|
||||
console.error('Error in respondToInvitation:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove a member from a space
|
||||
/**
|
||||
* Remove a member from a space
|
||||
*/
|
||||
export async function removeMember(spaceId: string, userId: string): Promise<boolean> {
|
||||
try {
|
||||
const { error } = await supabase
|
||||
.from('space_members')
|
||||
.delete()
|
||||
.eq('space_id', spaceId)
|
||||
.eq('user_id', userId);
|
||||
|
||||
if (error) {
|
||||
console.error('Error removing member:', error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return await spaceApi.removeMember(spaceId, userId);
|
||||
} catch (error) {
|
||||
console.error('Error in removeMember:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Change a member's role
|
||||
/**
|
||||
* Change a member's role
|
||||
*/
|
||||
export async function changeMemberRole(
|
||||
spaceId: string,
|
||||
userId: string,
|
||||
newRole: 'admin' | 'member' | 'viewer'
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const { error } = await supabase
|
||||
.from('space_members')
|
||||
.update({ role: newRole })
|
||||
.eq('space_id', spaceId)
|
||||
.eq('user_id', userId);
|
||||
|
||||
if (error) {
|
||||
console.error('Error changing member role:', error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return await spaceApi.changeMemberRole(spaceId, userId, newRole);
|
||||
} catch (error) {
|
||||
console.error('Error in changeMemberRole:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get user's role in a space
|
||||
/**
|
||||
* Get user's role in a space
|
||||
*/
|
||||
export async function getUserRoleInSpace(
|
||||
spaceId: string,
|
||||
userId: string
|
||||
): Promise<'owner' | 'admin' | 'member' | 'viewer' | null> {
|
||||
try {
|
||||
// First check if they're the owner
|
||||
const { data: space, error: spaceError } = await supabase
|
||||
.from('spaces')
|
||||
.select('owner_id')
|
||||
.eq('id', spaceId)
|
||||
.single();
|
||||
|
||||
if (spaceError) {
|
||||
console.error('Error checking space ownership:', spaceError);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (space.owner_id === userId) {
|
||||
return 'owner';
|
||||
}
|
||||
|
||||
// If not owner, check membership
|
||||
const { data: member, error: memberError } = await supabase
|
||||
.from('space_members')
|
||||
.select('role, invitation_status')
|
||||
.eq('space_id', spaceId)
|
||||
.eq('user_id', userId)
|
||||
.single();
|
||||
|
||||
if (memberError) {
|
||||
// This could mean they're not a member, which is fine
|
||||
return null;
|
||||
}
|
||||
|
||||
// Only return role if invitation is accepted
|
||||
if (member && member.invitation_status === 'accepted') {
|
||||
return member.role as 'admin' | 'member' | 'viewer';
|
||||
}
|
||||
|
||||
return null;
|
||||
return await spaceApi.getUserRoleInSpace(spaceId);
|
||||
} catch (error) {
|
||||
console.error('Error in getUserRoleInSpace:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Get pending space invitations for a user
|
||||
export async function getPendingInvitations(userId: string): Promise<Array<{
|
||||
invitation: SpaceMember;
|
||||
space: Space;
|
||||
}>> {
|
||||
/**
|
||||
* Get pending space invitations for a user
|
||||
*/
|
||||
export async function getPendingInvitations(
|
||||
userId: string
|
||||
): Promise<Array<{ invitation: SpaceMember; space: Space }>> {
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('space_members')
|
||||
.select(`
|
||||
*,
|
||||
space:space_id (*)
|
||||
`)
|
||||
.eq('user_id', userId)
|
||||
.eq('invitation_status', 'pending');
|
||||
|
||||
if (error) {
|
||||
console.error('Error fetching pending invitations:', error);
|
||||
return [];
|
||||
}
|
||||
|
||||
return data.map(item => ({
|
||||
invitation: {
|
||||
id: item.id,
|
||||
space_id: item.space_id,
|
||||
user_id: item.user_id,
|
||||
role: item.role,
|
||||
invitation_status: item.invitation_status,
|
||||
invited_by: item.invited_by,
|
||||
invited_at: item.invited_at,
|
||||
joined_at: item.joined_at,
|
||||
created_at: item.created_at,
|
||||
updated_at: item.updated_at
|
||||
},
|
||||
space: item.space
|
||||
const invitations = await spaceApi.getPendingInvitations();
|
||||
return invitations.map((inv) => ({
|
||||
invitation: toLocalSpaceMember(inv.invitation),
|
||||
space: toLocalSpace(inv.space),
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Error in getPendingInvitations:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue