@greatapps/common 1.1.774 → 1.1.775

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.
@@ -1,9 +1,10 @@
1
1
  import "server-only";
2
+ import { cache } from "react";
2
3
  import { UserProfile } from "../../users/schema";
3
4
  import { findUserById } from "../../users/action/find-user-by-id.action";
4
5
  import { ApiError } from "../../../infra/api/types";
5
6
  import { assertManagementSubscription } from "./assert-management-subscription";
6
- async function getCurrentUserProfile() {
7
+ const getCurrentUserProfile = cache(async () => {
7
8
  const result = await findUserById();
8
9
  if (!result.success || !result.data) {
9
10
  throw new ApiError(
@@ -13,7 +14,7 @@ async function getCurrentUserProfile() {
13
14
  );
14
15
  }
15
16
  return result.data.profile;
16
- }
17
+ });
17
18
  async function assertManagementPermission(permission) {
18
19
  const profile = await getCurrentUserProfile();
19
20
  switch (permission) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/modules/auth/utils/assert-management-permission.ts"],"sourcesContent":["import 'server-only';\n\nimport { UserProfile } from '../../users/schema';\nimport { findUserById } from '../../users/action/find-user-by-id.action';\nimport { ApiError } from '../../../infra/api/types';\nimport { assertManagementSubscription } from './assert-management-subscription';\nimport type { ManagementPermission } from '../types/management-permission.type';\n\nasync function getCurrentUserProfile() {\n const result = await findUserById();\n\n if (!result.success || !result.data) {\n throw new ApiError(\n ('error' in result && result.error) || 'Não foi possível identificar o usuário autenticado',\n 'USER_CONTEXT_FAILED',\n 403\n );\n }\n\n return result.data.profile;\n}\n\nexport async function assertManagementPermission(permission: ManagementPermission) {\n const profile = await getCurrentUserProfile();\n\n switch (permission) {\n case 'manage_pages':\n case 'manage_redirects':\n case 'manage_domains':\n case 'manage_ab_tests':\n case 'restore_pages':\n case 'create_pages':\n case 'update_pages':\n case 'delete_pages':\n if (profile === UserProfile.viewer) {\n throw new ApiError('Você não tem permissão para executar esta ação', 'FORBIDDEN', 403);\n }\n break;\n case 'manage_projects':\n case 'delete_leads':\n case 'view_cards':\n case 'manage_cards':\n case 'manage_subscriptions':\n if (profile !== UserProfile.owner && profile !== UserProfile.admin) {\n throw new ApiError('Você não tem permissão para executar esta ação', 'FORBIDDEN', 403);\n }\n break;\n default:\n throw new ApiError('Permissão inválida', 'INVALID_PERMISSION', 500);\n }\n\n const skipSubscriptionCheck: ManagementPermission[] = ['manage_subscriptions', 'manage_cards', 'view_cards'];\n if (!skipSubscriptionCheck.includes(permission)) {\n await assertManagementSubscription();\n }\n}\n"],"mappings":"AAAA,OAAO;AAEP,SAAS,mBAAmB;AAC5B,SAAS,oBAAoB;AAC7B,SAAS,gBAAgB;AACzB,SAAS,oCAAoC;AAG7C,eAAe,wBAAwB;AACrC,QAAM,SAAS,MAAM,aAAa;AAElC,MAAI,CAAC,OAAO,WAAW,CAAC,OAAO,MAAM;AACnC,UAAM,IAAI;AAAA,MACP,WAAW,UAAU,OAAO,SAAU;AAAA,MACvC;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,SAAO,OAAO,KAAK;AACrB;AAEA,eAAsB,2BAA2B,YAAkC;AACjF,QAAM,UAAU,MAAM,sBAAsB;AAE5C,UAAQ,YAAY;AAAA,IAClB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,UAAI,YAAY,YAAY,QAAQ;AAClC,cAAM,IAAI,SAAS,iEAAkD,aAAa,GAAG;AAAA,MACvF;AACA;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,UAAI,YAAY,YAAY,SAAS,YAAY,YAAY,OAAO;AAClE,cAAM,IAAI,SAAS,iEAAkD,aAAa,GAAG;AAAA,MACvF;AACA;AAAA,IACF;AACE,YAAM,IAAI,SAAS,4BAAsB,sBAAsB,GAAG;AAAA,EACtE;AAEA,QAAM,wBAAgD,CAAC,wBAAwB,gBAAgB,YAAY;AAC3G,MAAI,CAAC,sBAAsB,SAAS,UAAU,GAAG;AAC/C,UAAM,6BAA6B;AAAA,EACrC;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../../src/modules/auth/utils/assert-management-permission.ts"],"sourcesContent":["import 'server-only';\n\nimport { cache } from 'react';\n\nimport { UserProfile } from '../../users/schema';\nimport { findUserById } from '../../users/action/find-user-by-id.action';\nimport { ApiError } from '../../../infra/api/types';\nimport { assertManagementSubscription } from './assert-management-subscription';\nimport type { ManagementPermission } from '../types/management-permission.type';\n\n/**\n * Perfil do usuário autenticado, deduplicado por request via React.cache — mesma razão\n * do `requireValidSession`.\n *\n * Este gate abre TODA leitura e escrita gateada, e o perfil não depende da permissão que\n * está sendo checada: uma request que toca N services pagava N round-trips ao backend para\n * reler a mesma linha. Medido em produção (26/08, carregamento da aba de assinatura de uma\n * conta): ~30 GETs de `/accounts/{id}/users/{id}` em 20 s, 200–880 ms cada, competindo pelo\n * mesmo worker de banco que as chamadas de verdade.\n */\nconst getCurrentUserProfile = cache(async () => {\n const result = await findUserById();\n\n if (!result.success || !result.data) {\n throw new ApiError(\n ('error' in result && result.error) || 'Não foi possível identificar o usuário autenticado',\n 'USER_CONTEXT_FAILED',\n 403\n );\n }\n\n return result.data.profile;\n});\n\nexport async function assertManagementPermission(permission: ManagementPermission) {\n const profile = await getCurrentUserProfile();\n\n switch (permission) {\n case 'manage_pages':\n case 'manage_redirects':\n case 'manage_domains':\n case 'manage_ab_tests':\n case 'restore_pages':\n case 'create_pages':\n case 'update_pages':\n case 'delete_pages':\n if (profile === UserProfile.viewer) {\n throw new ApiError('Você não tem permissão para executar esta ação', 'FORBIDDEN', 403);\n }\n break;\n case 'manage_projects':\n case 'delete_leads':\n case 'view_cards':\n case 'manage_cards':\n case 'manage_subscriptions':\n if (profile !== UserProfile.owner && profile !== UserProfile.admin) {\n throw new ApiError('Você não tem permissão para executar esta ação', 'FORBIDDEN', 403);\n }\n break;\n default:\n throw new ApiError('Permissão inválida', 'INVALID_PERMISSION', 500);\n }\n\n const skipSubscriptionCheck: ManagementPermission[] = ['manage_subscriptions', 'manage_cards', 'view_cards'];\n if (!skipSubscriptionCheck.includes(permission)) {\n await assertManagementSubscription();\n }\n}\n"],"mappings":"AAAA,OAAO;AAEP,SAAS,aAAa;AAEtB,SAAS,mBAAmB;AAC5B,SAAS,oBAAoB;AAC7B,SAAS,gBAAgB;AACzB,SAAS,oCAAoC;AAa7C,MAAM,wBAAwB,MAAM,YAAY;AAC9C,QAAM,SAAS,MAAM,aAAa;AAElC,MAAI,CAAC,OAAO,WAAW,CAAC,OAAO,MAAM;AACnC,UAAM,IAAI;AAAA,MACP,WAAW,UAAU,OAAO,SAAU;AAAA,MACvC;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,SAAO,OAAO,KAAK;AACrB,CAAC;AAED,eAAsB,2BAA2B,YAAkC;AACjF,QAAM,UAAU,MAAM,sBAAsB;AAE5C,UAAQ,YAAY;AAAA,IAClB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,UAAI,YAAY,YAAY,QAAQ;AAClC,cAAM,IAAI,SAAS,iEAAkD,aAAa,GAAG;AAAA,MACvF;AACA;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,UAAI,YAAY,YAAY,SAAS,YAAY,YAAY,OAAO;AAClE,cAAM,IAAI,SAAS,iEAAkD,aAAa,GAAG;AAAA,MACvF;AACA;AAAA,IACF;AACE,YAAM,IAAI,SAAS,4BAAsB,sBAAsB,GAAG;AAAA,EACtE;AAEA,QAAM,wBAAgD,CAAC,wBAAwB,gBAAgB,YAAY;AAC3G,MAAI,CAAC,sBAAsB,SAAS,UAAU,GAAG;AAC/C,UAAM,6BAA6B;AAAA,EACrC;AACF;","names":[]}
@@ -1,8 +1,9 @@
1
1
  import "server-only";
2
+ import { cache } from "react";
2
3
  import { ApiError } from "../../../infra/api/types";
3
4
  import { listSubscriptionsAction } from "../../subscriptions/actions/list-subscriptions.action";
4
5
  import { getSubscriptionExpiryDate } from "../../subscriptions/utils/get-subscription-expiry-date";
5
- async function assertManagementSubscription() {
6
+ const assertManagementSubscription = cache(async () => {
6
7
  const result = await listSubscriptionsAction({ active: true });
7
8
  if (!result.success) {
8
9
  throw new ApiError(
@@ -28,7 +29,7 @@ async function assertManagementSubscription() {
28
29
  403
29
30
  );
30
31
  }
31
- }
32
+ });
32
33
  export {
33
34
  assertManagementSubscription
34
35
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/modules/auth/utils/assert-management-subscription.ts"],"sourcesContent":["import \"server-only\";\n\nimport { ApiError } from \"../../../infra/api/types\";\nimport { listSubscriptionsAction } from \"../../subscriptions/actions/list-subscriptions.action\";\nimport { getSubscriptionExpiryDate } from \"../../subscriptions/utils/get-subscription-expiry-date\";\n\nexport async function assertManagementSubscription() {\n const result = await listSubscriptionsAction({ active: true });\n\n if (!result.success) {\n throw new ApiError(\n result.error || \"Não foi possível validar a assinatura atual\",\n \"SUBSCRIPTION_CONTEXT_FAILED\",\n 403,\n );\n }\n\n const subscription = result.data?.data?.[0];\n\n if (!subscription) {\n throw new ApiError(\n \"Nenhuma assinatura ativa encontrada. Assine um plano para continuar.\",\n \"SUBSCRIPTION_NOT_FOUND\",\n 403,\n );\n }\n\n const expiryDate = getSubscriptionExpiryDate(subscription);\n const isExpired = !!expiryDate && Date.now() >= expiryDate.getTime();\n\n if (isExpired) {\n throw new ApiError(\n \"Sua assinatura está vencida. Assine um novo plano para continuar.\",\n \"SUBSCRIPTION_EXPIRED\",\n 403,\n );\n }\n}\n"],"mappings":"AAAA,OAAO;AAEP,SAAS,gBAAgB;AACzB,SAAS,+BAA+B;AACxC,SAAS,iCAAiC;AAE1C,eAAsB,+BAA+B;AACnD,QAAM,SAAS,MAAM,wBAAwB,EAAE,QAAQ,KAAK,CAAC;AAE7D,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI;AAAA,MACR,OAAO,SAAS;AAAA,MAChB;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,eAAe,OAAO,MAAM,OAAO,CAAC;AAE1C,MAAI,CAAC,cAAc;AACjB,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,aAAa,0BAA0B,YAAY;AACzD,QAAM,YAAY,CAAC,CAAC,cAAc,KAAK,IAAI,KAAK,WAAW,QAAQ;AAEnE,MAAI,WAAW;AACb,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../../src/modules/auth/utils/assert-management-subscription.ts"],"sourcesContent":["import \"server-only\";\n\nimport { cache } from \"react\";\n\nimport { ApiError } from \"../../../infra/api/types\";\nimport { listSubscriptionsAction } from \"../../subscriptions/actions/list-subscriptions.action\";\nimport { getSubscriptionExpiryDate } from \"../../subscriptions/utils/get-subscription-expiry-date\";\n\n/**\n * Deduplicado por request via React.cache, como `getCurrentUserProfile` e `requireValidSession`.\n *\n * A leitura por trás é `/accounts/{id}/subscriptions?limit=1`, e ela é MAIS cara que o gate de\n * perfil: para conta Stripe a listagem consulta a API do gateway ao vivo. Em produção (26/08) o\n * mesmo carregamento disparou quatro dessas em paralelo — 727 ms, 4,2 s, 5,0 s e 7,0 s, contra um\n * p50 de 528 ms para o endpoint. Elas se atrasam entre si: são cópias da mesma pergunta disputando\n * a mesma fila.\n */\nexport const assertManagementSubscription = cache(async () => {\n const result = await listSubscriptionsAction({ active: true });\n\n if (!result.success) {\n throw new ApiError(\n result.error || \"Não foi possível validar a assinatura atual\",\n \"SUBSCRIPTION_CONTEXT_FAILED\",\n 403,\n );\n }\n\n const subscription = result.data?.data?.[0];\n\n if (!subscription) {\n throw new ApiError(\n \"Nenhuma assinatura ativa encontrada. Assine um plano para continuar.\",\n \"SUBSCRIPTION_NOT_FOUND\",\n 403,\n );\n }\n\n const expiryDate = getSubscriptionExpiryDate(subscription);\n const isExpired = !!expiryDate && Date.now() >= expiryDate.getTime();\n\n if (isExpired) {\n throw new ApiError(\n \"Sua assinatura está vencida. Assine um novo plano para continuar.\",\n \"SUBSCRIPTION_EXPIRED\",\n 403,\n );\n }\n});\n"],"mappings":"AAAA,OAAO;AAEP,SAAS,aAAa;AAEtB,SAAS,gBAAgB;AACzB,SAAS,+BAA+B;AACxC,SAAS,iCAAiC;AAWnC,MAAM,+BAA+B,MAAM,YAAY;AAC5D,QAAM,SAAS,MAAM,wBAAwB,EAAE,QAAQ,KAAK,CAAC;AAE7D,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI;AAAA,MACR,OAAO,SAAS;AAAA,MAChB;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,eAAe,OAAO,MAAM,OAAO,CAAC;AAE1C,MAAI,CAAC,cAAc;AACjB,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,aAAa,0BAA0B,YAAY;AACzD,QAAM,YAAY,CAAC,CAAC,cAAc,KAAK,IAAI,KAAK,WAAW,QAAQ;AAEnE,MAAI,WAAW;AACb,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF,CAAC;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greatapps/common",
3
- "version": "1.1.774",
3
+ "version": "1.1.775",
4
4
  "description": "Shared library for GreatApps frontend applications",
5
5
  "main": "./dist/index.mjs",
6
6
  "types": "./src/index.ts",
@@ -1,12 +1,24 @@
1
1
  import 'server-only';
2
2
 
3
+ import { cache } from 'react';
4
+
3
5
  import { UserProfile } from '../../users/schema';
4
6
  import { findUserById } from '../../users/action/find-user-by-id.action';
5
7
  import { ApiError } from '../../../infra/api/types';
6
8
  import { assertManagementSubscription } from './assert-management-subscription';
7
9
  import type { ManagementPermission } from '../types/management-permission.type';
8
10
 
9
- async function getCurrentUserProfile() {
11
+ /**
12
+ * Perfil do usuário autenticado, deduplicado por request via React.cache — mesma razão
13
+ * do `requireValidSession`.
14
+ *
15
+ * Este gate abre TODA leitura e escrita gateada, e o perfil não depende da permissão que
16
+ * está sendo checada: uma request que toca N services pagava N round-trips ao backend para
17
+ * reler a mesma linha. Medido em produção (26/08, carregamento da aba de assinatura de uma
18
+ * conta): ~30 GETs de `/accounts/{id}/users/{id}` em 20 s, 200–880 ms cada, competindo pelo
19
+ * mesmo worker de banco que as chamadas de verdade.
20
+ */
21
+ const getCurrentUserProfile = cache(async () => {
10
22
  const result = await findUserById();
11
23
 
12
24
  if (!result.success || !result.data) {
@@ -18,7 +30,7 @@ async function getCurrentUserProfile() {
18
30
  }
19
31
 
20
32
  return result.data.profile;
21
- }
33
+ });
22
34
 
23
35
  export async function assertManagementPermission(permission: ManagementPermission) {
24
36
  const profile = await getCurrentUserProfile();
@@ -1,10 +1,21 @@
1
1
  import "server-only";
2
2
 
3
+ import { cache } from "react";
4
+
3
5
  import { ApiError } from "../../../infra/api/types";
4
6
  import { listSubscriptionsAction } from "../../subscriptions/actions/list-subscriptions.action";
5
7
  import { getSubscriptionExpiryDate } from "../../subscriptions/utils/get-subscription-expiry-date";
6
8
 
7
- export async function assertManagementSubscription() {
9
+ /**
10
+ * Deduplicado por request via React.cache, como `getCurrentUserProfile` e `requireValidSession`.
11
+ *
12
+ * A leitura por trás é `/accounts/{id}/subscriptions?limit=1`, e ela é MAIS cara que o gate de
13
+ * perfil: para conta Stripe a listagem consulta a API do gateway ao vivo. Em produção (26/08) o
14
+ * mesmo carregamento disparou quatro dessas em paralelo — 727 ms, 4,2 s, 5,0 s e 7,0 s, contra um
15
+ * p50 de 528 ms para o endpoint. Elas se atrasam entre si: são cópias da mesma pergunta disputando
16
+ * a mesma fila.
17
+ */
18
+ export const assertManagementSubscription = cache(async () => {
8
19
  const result = await listSubscriptionsAction({ active: true });
9
20
 
10
21
  if (!result.success) {
@@ -35,4 +46,4 @@ export async function assertManagementSubscription() {
35
46
  403,
36
47
  );
37
48
  }
38
- }
49
+ });