@deallony/shared 1.2.55 → 1.2.60

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/README.md CHANGED
@@ -1,44 +1,43 @@
1
- # @deallony/shared
2
-
3
- Shared TypeScript types, DTOs, Zod schemas, and utilities for Deallony projects.
4
-
5
- ## One-command releases
6
-
7
- Run from a clean, synchronized `main` branch with push access to `origin/main`.
8
-
9
- | Command | Action |
10
- | --- | --- |
11
- | `npm run deploy` | Bump patch, build, publish to npm, commit/tag/push GitLab |
12
- | `npm run deploy:minor` | npm + GitLab minor release |
13
- | `npm run deploy:major` | npm + GitLab major release |
14
- | `npm run deploy:dry-run` | Preview version and inspect current package, no mutation |
15
- | `npm run deploy:git` | Bump patch, build, commit/tag/push GitLab without npm publish |
16
- | `npm run deploy:git:minor` | GitLab-only minor release |
17
- | `npm run deploy:git:major` | GitLab-only major release |
18
- | `npm run deploy:git:dry-run` | Preview GitLab-only release |
19
- | `npm run test:release` | Unit tests for release helpers |
20
-
21
- Version increments follow semantic versioning; `1.2.99` becomes `1.2.100`.
22
-
23
- ### Automatic npm login
24
-
25
- `npm run deploy` first checks `npm whoami`. If npm returns E401 or ENEEDAUTH,
26
- the script runs `npm login --registry=https://registry.npmjs.org/ --auth-type=web` and
27
- opens the browser sign-in flow. After you finish logging in, it rechecks your
28
- identity and continues the same release. Network failures and canceled logins
29
- stop the release before modifying the package version. `deploy:git` never logs
30
- into npm.
31
-
32
- ### npm authentication
33
-
34
- Run `npm login --registry=https://registry.npmjs.org/` and ensure your account has
35
- publishing permission for the public scoped package `@deallony/shared`.
36
- Complete any npm 2FA prompts when required. Never commit npm tokens.
37
-
38
- ### Failure recovery
39
-
40
- If publishing fails, inspect and restore generated local changes before retrying.
41
- If npm publication succeeds but Git push/commit fails, **do not republish that version**.
42
- Resolve the Git issue and push the existing release commit and `vX.Y.Z` tag.
43
-
44
- Merge this feature branch first; merging does not trigger publication automatically.
1
+ # @deallony/shared
2
+
3
+ Shared TypeScript types, DTOs, Zod schemas, and utilities for Deallony projects.
4
+
5
+ ## One-command releases
6
+
7
+ Run from a clean, synchronized `main` branch with push access to `origin/main`.
8
+
9
+ | Command | Action |
10
+ | --- | --- |
11
+ | `npm run deploy` | Bump patch, build, publish to npm, commit/tag/push GitLab |
12
+ | `npm run deploy:minor` | npm + GitLab minor release |
13
+ | `npm run deploy:major` | npm + GitLab major release |
14
+ | `npm run deploy:dry-run` | Preview version and inspect current package, no mutation |
15
+ | `npm run deploy:git` | Bump patch, build, commit/tag/push GitLab without npm publish |
16
+ | `npm run deploy:git:minor` | GitLab-only minor release |
17
+ | `npm run deploy:git:major` | GitLab-only major release |
18
+ | `npm run deploy:git:dry-run` | Preview GitLab-only release |
19
+
20
+ Version increments follow semantic versioning; `1.2.99` becomes `1.2.100`.
21
+
22
+ ### Automatic npm login
23
+
24
+ `npm run deploy` first checks `npm whoami`. If npm returns E401 or ENEEDAUTH,
25
+ the script runs `npm login --registry=https://registry.npmjs.org/ --auth-type=web` and
26
+ opens the browser sign-in flow. After you finish logging in, it rechecks your
27
+ identity and continues the same release. Network failures and canceled logins
28
+ stop the release before modifying the package version. `deploy:git` never logs
29
+ into npm.
30
+
31
+ ### npm authentication
32
+
33
+ Run `npm login --registry=https://registry.npmjs.org/` and ensure your account has
34
+ publishing permission for the public scoped package `@deallony/shared`.
35
+ Complete any npm 2FA prompts when required. Never commit npm tokens.
36
+
37
+ ### Failure recovery
38
+
39
+ If publishing fails, inspect and restore generated local changes before retrying.
40
+ If npm publication succeeds but Git push/commit fails, **do not republish that version**.
41
+ Resolve the Git issue and push the existing release commit and `vX.Y.Z` tag.
42
+
43
+ Merge this feature branch first; merging does not trigger publication automatically.
@@ -0,0 +1,9 @@
1
+ import { z } from 'zod';
2
+ import { createGeminiApiKeySchema, geminiApiKeyIdBodySchema, leasedGeminiApiKeySchema, leaseGeminiApiKeySchema, reorderGeminiApiKeysSchema, reportGeminiApiKeyUsageSchema, updateGeminiApiKeySchema } from '../../schema/ai/gemini-api-key.schema';
3
+ export type CreateGeminiApiKeyDto = z.input<typeof createGeminiApiKeySchema>;
4
+ export type UpdateGeminiApiKeyDto = z.input<typeof updateGeminiApiKeySchema>;
5
+ export type GeminiApiKeyIdBodyDto = z.input<typeof geminiApiKeyIdBodySchema>;
6
+ export type ReorderGeminiApiKeysDto = z.input<typeof reorderGeminiApiKeysSchema>;
7
+ export type LeaseGeminiApiKeyDto = z.input<typeof leaseGeminiApiKeySchema>;
8
+ export type LeasedGeminiApiKey = z.infer<typeof leasedGeminiApiKeySchema>;
9
+ export type ReportGeminiApiKeyUsageDto = z.input<typeof reportGeminiApiKeyUsageSchema>;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -2,6 +2,7 @@ export { AdDto } from './ad/Ad.dto';
2
2
  export * as AD from './ad/Ad.dto';
3
3
  export * as EVENTS from './events';
4
4
  export * as Ai from './ai/Ai.dto';
5
+ export * as GEMINI_API_KEYS from './ai/gemini-api-key.dto';
5
6
  export * as PAYMENTS from './payments';
6
7
  export * as ADDRESSES from './addresses/addresses.dto';
7
8
  export * as POTENTIAL_USERS from './potential-users/potential-user-import.dto';
@@ -33,12 +33,13 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.POTENTIAL_USERS = exports.ADDRESSES = exports.PAYMENTS = exports.Ai = exports.EVENTS = exports.AD = exports.AdDto = void 0;
36
+ exports.POTENTIAL_USERS = exports.ADDRESSES = exports.PAYMENTS = exports.GEMINI_API_KEYS = exports.Ai = exports.EVENTS = exports.AD = exports.AdDto = void 0;
37
37
  var Ad_dto_1 = require("./ad/Ad.dto");
38
38
  Object.defineProperty(exports, "AdDto", { enumerable: true, get: function () { return Ad_dto_1.AdDto; } });
39
39
  exports.AD = __importStar(require("./ad/Ad.dto"));
40
40
  exports.EVENTS = __importStar(require("./events"));
41
41
  exports.Ai = __importStar(require("./ai/Ai.dto"));
42
+ exports.GEMINI_API_KEYS = __importStar(require("./ai/gemini-api-key.dto"));
42
43
  exports.PAYMENTS = __importStar(require("./payments"));
43
44
  exports.ADDRESSES = __importStar(require("./addresses/addresses.dto"));
44
45
  exports.POTENTIAL_USERS = __importStar(require("./potential-users/potential-user-import.dto"));
@@ -0,0 +1,41 @@
1
+ import { z } from 'zod';
2
+ /** Gemini model ID, e.g. gemini-3.1-flash-lite */
3
+ export declare const geminiModelNameSchema: z.ZodString;
4
+ export declare const createGeminiApiKeySchema: z.ZodObject<{
5
+ name: z.ZodString;
6
+ api_key: z.ZodString;
7
+ model_name: z.ZodString;
8
+ is_active: z.ZodDefault<z.ZodBoolean>;
9
+ }, z.core.$strip>;
10
+ /** Fields left out keep their current value; the key itself is only replaced when sent. */
11
+ export declare const updateGeminiApiKeySchema: z.ZodObject<{
12
+ id: z.ZodCoercedNumber<unknown>;
13
+ name: z.ZodOptional<z.ZodString>;
14
+ api_key: z.ZodOptional<z.ZodString>;
15
+ model_name: z.ZodOptional<z.ZodString>;
16
+ is_active: z.ZodOptional<z.ZodBoolean>;
17
+ }, z.core.$strip>;
18
+ export declare const geminiApiKeyIdBodySchema: z.ZodObject<{
19
+ id: z.ZodCoercedNumber<unknown>;
20
+ }, z.core.$strip>;
21
+ /** Every key ID in the new order: the first ID gets sort_order 1. */
22
+ export declare const reorderGeminiApiKeysSchema: z.ZodObject<{
23
+ ids: z.ZodArray<z.ZodCoercedNumber<unknown>>;
24
+ }, z.core.$strip>;
25
+ /** Ask backend-core for the least-used available key, optionally only keys for one model. */
26
+ export declare const leaseGeminiApiKeySchema: z.ZodObject<{
27
+ model_name: z.ZodOptional<z.ZodString>;
28
+ }, z.core.$strip>;
29
+ export declare const leasedGeminiApiKeySchema: z.ZodObject<{
30
+ id: z.ZodNumber;
31
+ api_key: z.ZodString;
32
+ model_name: z.ZodString;
33
+ }, z.core.$strip>;
34
+ /** Sent after every Gemini call made with a leased key. */
35
+ export declare const reportGeminiApiKeyUsageSchema: z.ZodObject<{
36
+ id: z.ZodCoercedNumber<unknown>;
37
+ total_tokens: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
38
+ success: z.ZodDefault<z.ZodBoolean>;
39
+ error_code: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
40
+ error_message: z.ZodOptional<z.ZodString>;
41
+ }, z.core.$strip>;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.reportGeminiApiKeyUsageSchema = exports.leasedGeminiApiKeySchema = exports.leaseGeminiApiKeySchema = exports.reorderGeminiApiKeysSchema = exports.geminiApiKeyIdBodySchema = exports.updateGeminiApiKeySchema = exports.createGeminiApiKeySchema = exports.geminiModelNameSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ /**
6
+ * Gemini API keys are stored in backend-core (one table) and shared by every Deallony service.
7
+ * The dashboard manages them through the admin schemas; other services lease a key and report
8
+ * the tokens they used through the internal schemas.
9
+ */
10
+ const geminiApiKeyIdSchema = zod_1.z.coerce.number().int().positive();
11
+ /** Gemini model ID, e.g. gemini-3.1-flash-lite */
12
+ exports.geminiModelNameSchema = zod_1.z.string().trim().min(1).max(100);
13
+ // ---------- Admin (dashboard) ----------
14
+ exports.createGeminiApiKeySchema = zod_1.z.object({
15
+ name: zod_1.z.string().trim().min(1).max(255),
16
+ api_key: zod_1.z.string().trim().min(20).max(512),
17
+ model_name: exports.geminiModelNameSchema,
18
+ is_active: zod_1.z.boolean().default(true),
19
+ });
20
+ /** Fields left out keep their current value; the key itself is only replaced when sent. */
21
+ exports.updateGeminiApiKeySchema = zod_1.z.object({
22
+ id: geminiApiKeyIdSchema,
23
+ name: zod_1.z.string().trim().min(1).max(255).optional(),
24
+ api_key: zod_1.z.string().trim().min(20).max(512).optional(),
25
+ model_name: exports.geminiModelNameSchema.optional(),
26
+ is_active: zod_1.z.boolean().optional(),
27
+ });
28
+ exports.geminiApiKeyIdBodySchema = zod_1.z.object({
29
+ id: geminiApiKeyIdSchema,
30
+ });
31
+ /** Every key ID in the new order: the first ID gets sort_order 1. */
32
+ exports.reorderGeminiApiKeysSchema = zod_1.z.object({
33
+ ids: zod_1.z.array(geminiApiKeyIdSchema).min(1),
34
+ });
35
+ // ---------- Internal (other services) ----------
36
+ /** Ask backend-core for the least-used available key, optionally only keys for one model. */
37
+ exports.leaseGeminiApiKeySchema = zod_1.z.object({
38
+ model_name: exports.geminiModelNameSchema.optional(),
39
+ });
40
+ exports.leasedGeminiApiKeySchema = zod_1.z.object({
41
+ id: zod_1.z.number().int().positive(),
42
+ api_key: zod_1.z.string(),
43
+ model_name: zod_1.z.string(),
44
+ });
45
+ /** Sent after every Gemini call made with a leased key. */
46
+ exports.reportGeminiApiKeyUsageSchema = zod_1.z.object({
47
+ id: geminiApiKeyIdSchema,
48
+ /** usageMetadata.totalTokenCount of the Gemini response; 0 when the call failed */
49
+ total_tokens: zod_1.z.coerce.number().int().min(0).default(0),
50
+ success: zod_1.z.boolean().default(true),
51
+ /** HTTP status Gemini answered with, e.g. 429 */
52
+ error_code: zod_1.z.coerce.number().int().optional(),
53
+ error_message: zod_1.z.string().trim().max(2000).optional(),
54
+ });
@@ -1,2 +1,3 @@
1
1
  export * from './ai-fill-form.schema';
2
2
  export * from './ai-fill-ad-form.schema';
3
+ export * from './gemini-api-key.schema';
@@ -16,3 +16,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./ai-fill-form.schema"), exports);
18
18
  __exportStar(require("./ai-fill-ad-form.schema"), exports);
19
+ __exportStar(require("./gemini-api-key.schema"), exports);
@@ -25,10 +25,8 @@ export declare const StartAiConversationSchema: z.ZodObject<{
25
25
  motors: "motors";
26
26
  used_products: "used_products";
27
27
  }>;
28
- initial_message: z.ZodString;
29
28
  context: z.ZodObject<{
30
29
  name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
31
- reason: z.ZodString;
32
30
  note: z.ZodOptional<z.ZodNullable<z.ZodString>>;
33
31
  previous_contact_count: z.ZodDefault<z.ZodNumber>;
34
32
  }, z.core.$strip>;
@@ -58,7 +56,85 @@ export declare const PotentialUserConversationEventSchema: z.ZodObject<{
58
56
  }>;
59
57
  summary: z.ZodOptional<z.ZodNullable<z.ZodString>>;
60
58
  }, z.core.$strip>;
59
+ export declare const AiConversationStatusSchema: z.ZodEnum<{
60
+ starting: "starting";
61
+ waiting_for_reply: "waiting_for_reply";
62
+ chatting: "chatting";
63
+ interested: "interested";
64
+ human_handoff: "human_handoff";
65
+ not_interested: "not_interested";
66
+ converted: "converted";
67
+ opted_out: "opted_out";
68
+ closed: "closed";
69
+ failed: "failed";
70
+ }>;
71
+ export declare const AiConversationPurposeSchema: z.ZodEnum<{
72
+ marketing: "marketing";
73
+ support: "support";
74
+ }>;
75
+ /** Statuses of a conversation that is still open with the customer. */
76
+ export declare const AI_CONVERSATION_ACTIVE_STATUSES: AiConversationStatus[];
77
+ export declare const AiConversationListSchema: z.ZodObject<{
78
+ page: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
79
+ limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
80
+ statuses: z.ZodOptional<z.ZodArray<z.ZodEnum<{
81
+ starting: "starting";
82
+ waiting_for_reply: "waiting_for_reply";
83
+ chatting: "chatting";
84
+ interested: "interested";
85
+ human_handoff: "human_handoff";
86
+ not_interested: "not_interested";
87
+ converted: "converted";
88
+ opted_out: "opted_out";
89
+ closed: "closed";
90
+ failed: "failed";
91
+ }>>>;
92
+ active_only: z.ZodDefault<z.ZodBoolean>;
93
+ purpose: z.ZodOptional<z.ZodEnum<{
94
+ marketing: "marketing";
95
+ support: "support";
96
+ }>>;
97
+ search: z.ZodOptional<z.ZodString>;
98
+ }, z.core.$strip>;
61
99
  export type MarketingSubjectType = z.infer<typeof MarketingSubjectTypeSchema>;
100
+ export type AiConversationStatus = z.infer<typeof AiConversationStatusSchema>;
101
+ export type AiConversationPurpose = z.infer<typeof AiConversationPurposeSchema>;
102
+ export type AiConversationList = z.infer<typeof AiConversationListSchema>;
103
+ export type AiConversationListItem = {
104
+ id: number;
105
+ potential_user_id: number | null;
106
+ user_id: number | null;
107
+ phone: string;
108
+ purpose: AiConversationPurpose;
109
+ subject_type: string;
110
+ status: AiConversationStatus;
111
+ ai_enabled: boolean;
112
+ human_takeover: boolean;
113
+ last_message_at: string | null;
114
+ created_at: string;
115
+ last_message: {
116
+ content: string;
117
+ direction: 'inbound' | 'outbound';
118
+ sender: 'user' | 'ai' | 'human';
119
+ created_at: string;
120
+ } | null;
121
+ /** Filled in by the marketplace from the potential user record. */
122
+ potential_user?: {
123
+ id: number;
124
+ name: string | null;
125
+ saved_by: number | null;
126
+ } | null;
127
+ };
128
+ export type AiConversationListResult = {
129
+ items: AiConversationListItem[];
130
+ page: number;
131
+ limit: number;
132
+ total: number;
133
+ total_pages: number;
134
+ /** Counts over all conversations matching purpose and search, not only this page. */
135
+ status_counts: Record<AiConversationStatus, number>;
136
+ active_count: number;
137
+ };
62
138
  export type ConversationLanguage = z.infer<typeof ConversationLanguageSchema>;
63
139
  export type StartAiConversation = z.infer<typeof StartAiConversationSchema>;
64
140
  export type ConversationEventType = z.infer<typeof ConversationEventTypeSchema>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PotentialUserConversationEventSchema = exports.ConversationEventTypeSchema = exports.StartAiConversationSchema = exports.ConversationLanguageSchema = exports.MarketingSubjectTypeSchema = void 0;
3
+ exports.AiConversationListSchema = exports.AI_CONVERSATION_ACTIVE_STATUSES = exports.AiConversationPurposeSchema = exports.AiConversationStatusSchema = exports.PotentialUserConversationEventSchema = exports.ConversationEventTypeSchema = exports.StartAiConversationSchema = exports.ConversationLanguageSchema = exports.MarketingSubjectTypeSchema = void 0;
4
4
  const zod_1 = require("zod");
5
5
  exports.MarketingSubjectTypeSchema = zod_1.z.enum([
6
6
  'motors',
@@ -17,10 +17,8 @@ exports.StartAiConversationSchema = zod_1.z.object({
17
17
  language: exports.ConversationLanguageSchema.default('ar'),
18
18
  purpose: zod_1.z.literal('marketing'),
19
19
  subject_type: exports.MarketingSubjectTypeSchema,
20
- initial_message: zod_1.z.string().min(1),
21
20
  context: zod_1.z.object({
22
21
  name: zod_1.z.string().nullable().optional(),
23
- reason: zod_1.z.string().min(1),
24
22
  note: zod_1.z.string().nullable().optional(),
25
23
  previous_contact_count: zod_1.z.number().int().nonnegative().default(0),
26
24
  }),
@@ -41,3 +39,32 @@ exports.PotentialUserConversationEventSchema = zod_1.z.object({
41
39
  event: exports.ConversationEventTypeSchema,
42
40
  summary: zod_1.z.string().max(2000).nullable().optional(),
43
41
  });
42
+ exports.AiConversationStatusSchema = zod_1.z.enum([
43
+ 'starting',
44
+ 'waiting_for_reply',
45
+ 'chatting',
46
+ 'interested',
47
+ 'human_handoff',
48
+ 'not_interested',
49
+ 'converted',
50
+ 'opted_out',
51
+ 'closed',
52
+ 'failed',
53
+ ]);
54
+ exports.AiConversationPurposeSchema = zod_1.z.enum(['marketing', 'support']);
55
+ /** Statuses of a conversation that is still open with the customer. */
56
+ exports.AI_CONVERSATION_ACTIVE_STATUSES = [
57
+ 'starting',
58
+ 'waiting_for_reply',
59
+ 'chatting',
60
+ 'interested',
61
+ 'human_handoff',
62
+ ];
63
+ exports.AiConversationListSchema = zod_1.z.object({
64
+ page: zod_1.z.coerce.number().int().positive().default(1),
65
+ limit: zod_1.z.coerce.number().int().min(1).max(100).default(20),
66
+ statuses: zod_1.z.array(exports.AiConversationStatusSchema).max(10).optional(),
67
+ active_only: zod_1.z.boolean().default(false),
68
+ purpose: exports.AiConversationPurposeSchema.optional(),
69
+ search: zod_1.z.string().trim().max(100).optional(),
70
+ });
@@ -1,11 +1,11 @@
1
1
  import { z } from 'zod';
2
2
  export declare const SupportTicketStatusSchema: z.ZodEnum<{
3
+ closed: "closed";
3
4
  open: "open";
4
5
  assigned: "assigned";
5
6
  in_progress: "in_progress";
6
7
  waiting_customer: "waiting_customer";
7
8
  resolved: "resolved";
8
- closed: "closed";
9
9
  reopened: "reopened";
10
10
  }>;
11
11
  export declare const SupportTicketPrioritySchema: z.ZodEnum<{
@@ -79,12 +79,12 @@ export declare const SupportTicketListSchema: z.ZodObject<{
79
79
  page: z.ZodDefault<z.ZodNumber>;
80
80
  limit: z.ZodDefault<z.ZodNumber>;
81
81
  status: z.ZodOptional<z.ZodEnum<{
82
+ closed: "closed";
82
83
  open: "open";
83
84
  assigned: "assigned";
84
85
  in_progress: "in_progress";
85
86
  waiting_customer: "waiting_customer";
86
87
  resolved: "resolved";
87
- closed: "closed";
88
88
  reopened: "reopened";
89
89
  }>>;
90
90
  priority: z.ZodOptional<z.ZodEnum<{
@@ -98,12 +98,12 @@ export declare const SupportTicketListSchema: z.ZodObject<{
98
98
  export declare const SupportTicketStatusUpdateSchema: z.ZodObject<{
99
99
  id: z.ZodNumber;
100
100
  status: z.ZodEnum<{
101
+ closed: "closed";
101
102
  open: "open";
102
103
  assigned: "assigned";
103
104
  in_progress: "in_progress";
104
105
  waiting_customer: "waiting_customer";
105
106
  resolved: "resolved";
106
- closed: "closed";
107
107
  reopened: "reopened";
108
108
  }>;
109
109
  }, z.core.$strip>;
@@ -0,0 +1,8 @@
1
+ /** Modules whose fetch endpoints report when their data last changed */
2
+ export type ModuleFetchEndpointKey = "categories";
3
+ /**
4
+ * When each module's data last changed on the server (ISO dates), from
5
+ * POST api/v1/application/module-fetch-endpoints-last-updated. Apps compare it with the date they
6
+ * saved and fetch the module again only when it differs.
7
+ */
8
+ export type IModuleFetchEndpointsLastUpdated = Partial<Record<ModuleFetchEndpointKey, string>>;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -3,6 +3,7 @@ export * as Response from "./common/Response";
3
3
  export * as Status from "./common/Status";
4
4
  export * as AccessType from "./service/AccessType";
5
5
  export * as Config from "./common/Config";
6
+ export * as ModuleFetchEndpoints from "./common/ModuleFetchEndpointsLastUpdated";
6
7
  export * as FilterParams from "./common/FilterParams";
7
8
  export * as Ad from "./ad/Ad";
8
9
  export * as AdLocation from "./ad/AdLocation";
@@ -62,5 +63,6 @@ export * as SocialMediaPlatform from "./media/SocialMediaPlatform";
62
63
  export * as SocialMediaProfile from "./media/SocialMediaProfile";
63
64
  export * as Kyc from "./kyc/Kyc";
64
65
  export * as Google from "./integrations/Google";
66
+ export * as GeminiApiKey from "./integrations/GeminiApiKey";
65
67
  export * as Analysis from "./analysis/AnalysisDailySnapshot";
66
68
  export * as Payment from "./payments/Payment";
@@ -36,14 +36,15 @@ var __importStar = (this && this.__importStar) || (function () {
36
36
  };
37
37
  })();
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.PotentialUser = exports.Loyalty = exports.Favorite = exports.Wallet = exports.Permission = exports.Role = exports.User = exports.EventVenue = exports.EventWedding = exports.EventTimeline = exports.EventRsvp = exports.Event = exports.District = exports.City = exports.AdminBoundary = exports.WorkType = exports.UsedProductCondition = exports.UsedProductAge = exports.Unit = exports.Transmission = exports.RegionalSpec = exports.PublisherType = exports.PropertyStatus = exports.PriceType = exports.OfferType = exports.LicenseType = exports.FuelType = exports.Feature = exports.Facility = exports.Currency = exports.Color = exports.CategoryWorkType = exports.Category = exports.CarBody = exports.UsedProduct = exports.Service = exports.PropertySubType = exports.Property = exports.MotorTrim = exports.Motor = exports.AdPromotion = exports.Addresses = exports.AdLocation = exports.Ad = exports.FilterParams = exports.Config = exports.AccessType = exports.Status = exports.Response = exports.LocalizedName = void 0;
40
- exports.Payment = exports.Analysis = exports.Google = exports.Kyc = exports.SocialMediaProfile = exports.SocialMediaPlatform = exports.Image = exports.Media = exports.File = exports.NotificationToken = exports.MessageTemplate = exports.Device = exports.Chat = exports.AppStartScreen = exports.MetaData = exports.Company = void 0;
39
+ exports.Loyalty = exports.Favorite = exports.Wallet = exports.Permission = exports.Role = exports.User = exports.EventVenue = exports.EventWedding = exports.EventTimeline = exports.EventRsvp = exports.Event = exports.District = exports.City = exports.AdminBoundary = exports.WorkType = exports.UsedProductCondition = exports.UsedProductAge = exports.Unit = exports.Transmission = exports.RegionalSpec = exports.PublisherType = exports.PropertyStatus = exports.PriceType = exports.OfferType = exports.LicenseType = exports.FuelType = exports.Feature = exports.Facility = exports.Currency = exports.Color = exports.CategoryWorkType = exports.Category = exports.CarBody = exports.UsedProduct = exports.Service = exports.PropertySubType = exports.Property = exports.MotorTrim = exports.Motor = exports.AdPromotion = exports.Addresses = exports.AdLocation = exports.Ad = exports.FilterParams = exports.ModuleFetchEndpoints = exports.Config = exports.AccessType = exports.Status = exports.Response = exports.LocalizedName = void 0;
40
+ exports.Payment = exports.Analysis = exports.GeminiApiKey = exports.Google = exports.Kyc = exports.SocialMediaProfile = exports.SocialMediaPlatform = exports.Image = exports.Media = exports.File = exports.NotificationToken = exports.MessageTemplate = exports.Device = exports.Chat = exports.AppStartScreen = exports.MetaData = exports.Company = exports.PotentialUser = void 0;
41
41
  // common
42
42
  exports.LocalizedName = __importStar(require("./common/LocalizedName"));
43
43
  exports.Response = __importStar(require("./common/Response"));
44
44
  exports.Status = __importStar(require("./common/Status"));
45
45
  exports.AccessType = __importStar(require("./service/AccessType"));
46
46
  exports.Config = __importStar(require("./common/Config"));
47
+ exports.ModuleFetchEndpoints = __importStar(require("./common/ModuleFetchEndpointsLastUpdated"));
47
48
  exports.FilterParams = __importStar(require("./common/FilterParams"));
48
49
  // listings
49
50
  exports.Ad = __importStar(require("./ad/Ad"));
@@ -112,6 +113,7 @@ exports.SocialMediaProfile = __importStar(require("./media/SocialMediaProfile"))
112
113
  exports.Kyc = __importStar(require("./kyc/Kyc"));
113
114
  // integrations
114
115
  exports.Google = __importStar(require("./integrations/Google"));
116
+ exports.GeminiApiKey = __importStar(require("./integrations/GeminiApiKey"));
115
117
  // analysis
116
118
  exports.Analysis = __importStar(require("./analysis/AnalysisDailySnapshot"));
117
119
  // payments
@@ -0,0 +1,26 @@
1
+ /**
2
+ * A Gemini API key as the dashboard sees it. The key itself never leaves backend-core in this
3
+ * shape: only `api_key_masked` is sent.
4
+ */
5
+ export type IGeminiApiKey = {
6
+ id: number;
7
+ name: string;
8
+ /** e.g. "AIza…f3Kq" */
9
+ api_key_masked: string;
10
+ model_name: string;
11
+ /** Dashboard order, starting at 1 */
12
+ sort_order: number;
13
+ is_active: boolean;
14
+ /** All tokens used with this key */
15
+ total_tokens: number;
16
+ /** Tokens used today; resets every day */
17
+ daily_tokens: number;
18
+ /** The day daily_tokens belongs to (YYYY-MM-DD) */
19
+ daily_usage_date: string | null;
20
+ /** The key is skipped until this time, after Gemini rejected it for quota */
21
+ cooldown_until: string | null;
22
+ last_used_at: string | null;
23
+ last_error: string | null;
24
+ created_at: string;
25
+ updated_at: string;
26
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ export {};
@@ -2,6 +2,7 @@ export { AdDto } from './ad/Ad.dto.js';
2
2
  export * as AD from './ad/Ad.dto.js';
3
3
  export * as EVENTS from './events/index.js';
4
4
  export * as Ai from './ai/Ai.dto.js';
5
+ export * as GEMINI_API_KEYS from './ai/gemini-api-key.dto.js';
5
6
  export * as PAYMENTS from './payments/index.js';
6
7
  export * as ADDRESSES from './addresses/addresses.dto.js';
7
8
  export * as POTENTIAL_USERS from './potential-users/potential-user-import.dto.js';
@@ -0,0 +1,51 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Gemini API keys are stored in backend-core (one table) and shared by every Deallony service.
4
+ * The dashboard manages them through the admin schemas; other services lease a key and report
5
+ * the tokens they used through the internal schemas.
6
+ */
7
+ const geminiApiKeyIdSchema = z.coerce.number().int().positive();
8
+ /** Gemini model ID, e.g. gemini-3.1-flash-lite */
9
+ export const geminiModelNameSchema = z.string().trim().min(1).max(100);
10
+ // ---------- Admin (dashboard) ----------
11
+ export const createGeminiApiKeySchema = z.object({
12
+ name: z.string().trim().min(1).max(255),
13
+ api_key: z.string().trim().min(20).max(512),
14
+ model_name: geminiModelNameSchema,
15
+ is_active: z.boolean().default(true),
16
+ });
17
+ /** Fields left out keep their current value; the key itself is only replaced when sent. */
18
+ export const updateGeminiApiKeySchema = z.object({
19
+ id: geminiApiKeyIdSchema,
20
+ name: z.string().trim().min(1).max(255).optional(),
21
+ api_key: z.string().trim().min(20).max(512).optional(),
22
+ model_name: geminiModelNameSchema.optional(),
23
+ is_active: z.boolean().optional(),
24
+ });
25
+ export const geminiApiKeyIdBodySchema = z.object({
26
+ id: geminiApiKeyIdSchema,
27
+ });
28
+ /** Every key ID in the new order: the first ID gets sort_order 1. */
29
+ export const reorderGeminiApiKeysSchema = z.object({
30
+ ids: z.array(geminiApiKeyIdSchema).min(1),
31
+ });
32
+ // ---------- Internal (other services) ----------
33
+ /** Ask backend-core for the least-used available key, optionally only keys for one model. */
34
+ export const leaseGeminiApiKeySchema = z.object({
35
+ model_name: geminiModelNameSchema.optional(),
36
+ });
37
+ export const leasedGeminiApiKeySchema = z.object({
38
+ id: z.number().int().positive(),
39
+ api_key: z.string(),
40
+ model_name: z.string(),
41
+ });
42
+ /** Sent after every Gemini call made with a leased key. */
43
+ export const reportGeminiApiKeyUsageSchema = z.object({
44
+ id: geminiApiKeyIdSchema,
45
+ /** usageMetadata.totalTokenCount of the Gemini response; 0 when the call failed */
46
+ total_tokens: z.coerce.number().int().min(0).default(0),
47
+ success: z.boolean().default(true),
48
+ /** HTTP status Gemini answered with, e.g. 429 */
49
+ error_code: z.coerce.number().int().optional(),
50
+ error_message: z.string().trim().max(2000).optional(),
51
+ });
@@ -1,2 +1,3 @@
1
1
  export * from './ai-fill-form.schema.js';
2
2
  export * from './ai-fill-ad-form.schema.js';
3
+ export * from './gemini-api-key.schema.js';
@@ -14,10 +14,8 @@ export const StartAiConversationSchema = z.object({
14
14
  language: ConversationLanguageSchema.default('ar'),
15
15
  purpose: z.literal('marketing'),
16
16
  subject_type: MarketingSubjectTypeSchema,
17
- initial_message: z.string().min(1),
18
17
  context: z.object({
19
18
  name: z.string().nullable().optional(),
20
- reason: z.string().min(1),
21
19
  note: z.string().nullable().optional(),
22
20
  previous_contact_count: z.number().int().nonnegative().default(0),
23
21
  }),
@@ -38,3 +36,32 @@ export const PotentialUserConversationEventSchema = z.object({
38
36
  event: ConversationEventTypeSchema,
39
37
  summary: z.string().max(2000).nullable().optional(),
40
38
  });
39
+ export const AiConversationStatusSchema = z.enum([
40
+ 'starting',
41
+ 'waiting_for_reply',
42
+ 'chatting',
43
+ 'interested',
44
+ 'human_handoff',
45
+ 'not_interested',
46
+ 'converted',
47
+ 'opted_out',
48
+ 'closed',
49
+ 'failed',
50
+ ]);
51
+ export const AiConversationPurposeSchema = z.enum(['marketing', 'support']);
52
+ /** Statuses of a conversation that is still open with the customer. */
53
+ export const AI_CONVERSATION_ACTIVE_STATUSES = [
54
+ 'starting',
55
+ 'waiting_for_reply',
56
+ 'chatting',
57
+ 'interested',
58
+ 'human_handoff',
59
+ ];
60
+ export const AiConversationListSchema = z.object({
61
+ page: z.coerce.number().int().positive().default(1),
62
+ limit: z.coerce.number().int().min(1).max(100).default(20),
63
+ statuses: z.array(AiConversationStatusSchema).max(10).optional(),
64
+ active_only: z.boolean().default(false),
65
+ purpose: AiConversationPurposeSchema.optional(),
66
+ search: z.string().trim().max(100).optional(),
67
+ });
@@ -7,6 +7,7 @@ export * as Response from "./common/Response.js";
7
7
  export * as Status from "./common/Status.js";
8
8
  export * as AccessType from "./service/AccessType.js";
9
9
  export * as Config from "./common/Config.js";
10
+ export * as ModuleFetchEndpoints from "./common/ModuleFetchEndpointsLastUpdated.js";
10
11
  export * as FilterParams from "./common/FilterParams.js";
11
12
  // listings
12
13
  export * as Ad from "./ad/Ad.js";
@@ -75,6 +76,7 @@ export * as SocialMediaProfile from "./media/SocialMediaProfile.js";
75
76
  export * as Kyc from "./kyc/Kyc.js";
76
77
  // integrations
77
78
  export * as Google from "./integrations/Google.js";
79
+ export * as GeminiApiKey from "./integrations/GeminiApiKey.js";
78
80
  // analysis
79
81
  export * as Analysis from "./analysis/AnalysisDailySnapshot.js";
80
82
  // payments
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deallony/shared",
3
- "version": "1.2.55",
3
+ "version": "1.2.60",
4
4
  "private": false,
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -42,7 +42,6 @@
42
42
  "deploy:git:minor": "node ./scripts/publish.js git minor",
43
43
  "deploy:git:major": "node ./scripts/publish.js git major",
44
44
  "deploy:git:dry-run": "node ./scripts/publish.js git patch --dry-run",
45
- "test:release": "node --test scripts/publish.test.js",
46
45
  "prepublishOnly": "npm run build"
47
46
  },
48
47
  "dependencies": {