mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-09 23:48:36 +01:00
new selfhosted version
This commit is contained in:
1
shared_global/.gitignore
vendored
Normal file
1
shared_global/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
673
shared_global/data/PLANS.ts
Normal file
673
shared_global/data/PLANS.ts
Normal file
@@ -0,0 +1,673 @@
|
||||
export type PLAN_TAG = typeof PLAN_TAGS[number];
|
||||
|
||||
export const PLAN_TAGS = [
|
||||
'FREE',
|
||||
'PLAN_1',
|
||||
'PLAN_2',
|
||||
'CUSTOM_1',
|
||||
'INCUBATION',
|
||||
'ACCELERATION',
|
||||
'GROWTH',
|
||||
'EXPANSION',
|
||||
'SCALING',
|
||||
'UNICORN',
|
||||
'LIFETIME_GROWTH_ONETIME',
|
||||
'GROWTH_DUMMY',
|
||||
'APPSUMO_INCUBATION',
|
||||
'APPSUMO_ACCELERATION',
|
||||
'APPSUMO_GROWTH',
|
||||
'APPSUMO_EXPANSION',
|
||||
'APPSUMO_UNICORN',
|
||||
|
||||
'FREE_TRIAL_LITLYX_PRO',
|
||||
'FREE_TRIAL_ENDED',
|
||||
|
||||
"MINI_ANNUAL",
|
||||
"MINI_MONTHLY",
|
||||
"BASIC_ANNUAL",
|
||||
"BASIC_MONTHLY",
|
||||
"PRO_ANNUAL",
|
||||
"PRO_MONTHLY",
|
||||
"LAUNCH_ANNUAL",
|
||||
"LAUNCH_MONTHLY",
|
||||
"SCALE_ANNUAL",
|
||||
"SCALE_MONTHLY",
|
||||
|
||||
"SELFHOSTED_FREE",
|
||||
"SELFHOSTED_PRO"
|
||||
] as const;
|
||||
|
||||
|
||||
export type PLAN_DATA = {
|
||||
COUNT_LIMIT: number,
|
||||
AI_MESSAGE_LIMIT: number,
|
||||
PRICE: string,
|
||||
PRICE_TEST: string,
|
||||
ID: number,
|
||||
COST: number,
|
||||
NAME: string,
|
||||
TAG: PLAN_TAG,
|
||||
features: {
|
||||
workspaces: number,
|
||||
members: number,
|
||||
data_retention: number,
|
||||
public_shareable_links: boolean,
|
||||
private_shareable_links: boolean,
|
||||
customizable_report: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export type STRICT_PLAN_DATA<T extends PLAN_TAG> = {
|
||||
COUNT_LIMIT: number,
|
||||
AI_MESSAGE_LIMIT: number,
|
||||
PRICE: string,
|
||||
PRICE_TEST: string,
|
||||
ID: number,
|
||||
COST: number,
|
||||
NAME: string,
|
||||
TAG: T,
|
||||
features: {
|
||||
workspaces: number,
|
||||
members: number,
|
||||
data_retention: number,
|
||||
public_shareable_links: boolean,
|
||||
private_shareable_links: boolean,
|
||||
customizable_report: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export type PLAN_DATA_MAP = {
|
||||
[k in PLAN_TAG]: STRICT_PLAN_DATA<k>
|
||||
}
|
||||
|
||||
export const PREMIUM_PLAN: PLAN_DATA_MAP = {
|
||||
FREE: {
|
||||
ID: 0,
|
||||
COUNT_LIMIT: 5_000,
|
||||
AI_MESSAGE_LIMIT: 10,
|
||||
PRICE: 'price_1POKCMB2lPUiVs9VLe3QjIHl',
|
||||
PRICE_TEST: 'price_1PNbHYB2lPUiVs9VZP32xglF',
|
||||
COST: 0,
|
||||
TAG: 'FREE',
|
||||
NAME: 'FREE',
|
||||
features: {
|
||||
workspaces: 2,
|
||||
members: 0,
|
||||
data_retention: 2 * 12,
|
||||
public_shareable_links: false,
|
||||
private_shareable_links: false,
|
||||
customizable_report: false
|
||||
}
|
||||
},
|
||||
PLAN_1: {
|
||||
ID: 1,
|
||||
COUNT_LIMIT: 150_000,
|
||||
AI_MESSAGE_LIMIT: 100,
|
||||
PRICE: 'price_1POKCOB2lPUiVs9VC13s2rQw',
|
||||
PRICE_TEST: 'price_1PNZjVB2lPUiVs9VrsTbJL04',
|
||||
COST: 0,
|
||||
TAG: 'PLAN_1',
|
||||
NAME: 'PLAN_1',
|
||||
features: {
|
||||
workspaces: 2,
|
||||
members: 0,
|
||||
data_retention: 2 * 12,
|
||||
public_shareable_links: false,
|
||||
private_shareable_links: false,
|
||||
customizable_report: false
|
||||
}
|
||||
},
|
||||
PLAN_2: {
|
||||
ID: 2,
|
||||
COUNT_LIMIT: 500_000,
|
||||
AI_MESSAGE_LIMIT: 5_000,
|
||||
PRICE: 'price_1POKCKB2lPUiVs9Vol8XOmhW',
|
||||
PRICE_TEST: 'price_1POK34B2lPUiVs9VIROb0IIV',
|
||||
COST: 0,
|
||||
TAG: 'PLAN_2',
|
||||
NAME: 'PLAN_2',
|
||||
features: {
|
||||
workspaces: 2,
|
||||
members: 0,
|
||||
data_retention: 2 * 12,
|
||||
public_shareable_links: false,
|
||||
private_shareable_links: false,
|
||||
customizable_report: false
|
||||
}
|
||||
},
|
||||
CUSTOM_1: {
|
||||
ID: 1001,
|
||||
COUNT_LIMIT: 10_000_000,
|
||||
AI_MESSAGE_LIMIT: 100_000,
|
||||
PRICE: 'price_1POKZyB2lPUiVs9VMAY6jXTV',
|
||||
PRICE_TEST: '',
|
||||
COST: 0,
|
||||
TAG: 'CUSTOM_1',
|
||||
NAME: 'CUSTOM_1',
|
||||
features: {
|
||||
workspaces: 2,
|
||||
members: 0,
|
||||
data_retention: 2 * 12,
|
||||
public_shareable_links: false,
|
||||
private_shareable_links: false,
|
||||
customizable_report: false
|
||||
}
|
||||
},
|
||||
INCUBATION: {
|
||||
ID: 101,
|
||||
COUNT_LIMIT: 50_000,
|
||||
AI_MESSAGE_LIMIT: 30,
|
||||
PRICE: 'price_1PdsyzB2lPUiVs9V4J246Jw0',
|
||||
PRICE_TEST: '',
|
||||
COST: 499,
|
||||
TAG: 'INCUBATION',
|
||||
NAME: 'Incubation',
|
||||
features: {
|
||||
workspaces: 2,
|
||||
members: 0,
|
||||
data_retention: 2 * 12,
|
||||
public_shareable_links: false,
|
||||
private_shareable_links: false,
|
||||
customizable_report: false
|
||||
}
|
||||
},
|
||||
ACCELERATION: {
|
||||
ID: 102,
|
||||
COUNT_LIMIT: 150_000,
|
||||
AI_MESSAGE_LIMIT: 100,
|
||||
PRICE: 'price_1Pdt5bB2lPUiVs9VhkuCouEt',
|
||||
PRICE_TEST: '',
|
||||
COST: 999,
|
||||
TAG: 'ACCELERATION',
|
||||
NAME: 'Acceleration',
|
||||
features: {
|
||||
workspaces: 10,
|
||||
members: 5,
|
||||
data_retention: 4 * 12,
|
||||
public_shareable_links: true,
|
||||
private_shareable_links: false,
|
||||
customizable_report: true
|
||||
}
|
||||
},
|
||||
GROWTH: {
|
||||
ID: 103,
|
||||
COUNT_LIMIT: 500_000,
|
||||
AI_MESSAGE_LIMIT: 3_000,
|
||||
PRICE: 'price_1PdszrB2lPUiVs9VIdkT3thv',
|
||||
PRICE_TEST: '',
|
||||
COST: 2999,
|
||||
TAG: 'GROWTH',
|
||||
NAME: 'Growth',
|
||||
features: {
|
||||
workspaces: 25,
|
||||
members: 8,
|
||||
data_retention: 5 * 12,
|
||||
public_shareable_links: true,
|
||||
private_shareable_links: true,
|
||||
customizable_report: true
|
||||
}
|
||||
},
|
||||
EXPANSION: {
|
||||
ID: 104,
|
||||
COUNT_LIMIT: 1_000_000,
|
||||
AI_MESSAGE_LIMIT: 5_000,
|
||||
PRICE: 'price_1Pdt0xB2lPUiVs9V0Rdt80Fe',
|
||||
PRICE_TEST: '',
|
||||
COST: 5999,
|
||||
TAG: 'EXPANSION',
|
||||
NAME: 'Expansion',
|
||||
features: {
|
||||
workspaces: 2,
|
||||
members: 0,
|
||||
data_retention: 2 * 12,
|
||||
public_shareable_links: false,
|
||||
private_shareable_links: false,
|
||||
customizable_report: false
|
||||
}
|
||||
},
|
||||
SCALING: {
|
||||
ID: 105,
|
||||
COUNT_LIMIT: 2_500_000,
|
||||
AI_MESSAGE_LIMIT: 10_000,
|
||||
PRICE: 'price_1Pdt1UB2lPUiVs9VUmxntSwZ',
|
||||
PRICE_TEST: '',
|
||||
COST: 9999,
|
||||
TAG: 'SCALING',
|
||||
NAME: 'SCALING',
|
||||
features: {
|
||||
workspaces: 2,
|
||||
members: 0,
|
||||
data_retention: 2 * 12,
|
||||
public_shareable_links: false,
|
||||
private_shareable_links: false,
|
||||
customizable_report: false
|
||||
}
|
||||
},
|
||||
UNICORN: {
|
||||
ID: 106,
|
||||
COUNT_LIMIT: 5_000_000,
|
||||
AI_MESSAGE_LIMIT: 20_000,
|
||||
PRICE: 'price_1Pdt2LB2lPUiVs9VGBFAIG9G',
|
||||
PRICE_TEST: '',
|
||||
COST: 14999,
|
||||
TAG: 'UNICORN',
|
||||
NAME: 'Unicorn',
|
||||
features: {
|
||||
workspaces: 999,
|
||||
members: 999,
|
||||
data_retention: 10 * 12,
|
||||
public_shareable_links: true,
|
||||
private_shareable_links: true,
|
||||
customizable_report: true
|
||||
}
|
||||
},
|
||||
LIFETIME_GROWTH_ONETIME: {
|
||||
ID: 2001,
|
||||
COUNT_LIMIT: 500_000,
|
||||
AI_MESSAGE_LIMIT: 3_000,
|
||||
PRICE: 'price_1PvewGB2lPUiVs9VLheJC8s1',
|
||||
PRICE_TEST: 'price_1Pvf7LB2lPUiVs9VMFNyzpim',
|
||||
COST: 239900,
|
||||
TAG: 'LIFETIME_GROWTH_ONETIME',
|
||||
NAME: 'LIFETIME_GROWTH_ONETIME',
|
||||
features: {
|
||||
workspaces: 2,
|
||||
members: 0,
|
||||
data_retention: 2 * 12,
|
||||
public_shareable_links: false,
|
||||
private_shareable_links: false,
|
||||
customizable_report: false
|
||||
}
|
||||
},
|
||||
GROWTH_DUMMY: {
|
||||
ID: 5001,
|
||||
COUNT_LIMIT: 500_000,
|
||||
AI_MESSAGE_LIMIT: 3_000,
|
||||
PRICE: 'price_1PvgoRB2lPUiVs9VC51YBT7J',
|
||||
PRICE_TEST: 'price_1PvgRTB2lPUiVs9V3kFSNC3G',
|
||||
COST: 0,
|
||||
TAG: 'GROWTH_DUMMY',
|
||||
NAME: 'GROWTH_DUMMY',
|
||||
features: {
|
||||
workspaces: 2,
|
||||
members: 0,
|
||||
data_retention: 2 * 12,
|
||||
public_shareable_links: false,
|
||||
private_shareable_links: false,
|
||||
customizable_report: false
|
||||
}
|
||||
},
|
||||
APPSUMO_INCUBATION: {
|
||||
ID: 6001,
|
||||
COUNT_LIMIT: 50_000,
|
||||
AI_MESSAGE_LIMIT: 30,
|
||||
PRICE: 'price_1QIXwbB2lPUiVs9VKSsoksaU',
|
||||
PRICE_TEST: 'price_1RBIUsB2lPUiVs9VojGan6WH',
|
||||
COST: 0,
|
||||
TAG: 'APPSUMO_INCUBATION',
|
||||
NAME: 'Appsumo Incubation',
|
||||
features: {
|
||||
workspaces: 2,
|
||||
members: 0,
|
||||
data_retention: 2 * 12,
|
||||
public_shareable_links: false,
|
||||
private_shareable_links: false,
|
||||
customizable_report: false
|
||||
}
|
||||
},
|
||||
APPSUMO_ACCELERATION: {
|
||||
ID: 6002,
|
||||
COUNT_LIMIT: 150_000,
|
||||
AI_MESSAGE_LIMIT: 100,
|
||||
PRICE: 'price_1QIXxRB2lPUiVs9VrjaVRoOl',
|
||||
PRICE_TEST: 'price_1RBIV5B2lPUiVs9VKQyxvhst',
|
||||
COST: 0,
|
||||
TAG: 'APPSUMO_ACCELERATION',
|
||||
NAME: 'Appsumo Acceleration',
|
||||
features: {
|
||||
workspaces: 10,
|
||||
members: 5,
|
||||
data_retention: 4 * 12,
|
||||
public_shareable_links: true,
|
||||
private_shareable_links: false,
|
||||
customizable_report: true
|
||||
}
|
||||
},
|
||||
APPSUMO_GROWTH: {
|
||||
ID: 6003,
|
||||
COUNT_LIMIT: 500_000,
|
||||
AI_MESSAGE_LIMIT: 3_000,
|
||||
PRICE: 'price_1QIXy8B2lPUiVs9VQBOUPAoE',
|
||||
PRICE_TEST: 'price_1RBIVFB2lPUiVs9VsMoldAu3',
|
||||
COST: 0,
|
||||
TAG: 'APPSUMO_GROWTH',
|
||||
NAME: 'Appsumo Growth',
|
||||
features: {
|
||||
workspaces: 25,
|
||||
members: 8,
|
||||
data_retention: 5 * 12,
|
||||
public_shareable_links: true,
|
||||
private_shareable_links: true,
|
||||
customizable_report: true
|
||||
}
|
||||
},
|
||||
APPSUMO_EXPANSION: {
|
||||
ID: 6004,
|
||||
COUNT_LIMIT: 1_000_000,
|
||||
AI_MESSAGE_LIMIT: 20_000,
|
||||
PRICE: 'price_1RHm4uB2lPUiVs9VTxZRr61B',
|
||||
PRICE_TEST: '',
|
||||
COST: 0,
|
||||
TAG: 'APPSUMO_EXPANSION',
|
||||
NAME: 'Appsumo Expansion',
|
||||
features: {
|
||||
workspaces: 999,
|
||||
members: 10,
|
||||
data_retention: 6 * 12,
|
||||
public_shareable_links: true,
|
||||
private_shareable_links: true,
|
||||
customizable_report: true
|
||||
}
|
||||
},
|
||||
APPSUMO_UNICORN: {
|
||||
ID: 6006,
|
||||
COUNT_LIMIT: 5_000_000,
|
||||
AI_MESSAGE_LIMIT: 20_000,
|
||||
PRICE: 'price_1Qls1lB2lPUiVs9VI6ej8hwE',
|
||||
PRICE_TEST: '',
|
||||
COST: 0,
|
||||
TAG: 'APPSUMO_UNICORN',
|
||||
NAME: 'Appsumo Unicorn',
|
||||
features: {
|
||||
workspaces: 999,
|
||||
members: 999,
|
||||
data_retention: 10 * 12,
|
||||
public_shareable_links: true,
|
||||
private_shareable_links: true,
|
||||
customizable_report: true
|
||||
}
|
||||
},
|
||||
|
||||
FREE_TRIAL_LITLYX_PRO: {
|
||||
ID: 7006,
|
||||
COUNT_LIMIT: 500_000,
|
||||
AI_MESSAGE_LIMIT: 20_000,
|
||||
PRICE: 'price_1RjJNYB2lPUiVs9Vc6m3NJg0',
|
||||
PRICE_TEST: 'price_1RYoQdB2lPUiVs9V6rU9oYOD',
|
||||
COST: 0,
|
||||
TAG: 'FREE_TRIAL_LITLYX_PRO',
|
||||
NAME: 'Free trial (Mini)',
|
||||
features: {
|
||||
workspaces: 2,
|
||||
members: 0,
|
||||
data_retention: 2 * 12,
|
||||
public_shareable_links: false,
|
||||
private_shareable_links: false,
|
||||
customizable_report: false
|
||||
}
|
||||
},
|
||||
FREE_TRIAL_ENDED: {
|
||||
ID: 7999,
|
||||
COUNT_LIMIT: 0,
|
||||
AI_MESSAGE_LIMIT: 0,
|
||||
PRICE: 'price_1RjJNeB2lPUiVs9VVHWvuy4B',
|
||||
PRICE_TEST: 'price_1RYogBB2lPUiVs9VjGWO1YIm',
|
||||
COST: 0,
|
||||
TAG: 'FREE_TRIAL_ENDED',
|
||||
NAME: 'Free trial ended',
|
||||
features: {
|
||||
workspaces: 2,
|
||||
members: 0,
|
||||
data_retention: 2 * 12,
|
||||
public_shareable_links: false,
|
||||
private_shareable_links: false,
|
||||
customizable_report: false
|
||||
}
|
||||
},
|
||||
|
||||
MINI_MONTHLY: {
|
||||
ID: 8001,
|
||||
COUNT_LIMIT: 10_000,
|
||||
AI_MESSAGE_LIMIT: 200,
|
||||
PRICE: 'price_1RjJNkB2lPUiVs9VaxakNtFO',
|
||||
PRICE_TEST: 'price_1RZXiZB2lPUiVs9V5imugokM',
|
||||
COST: 599,
|
||||
TAG: 'MINI_MONTHLY',
|
||||
NAME: 'Mini',
|
||||
features: {
|
||||
workspaces: 1,
|
||||
members: 0,
|
||||
data_retention: 2 * 12,
|
||||
public_shareable_links: false,
|
||||
private_shareable_links: false,
|
||||
customizable_report: false
|
||||
}
|
||||
},
|
||||
MINI_ANNUAL: {
|
||||
ID: 8002,
|
||||
COUNT_LIMIT: 10_000,
|
||||
AI_MESSAGE_LIMIT: 200,
|
||||
PRICE: 'price_1RjJNiB2lPUiVs9VUxXto69m',
|
||||
PRICE_TEST: 'price_1RZXesB2lPUiVs9VaF9NSyYm',
|
||||
COST: 5988,
|
||||
TAG: 'MINI_ANNUAL',
|
||||
NAME: 'Mini',
|
||||
features: {
|
||||
workspaces: 1,
|
||||
members: 0,
|
||||
data_retention: 2 * 12,
|
||||
public_shareable_links: false,
|
||||
private_shareable_links: false,
|
||||
customizable_report: false
|
||||
}
|
||||
},
|
||||
BASIC_MONTHLY: {
|
||||
ID: 8003,
|
||||
COUNT_LIMIT: 150_000,
|
||||
AI_MESSAGE_LIMIT: 999_999,
|
||||
PRICE: 'price_1RjJNpB2lPUiVs9VGeyTnznc',
|
||||
PRICE_TEST: 'price_1RZXnIB2lPUiVs9VQWj8jbvo',
|
||||
COST: 1799,
|
||||
TAG: 'BASIC_MONTHLY',
|
||||
NAME: 'Basic',
|
||||
features: {
|
||||
workspaces: 2,
|
||||
members: 0,
|
||||
data_retention: 3 * 12,
|
||||
public_shareable_links: true,
|
||||
private_shareable_links: false,
|
||||
customizable_report: false
|
||||
}
|
||||
},
|
||||
BASIC_ANNUAL: {
|
||||
ID: 8004,
|
||||
COUNT_LIMIT: 150_000,
|
||||
AI_MESSAGE_LIMIT: 999_999,
|
||||
PRICE: 'price_1RjJNoB2lPUiVs9VQRWOZBRA',
|
||||
PRICE_TEST: 'price_1RZXlaB2lPUiVs9VvffoSMMm',
|
||||
COST: 17988,
|
||||
TAG: 'BASIC_ANNUAL',
|
||||
NAME: 'Basic',
|
||||
features: {
|
||||
workspaces: 2,
|
||||
members: 0,
|
||||
data_retention: 3 * 12,
|
||||
public_shareable_links: true,
|
||||
private_shareable_links: false,
|
||||
customizable_report: false
|
||||
}
|
||||
},
|
||||
PRO_MONTHLY: {
|
||||
ID: 8005,
|
||||
COUNT_LIMIT: 500_000,
|
||||
AI_MESSAGE_LIMIT: 999_999,
|
||||
PRICE: 'price_1RjJNuB2lPUiVs9VUIbOIUA0',
|
||||
PRICE_TEST: 'price_1RZXpSB2lPUiVs9VIM1vwl7y',
|
||||
COST: 3799,
|
||||
TAG: 'PRO_MONTHLY',
|
||||
NAME: 'Pro',
|
||||
features: {
|
||||
workspaces: 3,
|
||||
members: 0,
|
||||
data_retention: 5 * 12,
|
||||
public_shareable_links: true,
|
||||
private_shareable_links: true,
|
||||
customizable_report: false
|
||||
}
|
||||
},
|
||||
PRO_ANNUAL: {
|
||||
ID: 8006,
|
||||
COUNT_LIMIT: 500_000,
|
||||
AI_MESSAGE_LIMIT: 999_999,
|
||||
PRICE: 'price_1RjJNsB2lPUiVs9V9Ml5ldi4',
|
||||
PRICE_TEST: 'price_1RZXokB2lPUiVs9V3aknwpBv',
|
||||
COST: 35988,
|
||||
TAG: 'PRO_ANNUAL',
|
||||
NAME: 'Pro',
|
||||
features: {
|
||||
workspaces: 3,
|
||||
members: 0,
|
||||
data_retention: 5 * 12,
|
||||
public_shareable_links: true,
|
||||
private_shareable_links: true,
|
||||
customizable_report: false
|
||||
}
|
||||
},
|
||||
LAUNCH_MONTHLY: {
|
||||
ID: 8007,
|
||||
COUNT_LIMIT: 2_000_000,
|
||||
AI_MESSAGE_LIMIT: 999_999,
|
||||
PRICE: 'price_1RjJNyB2lPUiVs9VJT2lvDVT',
|
||||
PRICE_TEST: 'price_1RZXr6B2lPUiVs9VCabwCOmJ',
|
||||
COST: 6799,
|
||||
TAG: 'LAUNCH_MONTHLY',
|
||||
NAME: 'Launch',
|
||||
features: {
|
||||
workspaces: 10,
|
||||
members: 3,
|
||||
data_retention: 6 * 12,
|
||||
public_shareable_links: true,
|
||||
private_shareable_links: true,
|
||||
customizable_report: true
|
||||
}
|
||||
},
|
||||
LAUNCH_ANNUAL: {
|
||||
ID: 8008,
|
||||
COUNT_LIMIT: 2_000_000,
|
||||
AI_MESSAGE_LIMIT: 999_999,
|
||||
PRICE: 'price_1RjJNwB2lPUiVs9VbJdPI9me',
|
||||
PRICE_TEST: 'price_1RZXqPB2lPUiVs9VAfJTyMtW',
|
||||
COST: 71988,
|
||||
TAG: 'LAUNCH_ANNUAL',
|
||||
NAME: 'Launch',
|
||||
features: {
|
||||
workspaces: 10,
|
||||
members: 3,
|
||||
data_retention: 6 * 12,
|
||||
public_shareable_links: true,
|
||||
private_shareable_links: true,
|
||||
customizable_report: true
|
||||
}
|
||||
},
|
||||
SCALE_MONTHLY: {
|
||||
ID: 8009,
|
||||
COUNT_LIMIT: 5_000_000,
|
||||
AI_MESSAGE_LIMIT: 999_999,
|
||||
PRICE: 'price_1RjJO1B2lPUiVs9VQxAyYdtl',
|
||||
PRICE_TEST: 'price_1RZXt4B2lPUiVs9VX9uCVXGC',
|
||||
COST: 9799,
|
||||
TAG: 'SCALE_MONTHLY',
|
||||
NAME: 'Scale',
|
||||
features: {
|
||||
workspaces: 25,
|
||||
members: 999,
|
||||
data_retention: 10 * 12,
|
||||
public_shareable_links: true,
|
||||
private_shareable_links: true,
|
||||
customizable_report: true
|
||||
}
|
||||
},
|
||||
SCALE_ANNUAL: {
|
||||
ID: 8010,
|
||||
COUNT_LIMIT: 5_000_000,
|
||||
AI_MESSAGE_LIMIT: 999_999,
|
||||
PRICE: 'price_1RjJNzB2lPUiVs9Vti52Wquo',
|
||||
PRICE_TEST: 'price_1RZXsJB2lPUiVs9VqPhR9neO',
|
||||
COST: 107900,
|
||||
TAG: 'SCALE_ANNUAL',
|
||||
NAME: 'Scale',
|
||||
features: {
|
||||
workspaces: 25,
|
||||
members: 999,
|
||||
data_retention: 10 * 12,
|
||||
public_shareable_links: true,
|
||||
private_shareable_links: true,
|
||||
customizable_report: true
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
SELFHOSTED_FREE: {
|
||||
ID: 9998,
|
||||
COUNT_LIMIT: 99_999_999_9999,
|
||||
AI_MESSAGE_LIMIT: 999_999,
|
||||
PRICE: '',
|
||||
PRICE_TEST: '',
|
||||
COST: 0,
|
||||
TAG: 'SELFHOSTED_FREE',
|
||||
NAME: 'Selfhosted free',
|
||||
features: {
|
||||
workspaces: 1,
|
||||
members: 0,
|
||||
data_retention: 0,
|
||||
public_shareable_links: false,
|
||||
private_shareable_links: false,
|
||||
customizable_report: true
|
||||
}
|
||||
},
|
||||
SELFHOSTED_PRO: {
|
||||
ID: 9999,
|
||||
COUNT_LIMIT: 99_999_999_9999,
|
||||
AI_MESSAGE_LIMIT: 999_999,
|
||||
PRICE: 'price_1SUSOtB2lPUiVs9VLHTTz1iA',
|
||||
PRICE_TEST: 'price_1SWzVYB2lPUiVs9VqNsodCGg',
|
||||
COST: 9900,
|
||||
TAG: 'SELFHOSTED_PRO',
|
||||
NAME: 'Selfhosted Pro',
|
||||
features: {
|
||||
workspaces: 25,
|
||||
members: 5,
|
||||
data_retention: 0,
|
||||
public_shareable_links: false,
|
||||
private_shareable_links: false,
|
||||
customizable_report: true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export function getPlanFromTag(tag: PLAN_TAG): PLAN_DATA | undefined {
|
||||
return PREMIUM_PLAN[tag];
|
||||
}
|
||||
|
||||
export function getPlanFromId(id: number): PLAN_DATA | undefined {
|
||||
for (const tag of PLAN_TAGS) {
|
||||
const plan = getPlanFromTag(tag);
|
||||
if (!plan) return;
|
||||
if (plan.ID === id) return plan;
|
||||
}
|
||||
}
|
||||
|
||||
export function getPlanFromPrice(price: string, testMode: boolean): PLAN_DATA | undefined {
|
||||
for (const tag of PLAN_TAGS) {
|
||||
const plan = getPlanFromTag(tag);
|
||||
if (!plan) return;
|
||||
if (testMode) {
|
||||
if (plan.PRICE_TEST === price) return plan;
|
||||
} else {
|
||||
if (plan.PRICE === price) return plan;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
export type PREMIUM_TAG = typeof PREMIUM_TAGS[number];
|
||||
|
||||
export const PREMIUM_TAGS = [
|
||||
'FREE',
|
||||
'PLAN_1',
|
||||
'PLAN_2',
|
||||
'CUSTOM_1',
|
||||
'INCUBATION',
|
||||
'ACCELERATION',
|
||||
'GROWTH',
|
||||
'EXPANSION',
|
||||
'SCALING',
|
||||
'UNICORN',
|
||||
'LIFETIME_GROWTH_ONETIME',
|
||||
'GROWTH_DUMMY',
|
||||
'APPSUMO_INCUBATION',
|
||||
'APPSUMO_ACCELERATION',
|
||||
'APPSUMO_GROWTH',
|
||||
'APPSUMO_UNICORN'
|
||||
] as const;
|
||||
|
||||
|
||||
export type PREMIUM_DATA = {
|
||||
COUNT_LIMIT: number,
|
||||
AI_MESSAGE_LIMIT: number,
|
||||
PRICE: string,
|
||||
PRICE_TEST: string,
|
||||
ID: number,
|
||||
COST: number,
|
||||
TAG: PREMIUM_TAG
|
||||
}
|
||||
|
||||
export const PREMIUM_PLAN: Record<PREMIUM_TAG, PREMIUM_DATA> = {
|
||||
FREE: {
|
||||
ID: 0,
|
||||
COUNT_LIMIT: 5_000,
|
||||
AI_MESSAGE_LIMIT: 10,
|
||||
PRICE: 'price_1POKCMB2lPUiVs9VLe3QjIHl',
|
||||
PRICE_TEST: 'price_1PNbHYB2lPUiVs9VZP32xglF',
|
||||
COST: 0,
|
||||
TAG: 'FREE'
|
||||
},
|
||||
PLAN_1: {
|
||||
ID: 1,
|
||||
COUNT_LIMIT: 150_000,
|
||||
AI_MESSAGE_LIMIT: 100,
|
||||
PRICE: 'price_1POKCOB2lPUiVs9VC13s2rQw',
|
||||
PRICE_TEST: 'price_1PNZjVB2lPUiVs9VrsTbJL04',
|
||||
COST: 0,
|
||||
TAG: 'PLAN_1'
|
||||
},
|
||||
PLAN_2: {
|
||||
ID: 2,
|
||||
COUNT_LIMIT: 500_000,
|
||||
AI_MESSAGE_LIMIT: 5_000,
|
||||
PRICE: 'price_1POKCKB2lPUiVs9Vol8XOmhW',
|
||||
PRICE_TEST: 'price_1POK34B2lPUiVs9VIROb0IIV',
|
||||
COST: 0,
|
||||
TAG: 'PLAN_2'
|
||||
},
|
||||
CUSTOM_1: {
|
||||
ID: 1001,
|
||||
COUNT_LIMIT: 10_000_000,
|
||||
AI_MESSAGE_LIMIT: 100_000,
|
||||
PRICE: 'price_1POKZyB2lPUiVs9VMAY6jXTV',
|
||||
PRICE_TEST: '',
|
||||
COST: 0,
|
||||
TAG: 'CUSTOM_1'
|
||||
},
|
||||
INCUBATION: {
|
||||
ID: 101,
|
||||
COUNT_LIMIT: 50_000,
|
||||
AI_MESSAGE_LIMIT: 30,
|
||||
PRICE: 'price_1PdsyzB2lPUiVs9V4J246Jw0',
|
||||
PRICE_TEST: '',
|
||||
COST: 499,
|
||||
TAG: 'INCUBATION'
|
||||
},
|
||||
ACCELERATION: {
|
||||
ID: 102,
|
||||
COUNT_LIMIT: 150_000,
|
||||
AI_MESSAGE_LIMIT: 100,
|
||||
PRICE: 'price_1Pdt5bB2lPUiVs9VhkuCouEt',
|
||||
PRICE_TEST: '',
|
||||
COST: 999,
|
||||
TAG: 'ACCELERATION'
|
||||
},
|
||||
GROWTH: {
|
||||
ID: 103,
|
||||
COUNT_LIMIT: 500_000,
|
||||
AI_MESSAGE_LIMIT: 3_000,
|
||||
PRICE: 'price_1PdszrB2lPUiVs9VIdkT3thv',
|
||||
PRICE_TEST: '',
|
||||
COST: 2999,
|
||||
TAG: 'GROWTH'
|
||||
},
|
||||
EXPANSION: {
|
||||
ID: 104,
|
||||
COUNT_LIMIT: 1_000_000,
|
||||
AI_MESSAGE_LIMIT: 5_000,
|
||||
PRICE: 'price_1Pdt0xB2lPUiVs9V0Rdt80Fe',
|
||||
PRICE_TEST: '',
|
||||
COST: 5999,
|
||||
TAG: 'EXPANSION'
|
||||
},
|
||||
SCALING: {
|
||||
ID: 105,
|
||||
COUNT_LIMIT: 2_500_000,
|
||||
AI_MESSAGE_LIMIT: 10_000,
|
||||
PRICE: 'price_1Pdt1UB2lPUiVs9VUmxntSwZ',
|
||||
PRICE_TEST: '',
|
||||
COST: 9999,
|
||||
TAG: 'SCALING'
|
||||
},
|
||||
UNICORN: {
|
||||
ID: 106,
|
||||
COUNT_LIMIT: 5_000_000,
|
||||
AI_MESSAGE_LIMIT: 20_000,
|
||||
PRICE: 'price_1Pdt2LB2lPUiVs9VGBFAIG9G',
|
||||
PRICE_TEST: '',
|
||||
COST: 14999,
|
||||
TAG: 'UNICORN'
|
||||
},
|
||||
LIFETIME_GROWTH_ONETIME: {
|
||||
ID: 2001,
|
||||
COUNT_LIMIT: 500_000,
|
||||
AI_MESSAGE_LIMIT: 3_000,
|
||||
PRICE: 'price_1PvewGB2lPUiVs9VLheJC8s1',
|
||||
PRICE_TEST: 'price_1Pvf7LB2lPUiVs9VMFNyzpim',
|
||||
COST: 239900,
|
||||
TAG: 'LIFETIME_GROWTH_ONETIME'
|
||||
},
|
||||
GROWTH_DUMMY: {
|
||||
ID: 5001,
|
||||
COUNT_LIMIT: 500_000,
|
||||
AI_MESSAGE_LIMIT: 3_000,
|
||||
PRICE: 'price_1PvgoRB2lPUiVs9VC51YBT7J',
|
||||
PRICE_TEST: 'price_1PvgRTB2lPUiVs9V3kFSNC3G',
|
||||
COST: 0,
|
||||
TAG: 'GROWTH_DUMMY'
|
||||
},
|
||||
APPSUMO_INCUBATION: {
|
||||
ID: 6001,
|
||||
COUNT_LIMIT: 50_000,
|
||||
AI_MESSAGE_LIMIT: 30,
|
||||
PRICE: 'price_1QIXwbB2lPUiVs9VKSsoksaU',
|
||||
PRICE_TEST: '',
|
||||
COST: 0,
|
||||
TAG: 'APPSUMO_INCUBATION'
|
||||
},
|
||||
APPSUMO_ACCELERATION: {
|
||||
ID: 6002,
|
||||
COUNT_LIMIT: 150_000,
|
||||
AI_MESSAGE_LIMIT: 100,
|
||||
PRICE: 'price_1QIXxRB2lPUiVs9VrjaVRoOl',
|
||||
PRICE_TEST: '',
|
||||
COST: 0,
|
||||
TAG: 'APPSUMO_ACCELERATION'
|
||||
},
|
||||
APPSUMO_GROWTH: {
|
||||
ID: 6003,
|
||||
COUNT_LIMIT: 500_000,
|
||||
AI_MESSAGE_LIMIT: 3_000,
|
||||
PRICE: 'price_1QIXy8B2lPUiVs9VQBOUPAoE',
|
||||
PRICE_TEST: '',
|
||||
COST: 0,
|
||||
TAG: 'APPSUMO_GROWTH'
|
||||
},
|
||||
APPSUMO_UNICORN: {
|
||||
ID: 6006,
|
||||
COUNT_LIMIT: 5_000_000,
|
||||
AI_MESSAGE_LIMIT: 20_000,
|
||||
PRICE: 'price_1Qls1lB2lPUiVs9VI6ej8hwE',
|
||||
PRICE_TEST: '',
|
||||
COST: 0,
|
||||
TAG: 'APPSUMO_UNICORN'
|
||||
}
|
||||
}
|
||||
|
||||
export function getPlanFromTag(tag: PREMIUM_TAG) {
|
||||
return PREMIUM_PLAN[tag];
|
||||
}
|
||||
|
||||
export function getPlanFromId(id: number) {
|
||||
for (const tag of PREMIUM_TAGS) {
|
||||
const plan = getPlanFromTag(tag);
|
||||
if (plan.ID === id) return plan;
|
||||
}
|
||||
}
|
||||
|
||||
export function getPlanFromPrice(price: string, testMode: boolean) {
|
||||
for (const tag of PREMIUM_TAGS) {
|
||||
const plan = getPlanFromTag(tag);
|
||||
if (testMode) {
|
||||
if (plan.PRICE_TEST === price) return plan;
|
||||
} else {
|
||||
if (plan.PRICE === price) return plan;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
26
shared_global/schema/PremiumSchema.ts
Normal file
26
shared_global/schema/PremiumSchema.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { model, Schema, Types } from 'mongoose';
|
||||
|
||||
export type TPremium = {
|
||||
user_id: Schema.Types.ObjectId,
|
||||
premium_type: number,
|
||||
customer_id: string,
|
||||
subscription_id: string,
|
||||
expire_at: number,
|
||||
payment_failed?: boolean,
|
||||
plan_cancelled?: boolean,
|
||||
created_at: Date,
|
||||
}
|
||||
|
||||
const PremiumSchema = new Schema<TPremium>({
|
||||
user_id: { type: Types.ObjectId, unique: true, index: 1 },
|
||||
customer_id: { type: String },
|
||||
premium_type: { type: Number },
|
||||
subscription_id: { type: String },
|
||||
expire_at: { type: Number },
|
||||
payment_failed: { type: Boolean },
|
||||
plan_cancelled: { type: Boolean },
|
||||
created_at: { type: Date, default: () => Date.now() }
|
||||
})
|
||||
|
||||
export const PremiumModel = model<TPremium>('premiums', PremiumSchema);
|
||||
|
||||
@@ -3,12 +3,14 @@ import { model, Schema, Types } from 'mongoose';
|
||||
export type TRegister = {
|
||||
email: string,
|
||||
password: string,
|
||||
code: string,
|
||||
created_at: Date
|
||||
}
|
||||
|
||||
const RegisterSchema = new Schema<TRegister>({
|
||||
email: { type: String },
|
||||
password: { type: String },
|
||||
code: { type: String },
|
||||
created_at: { type: Date, default: () => Date.now() }
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { model, Schema, Types } from 'mongoose';
|
||||
|
||||
export type TProjectLimit = {
|
||||
export type TUserLimit = {
|
||||
_id: Schema.Types.ObjectId,
|
||||
project_id: Schema.Types.ObjectId,
|
||||
user_id: Schema.Types.ObjectId,
|
||||
events: number,
|
||||
visits: number,
|
||||
ai_messages: number,
|
||||
@@ -12,8 +12,8 @@ export type TProjectLimit = {
|
||||
billing_start_at: Date,
|
||||
}
|
||||
|
||||
const ProjectLimitSchema = new Schema<TProjectLimit>({
|
||||
project_id: { type: Types.ObjectId, index: true, unique: true },
|
||||
const UserLimitSchema = new Schema<TUserLimit>({
|
||||
user_id: { type: Types.ObjectId, index: true, unique: true },
|
||||
events: { type: Number, required: true, default: 0 },
|
||||
visits: { type: Number, required: true, default: 0 },
|
||||
ai_messages: { type: Number, required: true, default: 0 },
|
||||
@@ -23,4 +23,4 @@ const ProjectLimitSchema = new Schema<TProjectLimit>({
|
||||
billing_expire_at: { type: Date, required: true },
|
||||
});
|
||||
|
||||
export const ProjectLimitModel = model<TProjectLimit>('project_limits', ProjectLimitSchema);
|
||||
export const UserLimitModel = model<TUserLimit>('user_limits', UserLimitSchema);
|
||||
@@ -7,14 +7,6 @@ export type TUser = {
|
||||
locale: string,
|
||||
picture: string,
|
||||
created_at: Date,
|
||||
google_tokens?: {
|
||||
refresh_token?: string;
|
||||
expiry_date?: number;
|
||||
access_token?: string;
|
||||
token_type?: string;
|
||||
id_token?: string;
|
||||
scope?: string;
|
||||
}
|
||||
}
|
||||
|
||||
const UserSchema = new Schema<TUser>({
|
||||
@@ -22,15 +14,7 @@ const UserSchema = new Schema<TUser>({
|
||||
name: String,
|
||||
given_name: String,
|
||||
locale: String,
|
||||
picture: String,
|
||||
google_tokens: {
|
||||
refresh_token: String,
|
||||
expiry_date: Number,
|
||||
access_token: String,
|
||||
token_type: String,
|
||||
id_token: String,
|
||||
scope: String
|
||||
},
|
||||
picture: String,
|
||||
created_at: { type: Date, default: () => Date.now() }
|
||||
})
|
||||
|
||||
|
||||
20
shared_global/schema/aggregation/AggBouncingSchema.ts
Normal file
20
shared_global/schema/aggregation/AggBouncingSchema.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { model, Schema } from 'mongoose';
|
||||
|
||||
export type TAggBouncing = {
|
||||
project_id: Schema.Types.ObjectId,
|
||||
domain: string,
|
||||
from: Date,
|
||||
to: Date,
|
||||
data: { _id: { date: Date }, count: number, timestamp: number }[]
|
||||
}
|
||||
|
||||
const AggBouncingSchema = new Schema<TAggBouncing>({
|
||||
project_id: { type: Schema.Types.ObjectId, index: true },
|
||||
domain: { type: String, required: true },
|
||||
from: { type: Date, required: true },
|
||||
to: { type: Date, required: true },
|
||||
data: [{ type: Schema.Types.Mixed }]
|
||||
});
|
||||
|
||||
export const AggBouncingModel = model<TAggBouncing>('agg_bouncings', AggBouncingSchema);
|
||||
|
||||
20
shared_global/schema/aggregation/AggDurationSchema.ts
Normal file
20
shared_global/schema/aggregation/AggDurationSchema.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { model, Schema } from 'mongoose';
|
||||
|
||||
export type TAggDuration = {
|
||||
project_id: Schema.Types.ObjectId,
|
||||
domain: string,
|
||||
from: Date,
|
||||
to: Date,
|
||||
data: { _id: { date: Date }, count: number, timestamp: number }[]
|
||||
}
|
||||
|
||||
const AggDurationSchema = new Schema<TAggDuration>({
|
||||
project_id: { type: Schema.Types.ObjectId, index: true },
|
||||
domain: { type: String, required: true },
|
||||
from: { type: Date, required: true },
|
||||
to: { type: Date, required: true },
|
||||
data: [{ type: Schema.Types.Mixed }]
|
||||
});
|
||||
|
||||
export const AggDurationModel = model<TAggDuration>('agg_durations', AggDurationSchema);
|
||||
|
||||
25
shared_global/schema/aggregation/AggSchema.ts
Normal file
25
shared_global/schema/aggregation/AggSchema.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { model, Schema } from 'mongoose';
|
||||
|
||||
export type TAgg = {
|
||||
project_id: Schema.Types.ObjectId,
|
||||
domain: string,
|
||||
data_type: string,
|
||||
date: Date,
|
||||
data: number
|
||||
}
|
||||
|
||||
const AggSchema = new Schema<TAgg>({
|
||||
project_id: { type: Schema.Types.ObjectId, index: true },
|
||||
data_type: { type: String, index: true, required: true },
|
||||
domain: { type: String, required: true },
|
||||
date: { type: Date, required: true },
|
||||
data: { type: Number, required: true }
|
||||
});
|
||||
|
||||
AggSchema.index(
|
||||
{ project_id: 1, date: 1, domain: 1, data_type: 1 },
|
||||
{ unique: true }
|
||||
);
|
||||
|
||||
export const AggModel = model<TAgg>('aggregations', AggSchema);
|
||||
|
||||
20
shared_global/schema/aggregation/AggSessionSchema.ts
Normal file
20
shared_global/schema/aggregation/AggSessionSchema.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { model, Schema } from 'mongoose';
|
||||
|
||||
export type TAggSession = {
|
||||
project_id: Schema.Types.ObjectId,
|
||||
domain: string,
|
||||
from: Date,
|
||||
to: Date,
|
||||
data: { _id: { date: Date }, count: number, timestamp: number }[]
|
||||
}
|
||||
|
||||
const AggSessionSchema = new Schema<TAggSession>({
|
||||
project_id: { type: Schema.Types.ObjectId, index: true },
|
||||
domain: { type: String, required: true },
|
||||
from: { type: Date, required: true },
|
||||
to: { type: Date, required: true },
|
||||
data: [{ type: Schema.Types.Mixed }]
|
||||
});
|
||||
|
||||
export const AggSessionModel = model<TAggSession>('agg_sessions', AggSessionSchema);
|
||||
|
||||
20
shared_global/schema/aggregation/AggVisitSchema.ts
Normal file
20
shared_global/schema/aggregation/AggVisitSchema.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { model, Schema } from 'mongoose';
|
||||
|
||||
export type TAggVisit = {
|
||||
project_id: Schema.Types.ObjectId,
|
||||
domain: string,
|
||||
from: Date,
|
||||
to: Date,
|
||||
data: { _id: { date: Date }, count: number, timestamp: number }[]
|
||||
}
|
||||
|
||||
const AggVisitSchema = new Schema<TAggVisit>({
|
||||
project_id: { type: Schema.Types.ObjectId, index: true },
|
||||
domain: { type: String, required: true },
|
||||
from: { type: Date, required: true },
|
||||
to: { type: Date, required: true },
|
||||
data: [{ type: Schema.Types.Mixed }]
|
||||
});
|
||||
|
||||
export const AggVisitModel = model<TAggVisit>('agg_visits', AggVisitSchema);
|
||||
|
||||
24
shared_global/schema/ai/AiNewChatSchema.ts
Normal file
24
shared_global/schema/ai/AiNewChatSchema.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { model, Schema } from 'mongoose';
|
||||
|
||||
export type TAiNewChatSchema = {
|
||||
_id: Schema.Types.ObjectId,
|
||||
project_id: Schema.Types.ObjectId,
|
||||
messages: any[],
|
||||
status: string,
|
||||
title: string,
|
||||
deleted: boolean,
|
||||
created_at: Date,
|
||||
updated_at: Date
|
||||
}
|
||||
|
||||
const AiNewChatSchema = new Schema<TAiNewChatSchema>({
|
||||
project_id: { type: Schema.Types.ObjectId, index: 1 },
|
||||
status: { type: String },
|
||||
messages: [{ _id: false, type: Schema.Types.Mixed }],
|
||||
title: { type: String, required: true },
|
||||
deleted: { type: Boolean, default: false },
|
||||
created_at: { type: Date, default: () => Date.now() },
|
||||
updated_at: { type: Date, default: () => Date.now() },
|
||||
});
|
||||
|
||||
export const AiNewChatModel = model<TAiNewChatSchema>('ai_new_chats', AiNewChatSchema);
|
||||
@@ -2,14 +2,14 @@ import { model, Schema, Types } from 'mongoose';
|
||||
|
||||
export type TLimitNotify = {
|
||||
_id: Schema.Types.ObjectId,
|
||||
project_id: Schema.Types.ObjectId,
|
||||
user_id: Schema.Types.ObjectId,
|
||||
limit1: boolean,
|
||||
limit2: boolean,
|
||||
limit3: boolean
|
||||
}
|
||||
|
||||
const LimitNotifySchema = new Schema<TLimitNotify>({
|
||||
project_id: { type: Types.ObjectId, index: 1 },
|
||||
user_id: { type: Types.ObjectId, index: 1 },
|
||||
limit1: { type: Boolean },
|
||||
limit2: { type: Boolean },
|
||||
limit3: { type: Boolean }
|
||||
|
||||
28
shared_global/schema/emails/EmailNotifySchema.ts
Normal file
28
shared_global/schema/emails/EmailNotifySchema.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { model, Schema, Types } from 'mongoose';
|
||||
|
||||
export type TEmailNotify = {
|
||||
_id: Schema.Types.ObjectId,
|
||||
user_id: Schema.Types.ObjectId,
|
||||
n1: boolean,
|
||||
n2: boolean,
|
||||
n3: boolean,
|
||||
n4: boolean,
|
||||
n5: boolean,
|
||||
n6: boolean,
|
||||
n7: boolean,
|
||||
n8: boolean
|
||||
}
|
||||
|
||||
const EmailNotifySchema = new Schema<TEmailNotify>({
|
||||
user_id: { type: Types.ObjectId, index: 1 },
|
||||
n1: { type: Boolean },
|
||||
n2: { type: Boolean },
|
||||
n3: { type: Boolean },
|
||||
n4: { type: Boolean },
|
||||
n5: { type: Boolean },
|
||||
n6: { type: Boolean },
|
||||
n7: { type: Boolean },
|
||||
n8: { type: Boolean },
|
||||
});
|
||||
|
||||
export const EmailNotifyModel = model<TEmailNotify>('email_notifies', EmailNotifySchema);
|
||||
@@ -15,7 +15,7 @@ const EventSchema = new Schema<TEvent>({
|
||||
name: { type: String, required: true, index: 1 },
|
||||
metadata: Schema.Types.Mixed,
|
||||
session: { type: String, index: 1 },
|
||||
flowHash: { type: String },
|
||||
flowHash: { type: String, index: 1 },
|
||||
website: { type: String, index: 1 },
|
||||
created_at: { type: Date, default: () => Date.now(), index: true },
|
||||
})
|
||||
|
||||
@@ -8,11 +8,20 @@ export type TVisit = {
|
||||
|
||||
continent: string,
|
||||
country: string,
|
||||
region: string,
|
||||
city: string,
|
||||
|
||||
session: string,
|
||||
flowHash: string,
|
||||
device: string,
|
||||
|
||||
utm_medium: string,
|
||||
utm_source: string,
|
||||
utm_term: string,
|
||||
utm_campaign: string,
|
||||
utm_content: string,
|
||||
|
||||
|
||||
website: string,
|
||||
page: string,
|
||||
referrer: string,
|
||||
@@ -28,18 +37,29 @@ const VisitSchema = new Schema<TVisit>({
|
||||
|
||||
continent: { type: String },
|
||||
country: { type: String },
|
||||
region: { type: String },
|
||||
city: { type: String },
|
||||
|
||||
session: { type: String, index: true },
|
||||
flowHash: { type: String },
|
||||
session: { type: String },
|
||||
flowHash: { type: String, index: true },
|
||||
device: { type: String },
|
||||
|
||||
website: { type: String, required: true, index: true },
|
||||
utm_medium: { type: String },
|
||||
utm_source: { type: String },
|
||||
utm_term: { type: String },
|
||||
utm_campaign: { type: String },
|
||||
utm_content: { type: String },
|
||||
|
||||
website: { type: String, required: true },
|
||||
page: { type: String, required: true },
|
||||
referrer: { type: String, required: true },
|
||||
created_at: { type: Date, default: () => Date.now() },
|
||||
})
|
||||
|
||||
VisitSchema.index({ project_id: 1, created_at: -1 });
|
||||
VisitSchema.index({ _id: 1, project_id: 1 });
|
||||
VisitSchema.index({ project_id: 1, website: 1 });
|
||||
VisitSchema.index({ project_id: 1, session: 1, created_at: 1, });
|
||||
|
||||
export const VisitModel = model<TVisit>('visits', VisitSchema);
|
||||
|
||||
|
||||
@@ -4,22 +4,12 @@ export type TProject = {
|
||||
_id: Schema.Types.ObjectId,
|
||||
owner: Schema.Types.ObjectId,
|
||||
name: string,
|
||||
premium: boolean,
|
||||
premium_type: number,
|
||||
customer_id: string,
|
||||
subscription_id: string,
|
||||
premium_expire_at: Date,
|
||||
created_at: Date
|
||||
}
|
||||
|
||||
const ProjectSchema = new Schema<TProject>({
|
||||
owner: { type: Types.ObjectId, index: 1 },
|
||||
name: { type: String, required: true },
|
||||
premium: { type: Boolean, default: false },
|
||||
premium_type: { type: Number, default: 0 },
|
||||
customer_id: { type: String, required: true },
|
||||
subscription_id: { type: String, required: true },
|
||||
premium_expire_at: { type: Date, required: true },
|
||||
created_at: { type: Date, default: () => Date.now() },
|
||||
})
|
||||
|
||||
|
||||
20
shared_global/schema/project/ProjectShareSchema.ts
Normal file
20
shared_global/schema/project/ProjectShareSchema.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { model, Schema, Types } from 'mongoose';
|
||||
|
||||
export type TProjectShare = {
|
||||
_id: Schema.Types.ObjectId,
|
||||
project_id: Schema.Types.ObjectId,
|
||||
domain: string,
|
||||
link: string,
|
||||
password: string,
|
||||
description: string
|
||||
}
|
||||
|
||||
const ProjectShareSchema = new Schema<TProjectShare>({
|
||||
project_id: { type: Types.ObjectId, index: true },
|
||||
domain: { type: String, required: true },
|
||||
link: { type: String, required: true },
|
||||
password: { type: String },
|
||||
description: { type: String }
|
||||
});
|
||||
|
||||
export const ProjectShareModel = model<TProjectShare>('project_shares', ProjectShareSchema);
|
||||
21
shared_global/schema/report/ReportCustomizationSchema.ts
Normal file
21
shared_global/schema/report/ReportCustomizationSchema.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
|
||||
|
||||
import { model, Schema, Types } from 'mongoose';
|
||||
|
||||
export type TReportCustomization = {
|
||||
_id: Schema.Types.ObjectId,
|
||||
project_id: Schema.Types.ObjectId,
|
||||
bg: string,
|
||||
text: string,
|
||||
logo: string
|
||||
}
|
||||
|
||||
const ReportCustomizationSchema = new Schema<TReportCustomization>({
|
||||
project_id: { type: Types.ObjectId, index: 1 },
|
||||
bg: { type: String, required: true },
|
||||
text: { type: String, required: true },
|
||||
logo: { type: String, required: true },
|
||||
});
|
||||
|
||||
export const ReportCustomizationModel = model<TReportCustomization>('repo_customizations', ReportCustomizationSchema);
|
||||
18
shared_global/schema/shields/AddressBlacklistSchema.ts
Normal file
18
shared_global/schema/shields/AddressBlacklistSchema.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { model, Schema, Types } from 'mongoose';
|
||||
|
||||
export type TAddressBlacklistSchema = {
|
||||
_id: Schema.Types.ObjectId,
|
||||
project_id: Schema.Types.ObjectId,
|
||||
address: string,
|
||||
description: string,
|
||||
created_at: Date
|
||||
}
|
||||
|
||||
const AddressBlacklistSchema = new Schema<TAddressBlacklistSchema>({
|
||||
project_id: { type: Types.ObjectId, index: 1 },
|
||||
address: { type: String, required: true },
|
||||
description: { type: String },
|
||||
created_at: { type: Date, default: () => Date.now() },
|
||||
});
|
||||
|
||||
export const AddressBlacklistModel = model<TAddressBlacklistSchema>('address_blacklists', AddressBlacklistSchema);
|
||||
16
shared_global/schema/shields/BotTrafficOptionSchema.ts
Normal file
16
shared_global/schema/shields/BotTrafficOptionSchema.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { model, Schema, Types } from 'mongoose';
|
||||
|
||||
export type TBotTrafficOptionSchema = {
|
||||
_id: Schema.Types.ObjectId,
|
||||
project_id: Schema.Types.ObjectId,
|
||||
block: boolean,
|
||||
created_at: Date
|
||||
}
|
||||
|
||||
const BotTrafficOptionSchema = new Schema<TBotTrafficOptionSchema>({
|
||||
project_id: { type: Types.ObjectId, index: 1 },
|
||||
block: { type: Boolean, required: true },
|
||||
created_at: { type: Date, default: () => Date.now() },
|
||||
});
|
||||
|
||||
export const BotTrafficOptionModel = model<TBotTrafficOptionSchema>('bot_traffic_options', BotTrafficOptionSchema);
|
||||
18
shared_global/schema/shields/CountryBlacklistSchema.ts
Normal file
18
shared_global/schema/shields/CountryBlacklistSchema.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { model, Schema, Types } from 'mongoose';
|
||||
|
||||
export type TCountryBlacklistSchema = {
|
||||
_id: Schema.Types.ObjectId,
|
||||
project_id: Schema.Types.ObjectId,
|
||||
country: string,
|
||||
description: string,
|
||||
created_at: Date
|
||||
}
|
||||
|
||||
const CountryBlacklistSchema = new Schema<TCountryBlacklistSchema>({
|
||||
project_id: { type: Types.ObjectId, index: 1 },
|
||||
country: { type: String, required: true },
|
||||
description: { type: String },
|
||||
created_at: { type: Date, default: () => Date.now() },
|
||||
});
|
||||
|
||||
export const CountryBlacklistModel = model<TCountryBlacklistSchema>('country_blacklists', CountryBlacklistSchema);
|
||||
16
shared_global/schema/shields/DomainWhitelistSchema.ts
Normal file
16
shared_global/schema/shields/DomainWhitelistSchema.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { model, Schema, Types } from 'mongoose';
|
||||
|
||||
export type TDomainWhitelistSchema = {
|
||||
_id: Schema.Types.ObjectId,
|
||||
project_id: Schema.Types.ObjectId,
|
||||
domains: string[],
|
||||
created_at: Date
|
||||
}
|
||||
|
||||
const DomainWhitelistSchema = new Schema<TDomainWhitelistSchema>({
|
||||
project_id: { type: Types.ObjectId, index: 1 },
|
||||
domains: [{ type: String, required: true }],
|
||||
created_at: { type: Date, default: () => Date.now() },
|
||||
});
|
||||
|
||||
export const DomainWhitelistModel = model<TDomainWhitelistSchema>('domain_whitelists', DomainWhitelistSchema);
|
||||
@@ -2,14 +2,13 @@
|
||||
import dayjs from 'dayjs';
|
||||
import * as fns from 'date-fns';
|
||||
|
||||
export type Slice = keyof typeof slicesData;
|
||||
|
||||
const slicesData = {
|
||||
hour: {},
|
||||
day: {},
|
||||
week: {},
|
||||
month: {},
|
||||
year: {}
|
||||
const slices = ['hour', 'day', 'week', 'month', 'year'] as const;
|
||||
|
||||
export type Slice = typeof slices[number];
|
||||
|
||||
export function isValidSlice(slice: string): asserts slice is Slice {
|
||||
if (!slices.includes(slice as any)) throw Error('Slice not valid');
|
||||
}
|
||||
|
||||
const startOfFunctions: { [key in Slice]: (date: Date) => Date } = {
|
||||
@@ -30,16 +29,22 @@ const endOfFunctions: { [key in Slice]: (date: Date) => Date } = {
|
||||
|
||||
class DateService {
|
||||
|
||||
public slicesData = slicesData;
|
||||
|
||||
getChartLabelFromISO(iso: string, offset: number, slice: Slice) {
|
||||
const date = new Date(new Date(iso).getTime() + offset * 1000 * 60);
|
||||
getChartLabelFromISO(timestamp: number, slice: Slice) {
|
||||
const date = new Date(timestamp);
|
||||
if (slice === 'hour') return fns.format(date, 'HH:mm');
|
||||
if (slice === 'day') return fns.format(date, 'dd/MM');
|
||||
if (slice === 'week') return fns.format(date, 'dd/MM');
|
||||
if (slice === 'month') return fns.format(date, 'MMMM');
|
||||
if (slice === 'year') return fns.format(date, 'YYYY');
|
||||
return iso;
|
||||
return date.toISOString();
|
||||
}
|
||||
|
||||
public sliceAvailabilityMap: Record<Slice, [number, number]> = {
|
||||
hour: [0, 3],
|
||||
day: [2, 31 * 2],
|
||||
week: [0, 0],
|
||||
month: [31 * 2, 365 * 4],
|
||||
year: [365, 365 * 20]
|
||||
}
|
||||
|
||||
canUseSlice(from: string | number | Date, to: string | number | Date, slice: Slice) {
|
||||
@@ -80,7 +85,6 @@ class DateService {
|
||||
return fn(date);
|
||||
}
|
||||
|
||||
|
||||
getGranularityData(slice: Slice, dateField: string) {
|
||||
|
||||
const dateFromParts: Record<string, any> = {};
|
||||
@@ -104,74 +108,6 @@ class DateService {
|
||||
return { dateFromParts, granularity }
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated interal to generateDateSlices
|
||||
*/
|
||||
prepareDateRange(from: string, to: string, slice: Slice) {
|
||||
|
||||
let fromDate = dayjs(from).minute(0).second(0).millisecond(0);
|
||||
let toDate = dayjs(to).minute(0).second(0).millisecond(0);
|
||||
|
||||
switch (slice) {
|
||||
case 'day':
|
||||
fromDate = fromDate.hour(0);
|
||||
toDate = toDate.hour(0);
|
||||
break;
|
||||
case 'hour':
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
from: fromDate.toDate(),
|
||||
to: toDate.toDate()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated interal to generateDateSlices
|
||||
*/
|
||||
createBetweenDates(from: string, to: string, slice: Slice) {
|
||||
let start = dayjs(from);
|
||||
const end = dayjs(to);
|
||||
const filledDates: dayjs.Dayjs[] = [];
|
||||
while (start.isBefore(end) || start.isSame(end)) {
|
||||
filledDates.push(start);
|
||||
start = start.add(1, slice);
|
||||
}
|
||||
return { dates: filledDates, from, to };
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use generateDateSlices
|
||||
*/
|
||||
fillDates(dates: string[], slice: Slice) {
|
||||
const allDates: dayjs.Dayjs[] = [];
|
||||
const firstDate = dayjs(dates.at(0));
|
||||
const lastDate = dayjs(dates.at(-1));
|
||||
let currentDate = firstDate.clone();
|
||||
|
||||
allDates.push(currentDate);
|
||||
|
||||
while (currentDate.isBefore(lastDate, slice)) {
|
||||
currentDate = currentDate.add(1, slice);
|
||||
allDates.push(currentDate);
|
||||
}
|
||||
|
||||
return allDates;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use mergeDates
|
||||
*/
|
||||
mergeFilledDates<T extends Record<string, any>, K extends keyof T>(dates: dayjs.Dayjs[], items: T[], dateField: K, slice: Slice, fillData: Omit<T, K>) {
|
||||
const result = new Array<T>();
|
||||
for (const date of dates) {
|
||||
const item = items.find(e => dayjs(e[dateField]).isSame(date, slice));
|
||||
result.push(item ?? { ...fillData, [dateField]: date.format() } as T);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
generateDateSlices(slice: Slice, fromDate: Date, toDate: Date) {
|
||||
const slices: Date[] = [];
|
||||
let currentDate = fromDate;
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
const templateMap = {
|
||||
confirm: '/confirm',
|
||||
welcome: '/welcome',
|
||||
purchase: '/purchase',
|
||||
reset_password: '/reset_password',
|
||||
anomaly_domain: '/anomaly/domain',
|
||||
anomaly_visits_events: '/anomaly_visits_events',
|
||||
limit_50: '/limit/50',
|
||||
limit_90: '/limit/90',
|
||||
limit_max: '/limit/max',
|
||||
invite_project: '/invite',
|
||||
invite_project_noaccount: '/invite/noaccount'
|
||||
} as const;
|
||||
|
||||
export type EmailTemplate = keyof typeof templateMap;
|
||||
export type EmailServerInfo = { url: string, body: Record<string, any>, headers: Record<string, string> };
|
||||
|
||||
type EmailData =
|
||||
| { template: 'confirm', data: { target: string, link: string } }
|
||||
| { template: 'welcome', data: { target: string } }
|
||||
| { template: 'purchase', data: { target: string, projectName: string } }
|
||||
| { template: 'reset_password', data: { target: string, newPassword: string } }
|
||||
| { template: 'anomaly_domain', data: { target: string, projectName: string, domains: string[] } }
|
||||
| { template: 'anomaly_visits_events', data: { target: string, projectName: string, data: any[] } }
|
||||
| { template: 'limit_50', data: { target: string, projectName: string } }
|
||||
| { template: 'limit_90', data: { target: string, projectName: string } }
|
||||
| { template: 'limit_max', data: { target: string, projectName: string } }
|
||||
| { template: 'invite_project', data: { target: string, projectName: string, link: string } }
|
||||
| { template: 'invite_project_noaccount', data: { target: string, projectName: string, link: string } }
|
||||
|
||||
export class EmailService {
|
||||
static getEmailServerInfo<T extends EmailTemplate>(template: T, data: Extract<EmailData, { template: T }>['data']): EmailServerInfo {
|
||||
return {
|
||||
url: `https://mail-service.litlyx.com/send${templateMap[template]}`,
|
||||
body: data,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ export class RedisStreamService {
|
||||
|
||||
|
||||
private static METRICS_MAX_ENTRIES = 1000;
|
||||
private static METRICS_MAX_ENTRIES_PRODUCER = 1000;
|
||||
|
||||
static async METRICS_onProcess(id: string, time: number) {
|
||||
const key = `___dev_metrics`;
|
||||
@@ -39,6 +40,18 @@ export class RedisStreamService {
|
||||
return data.map(e => e.split(':')) as [string, string][];
|
||||
}
|
||||
|
||||
static async METRICS_PRODUCER_onProcess(id: string, time: number) {
|
||||
const key = `___dev_metrics_producer`;
|
||||
await this.client.lPush(key, `${id}:${time.toString()}`);
|
||||
await this.client.lTrim(key, 0, this.METRICS_MAX_ENTRIES_PRODUCER - 1);
|
||||
}
|
||||
|
||||
static async METRICS_PRODUCER_get() {
|
||||
const key = `___dev_metrics_producer`;
|
||||
const data = await this.client.lRange(key, 0, -1);
|
||||
return data.map(e => e.split(':')) as [string, string][];
|
||||
}
|
||||
|
||||
|
||||
static async connect() {
|
||||
await this.client.connect();
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "NodeNext",
|
||||
"target": "ESNext"
|
||||
"module": "nodenext",
|
||||
"target": "esnext",
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "nodenext"
|
||||
},
|
||||
"include": [
|
||||
"./**/*.ts"
|
||||
"schema/**/*.ts",
|
||||
"services/**/*.ts",
|
||||
"data/**/*.ts",
|
||||
"utils/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
|
||||
Reference in New Issue
Block a user