@brightweblabs/module-orgs 0.3.20 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@brightweblabs/module-orgs",
3
3
  "private": false,
4
- "version": "0.3.20",
4
+ "version": "0.4.1",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
7
7
  "files": [
@@ -23,8 +23,8 @@
23
23
  "dependencies": {
24
24
  "@supabase/supabase-js": "^2.110.8",
25
25
  "server-only": "^0.0.1",
26
- "@brightweblabs/app-shell": "0.10.3",
27
- "@brightweblabs/core-auth": "0.9.5",
26
+ "@brightweblabs/app-shell": "0.11.1",
27
+ "@brightweblabs/core-auth": "0.10.1",
28
28
  "@brightweblabs/infra": "0.7.0"
29
29
  }
30
30
  }
package/src/data.ts CHANGED
@@ -220,6 +220,35 @@ export async function updateOrganization(
220
220
  return normalizeOrganization(data as RawOrganization);
221
221
  }
222
222
 
223
+ export async function deleteOrganization(
224
+ supabase: SupabaseClient,
225
+ organizationId: string,
226
+ ): Promise<{ id: string; name: string }> {
227
+ const { count: projectCount, error: projectError } = await supabase
228
+ .from("projects")
229
+ .select("id", { count: "exact", head: true })
230
+ .eq("organization_id", organizationId);
231
+ if (projectError && projectError.code !== "42P01" && projectError.code !== "PGRST205") {
232
+ throw new Error(projectError.message);
233
+ }
234
+ if ((projectCount ?? 0) > 0) {
235
+ throw new Error("A organização tem projetos associados. Elimine ou mova esses projetos primeiro.");
236
+ }
237
+
238
+ const { data, error } = await supabase
239
+ .from("organizations")
240
+ .delete()
241
+ .eq("id", organizationId)
242
+ .select("id, name")
243
+ .maybeSingle<{ id: string; name: string }>();
244
+ if (error?.code === "23503") {
245
+ throw new Error("A organização tem projetos associados. Elimine ou mova esses projetos primeiro.");
246
+ }
247
+ if (error) throw new Error(error.message);
248
+ if (!data) throw new Error("Organização não encontrada.");
249
+ return data;
250
+ }
251
+
223
252
  export async function listOrganizationMembers(
224
253
  supabase: SupabaseClient,
225
254
  organizationId: string,
package/src/handlers.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { requireOrganizationManageAccess, requireOrganizationsStaffAccess } from "./access";
2
- import { createOrganization, updateOrganization } from "./data";
2
+ import { createOrganization, deleteOrganization, updateOrganization } from "./data";
3
3
  import {
4
4
  inviteOrganizationMembers,
5
5
  logOrganizationActivity,
@@ -9,6 +9,7 @@ import {
9
9
  } from "./invitations";
10
10
  import {
11
11
  createOrganizationInvitationDeleteHandler,
12
+ createOrganizationDeleteHandler,
12
13
  createOrganizationInvitationsHandler,
13
14
  createOrganizationPatchHandler,
14
15
  createOrganizationsPostHandler,
@@ -19,6 +20,7 @@ const writeDependencies = {
19
20
  getManageAccess: requireOrganizationManageAccess,
20
21
  createOrganization,
21
22
  updateOrganization,
23
+ deleteOrganization,
22
24
  inviteMembers: inviteOrganizationMembers,
23
25
  logActivity: logOrganizationActivity,
24
26
  };
@@ -34,6 +36,7 @@ const invitationDependencies = {
34
36
 
35
37
  export const handleOrganizationsPostRequest = createOrganizationsPostHandler(writeDependencies);
36
38
  export const handleOrganizationPatchRequest = createOrganizationPatchHandler(writeDependencies);
39
+ export const handleOrganizationDeleteRequest = createOrganizationDeleteHandler(writeDependencies);
37
40
 
38
41
  const invitationHandlers = createOrganizationInvitationsHandler(invitationDependencies);
39
42
  export const handleOrganizationInvitationsGetRequest = invitationHandlers.GET;
package/src/http.ts CHANGED
@@ -17,7 +17,7 @@ export function json(body: unknown, init?: ResponseInit) {
17
17
  }
18
18
 
19
19
  type OrganizationAccess =
20
- | { ok: true; serviceSupabase: SupabaseClient; profileId: string }
20
+ | { ok: true; serviceSupabase: SupabaseClient; profileId: string; role?: string | null }
21
21
  | { ok: false; status: number; error: string };
22
22
 
23
23
  type OrganizationWriteDependencies = {
@@ -25,6 +25,7 @@ type OrganizationWriteDependencies = {
25
25
  getManageAccess(organizationId: string): Promise<OrganizationAccess>;
26
26
  createOrganization: (supabase: SupabaseClient, input: CreateOrganizationInput) => Promise<unknown>;
27
27
  updateOrganization: (supabase: SupabaseClient, id: string, input: CreateOrganizationInput) => Promise<unknown>;
28
+ deleteOrganization: (supabase: SupabaseClient, id: string) => Promise<{ id: string; name: string }>;
28
29
  inviteMembers: typeof inviteOrganizationMembers;
29
30
  logActivity: typeof logOrganizationActivity;
30
31
  };
@@ -86,6 +87,9 @@ function parseOrganizationInput(body: Record<string, unknown>): CreateOrganizati
86
87
 
87
88
  function organizationError(error: unknown): Response {
88
89
  const message = error instanceof Error ? error.message : "";
90
+ if (message === "Convite pendente não encontrado.") {
91
+ return json({ error: message }, { status: 404 });
92
+ }
89
93
  if (message.startsWith("Não foi possível enviar o email de convite.")) {
90
94
  return json({ error: message }, { status: 502 });
91
95
  }
@@ -166,6 +170,34 @@ export function createOrganizationPatchHandler(dependencies: OrganizationWriteDe
166
170
  };
167
171
  }
168
172
 
173
+ export function createOrganizationDeleteHandler(dependencies: OrganizationWriteDependencies) {
174
+ return async function handleOrganizationDeleteRequest(
175
+ _request: Request,
176
+ context: { params: Promise<{ id: string }> },
177
+ ): Promise<Response> {
178
+ const { id } = await context.params;
179
+ if (!id) return json({ error: "id é obrigatório." }, { status: 400 });
180
+ const access = await dependencies.getManageAccess(id);
181
+ if (!access.ok) return json({ error: access.error }, { status: access.status });
182
+ try {
183
+ const organization = await dependencies.deleteOrganization(access.serviceSupabase, id);
184
+ await dependencies.logActivity(access.serviceSupabase, {
185
+ actorProfileId: access.profileId,
186
+ organizationId: id,
187
+ eventType: "crm_organization_deleted",
188
+ summary: `Organização CRM eliminada: ${organization.name}`,
189
+ payload: { organization_id: id, organization_name: organization.name },
190
+ });
191
+ return json({ data: { deletedId: id } });
192
+ } catch (error) {
193
+ const message = error instanceof Error ? error.message : "";
194
+ if (message.includes("projetos associados")) return json({ error: message }, { status: 409 });
195
+ if (message === "Organização não encontrada.") return json({ error: message }, { status: 404 });
196
+ return organizationError(error);
197
+ }
198
+ };
199
+ }
200
+
169
201
  export function createOrganizationInvitationsHandler(dependencies: OrganizationInvitationDependencies) {
170
202
  return {
171
203
  GET: async (_request: Request, context: { params: Promise<{ id: string }> }): Promise<Response> => {
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ export {
2
2
  ORGANIZATIONS_DEFAULT_PAGE_SIZE,
3
3
  ORGANIZATIONS_MAX_PAGE_SIZE,
4
4
  createOrganization,
5
+ deleteOrganization,
5
6
  listOrganizationMembers,
6
7
  listOrganizations,
7
8
  setOrganizationMemberRole,
@@ -17,6 +18,7 @@ export {
17
18
  } from "./data";
18
19
  export {
19
20
  handleOrganizationInvitationDeleteRequest,
21
+ handleOrganizationDeleteRequest,
20
22
  handleOrganizationInvitationsGetRequest,
21
23
  handleOrganizationInvitationsPostRequest,
22
24
  handleOrganizationPatchRequest,
@@ -390,10 +390,12 @@ export async function revokeOrganizationInvitation(
390
390
  organizationId: string,
391
391
  invitationId: string,
392
392
  ): Promise<void> {
393
- const { error } = await supabase.from("organization_invitations")
393
+ const { data, error } = await supabase.from("organization_invitations")
394
394
  .update({ status: "revoked", revoked_at: new Date().toISOString() })
395
- .eq("id", invitationId).eq("organization_id", organizationId).eq("status", "pending");
395
+ .eq("id", invitationId).eq("organization_id", organizationId).eq("status", "pending")
396
+ .select("id");
396
397
  if (error) throw new Error(error.message);
398
+ if (!(data ?? []).some((row) => row.id === invitationId)) throw new Error("Convite pendente não encontrado.");
397
399
  }
398
400
 
399
401
  export async function acceptOrganizationInvitation(