@deallony/shared 1.2.67 → 1.2.71

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.
Files changed (39) hide show
  1. package/dist/cjs/constants/routes/app.d.ts +27 -0
  2. package/dist/cjs/constants/routes/app.js +14 -0
  3. package/dist/cjs/dto/ai/gemini-api-key.dto.d.ts +4 -1
  4. package/dist/cjs/schema/ad/save-ad.schema.d.ts +31 -0
  5. package/dist/cjs/schema/ad/save-ad.schema.js +22 -1
  6. package/dist/cjs/schema/ai/gemini-api-key.schema.d.ts +46 -3
  7. package/dist/cjs/schema/ai/gemini-api-key.schema.js +36 -9
  8. package/dist/cjs/schema/communication/index.d.ts +1 -0
  9. package/dist/cjs/schema/communication/index.js +1 -0
  10. package/dist/cjs/schema/communication/offer.schema.d.ts +49 -0
  11. package/dist/cjs/schema/communication/offer.schema.js +31 -0
  12. package/dist/cjs/types/ad/Ad.d.ts +25 -1
  13. package/dist/cjs/types/ad-common/PriceType.d.ts +2 -0
  14. package/dist/cjs/types/common/MetaData.d.ts +3 -0
  15. package/dist/cjs/types/communication/Chat.d.ts +30 -1
  16. package/dist/cjs/types/communication/Chat.js +10 -1
  17. package/dist/cjs/types/communication/Notification.d.ts +57 -0
  18. package/dist/cjs/types/communication/Notification.js +11 -0
  19. package/dist/cjs/types/index.d.ts +1 -0
  20. package/dist/cjs/types/index.js +2 -1
  21. package/dist/cjs/types/integrations/GeminiApiKey.d.ts +11 -2
  22. package/dist/cjs/types/user/Favorite.d.ts +38 -0
  23. package/dist/cjs/types/user/Favorite.js +8 -1
  24. package/dist/cjs/utils/index.d.ts +1 -0
  25. package/dist/cjs/utils/index.js +1 -0
  26. package/dist/cjs/utils/rentPrices.d.ts +8 -0
  27. package/dist/cjs/utils/rentPrices.js +33 -0
  28. package/dist/esm/constants/routes/app.js +14 -0
  29. package/dist/esm/schema/ad/save-ad.schema.js +21 -0
  30. package/dist/esm/schema/ai/gemini-api-key.schema.js +35 -8
  31. package/dist/esm/schema/communication/index.js +1 -0
  32. package/dist/esm/schema/communication/offer.schema.js +28 -0
  33. package/dist/esm/types/communication/Chat.js +9 -0
  34. package/dist/esm/types/communication/Notification.js +8 -0
  35. package/dist/esm/types/index.js +1 -0
  36. package/dist/esm/types/user/Favorite.js +7 -0
  37. package/dist/esm/utils/index.js +1 -0
  38. package/dist/esm/utils/rentPrices.js +26 -0
  39. package/package.json +58 -58
@@ -6,6 +6,30 @@ export declare const APP_ROUTES: {
6
6
  pathname: string;
7
7
  };
8
8
  };
9
+ readonly notifications: {
10
+ /** The notification centre (favorites updates) */
11
+ readonly list: () => {
12
+ pathname: string;
13
+ };
14
+ readonly settings: () => {
15
+ pathname: string;
16
+ };
17
+ };
18
+ readonly favorites: {
19
+ readonly compare: (ad_ids: (number | string)[]) => {
20
+ pathname: string;
21
+ params: {
22
+ ids: string;
23
+ };
24
+ };
25
+ /** A favorites list someone shared */
26
+ readonly sharedList: (token: string) => {
27
+ pathname: string;
28
+ params: {
29
+ token: string;
30
+ };
31
+ };
32
+ };
9
33
  readonly categories: {
10
34
  readonly all: (params: {
11
35
  type: SubjectTypeValue;
@@ -300,6 +324,8 @@ export declare const APP_ROUTES: {
300
324
  by_message: string;
301
325
  by_whatsapp: string;
302
326
  phone: string;
327
+ /** Rent ads: JSON of [{ price_type_id, price }]; the modal then sends a rent offer */
328
+ rent_prices?: string;
303
329
  }) => {
304
330
  pathname: string;
305
331
  params: {
@@ -312,6 +338,7 @@ export declare const APP_ROUTES: {
312
338
  by_whatsapp: string;
313
339
  phone: string;
314
340
  ad_price: number;
341
+ rent_prices: string | undefined;
315
342
  };
316
343
  };
317
344
  };
@@ -5,6 +5,19 @@ exports.APP_ROUTES = {
5
5
  main: {
6
6
  home: () => ({ pathname: "/(tabs)" }),
7
7
  },
8
+ notifications: {
9
+ /** The notification centre (favorites updates) */
10
+ list: () => ({ pathname: "/notifications" }),
11
+ settings: () => ({ pathname: "/notifications/settings" }),
12
+ },
13
+ favorites: {
14
+ compare: (ad_ids) => ({
15
+ pathname: "/favorites/compare",
16
+ params: { ids: ad_ids.join(",") },
17
+ }),
18
+ /** A favorites list someone shared */
19
+ sharedList: (token) => ({ pathname: "/favorites/list/[token]", params: { token } }),
20
+ },
8
21
  categories: {
9
22
  all: (params) => ({
10
23
  pathname: "/category/all-category",
@@ -138,6 +151,7 @@ exports.APP_ROUTES = {
138
151
  by_whatsapp: props.by_whatsapp,
139
152
  phone: props.phone,
140
153
  ad_price: props.ad_price,
154
+ rent_prices: props.rent_prices,
141
155
  },
142
156
  }),
143
157
  },
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { createGeminiApiKeySchema, geminiApiKeyIdBodySchema, leasedGeminiApiKeySchema, leaseGeminiApiKeySchema, reorderGeminiApiKeysSchema, reportGeminiApiKeyUsageSchema, updateGeminiApiKeySchema } from '../../schema/ai/gemini-api-key.schema';
2
+ import { aiKeySettingsSchema, aiProviderSchema, aiKeySelectionModeSchema, createGeminiApiKeySchema, geminiApiKeyIdBodySchema, leasedGeminiApiKeySchema, leaseGeminiApiKeySchema, reorderGeminiApiKeysSchema, reportGeminiApiKeyUsageSchema, updateGeminiApiKeySchema } from '../../schema/ai/gemini-api-key.schema';
3
3
  export type CreateGeminiApiKeyDto = z.input<typeof createGeminiApiKeySchema>;
4
4
  export type UpdateGeminiApiKeyDto = z.input<typeof updateGeminiApiKeySchema>;
5
5
  export type GeminiApiKeyIdBodyDto = z.input<typeof geminiApiKeyIdBodySchema>;
@@ -7,3 +7,6 @@ export type ReorderGeminiApiKeysDto = z.input<typeof reorderGeminiApiKeysSchema>
7
7
  export type LeaseGeminiApiKeyDto = z.input<typeof leaseGeminiApiKeySchema>;
8
8
  export type LeasedGeminiApiKey = z.infer<typeof leasedGeminiApiKeySchema>;
9
9
  export type ReportGeminiApiKeyUsageDto = z.input<typeof reportGeminiApiKeyUsageSchema>;
10
+ export type AiProvider = z.infer<typeof aiProviderSchema>;
11
+ export type AiKeySelectionMode = z.infer<typeof aiKeySelectionModeSchema>;
12
+ export type AiKeySettingsDto = z.input<typeof aiKeySettingsSchema>;
@@ -6,6 +6,17 @@ export declare const subjectTypeSchema: z.ZodEnum<{
6
6
  used_products: "used_products";
7
7
  }>;
8
8
  export declare const adMediaSchema: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodAny>>>;
9
+ /** One rent price per period. Backend checks the period belongs to "For Rent". */
10
+ export declare const adRentPriceSchema: z.ZodObject<{
11
+ price_type_id: z.ZodCoercedNumber<unknown>;
12
+ price: z.ZodCoercedNumber<unknown>;
13
+ is_main: z.ZodOptional<z.ZodCoercedBoolean<unknown>>;
14
+ }, z.core.$strip>;
15
+ export declare const adRentPricesSchema: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodObject<{
16
+ price_type_id: z.ZodCoercedNumber<unknown>;
17
+ price: z.ZodCoercedNumber<unknown>;
18
+ is_main: z.ZodOptional<z.ZodCoercedBoolean<unknown>>;
19
+ }, z.core.$strip>>>>;
9
20
  export declare const createAdSchema: z.ZodObject<{
10
21
  motor: z.ZodNullable<z.ZodOptional<z.ZodObject<{
11
22
  ad_id: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
@@ -98,6 +109,11 @@ export declare const createAdSchema: z.ZodObject<{
98
109
  ad_id: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
99
110
  admin_boundary_id: z.ZodCoercedNumber<unknown>;
100
111
  }, z.core.$strip>>>>;
112
+ rent_prices: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodObject<{
113
+ price_type_id: z.ZodCoercedNumber<unknown>;
114
+ price: z.ZodCoercedNumber<unknown>;
115
+ is_main: z.ZodOptional<z.ZodCoercedBoolean<unknown>>;
116
+ }, z.core.$strip>>>>;
101
117
  user_id: z.ZodCoercedNumber<unknown>;
102
118
  id: z.ZodOptional<z.ZodNull>;
103
119
  }, z.core.$strip>;
@@ -197,6 +213,11 @@ export declare const updateAdSchema: z.ZodObject<{
197
213
  ad_id: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
198
214
  admin_boundary_id: z.ZodCoercedNumber<unknown>;
199
215
  }, z.core.$strip>>>>;
216
+ rent_prices: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodObject<{
217
+ price_type_id: z.ZodCoercedNumber<unknown>;
218
+ price: z.ZodCoercedNumber<unknown>;
219
+ is_main: z.ZodOptional<z.ZodCoercedBoolean<unknown>>;
220
+ }, z.core.$strip>>>>;
200
221
  user_id: z.ZodCoercedNumber<unknown>;
201
222
  }, z.core.$strip>;
202
223
  export declare const saveAdSchema: z.ZodUnion<readonly [z.ZodObject<{
@@ -291,6 +312,11 @@ export declare const saveAdSchema: z.ZodUnion<readonly [z.ZodObject<{
291
312
  ad_id: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
292
313
  admin_boundary_id: z.ZodCoercedNumber<unknown>;
293
314
  }, z.core.$strip>>>>;
315
+ rent_prices: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodObject<{
316
+ price_type_id: z.ZodCoercedNumber<unknown>;
317
+ price: z.ZodCoercedNumber<unknown>;
318
+ is_main: z.ZodOptional<z.ZodCoercedBoolean<unknown>>;
319
+ }, z.core.$strip>>>>;
294
320
  user_id: z.ZodCoercedNumber<unknown>;
295
321
  id: z.ZodOptional<z.ZodNull>;
296
322
  }, z.core.$strip>, z.ZodObject<{
@@ -389,5 +415,10 @@ export declare const saveAdSchema: z.ZodUnion<readonly [z.ZodObject<{
389
415
  ad_id: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
390
416
  admin_boundary_id: z.ZodCoercedNumber<unknown>;
391
417
  }, z.core.$strip>>>>;
418
+ rent_prices: z.ZodNullable<z.ZodOptional<z.ZodArray<z.ZodObject<{
419
+ price_type_id: z.ZodCoercedNumber<unknown>;
420
+ price: z.ZodCoercedNumber<unknown>;
421
+ is_main: z.ZodOptional<z.ZodCoercedBoolean<unknown>>;
422
+ }, z.core.$strip>>>>;
392
423
  user_id: z.ZodCoercedNumber<unknown>;
393
424
  }, z.core.$strip>]>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.saveAdSchema = exports.updateAdSchema = exports.createAdSchema = exports.adMediaSchema = exports.subjectTypeSchema = void 0;
3
+ exports.saveAdSchema = exports.updateAdSchema = exports.createAdSchema = exports.adRentPricesSchema = exports.adRentPriceSchema = exports.adMediaSchema = exports.subjectTypeSchema = void 0;
4
4
  const zod_1 = require("zod");
5
5
  const AdMedia_1 = require("../../constants/ad/AdMedia");
6
6
  const adMedia_1 = require("../../utils/adMedia");
@@ -166,6 +166,26 @@ exports.adMediaSchema = zod_1.z
166
166
  })
167
167
  .optional()
168
168
  .nullable();
169
+ /** One rent price per period. Backend checks the period belongs to "For Rent". */
170
+ exports.adRentPriceSchema = zod_1.z.object({
171
+ price_type_id: zod_1.z.coerce.number().int().positive(),
172
+ price: zod_1.z.coerce.number().positive(),
173
+ is_main: zod_1.z.coerce.boolean().optional(),
174
+ });
175
+ exports.adRentPricesSchema = zod_1.z
176
+ .array(exports.adRentPriceSchema)
177
+ .max(10)
178
+ .superRefine((prices, ctx) => {
179
+ const ids = prices.map((item) => item.price_type_id);
180
+ if (new Set(ids).size !== ids.length) {
181
+ ctx.addIssue({ code: "custom", message: "Each rent period can be listed once" });
182
+ }
183
+ if (prices.filter((item) => item.is_main).length > 1) {
184
+ ctx.addIssue({ code: "custom", message: "Only one rent price can be the main price" });
185
+ }
186
+ })
187
+ .optional()
188
+ .nullable();
169
189
  const baseAdCommon = zod_1.z.object({
170
190
  title: zod_1.z.string().trim().optional(),
171
191
  status_id: zod_1.z.coerce.number().int().positive().optional(),
@@ -197,6 +217,7 @@ const baseAdCommon = zod_1.z.object({
197
217
  company_id: zod_1.z.coerce.number().int().positive().optional().nullable(),
198
218
  expires_at: zod_1.z.iso.datetime().optional().nullable(),
199
219
  adLocations: zod_1.z.array(adLocationSchema).optional().nullable(),
220
+ rent_prices: exports.adRentPricesSchema,
200
221
  });
201
222
  exports.createAdSchema = zod_1.z.object({
202
223
  user_id: zod_1.z.coerce.number().int().positive(),
@@ -1,8 +1,29 @@
1
1
  import { z } from 'zod';
2
- /** Gemini model ID, e.g. gemini-3.1-flash-lite */
2
+ /** Which company's API a key belongs to: Google Gemini or Anthropic Claude */
3
+ export declare const AI_PROVIDERS: readonly ["gemini", "anthropic"];
4
+ export declare const aiProviderSchema: z.ZodEnum<{
5
+ gemini: "gemini";
6
+ anthropic: "anthropic";
7
+ }>;
8
+ /** Models offered in the dashboard for each provider; any other model ID can still be typed */
9
+ export declare const AI_PROVIDER_MODELS: Record<(typeof AI_PROVIDERS)[number], readonly string[]>;
10
+ /**
11
+ * How backend-core picks a key: `priority` always takes the first available key in the dashboard
12
+ * order, `balanced` takes the key with the fewest tokens used today.
13
+ */
14
+ export declare const AI_KEY_SELECTION_MODES: readonly ["priority", "balanced"];
15
+ export declare const aiKeySelectionModeSchema: z.ZodEnum<{
16
+ priority: "priority";
17
+ balanced: "balanced";
18
+ }>;
19
+ /** Default model ID of a key, e.g. gemini-3.1-flash-lite or claude-haiku-5-5 */
3
20
  export declare const geminiModelNameSchema: z.ZodString;
4
21
  export declare const createGeminiApiKeySchema: z.ZodObject<{
5
22
  name: z.ZodString;
23
+ provider: z.ZodDefault<z.ZodEnum<{
24
+ gemini: "gemini";
25
+ anthropic: "anthropic";
26
+ }>>;
6
27
  api_key: z.ZodString;
7
28
  model_name: z.ZodString;
8
29
  is_active: z.ZodDefault<z.ZodBoolean>;
@@ -11,6 +32,10 @@ export declare const createGeminiApiKeySchema: z.ZodObject<{
11
32
  export declare const updateGeminiApiKeySchema: z.ZodObject<{
12
33
  id: z.ZodCoercedNumber<unknown>;
13
34
  name: z.ZodOptional<z.ZodString>;
35
+ provider: z.ZodOptional<z.ZodEnum<{
36
+ gemini: "gemini";
37
+ anthropic: "anthropic";
38
+ }>>;
14
39
  api_key: z.ZodOptional<z.ZodString>;
15
40
  model_name: z.ZodOptional<z.ZodString>;
16
41
  is_active: z.ZodOptional<z.ZodBoolean>;
@@ -22,16 +47,34 @@ export declare const geminiApiKeyIdBodySchema: z.ZodObject<{
22
47
  export declare const reorderGeminiApiKeysSchema: z.ZodObject<{
23
48
  ids: z.ZodArray<z.ZodCoercedNumber<unknown>>;
24
49
  }, z.core.$strip>;
25
- /** Ask backend-core for the least-used available key, optionally only keys for one model. */
50
+ export declare const aiKeySettingsSchema: z.ZodObject<{
51
+ selection_mode: z.ZodEnum<{
52
+ priority: "priority";
53
+ balanced: "balanced";
54
+ }>;
55
+ }, z.core.$strip>;
56
+ /**
57
+ * Ask backend-core for the next available key. `exclude_ids` are keys that already failed for
58
+ * this call, so a retry moves on to the next key in the order.
59
+ */
26
60
  export declare const leaseGeminiApiKeySchema: z.ZodObject<{
27
61
  model_name: z.ZodOptional<z.ZodString>;
62
+ providers: z.ZodOptional<z.ZodArray<z.ZodEnum<{
63
+ gemini: "gemini";
64
+ anthropic: "anthropic";
65
+ }>>>;
66
+ exclude_ids: z.ZodOptional<z.ZodArray<z.ZodCoercedNumber<unknown>>>;
28
67
  }, z.core.$strip>;
29
68
  export declare const leasedGeminiApiKeySchema: z.ZodObject<{
30
69
  id: z.ZodNumber;
70
+ provider: z.ZodEnum<{
71
+ gemini: "gemini";
72
+ anthropic: "anthropic";
73
+ }>;
31
74
  api_key: z.ZodString;
32
75
  model_name: z.ZodString;
33
76
  }, z.core.$strip>;
34
- /** Sent after every Gemini call made with a leased key. */
77
+ /** Sent after every AI call made with a leased key. */
35
78
  export declare const reportGeminiApiKeyUsageSchema: z.ZodObject<{
36
79
  id: z.ZodCoercedNumber<unknown>;
37
80
  total_tokens: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
@@ -1,18 +1,33 @@
1
1
  "use strict";
2
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;
3
+ exports.reportGeminiApiKeyUsageSchema = exports.leasedGeminiApiKeySchema = exports.leaseGeminiApiKeySchema = exports.aiKeySettingsSchema = exports.reorderGeminiApiKeysSchema = exports.geminiApiKeyIdBodySchema = exports.updateGeminiApiKeySchema = exports.createGeminiApiKeySchema = exports.geminiModelNameSchema = exports.aiKeySelectionModeSchema = exports.AI_KEY_SELECTION_MODES = exports.AI_PROVIDER_MODELS = exports.aiProviderSchema = exports.AI_PROVIDERS = void 0;
4
4
  const zod_1 = require("zod");
5
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.
6
+ * AI API keys (Gemini and Claude) are stored in backend-core (one table) and shared by every
7
+ * Deallony service. The dashboard manages them through the admin schemas; other services lease a
8
+ * key and report the tokens they used through the internal schemas.
9
9
  */
10
10
  const geminiApiKeyIdSchema = zod_1.z.coerce.number().int().positive();
11
- /** Gemini model ID, e.g. gemini-3.1-flash-lite */
11
+ /** Which company's API a key belongs to: Google Gemini or Anthropic Claude */
12
+ exports.AI_PROVIDERS = ['gemini', 'anthropic'];
13
+ exports.aiProviderSchema = zod_1.z.enum(exports.AI_PROVIDERS);
14
+ /** Models offered in the dashboard for each provider; any other model ID can still be typed */
15
+ exports.AI_PROVIDER_MODELS = {
16
+ gemini: ['gemini-3.5-flash-lite', 'gemini-3.1-flash-lite'],
17
+ anthropic: ['claude-haiku-5-5', 'claude-sonnet-5-5', 'claude-opus-5-5'],
18
+ };
19
+ /**
20
+ * How backend-core picks a key: `priority` always takes the first available key in the dashboard
21
+ * order, `balanced` takes the key with the fewest tokens used today.
22
+ */
23
+ exports.AI_KEY_SELECTION_MODES = ['priority', 'balanced'];
24
+ exports.aiKeySelectionModeSchema = zod_1.z.enum(exports.AI_KEY_SELECTION_MODES);
25
+ /** Default model ID of a key, e.g. gemini-3.1-flash-lite or claude-haiku-5-5 */
12
26
  exports.geminiModelNameSchema = zod_1.z.string().trim().min(1).max(100);
13
27
  // ---------- Admin (dashboard) ----------
14
28
  exports.createGeminiApiKeySchema = zod_1.z.object({
15
29
  name: zod_1.z.string().trim().min(1).max(255),
30
+ provider: exports.aiProviderSchema.default('gemini'),
16
31
  api_key: zod_1.z.string().trim().min(20).max(512),
17
32
  model_name: exports.geminiModelNameSchema,
18
33
  is_active: zod_1.z.boolean().default(true),
@@ -21,6 +36,7 @@ exports.createGeminiApiKeySchema = zod_1.z.object({
21
36
  exports.updateGeminiApiKeySchema = zod_1.z.object({
22
37
  id: geminiApiKeyIdSchema,
23
38
  name: zod_1.z.string().trim().min(1).max(255).optional(),
39
+ provider: exports.aiProviderSchema.optional(),
24
40
  api_key: zod_1.z.string().trim().min(20).max(512).optional(),
25
41
  model_name: exports.geminiModelNameSchema.optional(),
26
42
  is_active: zod_1.z.boolean().optional(),
@@ -32,23 +48,34 @@ exports.geminiApiKeyIdBodySchema = zod_1.z.object({
32
48
  exports.reorderGeminiApiKeysSchema = zod_1.z.object({
33
49
  ids: zod_1.z.array(geminiApiKeyIdSchema).min(1),
34
50
  });
51
+ exports.aiKeySettingsSchema = zod_1.z.object({
52
+ selection_mode: exports.aiKeySelectionModeSchema,
53
+ });
35
54
  // ---------- Internal (other services) ----------
36
- /** Ask backend-core for the least-used available key, optionally only keys for one model. */
55
+ /**
56
+ * Ask backend-core for the next available key. `exclude_ids` are keys that already failed for
57
+ * this call, so a retry moves on to the next key in the order.
58
+ */
37
59
  exports.leaseGeminiApiKeySchema = zod_1.z.object({
60
+ /** Only keys whose default model is this one */
38
61
  model_name: exports.geminiModelNameSchema.optional(),
62
+ /** Only keys of these providers; all providers when left out */
63
+ providers: zod_1.z.array(exports.aiProviderSchema).min(1).optional(),
64
+ exclude_ids: zod_1.z.array(geminiApiKeyIdSchema).optional(),
39
65
  });
40
66
  exports.leasedGeminiApiKeySchema = zod_1.z.object({
41
67
  id: zod_1.z.number().int().positive(),
68
+ provider: exports.aiProviderSchema,
42
69
  api_key: zod_1.z.string(),
43
70
  model_name: zod_1.z.string(),
44
71
  });
45
- /** Sent after every Gemini call made with a leased key. */
72
+ /** Sent after every AI call made with a leased key. */
46
73
  exports.reportGeminiApiKeyUsageSchema = zod_1.z.object({
47
74
  id: geminiApiKeyIdSchema,
48
- /** usageMetadata.totalTokenCount of the Gemini response; 0 when the call failed */
75
+ /** Total tokens of the response (input + output); 0 when the call failed */
49
76
  total_tokens: zod_1.z.coerce.number().int().min(0).default(0),
50
77
  success: zod_1.z.boolean().default(true),
51
- /** HTTP status Gemini answered with, e.g. 429 */
78
+ /** HTTP status the provider answered with, e.g. 429 */
52
79
  error_code: zod_1.z.coerce.number().int().optional(),
53
80
  error_message: zod_1.z.string().trim().max(2000).optional(),
54
81
  });
@@ -1,2 +1,3 @@
1
1
  export * from './ai-conversation.schema';
2
2
  export * from './support-ticket.schema';
3
+ export * from './offer.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-conversation.schema"), exports);
18
18
  __exportStar(require("./support-ticket.schema"), exports);
19
+ __exportStar(require("./offer.schema"), exports);
@@ -0,0 +1,49 @@
1
+ import { z } from "zod";
2
+ export declare const offerStatusSchema: z.ZodEnum<{
3
+ pending: "pending";
4
+ accepted: "accepted";
5
+ countered: "countered";
6
+ declined: "declined";
7
+ }>;
8
+ /** PRICE_OFFER payload. Replies (accept, counter, decline) point at the offer they answer. */
9
+ export declare const priceOfferPayloadSchema: z.ZodObject<{
10
+ provider: z.ZodObject<{
11
+ offered_price: z.ZodCoercedNumber<unknown>;
12
+ currency_id: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
13
+ }, z.core.$strip>;
14
+ requester: z.ZodObject<{
15
+ offered_price: z.ZodCoercedNumber<unknown>;
16
+ currency_id: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
17
+ }, z.core.$strip>;
18
+ status: z.ZodOptional<z.ZodEnum<{
19
+ pending: "pending";
20
+ accepted: "accepted";
21
+ countered: "countered";
22
+ declined: "declined";
23
+ }>>;
24
+ reply_to_message_id: z.ZodNullable<z.ZodOptional<z.ZodCoercedNumber<unknown>>>;
25
+ note: z.ZodNullable<z.ZodOptional<z.ZodString>>;
26
+ }, z.core.$strip>;
27
+ /** RENT_OFFER payload: prices are per period, duration counts periods. */
28
+ export declare const rentOfferPayloadSchema: z.ZodObject<{
29
+ provider: z.ZodObject<{
30
+ offered_price: z.ZodCoercedNumber<unknown>;
31
+ currency_id: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
32
+ }, z.core.$strip>;
33
+ requester: z.ZodObject<{
34
+ offered_price: z.ZodCoercedNumber<unknown>;
35
+ currency_id: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
36
+ }, z.core.$strip>;
37
+ status: z.ZodOptional<z.ZodEnum<{
38
+ pending: "pending";
39
+ accepted: "accepted";
40
+ countered: "countered";
41
+ declined: "declined";
42
+ }>>;
43
+ reply_to_message_id: z.ZodNullable<z.ZodOptional<z.ZodCoercedNumber<unknown>>>;
44
+ note: z.ZodNullable<z.ZodOptional<z.ZodString>>;
45
+ price_type_id: z.ZodCoercedNumber<unknown>;
46
+ currency_id: z.ZodCoercedNumber<unknown>;
47
+ duration: z.ZodCoercedNumber<unknown>;
48
+ start_date: z.ZodNullable<z.ZodOptional<z.ZodString>>;
49
+ }, z.core.$strip>;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rentOfferPayloadSchema = exports.priceOfferPayloadSchema = exports.offerStatusSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ exports.offerStatusSchema = zod_1.z.enum(["pending", "accepted", "countered", "declined"]);
6
+ // The seller's side can be 0 when the ad has no price; the backend requires a positive buyer price
7
+ const offerSideSchema = zod_1.z.object({
8
+ offered_price: zod_1.z.coerce.number().min(0),
9
+ currency_id: zod_1.z.coerce.number().int().positive().optional(),
10
+ });
11
+ /** PRICE_OFFER payload. Replies (accept, counter, decline) point at the offer they answer. */
12
+ exports.priceOfferPayloadSchema = zod_1.z.object({
13
+ provider: offerSideSchema,
14
+ requester: offerSideSchema,
15
+ status: exports.offerStatusSchema.optional(),
16
+ reply_to_message_id: zod_1.z.coerce.number().int().positive().optional().nullable(),
17
+ /** Free text the sender added; the message text itself is a readable summary for older apps */
18
+ note: zod_1.z.string().trim().max(1000).optional().nullable(),
19
+ });
20
+ /** RENT_OFFER payload: prices are per period, duration counts periods. */
21
+ exports.rentOfferPayloadSchema = exports.priceOfferPayloadSchema.extend({
22
+ price_type_id: zod_1.z.coerce.number().int().positive(),
23
+ currency_id: zod_1.z.coerce.number().int().positive(),
24
+ duration: zod_1.z.coerce.number().int().min(1).max(1000),
25
+ start_date: zod_1.z
26
+ .string()
27
+ .trim()
28
+ .regex(/^\d{4}-\d{2}-\d{2}$/)
29
+ .optional()
30
+ .nullable(),
31
+ });
@@ -9,6 +9,19 @@ import { IUsedProduct } from "../used-product/UsedProduct";
9
9
  import { Category, Currency } from "..";
10
10
  import { IAdminBoundary } from "../geo";
11
11
  import type { ICompany } from "../company/Company";
12
+ import type { IPriceType } from "../ad-common/PriceType";
13
+ /** One rent price of an ad for one period (a price type linked to "For Rent", e.g. Daily). */
14
+ export type IAdRentPrice = {
15
+ id?: number;
16
+ ad_id?: number;
17
+ price_type_id: number;
18
+ price: number;
19
+ /** price converted to the base currency, used for filtering and sorting */
20
+ base_price?: number | null;
21
+ /** the price shown on cards; mirrors ads.price */
22
+ is_main?: boolean;
23
+ price_type?: IPriceType | null;
24
+ };
12
25
  export type IAd = {
13
26
  id?: number;
14
27
  user_id?: number;
@@ -48,11 +61,18 @@ export type IAd = {
48
61
  service?: IService | null;
49
62
  product?: IUsedProduct | null;
50
63
  adminBoundary?: IAdminBoundary | null;
64
+ /** Rent ads (motors and properties): one price per period */
65
+ rent_prices?: IAdRentPrice[] | null;
66
+ /** In a list filtered by rent_price_type_id: the rent price of that period */
67
+ matched_rent_price?: IAdRentPrice | null;
51
68
  created_at?: string | Date;
52
69
  updated_at?: string | Date;
53
70
  expires_at?: string | Date | null;
71
+ deleted_at?: string | Date | null;
72
+ /** My ads: how many users saved this ad */
73
+ favorites_count?: number;
54
74
  };
55
- export type SaveAdDto = Partial<Omit<IAd, "created_at" | "updated_at" | "views" | "user" | "promotions" | "company">> & {
75
+ export type SaveAdDto = Partial<Omit<IAd, "created_at" | "updated_at" | "views" | "user" | "promotions" | "company" | "deleted_at" | "favorites_count">> & {
56
76
  id?: number;
57
77
  latitude?: number;
58
78
  longitude?: number;
@@ -75,6 +95,10 @@ export type GetAdsFilterDto = {
75
95
  currency_id?: number;
76
96
  min_price?: number;
77
97
  max_price?: number;
98
+ /** Rent period (price type id): price range and price sorting then use that period's price */
99
+ rent_price_type_id?: number;
100
+ sort_by?: "price" | "created_at" | "views";
101
+ sort_order?: "ASC" | "DESC";
78
102
  parent_category_id?: number;
79
103
  property?: {
80
104
  property_type_id?: number;
@@ -4,4 +4,6 @@ export type IPriceType = {
4
4
  name: LocalizedName;
5
5
  display_order?: number;
6
6
  offer_type_id?: number;
7
+ /** Length of the period in days (Daily 1, Monthly 30, Yearly 365); null for non rent types */
8
+ period_days?: number | null;
7
9
  };
@@ -13,6 +13,9 @@ export type MetaDataType = {
13
13
  maxPriceProperties: number;
14
14
  maxPriceMotors: number;
15
15
  maxPriceUsedProducts: number;
16
+ /** Highest price per rent period (price type id → base price) */
17
+ maxRentPricesProperties?: Record<number, number>;
18
+ maxRentPricesMotors?: Record<number, number>;
16
19
  };
17
20
  };
18
21
  export type MetaDataDefaults = {
@@ -41,8 +41,37 @@ export type IChatMessage = {
41
41
  export declare enum MessageTypeEnum {
42
42
  TEXT = 1,
43
43
  PRICE_OFFER = 2,
44
- CONTACT_INVITATION = 3
44
+ CONTACT_INVITATION = 3,
45
+ RENT_OFFER = 4
45
46
  }
47
+ /** Lifecycle of a price or rent offer. A reply is a new message pointing at the offer it answers. */
48
+ export declare enum OfferStatusEnum {
49
+ PENDING = "pending",
50
+ ACCEPTED = "accepted",
51
+ COUNTERED = "countered",
52
+ DECLINED = "declined"
53
+ }
54
+ export type OfferSide = {
55
+ offered_price: number;
56
+ currency_id?: number;
57
+ };
58
+ /** Payload of PRICE_OFFER messages (sale offers). status and reply_to_message_id are optional for old messages. */
59
+ export type PriceOfferPayload = {
60
+ provider: OfferSide;
61
+ requester: OfferSide;
62
+ status?: OfferStatusEnum;
63
+ reply_to_message_id?: number | null;
64
+ note?: string | null;
65
+ };
66
+ /** Payload of RENT_OFFER messages: prices are per period (price_type_id). */
67
+ export type RentOfferPayload = PriceOfferPayload & {
68
+ price_type_id: number;
69
+ currency_id: number;
70
+ /** number of periods, e.g. 3 (months) */
71
+ duration: number;
72
+ /** YYYY-MM-DD */
73
+ start_date?: string | null;
74
+ };
46
75
  export { MessageTypeEnum as MessageTypeType };
47
76
  export type ContactInvitationPayload = {
48
77
  version: 1;
@@ -1,9 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MessageTypeType = exports.MessageTypeEnum = void 0;
3
+ exports.MessageTypeType = exports.OfferStatusEnum = exports.MessageTypeEnum = void 0;
4
4
  var MessageTypeEnum;
5
5
  (function (MessageTypeEnum) {
6
6
  MessageTypeEnum[MessageTypeEnum["TEXT"] = 1] = "TEXT";
7
7
  MessageTypeEnum[MessageTypeEnum["PRICE_OFFER"] = 2] = "PRICE_OFFER";
8
8
  MessageTypeEnum[MessageTypeEnum["CONTACT_INVITATION"] = 3] = "CONTACT_INVITATION";
9
+ MessageTypeEnum[MessageTypeEnum["RENT_OFFER"] = 4] = "RENT_OFFER";
9
10
  })(MessageTypeEnum || (exports.MessageTypeType = exports.MessageTypeEnum = MessageTypeEnum = {}));
11
+ /** Lifecycle of a price or rent offer. A reply is a new message pointing at the offer it answers. */
12
+ var OfferStatusEnum;
13
+ (function (OfferStatusEnum) {
14
+ OfferStatusEnum["PENDING"] = "pending";
15
+ OfferStatusEnum["ACCEPTED"] = "accepted";
16
+ OfferStatusEnum["COUNTERED"] = "countered";
17
+ OfferStatusEnum["DECLINED"] = "declined";
18
+ })(OfferStatusEnum || (exports.OfferStatusEnum = OfferStatusEnum = {}));
@@ -0,0 +1,57 @@
1
+ import { IAd } from "../ad/Ad";
2
+ /** Notifications kept in the user's notification centre. Only favorites updates for now. */
3
+ export declare enum NotificationTypeEnum {
4
+ FAVORITE_PRICE_DROP = "favorite_price_drop",
5
+ FAVORITE_SOLD = "favorite_sold",
6
+ FAVORITE_BACK = "favorite_back",
7
+ FAVORITE_SIMILAR = "favorite_similar"
8
+ }
9
+ export type NotificationText = {
10
+ ar: string;
11
+ en: string;
12
+ };
13
+ export type INotificationData = {
14
+ ad_id?: number;
15
+ old_price?: number;
16
+ new_price?: number;
17
+ currency_id?: number | null;
18
+ /** FAVORITE_SIMILAR: the new ads and what they match */
19
+ ad_ids?: number[];
20
+ count?: number;
21
+ subject_type?: string;
22
+ category_id?: number | null;
23
+ };
24
+ export type INotification = {
25
+ id: number;
26
+ user_id: number;
27
+ type: NotificationTypeEnum;
28
+ title: NotificationText;
29
+ body: NotificationText;
30
+ ad_id: number | null;
31
+ ad?: IAd | null;
32
+ data: INotificationData | null;
33
+ /** Same key within 24 hours updates the existing row instead of adding one */
34
+ group_key: string | null;
35
+ read_at: Date | string | null;
36
+ push_status: "pending" | "sent" | "skipped" | "failed";
37
+ pushed_at: Date | string | null;
38
+ created_at: Date | string;
39
+ updated_at?: Date | string;
40
+ };
41
+ export type INotificationSetting = {
42
+ type: NotificationTypeEnum;
43
+ push_enabled: boolean;
44
+ inbox_enabled: boolean;
45
+ };
46
+ export type GetNotificationsDto = {
47
+ page?: number;
48
+ limit?: number;
49
+ type?: NotificationTypeEnum;
50
+ };
51
+ export type MarkNotificationsReadDto = {
52
+ ids?: number[];
53
+ all?: boolean;
54
+ };
55
+ export type UpdateNotificationSettingsDto = {
56
+ settings: INotificationSetting[];
57
+ };
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NotificationTypeEnum = void 0;
4
+ /** Notifications kept in the user's notification centre. Only favorites updates for now. */
5
+ var NotificationTypeEnum;
6
+ (function (NotificationTypeEnum) {
7
+ NotificationTypeEnum["FAVORITE_PRICE_DROP"] = "favorite_price_drop";
8
+ NotificationTypeEnum["FAVORITE_SOLD"] = "favorite_sold";
9
+ NotificationTypeEnum["FAVORITE_BACK"] = "favorite_back";
10
+ NotificationTypeEnum["FAVORITE_SIMILAR"] = "favorite_similar";
11
+ })(NotificationTypeEnum || (exports.NotificationTypeEnum = NotificationTypeEnum = {}));
@@ -57,6 +57,7 @@ export * as Chat from "./communication/Chat";
57
57
  export * as Device from "./communication/Device";
58
58
  export * as MessageTemplate from "./communication/MessageTemplate";
59
59
  export * as NotificationToken from "./communication/NotificationToken";
60
+ export * as Notification from "./communication/Notification";
60
61
  export * as File from "./media/File";
61
62
  export * as Media from "./media/Media";
62
63
  export * as Image from "./media/Image";
@@ -37,7 +37,7 @@ var __importStar = (this && this.__importStar) || (function () {
37
37
  })();
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  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.AdDailyViews = 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 = exports.Loyalty = void 0;
40
+ exports.Payment = exports.Analysis = exports.GeminiApiKey = exports.Google = exports.Kyc = exports.SocialMediaProfile = exports.SocialMediaPlatform = exports.Image = exports.Media = exports.File = exports.Notification = exports.NotificationToken = exports.MessageTemplate = exports.Device = exports.Chat = exports.AppStartScreen = exports.MetaData = exports.Company = exports.PotentialUser = exports.Loyalty = void 0;
41
41
  // common
42
42
  exports.LocalizedName = __importStar(require("./common/LocalizedName"));
43
43
  exports.Response = __importStar(require("./common/Response"));
@@ -104,6 +104,7 @@ exports.Chat = __importStar(require("./communication/Chat"));
104
104
  exports.Device = __importStar(require("./communication/Device"));
105
105
  exports.MessageTemplate = __importStar(require("./communication/MessageTemplate"));
106
106
  exports.NotificationToken = __importStar(require("./communication/NotificationToken"));
107
+ exports.Notification = __importStar(require("./communication/Notification"));
107
108
  // media
108
109
  exports.File = __importStar(require("./media/File"));
109
110
  exports.Media = __importStar(require("./media/Media"));
@@ -1,12 +1,21 @@
1
+ /** google = Gemini, anthropic = Claude */
2
+ export type AiProvider = 'gemini' | 'anthropic';
3
+ /** priority = first available key in order; balanced = least tokens used today */
4
+ export type AiKeySelectionMode = 'priority' | 'balanced';
5
+ export type IAiKeySettings = {
6
+ selection_mode: AiKeySelectionMode;
7
+ };
1
8
  /**
2
- * A Gemini API key as the dashboard sees it. The key itself never leaves backend-core in this
9
+ * An AI API key (Gemini or Claude) as the dashboard sees it. The key itself never leaves backend-core in this
3
10
  * shape: only `api_key_masked` is sent.
4
11
  */
5
12
  export type IGeminiApiKey = {
6
13
  id: number;
7
14
  name: string;
15
+ provider: AiProvider;
8
16
  /** e.g. "AIza…f3Kq" */
9
17
  api_key_masked: string;
18
+ /** Default model; a service may ask for another model of the same provider */
10
19
  model_name: string;
11
20
  /** Dashboard order, starting at 1 */
12
21
  sort_order: number;
@@ -17,7 +26,7 @@ export type IGeminiApiKey = {
17
26
  daily_tokens: number;
18
27
  /** The day daily_tokens belongs to (YYYY-MM-DD) */
19
28
  daily_usage_date: string | null;
20
- /** The key is skipped until this time, after Gemini rejected it for quota */
29
+ /** The key is skipped until this time, after the provider rejected it (quota, overload, bad key) */
21
30
  cooldown_until: string | null;
22
31
  last_used_at: string | null;
23
32
  last_error: string | null;
@@ -4,11 +4,49 @@ export declare enum FavoritableType {
4
4
  USED_PRODUCTS = "used_products",
5
5
  SERVICES = "services"
6
6
  }
7
+ /** A named group of the user's favorites, e.g. "Family car". Shared through share_token. */
8
+ export type IFavoriteList = {
9
+ id: number;
10
+ user_id: number;
11
+ name: string;
12
+ share_token: string;
13
+ created_at?: Date | string;
14
+ /** Number of favorites in the list, sent with the user's lists */
15
+ count?: number;
16
+ /** Up to four ads for the list's cover */
17
+ preview_ads?: IAd[];
18
+ };
7
19
  export type IFavorite = {
8
20
  id: number;
9
21
  user_id: number;
10
22
  user: IUser;
11
23
  created_at: Date | string;
12
24
  ad: IAd;
25
+ /** The ad's price and currency when it was saved, to show how much it dropped */
26
+ saved_price?: number | null;
27
+ saved_currency_id?: number | null;
28
+ /** The last price the user was notified about, so a drop is not announced twice */
29
+ last_notified_price?: number | null;
30
+ /** Price-drop, sold and back-on-sale alerts for this ad */
31
+ notify?: boolean;
32
+ /** Private note, only the user sees it */
33
+ note?: string | null;
34
+ list_id?: number | null;
13
35
  };
14
36
  export type FavoriteType = IFavorite;
37
+ /** Availability of a favorite's ad, derived from its status and deletion */
38
+ export declare enum FavoriteAvailability {
39
+ AVAILABLE = "available",
40
+ SOLD = "sold",
41
+ UNAVAILABLE = "unavailable"
42
+ }
43
+ export type UpdateFavoriteDto = {
44
+ ad_id: number;
45
+ note?: string | null;
46
+ notify?: boolean;
47
+ list_id?: number | null;
48
+ };
49
+ export type SaveFavoriteListDto = {
50
+ id?: number;
51
+ name: string;
52
+ };
@@ -1,8 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FavoritableType = void 0;
3
+ exports.FavoriteAvailability = exports.FavoritableType = void 0;
4
4
  var FavoritableType;
5
5
  (function (FavoritableType) {
6
6
  FavoritableType["USED_PRODUCTS"] = "used_products";
7
7
  FavoritableType["SERVICES"] = "services";
8
8
  })(FavoritableType || (exports.FavoritableType = FavoritableType = {}));
9
+ /** Availability of a favorite's ad, derived from its status and deletion */
10
+ var FavoriteAvailability;
11
+ (function (FavoriteAvailability) {
12
+ FavoriteAvailability["AVAILABLE"] = "available";
13
+ FavoriteAvailability["SOLD"] = "sold";
14
+ FavoriteAvailability["UNAVAILABLE"] = "unavailable";
15
+ })(FavoriteAvailability || (exports.FavoriteAvailability = FavoriteAvailability = {}));
@@ -2,3 +2,4 @@ export * from './converter';
2
2
  export * from './routes';
3
3
  export * from './adMedia';
4
4
  export * from './analysis';
5
+ export * from './rentPrices';
@@ -18,3 +18,4 @@ __exportStar(require("./converter"), exports);
18
18
  __exportStar(require("./routes"), exports);
19
19
  __exportStar(require("./adMedia"), exports);
20
20
  __exportStar(require("./analysis"), exports);
21
+ __exportStar(require("./rentPrices"), exports);
@@ -0,0 +1,8 @@
1
+ import type { IAd, IAdRentPrice } from "../types/ad/Ad";
2
+ /** The rent price shown on cards: the one marked main, else the first listed. */
3
+ export declare const getMainRentPrice: (ad?: Pick<IAd, "rent_prices"> | null) => IAdRentPrice | null;
4
+ /** The price to show for an ad in a list: the filtered period, else the main rent price. */
5
+ export declare const getDisplayRentPrice: (ad?: Pick<IAd, "rent_prices" | "matched_rent_price"> | null, priceTypeId?: number | null) => IAdRentPrice | null;
6
+ /** Rent prices ordered from the shortest period to the longest. */
7
+ export declare const sortRentPrices: (prices?: IAdRentPrice[] | null) => IAdRentPrice[];
8
+ export declare const rentOfferTotal: (pricePerPeriod: number, duration: number) => number;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rentOfferTotal = exports.sortRentPrices = exports.getDisplayRentPrice = exports.getMainRentPrice = void 0;
4
+ /** The rent price shown on cards: the one marked main, else the first listed. */
5
+ const getMainRentPrice = (ad) => {
6
+ var _a, _b, _c;
7
+ const prices = (_a = ad === null || ad === void 0 ? void 0 : ad.rent_prices) !== null && _a !== void 0 ? _a : [];
8
+ return (_c = (_b = prices.find((item) => item.is_main)) !== null && _b !== void 0 ? _b : prices[0]) !== null && _c !== void 0 ? _c : null;
9
+ };
10
+ exports.getMainRentPrice = getMainRentPrice;
11
+ /** The price to show for an ad in a list: the filtered period, else the main rent price. */
12
+ const getDisplayRentPrice = (ad, priceTypeId) => {
13
+ var _a;
14
+ if (ad === null || ad === void 0 ? void 0 : ad.matched_rent_price)
15
+ return ad.matched_rent_price;
16
+ if (priceTypeId) {
17
+ const match = (_a = ad === null || ad === void 0 ? void 0 : ad.rent_prices) === null || _a === void 0 ? void 0 : _a.find((item) => item.price_type_id === Number(priceTypeId));
18
+ if (match)
19
+ return match;
20
+ }
21
+ return (0, exports.getMainRentPrice)(ad);
22
+ };
23
+ exports.getDisplayRentPrice = getDisplayRentPrice;
24
+ /** Rent prices ordered from the shortest period to the longest. */
25
+ const sortRentPrices = (prices) => [...(prices !== null && prices !== void 0 ? prices : [])].sort((a, b) => {
26
+ var _a, _b, _c, _d, _e, _f, _g, _h;
27
+ return ((_b = (_a = a.price_type) === null || _a === void 0 ? void 0 : _a.period_days) !== null && _b !== void 0 ? _b : Number.MAX_SAFE_INTEGER) -
28
+ ((_d = (_c = b.price_type) === null || _c === void 0 ? void 0 : _c.period_days) !== null && _d !== void 0 ? _d : Number.MAX_SAFE_INTEGER) ||
29
+ ((_f = (_e = a.price_type) === null || _e === void 0 ? void 0 : _e.display_order) !== null && _f !== void 0 ? _f : 0) - ((_h = (_g = b.price_type) === null || _g === void 0 ? void 0 : _g.display_order) !== null && _h !== void 0 ? _h : 0);
30
+ });
31
+ exports.sortRentPrices = sortRentPrices;
32
+ const rentOfferTotal = (pricePerPeriod, duration) => Math.max(0, Number(pricePerPeriod) || 0) * Math.max(0, Math.floor(Number(duration) || 0));
33
+ exports.rentOfferTotal = rentOfferTotal;
@@ -2,6 +2,19 @@ export const APP_ROUTES = {
2
2
  main: {
3
3
  home: () => ({ pathname: "/(tabs)" }),
4
4
  },
5
+ notifications: {
6
+ /** The notification centre (favorites updates) */
7
+ list: () => ({ pathname: "/notifications" }),
8
+ settings: () => ({ pathname: "/notifications/settings" }),
9
+ },
10
+ favorites: {
11
+ compare: (ad_ids) => ({
12
+ pathname: "/favorites/compare",
13
+ params: { ids: ad_ids.join(",") },
14
+ }),
15
+ /** A favorites list someone shared */
16
+ sharedList: (token) => ({ pathname: "/favorites/list/[token]", params: { token } }),
17
+ },
5
18
  categories: {
6
19
  all: (params) => ({
7
20
  pathname: "/category/all-category",
@@ -135,6 +148,7 @@ export const APP_ROUTES = {
135
148
  by_whatsapp: props.by_whatsapp,
136
149
  phone: props.phone,
137
150
  ad_price: props.ad_price,
151
+ rent_prices: props.rent_prices,
138
152
  },
139
153
  }),
140
154
  },
@@ -163,6 +163,26 @@ export const adMediaSchema = z
163
163
  })
164
164
  .optional()
165
165
  .nullable();
166
+ /** One rent price per period. Backend checks the period belongs to "For Rent". */
167
+ export const adRentPriceSchema = z.object({
168
+ price_type_id: z.coerce.number().int().positive(),
169
+ price: z.coerce.number().positive(),
170
+ is_main: z.coerce.boolean().optional(),
171
+ });
172
+ export const adRentPricesSchema = z
173
+ .array(adRentPriceSchema)
174
+ .max(10)
175
+ .superRefine((prices, ctx) => {
176
+ const ids = prices.map((item) => item.price_type_id);
177
+ if (new Set(ids).size !== ids.length) {
178
+ ctx.addIssue({ code: "custom", message: "Each rent period can be listed once" });
179
+ }
180
+ if (prices.filter((item) => item.is_main).length > 1) {
181
+ ctx.addIssue({ code: "custom", message: "Only one rent price can be the main price" });
182
+ }
183
+ })
184
+ .optional()
185
+ .nullable();
166
186
  const baseAdCommon = z.object({
167
187
  title: z.string().trim().optional(),
168
188
  status_id: z.coerce.number().int().positive().optional(),
@@ -194,6 +214,7 @@ const baseAdCommon = z.object({
194
214
  company_id: z.coerce.number().int().positive().optional().nullable(),
195
215
  expires_at: z.iso.datetime().optional().nullable(),
196
216
  adLocations: z.array(adLocationSchema).optional().nullable(),
217
+ rent_prices: adRentPricesSchema,
197
218
  });
198
219
  export const createAdSchema = z.object({
199
220
  user_id: z.coerce.number().int().positive(),
@@ -1,15 +1,30 @@
1
1
  import { z } from 'zod';
2
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.
3
+ * AI API keys (Gemini and Claude) are stored in backend-core (one table) and shared by every
4
+ * Deallony service. The dashboard manages them through the admin schemas; other services lease a
5
+ * key and report the tokens they used through the internal schemas.
6
6
  */
7
7
  const geminiApiKeyIdSchema = z.coerce.number().int().positive();
8
- /** Gemini model ID, e.g. gemini-3.1-flash-lite */
8
+ /** Which company's API a key belongs to: Google Gemini or Anthropic Claude */
9
+ export const AI_PROVIDERS = ['gemini', 'anthropic'];
10
+ export const aiProviderSchema = z.enum(AI_PROVIDERS);
11
+ /** Models offered in the dashboard for each provider; any other model ID can still be typed */
12
+ export const AI_PROVIDER_MODELS = {
13
+ gemini: ['gemini-3.5-flash-lite', 'gemini-3.1-flash-lite'],
14
+ anthropic: ['claude-haiku-5-5', 'claude-sonnet-5-5', 'claude-opus-5-5'],
15
+ };
16
+ /**
17
+ * How backend-core picks a key: `priority` always takes the first available key in the dashboard
18
+ * order, `balanced` takes the key with the fewest tokens used today.
19
+ */
20
+ export const AI_KEY_SELECTION_MODES = ['priority', 'balanced'];
21
+ export const aiKeySelectionModeSchema = z.enum(AI_KEY_SELECTION_MODES);
22
+ /** Default model ID of a key, e.g. gemini-3.1-flash-lite or claude-haiku-5-5 */
9
23
  export const geminiModelNameSchema = z.string().trim().min(1).max(100);
10
24
  // ---------- Admin (dashboard) ----------
11
25
  export const createGeminiApiKeySchema = z.object({
12
26
  name: z.string().trim().min(1).max(255),
27
+ provider: aiProviderSchema.default('gemini'),
13
28
  api_key: z.string().trim().min(20).max(512),
14
29
  model_name: geminiModelNameSchema,
15
30
  is_active: z.boolean().default(true),
@@ -18,6 +33,7 @@ export const createGeminiApiKeySchema = z.object({
18
33
  export const updateGeminiApiKeySchema = z.object({
19
34
  id: geminiApiKeyIdSchema,
20
35
  name: z.string().trim().min(1).max(255).optional(),
36
+ provider: aiProviderSchema.optional(),
21
37
  api_key: z.string().trim().min(20).max(512).optional(),
22
38
  model_name: geminiModelNameSchema.optional(),
23
39
  is_active: z.boolean().optional(),
@@ -29,23 +45,34 @@ export const geminiApiKeyIdBodySchema = z.object({
29
45
  export const reorderGeminiApiKeysSchema = z.object({
30
46
  ids: z.array(geminiApiKeyIdSchema).min(1),
31
47
  });
48
+ export const aiKeySettingsSchema = z.object({
49
+ selection_mode: aiKeySelectionModeSchema,
50
+ });
32
51
  // ---------- Internal (other services) ----------
33
- /** Ask backend-core for the least-used available key, optionally only keys for one model. */
52
+ /**
53
+ * Ask backend-core for the next available key. `exclude_ids` are keys that already failed for
54
+ * this call, so a retry moves on to the next key in the order.
55
+ */
34
56
  export const leaseGeminiApiKeySchema = z.object({
57
+ /** Only keys whose default model is this one */
35
58
  model_name: geminiModelNameSchema.optional(),
59
+ /** Only keys of these providers; all providers when left out */
60
+ providers: z.array(aiProviderSchema).min(1).optional(),
61
+ exclude_ids: z.array(geminiApiKeyIdSchema).optional(),
36
62
  });
37
63
  export const leasedGeminiApiKeySchema = z.object({
38
64
  id: z.number().int().positive(),
65
+ provider: aiProviderSchema,
39
66
  api_key: z.string(),
40
67
  model_name: z.string(),
41
68
  });
42
- /** Sent after every Gemini call made with a leased key. */
69
+ /** Sent after every AI call made with a leased key. */
43
70
  export const reportGeminiApiKeyUsageSchema = z.object({
44
71
  id: geminiApiKeyIdSchema,
45
- /** usageMetadata.totalTokenCount of the Gemini response; 0 when the call failed */
72
+ /** Total tokens of the response (input + output); 0 when the call failed */
46
73
  total_tokens: z.coerce.number().int().min(0).default(0),
47
74
  success: z.boolean().default(true),
48
- /** HTTP status Gemini answered with, e.g. 429 */
75
+ /** HTTP status the provider answered with, e.g. 429 */
49
76
  error_code: z.coerce.number().int().optional(),
50
77
  error_message: z.string().trim().max(2000).optional(),
51
78
  });
@@ -1,2 +1,3 @@
1
1
  export * from './ai-conversation.schema.js';
2
2
  export * from './support-ticket.schema.js';
3
+ export * from './offer.schema.js';
@@ -0,0 +1,28 @@
1
+ import { z } from "zod";
2
+ export const offerStatusSchema = z.enum(["pending", "accepted", "countered", "declined"]);
3
+ // The seller's side can be 0 when the ad has no price; the backend requires a positive buyer price
4
+ const offerSideSchema = z.object({
5
+ offered_price: z.coerce.number().min(0),
6
+ currency_id: z.coerce.number().int().positive().optional(),
7
+ });
8
+ /** PRICE_OFFER payload. Replies (accept, counter, decline) point at the offer they answer. */
9
+ export const priceOfferPayloadSchema = z.object({
10
+ provider: offerSideSchema,
11
+ requester: offerSideSchema,
12
+ status: offerStatusSchema.optional(),
13
+ reply_to_message_id: z.coerce.number().int().positive().optional().nullable(),
14
+ /** Free text the sender added; the message text itself is a readable summary for older apps */
15
+ note: z.string().trim().max(1000).optional().nullable(),
16
+ });
17
+ /** RENT_OFFER payload: prices are per period, duration counts periods. */
18
+ export const rentOfferPayloadSchema = priceOfferPayloadSchema.extend({
19
+ price_type_id: z.coerce.number().int().positive(),
20
+ currency_id: z.coerce.number().int().positive(),
21
+ duration: z.coerce.number().int().min(1).max(1000),
22
+ start_date: z
23
+ .string()
24
+ .trim()
25
+ .regex(/^\d{4}-\d{2}-\d{2}$/)
26
+ .optional()
27
+ .nullable(),
28
+ });
@@ -3,5 +3,14 @@ export var MessageTypeEnum;
3
3
  MessageTypeEnum[MessageTypeEnum["TEXT"] = 1] = "TEXT";
4
4
  MessageTypeEnum[MessageTypeEnum["PRICE_OFFER"] = 2] = "PRICE_OFFER";
5
5
  MessageTypeEnum[MessageTypeEnum["CONTACT_INVITATION"] = 3] = "CONTACT_INVITATION";
6
+ MessageTypeEnum[MessageTypeEnum["RENT_OFFER"] = 4] = "RENT_OFFER";
6
7
  })(MessageTypeEnum || (MessageTypeEnum = {}));
8
+ /** Lifecycle of a price or rent offer. A reply is a new message pointing at the offer it answers. */
9
+ export var OfferStatusEnum;
10
+ (function (OfferStatusEnum) {
11
+ OfferStatusEnum["PENDING"] = "pending";
12
+ OfferStatusEnum["ACCEPTED"] = "accepted";
13
+ OfferStatusEnum["COUNTERED"] = "countered";
14
+ OfferStatusEnum["DECLINED"] = "declined";
15
+ })(OfferStatusEnum || (OfferStatusEnum = {}));
7
16
  export { MessageTypeEnum as MessageTypeType };
@@ -0,0 +1,8 @@
1
+ /** Notifications kept in the user's notification centre. Only favorites updates for now. */
2
+ export var NotificationTypeEnum;
3
+ (function (NotificationTypeEnum) {
4
+ NotificationTypeEnum["FAVORITE_PRICE_DROP"] = "favorite_price_drop";
5
+ NotificationTypeEnum["FAVORITE_SOLD"] = "favorite_sold";
6
+ NotificationTypeEnum["FAVORITE_BACK"] = "favorite_back";
7
+ NotificationTypeEnum["FAVORITE_SIMILAR"] = "favorite_similar";
8
+ })(NotificationTypeEnum || (NotificationTypeEnum = {}));
@@ -67,6 +67,7 @@ export * as Chat from "./communication/Chat.js";
67
67
  export * as Device from "./communication/Device.js";
68
68
  export * as MessageTemplate from "./communication/MessageTemplate.js";
69
69
  export * as NotificationToken from "./communication/NotificationToken.js";
70
+ export * as Notification from "./communication/Notification.js";
70
71
  // media
71
72
  export * as File from "./media/File.js";
72
73
  export * as Media from "./media/Media.js";
@@ -3,3 +3,10 @@ export var FavoritableType;
3
3
  FavoritableType["USED_PRODUCTS"] = "used_products";
4
4
  FavoritableType["SERVICES"] = "services";
5
5
  })(FavoritableType || (FavoritableType = {}));
6
+ /** Availability of a favorite's ad, derived from its status and deletion */
7
+ export var FavoriteAvailability;
8
+ (function (FavoriteAvailability) {
9
+ FavoriteAvailability["AVAILABLE"] = "available";
10
+ FavoriteAvailability["SOLD"] = "sold";
11
+ FavoriteAvailability["UNAVAILABLE"] = "unavailable";
12
+ })(FavoriteAvailability || (FavoriteAvailability = {}));
@@ -2,3 +2,4 @@ export * from './converter.js';
2
2
  export * from './routes.js';
3
3
  export * from './adMedia.js';
4
4
  export * from './analysis.js';
5
+ export * from './rentPrices.js';
@@ -0,0 +1,26 @@
1
+ /** The rent price shown on cards: the one marked main, else the first listed. */
2
+ export const getMainRentPrice = (ad) => {
3
+ var _a, _b, _c;
4
+ const prices = (_a = ad === null || ad === void 0 ? void 0 : ad.rent_prices) !== null && _a !== void 0 ? _a : [];
5
+ return (_c = (_b = prices.find((item) => item.is_main)) !== null && _b !== void 0 ? _b : prices[0]) !== null && _c !== void 0 ? _c : null;
6
+ };
7
+ /** The price to show for an ad in a list: the filtered period, else the main rent price. */
8
+ export const getDisplayRentPrice = (ad, priceTypeId) => {
9
+ var _a;
10
+ if (ad === null || ad === void 0 ? void 0 : ad.matched_rent_price)
11
+ return ad.matched_rent_price;
12
+ if (priceTypeId) {
13
+ const match = (_a = ad === null || ad === void 0 ? void 0 : ad.rent_prices) === null || _a === void 0 ? void 0 : _a.find((item) => item.price_type_id === Number(priceTypeId));
14
+ if (match)
15
+ return match;
16
+ }
17
+ return getMainRentPrice(ad);
18
+ };
19
+ /** Rent prices ordered from the shortest period to the longest. */
20
+ export const sortRentPrices = (prices) => [...(prices !== null && prices !== void 0 ? prices : [])].sort((a, b) => {
21
+ var _a, _b, _c, _d, _e, _f, _g, _h;
22
+ return ((_b = (_a = a.price_type) === null || _a === void 0 ? void 0 : _a.period_days) !== null && _b !== void 0 ? _b : Number.MAX_SAFE_INTEGER) -
23
+ ((_d = (_c = b.price_type) === null || _c === void 0 ? void 0 : _c.period_days) !== null && _d !== void 0 ? _d : Number.MAX_SAFE_INTEGER) ||
24
+ ((_f = (_e = a.price_type) === null || _e === void 0 ? void 0 : _e.display_order) !== null && _f !== void 0 ? _f : 0) - ((_h = (_g = b.price_type) === null || _g === void 0 ? void 0 : _g.display_order) !== null && _h !== void 0 ? _h : 0);
25
+ });
26
+ export const rentOfferTotal = (pricePerPeriod, duration) => Math.max(0, Number(pricePerPeriod) || 0) * Math.max(0, Math.floor(Number(duration) || 0));
package/package.json CHANGED
@@ -1,58 +1,58 @@
1
- {
2
- "name": "@deallony/shared",
3
- "version": "1.2.67",
4
- "private": false,
5
- "main": "dist/cjs/index.js",
6
- "module": "dist/esm/index.js",
7
- "react-native": "dist/esm/index.js",
8
- "types": "dist/cjs/index.d.ts",
9
- "sideEffects": false,
10
- "exports": {
11
- ".": {
12
- "require": "./dist/cjs/index.js",
13
- "import": "./dist/esm/index.js",
14
- "types": "./dist/cjs/index.d.ts"
15
- }
16
- },
17
- "files": [
18
- "dist"
19
- ],
20
- "scripts": {
21
- "deploy": "node ./scripts/publish.js npm patch",
22
- "=========": "============= UPDATE APPS LIBRARY =============",
23
- "all:update": "npm run mobile:update && npm run backend:update && npm run dashboard:update && npm run whatsapp:update && npm run collect:update",
24
- "mobile:update": "cd ../app && npm run upgrade:deallony:shared",
25
- "backend:update": "cd ../backend-service-marketplace && pnpm run upgrade:deallony:shared",
26
- "dashboard:update": "cd ../frontend-react-aloo && pnpm run upgrade:deallony:shared",
27
- "whatsapp:update": "cd ../backend-nest-whatsapp-service-aloo && pnpm run upgrade:deallony:shared",
28
- "collect:update": "cd ../qatayef-collect/backend && npm run upgrade:deallony:shared",
29
- "current": "node -p \"require('./package.json').version\"",
30
- "============================= Links with project =============================": "",
31
- "link:backend": "cd ../backend-service-marketplace && pnpm add @deallony/shared@file:../shared-deallony",
32
- "link:dashboard": "cd ../frontend-react-aloo && pnpm add @deallony/shared@file:../shared-deallony",
33
- "link:mobile": "cd ../app && npm install ../shared-deallony",
34
- "========": "============ DEFAULT ============",
35
- "build": "tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json && node ./scripts/fix-esm-imports.mjs",
36
- "watch": "tsc -p tsconfig.cjs.json --watch",
37
- "deploy:patch": "node ./scripts/publish.js npm patch",
38
- "deploy:minor": "node ./scripts/publish.js npm minor",
39
- "deploy:major": "node ./scripts/publish.js npm major",
40
- "deploy:dry-run": "node ./scripts/publish.js npm patch --dry-run",
41
- "deploy:git": "node ./scripts/publish.js git patch",
42
- "deploy:git:minor": "node ./scripts/publish.js git minor",
43
- "deploy:git:major": "node ./scripts/publish.js git major",
44
- "deploy:git:dry-run": "node ./scripts/publish.js git patch --dry-run",
45
- "prepublishOnly": "npm run build"
46
- },
47
- "dependencies": {
48
- "zod": "^4.2.1"
49
- },
50
- "devDependencies": {
51
- "@types/node": "^25.0.3",
52
- "typescript": "^5.9.3"
53
- },
54
- "publishConfig": {
55
- "registry": "https://registry.npmjs.org/",
56
- "access": "public"
57
- }
58
- }
1
+ {
2
+ "name": "@deallony/shared",
3
+ "version": "1.2.71",
4
+ "private": false,
5
+ "main": "dist/cjs/index.js",
6
+ "module": "dist/esm/index.js",
7
+ "react-native": "dist/esm/index.js",
8
+ "types": "dist/cjs/index.d.ts",
9
+ "sideEffects": false,
10
+ "exports": {
11
+ ".": {
12
+ "require": "./dist/cjs/index.js",
13
+ "import": "./dist/esm/index.js",
14
+ "types": "./dist/cjs/index.d.ts"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "scripts": {
21
+ "deploy": "node ./scripts/publish.js npm patch",
22
+ "=========": "============= UPDATE APPS LIBRARY =============",
23
+ "all:update": "npm run mobile:update && npm run backend:update && npm run dashboard:update && npm run whatsapp:update && npm run collect:update",
24
+ "mobile:update": "cd ../app && npm run upgrade:deallony:shared",
25
+ "backend:update": "cd ../backend-service-marketplace && pnpm run upgrade:deallony:shared",
26
+ "dashboard:update": "cd ../frontend-react-aloo && pnpm run upgrade:deallony:shared",
27
+ "whatsapp:update": "cd ../backend-nest-whatsapp-service-aloo && pnpm run upgrade:deallony:shared",
28
+ "collect:update": "cd ../qatayef-collect/backend && npm run upgrade:deallony:shared",
29
+ "current": "node -p \"require('./package.json').version\"",
30
+ "============================= Links with project =============================": "",
31
+ "link:backend": "cd ../backend-service-marketplace && pnpm add @deallony/shared@file:../shared-deallony",
32
+ "link:dashboard": "cd ../frontend-react-aloo && pnpm add @deallony/shared@file:../shared-deallony",
33
+ "link:mobile": "cd ../app && npm install ../shared-deallony",
34
+ "========": "============ DEFAULT ============",
35
+ "build": "tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json && node ./scripts/fix-esm-imports.mjs",
36
+ "watch": "tsc -p tsconfig.cjs.json --watch",
37
+ "deploy:patch": "node ./scripts/publish.js npm patch",
38
+ "deploy:minor": "node ./scripts/publish.js npm minor",
39
+ "deploy:major": "node ./scripts/publish.js npm major",
40
+ "deploy:dry-run": "node ./scripts/publish.js npm patch --dry-run",
41
+ "deploy:git": "node ./scripts/publish.js git patch",
42
+ "deploy:git:minor": "node ./scripts/publish.js git minor",
43
+ "deploy:git:major": "node ./scripts/publish.js git major",
44
+ "deploy:git:dry-run": "node ./scripts/publish.js git patch --dry-run",
45
+ "prepublishOnly": "npm run build"
46
+ },
47
+ "dependencies": {
48
+ "zod": "^4.2.1"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^25.0.3",
52
+ "typescript": "^5.9.3"
53
+ },
54
+ "publishConfig": {
55
+ "registry": "https://registry.npmjs.org/",
56
+ "access": "public"
57
+ }
58
+ }