Archived
feat: implement group creation and management (US #28)
Backend: - Add groups Django app with models: Group, GroupMember, GroupInvite, JoinRequest - Create serializers with business rule validation - Implement GroupViewSet with full CRUD + custom actions (members, invites, roles, leave, join requests) - Add JoinGroupViewSet for invite-based joining flow - Register app in Django config and URL routing Frontend: - Add shared types for groups to @cloud-reader/shared - Create groups API client (groupsApi) - Build GroupsListPage, GroupDetailPage (member mgmt, invites, role transfer) - Build CreateGroupPage and JoinGroupPage - Add lazy-loaded routes to App.tsx with ProtectedRoute - Add navigation links to Library header Ref: #28
This commit is contained in:
@@ -215,4 +215,100 @@ export interface PaginatedResponse<T> {
|
||||
export interface ApiError {
|
||||
detail?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
// ---- Groups ----
|
||||
|
||||
export type GroupRole = "admin" | "member";
|
||||
|
||||
export type JoinRequestStatus = "pending" | "approved" | "rejected";
|
||||
|
||||
export interface GroupListItem {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
created_by: number;
|
||||
member_count: number;
|
||||
user_role: GroupRole | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface GroupMember {
|
||||
id: number;
|
||||
user_id: number;
|
||||
user_email: string;
|
||||
user_username: string;
|
||||
role: GroupRole;
|
||||
joined_at: string;
|
||||
}
|
||||
|
||||
export interface GroupDetail {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
created_by: number;
|
||||
created_by_email: string;
|
||||
created_by_username: string;
|
||||
members: GroupMember[];
|
||||
member_count: number;
|
||||
user_role: GroupRole | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface CreateGroupPayload {
|
||||
name: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface UpdateGroupPayload {
|
||||
name?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface GroupInvite {
|
||||
id: number;
|
||||
group: number;
|
||||
group_name: string;
|
||||
code: string;
|
||||
created_by: number;
|
||||
created_by_email: string;
|
||||
max_uses: number;
|
||||
use_count: number;
|
||||
is_active: boolean;
|
||||
join_url: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface CreateInvitePayload {
|
||||
max_uses?: number;
|
||||
}
|
||||
|
||||
export interface JoinRequest {
|
||||
id: number;
|
||||
group: number;
|
||||
group_name: string;
|
||||
user: number;
|
||||
user_id: number;
|
||||
user_email: string;
|
||||
user_username: string;
|
||||
invite: number | null;
|
||||
invite_code: string | null;
|
||||
status: JoinRequestStatus;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface InviteValidation {
|
||||
group: {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
created_by_email: string;
|
||||
member_count: number;
|
||||
};
|
||||
invite: {
|
||||
code: string;
|
||||
created_by_email: string;
|
||||
};
|
||||
}
|
||||
@@ -114,4 +114,18 @@ export const API_ENDPOINTS = {
|
||||
bookmarkDetail: (id: string) => `/api/annotations/bookmarks/${id}/`,
|
||||
noteDetail: (id: string) => `/api/annotations/notes/${id}/`,
|
||||
},
|
||||
groups: {
|
||||
list: "/api/groups/",
|
||||
detail: (id: number) => `/api/groups/${id}/`,
|
||||
members: (id: number) => `/api/groups/${id}/members/`,
|
||||
removeMember: (id: number, userId: number) => `/api/groups/${id}/members/${userId}/`,
|
||||
updateRole: (id: number, userId: number) => `/api/groups/${id}/members/${userId}/role/`,
|
||||
leave: (id: number) => `/api/groups/${id}/leave/`,
|
||||
invites: (id: number) => `/api/groups/${id}/invites/`,
|
||||
revokeInvite: (id: number, inviteId: number) => `/api/groups/${id}/invites/${inviteId}/`,
|
||||
requests: (id: number) => `/api/groups/${id}/requests/`,
|
||||
approveRequest: (id: number, requestId: number) => `/api/groups/${id}/requests/${requestId}/approve/`,
|
||||
rejectRequest: (id: number, requestId: number) => `/api/groups/${id}/requests/${requestId}/reject/`,
|
||||
join: (code: string) => `/api/join/${code}/`,
|
||||
},
|
||||
} as const;
|
||||
Reference in New Issue
Block a user