315 lines
8.7 KiB
TypeScript
315 lines
8.7 KiB
TypeScript
'use server';
|
|
import { createServerClient } from '@/utils/supabase';
|
|
import type { Profile, Result } from '@/utils/supabase';
|
|
import { getUser, getProfile, getSignedUrl } from '@/lib/actions';
|
|
|
|
export type UserWithStatus = {
|
|
id?: string;
|
|
user: Profile;
|
|
status: string;
|
|
created_at: string;
|
|
updated_by?: Profile;
|
|
};
|
|
|
|
type PaginatedHistory = {
|
|
statuses: UserWithStatus[];
|
|
meta: {
|
|
current_page: number;
|
|
per_page: number;
|
|
total_pages: number;
|
|
total_count: number;
|
|
};
|
|
};
|
|
|
|
export const getRecentUsersWithStatuses = async (): Promise<
|
|
Result<UserWithStatus[]>
|
|
> => {
|
|
try {
|
|
const supabase = await createServerClient();
|
|
const oneDayAgo = new Date(Date.now() - 1000 * 60 * 60 * 24);
|
|
|
|
const { data, error } = (await supabase
|
|
.from('statuses')
|
|
.select(`
|
|
user:profiles!user_id(*),
|
|
status,
|
|
created_at,
|
|
updated_by:profiles!updated_by_id(*)
|
|
`)
|
|
.gte('created_at', oneDayAgo.toISOString())
|
|
.order('created_at', { ascending: false })) as {
|
|
data: UserWithStatus[];
|
|
error: unknown;
|
|
};
|
|
|
|
if (error) throw error as Error;
|
|
if (!data?.length) return { success: true, data: [] };
|
|
|
|
const seen = new Set<string>();
|
|
const filtered = data.filter((row) => {
|
|
if (seen.has(row.user.id)) return false;
|
|
seen.add(row.user.id);
|
|
return true;
|
|
});
|
|
|
|
const filteredWithAvatars = new Array<UserWithStatus>();
|
|
|
|
for (const userWithStatus of filtered) {
|
|
|
|
if (userWithStatus.user.avatar_url) {
|
|
const avatarResponse = await getSignedUrl({
|
|
bucket: 'avatars',
|
|
url: userWithStatus.user.avatar_url,
|
|
});
|
|
if (avatarResponse.success) {
|
|
userWithStatus.user.avatar_url = avatarResponse.data;
|
|
} else userWithStatus.user.avatar_url = null;
|
|
} else userWithStatus.user.avatar_url = null;
|
|
|
|
if (userWithStatus.updated_by?.avatar_url) {
|
|
const updatedByAvatarResponse = await getSignedUrl({
|
|
bucket: 'avatars',
|
|
url: userWithStatus.updated_by.avatar_url ?? '',
|
|
});
|
|
if (updatedByAvatarResponse.success) {
|
|
userWithStatus.updated_by.avatar_url = updatedByAvatarResponse.data;
|
|
} else userWithStatus.updated_by.avatar_url = null;
|
|
} else {
|
|
if (userWithStatus.updated_by) userWithStatus.updated_by.avatar_url = null;
|
|
}
|
|
filteredWithAvatars.push(userWithStatus);
|
|
}
|
|
return { success: true, data: filteredWithAvatars };
|
|
} catch (error) {
|
|
return { success: false, error: `Error: ${error as Error}` };
|
|
}
|
|
};
|
|
|
|
export const broadcastStatusUpdates = async (
|
|
userStatuses: UserWithStatus[],
|
|
): Promise<Result<void>> => {
|
|
try {
|
|
const supabase = await createServerClient();
|
|
|
|
for (const userStatus of userStatuses) {
|
|
const broadcast = await supabase.channel('status_updates').send({
|
|
type: 'broadcast',
|
|
event: 'status_updated',
|
|
payload: {
|
|
user_status: userStatus,
|
|
timestamp: new Date().toISOString(),
|
|
},
|
|
});
|
|
if (broadcast === 'error' || broadcast === 'timed out')
|
|
throw new Error(
|
|
'Failed to broadcast status update. Timed out or errored.',
|
|
);
|
|
}
|
|
return { success: true, data: undefined };
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Unknown error',
|
|
};
|
|
}
|
|
};
|
|
|
|
export const updateStatuses = async (
|
|
userIds: string[],
|
|
status: string,
|
|
): Promise<Result<void>> => {
|
|
try {
|
|
const supabase = await createServerClient();
|
|
const profileResponse = await getProfile();
|
|
if (!profileResponse.success) throw new Error('Not authenticated!');
|
|
const userProfile = profileResponse.data;
|
|
|
|
const inserts = userIds.map((userId) => ({
|
|
user_id: userId,
|
|
status,
|
|
updated_by_id: userProfile.id,
|
|
}));
|
|
|
|
const { data: insertedStatuses, error: insertedStatusesError } =
|
|
await supabase.from('statuses').insert(inserts).select();
|
|
if (insertedStatusesError) throw insertedStatusesError as Error;
|
|
|
|
if (insertedStatuses) {
|
|
const broadcastArray = new Array<UserWithStatus>(insertedStatuses.length);
|
|
for (const insertedStatus of insertedStatuses) {
|
|
const profileResponse = await getProfile(insertedStatus.user_id)
|
|
if (!profileResponse.success) throw new Error(profileResponse.error);
|
|
const profile = profileResponse.data;
|
|
if (profile) {
|
|
broadcastArray.push({
|
|
user: profile,
|
|
status: insertedStatus.status,
|
|
created_at: insertedStatus.created_at,
|
|
updated_by: userProfile,
|
|
});
|
|
}
|
|
}
|
|
await broadcastStatusUpdates(broadcastArray);
|
|
}
|
|
return { success: true, data: undefined };
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Unknown error',
|
|
};
|
|
}
|
|
};
|
|
|
|
export const updateUserStatus = async (
|
|
status: string,
|
|
): Promise<Result<void>> => {
|
|
try {
|
|
const supabase = await createServerClient();
|
|
const profileResponse = await getProfile();
|
|
if (!profileResponse.success)
|
|
throw new Error(`Not authenticated! ${profileResponse.error}`);
|
|
const userProfile = profileResponse.data;
|
|
|
|
const { data: insertedStatus, error: insertedStatusError } = await supabase
|
|
.from('statuses')
|
|
.insert({
|
|
user_id: userProfile.id,
|
|
status,
|
|
updated_by_id: userProfile.id,
|
|
})
|
|
.select()
|
|
.single();
|
|
if (insertedStatusError) throw insertedStatusError as Error;
|
|
|
|
const userStatus: UserWithStatus = {
|
|
user: userProfile,
|
|
status: insertedStatus.status,
|
|
created_at: insertedStatus.created_at,
|
|
updated_by: userProfile,
|
|
};
|
|
|
|
await broadcastStatusUpdates([userStatus]);
|
|
return { success: true, data: undefined };
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: `Error updating user's status: ${error as Error}`,
|
|
};
|
|
}
|
|
};
|
|
|
|
export const getUserHistory = async (
|
|
userId: string,
|
|
page = 1,
|
|
perPage = 50,
|
|
): Promise<Result<PaginatedHistory>> => {
|
|
try {
|
|
const supabase = await createServerClient();
|
|
const userResponse = await getUser();
|
|
if (!userResponse.success)
|
|
throw new Error(`Not authenticated! ${userResponse.error}`);
|
|
|
|
const offset = (page - 1) * perPage;
|
|
const { count } = await supabase
|
|
.from('statuses')
|
|
.select('*', { count: 'exact', head: true })
|
|
.eq('user_id', userId);
|
|
|
|
const { data: statuses, error: statusesError } = (await supabase
|
|
.from('statuses')
|
|
.select(
|
|
`
|
|
id,
|
|
user:profiles!user_id(*),
|
|
status,
|
|
created_at,
|
|
updated_by:profiles!updated_by_id(*)
|
|
`,
|
|
)
|
|
.eq('user_id', userId)
|
|
.order('created_at', { ascending: false })
|
|
.range(offset, offset + perPage - 1)) as {
|
|
data: UserWithStatus[];
|
|
error: unknown;
|
|
};
|
|
if (statusesError) throw statusesError as Error;
|
|
|
|
const totalCount = count ?? 0;
|
|
const totalPages = Math.ceil(totalCount / perPage);
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
statuses,
|
|
meta: {
|
|
current_page: page,
|
|
per_page: perPage,
|
|
total_pages: totalPages,
|
|
total_count: totalCount,
|
|
},
|
|
},
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: `Error getting user's history: ${error as Error}`,
|
|
};
|
|
}
|
|
};
|
|
|
|
export const getAllHistory = async (
|
|
page = 1,
|
|
perPage = 50,
|
|
): Promise<Result<PaginatedHistory>> => {
|
|
try {
|
|
const supabase = await createServerClient();
|
|
const userResponse = await getUser();
|
|
if (!userResponse.success)
|
|
throw new Error(`Not authenticated! ${userResponse.error}`);
|
|
|
|
const offset = (page - 1) * perPage;
|
|
const { count } = await supabase
|
|
.from('statuses')
|
|
.select('*', { count: 'exact', head: true });
|
|
|
|
const { data: statuses, error: statusesError } = (await supabase
|
|
.from('statuses')
|
|
.select(
|
|
`
|
|
id,
|
|
user:profiles!user_id(*),
|
|
status,
|
|
created_at,
|
|
updated_by:profiles!updated_by_id(*)
|
|
`,
|
|
)
|
|
.order('created_at', { ascending: false })
|
|
.range(offset, offset + perPage - 1)) as {
|
|
data: UserWithStatus[];
|
|
error: unknown;
|
|
};
|
|
if (statusesError) throw statusesError as Error;
|
|
|
|
const totalCount = count ?? 0;
|
|
const totalPages = Math.ceil(totalCount / perPage);
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
statuses,
|
|
meta: {
|
|
current_page: page,
|
|
per_page: perPage,
|
|
total_pages: totalPages,
|
|
total_count: totalCount,
|
|
},
|
|
},
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: `Error getting all history: ${error as Error}`,
|
|
};
|
|
}
|
|
};
|