make auth working

This commit is contained in:
Wuesteon 2025-11-26 01:31:12 +01:00
parent 7a1f1e9aef
commit 25824ed0ac
73 changed files with 9093 additions and 3877 deletions

View file

@ -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 [];
}
}
}