mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-10 07:48:37 +01:00
remove shared
This commit is contained in:
2
dashboard/.gitignore
vendored
2
dashboard/.gitignore
vendored
@@ -39,3 +39,5 @@ explains
|
|||||||
#Ecosystem
|
#Ecosystem
|
||||||
ecosystem.config.cjs
|
ecosystem.config.cjs
|
||||||
ecosystem.config.js
|
ecosystem.config.js
|
||||||
|
|
||||||
|
shared
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
export const ADMIN_EMAILS = [
|
|
||||||
'laura.emily.lovi@gmail.com',
|
|
||||||
'mangaiomaster@gmail.com',
|
|
||||||
'helplitlyx@gmail.com'
|
|
||||||
]
|
|
||||||
@@ -1,184 +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
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
},
|
|
||||||
PLAN_1: {
|
|
||||||
ID: 1,
|
|
||||||
COUNT_LIMIT: 150_000,
|
|
||||||
AI_MESSAGE_LIMIT: 100,
|
|
||||||
PRICE: 'price_1POKCOB2lPUiVs9VC13s2rQw',
|
|
||||||
PRICE_TEST: 'price_1PNZjVB2lPUiVs9VrsTbJL04',
|
|
||||||
COST: 0
|
|
||||||
},
|
|
||||||
PLAN_2: {
|
|
||||||
ID: 2,
|
|
||||||
COUNT_LIMIT: 500_000,
|
|
||||||
AI_MESSAGE_LIMIT: 5_000,
|
|
||||||
PRICE: 'price_1POKCKB2lPUiVs9Vol8XOmhW',
|
|
||||||
PRICE_TEST: 'price_1POK34B2lPUiVs9VIROb0IIV',
|
|
||||||
COST: 0
|
|
||||||
},
|
|
||||||
CUSTOM_1: {
|
|
||||||
ID: 1001,
|
|
||||||
COUNT_LIMIT: 10_000_000,
|
|
||||||
AI_MESSAGE_LIMIT: 100_000,
|
|
||||||
PRICE: 'price_1POKZyB2lPUiVs9VMAY6jXTV',
|
|
||||||
PRICE_TEST: '',
|
|
||||||
COST: 0
|
|
||||||
},
|
|
||||||
INCUBATION: {
|
|
||||||
ID: 101,
|
|
||||||
COUNT_LIMIT: 50_000,
|
|
||||||
AI_MESSAGE_LIMIT: 30,
|
|
||||||
PRICE: 'price_1PdsyzB2lPUiVs9V4J246Jw0',
|
|
||||||
PRICE_TEST: '',
|
|
||||||
COST: 499
|
|
||||||
},
|
|
||||||
ACCELERATION: {
|
|
||||||
ID: 102,
|
|
||||||
COUNT_LIMIT: 150_000,
|
|
||||||
AI_MESSAGE_LIMIT: 100,
|
|
||||||
PRICE: 'price_1Pdt5bB2lPUiVs9VhkuCouEt',
|
|
||||||
PRICE_TEST: '',
|
|
||||||
COST: 999
|
|
||||||
},
|
|
||||||
GROWTH: {
|
|
||||||
ID: 103,
|
|
||||||
COUNT_LIMIT: 500_000,
|
|
||||||
AI_MESSAGE_LIMIT: 3_000,
|
|
||||||
PRICE: 'price_1PdszrB2lPUiVs9VIdkT3thv',
|
|
||||||
PRICE_TEST: '',
|
|
||||||
COST: 2999
|
|
||||||
},
|
|
||||||
EXPANSION: {
|
|
||||||
ID: 104,
|
|
||||||
COUNT_LIMIT: 1_000_000,
|
|
||||||
AI_MESSAGE_LIMIT: 5_000,
|
|
||||||
PRICE: 'price_1Pdt0xB2lPUiVs9V0Rdt80Fe',
|
|
||||||
PRICE_TEST: '',
|
|
||||||
COST: 5999
|
|
||||||
},
|
|
||||||
SCALING: {
|
|
||||||
ID: 105,
|
|
||||||
COUNT_LIMIT: 2_500_000,
|
|
||||||
AI_MESSAGE_LIMIT: 10_000,
|
|
||||||
PRICE: 'price_1Pdt1UB2lPUiVs9VUmxntSwZ',
|
|
||||||
PRICE_TEST: '',
|
|
||||||
COST: 9999
|
|
||||||
},
|
|
||||||
UNICORN: {
|
|
||||||
ID: 106,
|
|
||||||
COUNT_LIMIT: 5_000_000,
|
|
||||||
AI_MESSAGE_LIMIT: 20_000,
|
|
||||||
PRICE: 'price_1Pdt2LB2lPUiVs9VGBFAIG9G',
|
|
||||||
PRICE_TEST: '',
|
|
||||||
COST: 14999
|
|
||||||
},
|
|
||||||
LIFETIME_GROWTH_ONETIME: {
|
|
||||||
ID: 2001,
|
|
||||||
COUNT_LIMIT: 500_000,
|
|
||||||
AI_MESSAGE_LIMIT: 3_000,
|
|
||||||
PRICE: 'price_1PvewGB2lPUiVs9VLheJC8s1',
|
|
||||||
PRICE_TEST: 'price_1Pvf7LB2lPUiVs9VMFNyzpim',
|
|
||||||
COST: 239900
|
|
||||||
},
|
|
||||||
GROWTH_DUMMY: {
|
|
||||||
ID: 5001,
|
|
||||||
COUNT_LIMIT: 500_000,
|
|
||||||
AI_MESSAGE_LIMIT: 3_000,
|
|
||||||
PRICE: 'price_1PvgoRB2lPUiVs9VC51YBT7J',
|
|
||||||
PRICE_TEST: 'price_1PvgRTB2lPUiVs9V3kFSNC3G',
|
|
||||||
COST: 0
|
|
||||||
},
|
|
||||||
APPSUMO_INCUBATION: {
|
|
||||||
ID: 6001,
|
|
||||||
COUNT_LIMIT: 50_000,
|
|
||||||
AI_MESSAGE_LIMIT: 30,
|
|
||||||
PRICE: 'price_1QIXwbB2lPUiVs9VKSsoksaU',
|
|
||||||
PRICE_TEST: '',
|
|
||||||
COST: 0
|
|
||||||
},
|
|
||||||
APPSUMO_ACCELERATION: {
|
|
||||||
ID: 6002,
|
|
||||||
COUNT_LIMIT: 150_000,
|
|
||||||
AI_MESSAGE_LIMIT: 100,
|
|
||||||
PRICE: 'price_1QIXxRB2lPUiVs9VrjaVRoOl',
|
|
||||||
PRICE_TEST: '',
|
|
||||||
COST: 0
|
|
||||||
},
|
|
||||||
APPSUMO_GROWTH: {
|
|
||||||
ID: 6003,
|
|
||||||
COUNT_LIMIT: 500_000,
|
|
||||||
AI_MESSAGE_LIMIT: 3_000,
|
|
||||||
PRICE: 'price_1QIXy8B2lPUiVs9VQBOUPAoE',
|
|
||||||
PRICE_TEST: '',
|
|
||||||
COST: 0
|
|
||||||
},
|
|
||||||
APPSUMO_UNICORN: {
|
|
||||||
ID: 6006,
|
|
||||||
COUNT_LIMIT: 5_000_000,
|
|
||||||
AI_MESSAGE_LIMIT: 20_000,
|
|
||||||
PRICE: 'price_1Qls1lB2lPUiVs9VI6ej8hwE',
|
|
||||||
PRICE_TEST: '',
|
|
||||||
COST: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
// Default: 1.01
|
|
||||||
// ((events + visits) * VALUE) > limit
|
|
||||||
export const MAX_LOG_LIMIT_PERCENT = 1.01;
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { model, Schema, Types } from 'mongoose';
|
|
||||||
|
|
||||||
export type TApiSettings = {
|
|
||||||
_id: Schema.Types.ObjectId,
|
|
||||||
project_id: Schema.Types.ObjectId,
|
|
||||||
apiKey: string,
|
|
||||||
apiName: string,
|
|
||||||
usage: number,
|
|
||||||
created_at: Date
|
|
||||||
}
|
|
||||||
|
|
||||||
const ApiSettingsSchema = new Schema<TApiSettings>({
|
|
||||||
project_id: { type: Types.ObjectId, index: 1 },
|
|
||||||
apiKey: { type: String, required: true },
|
|
||||||
apiName: { type: String, required: true },
|
|
||||||
usage: { type: Number, default: 0, required: true, },
|
|
||||||
created_at: { type: Date, default: () => Date.now() },
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ApiSettingsModel = model<TApiSettings>('api_settings', ApiSettingsSchema);
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { model, Schema, Types } from 'mongoose';
|
|
||||||
|
|
||||||
export type TFeedback = {
|
|
||||||
user_id: Types.ObjectId,
|
|
||||||
project_id: Types.ObjectId,
|
|
||||||
text: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const FeedbackSchema = new Schema<TFeedback>({
|
|
||||||
user_id: { type: Schema.Types.ObjectId, required: true },
|
|
||||||
project_id: { type: Schema.Types.ObjectId, required: true },
|
|
||||||
text: { type: String, required: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
export const FeedbackModel = model<TFeedback>('feedbacks', FeedbackSchema);
|
|
||||||
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { model, Schema, Types } from 'mongoose';
|
|
||||||
|
|
||||||
export type TOnboarding = {
|
|
||||||
user_id: Types.ObjectId,
|
|
||||||
analytics: string,
|
|
||||||
job: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const OnboardingSchema = new Schema<TOnboarding>({
|
|
||||||
user_id: { type: Schema.Types.ObjectId, required: true },
|
|
||||||
analytics: { type: String, required: false },
|
|
||||||
job: { type: String, required: false },
|
|
||||||
});
|
|
||||||
|
|
||||||
export const OnboardingModel = model<TOnboarding>('onboardings', OnboardingSchema);
|
|
||||||
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { model, Schema, Types } from 'mongoose';
|
|
||||||
|
|
||||||
export type TPassword = {
|
|
||||||
email: string,
|
|
||||||
password: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
const PasswordSchema = new Schema<TPassword>({
|
|
||||||
email: { type: String, index: true, unique: true },
|
|
||||||
password: { type: String },
|
|
||||||
});
|
|
||||||
|
|
||||||
export const PasswordModel = model<TPassword>('passwords', PasswordSchema);
|
|
||||||
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { model, Schema, Types } from 'mongoose';
|
|
||||||
|
|
||||||
export type TRegister = {
|
|
||||||
email: string,
|
|
||||||
password: string,
|
|
||||||
created_at: Date
|
|
||||||
}
|
|
||||||
|
|
||||||
const RegisterSchema = new Schema<TRegister>({
|
|
||||||
email: { type: String },
|
|
||||||
password: { type: String },
|
|
||||||
created_at: { type: Date, default: () => Date.now() }
|
|
||||||
});
|
|
||||||
|
|
||||||
export const RegisterModel = model<TRegister>('registers', RegisterSchema);
|
|
||||||
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { model, Schema, Types } from 'mongoose';
|
|
||||||
|
|
||||||
export type TeamMemberRole = 'ADMIN' | 'GUEST';
|
|
||||||
|
|
||||||
export type TTeamMember = {
|
|
||||||
_id: Schema.Types.ObjectId,
|
|
||||||
project_id: Schema.Types.ObjectId,
|
|
||||||
user_id: Schema.Types.ObjectId,
|
|
||||||
role: TeamMemberRole,
|
|
||||||
pending: boolean,
|
|
||||||
created_at: Date,
|
|
||||||
}
|
|
||||||
|
|
||||||
const TeamMemberSchema = new Schema<TTeamMember>({
|
|
||||||
project_id: { type: Types.ObjectId, index: true },
|
|
||||||
user_id: { type: Types.ObjectId, index: true },
|
|
||||||
role: { type: String, required: true },
|
|
||||||
pending: { type: Boolean, required: true },
|
|
||||||
created_at: { type: Date, required: true, default: () => Date.now() },
|
|
||||||
});
|
|
||||||
|
|
||||||
export const TeamMemberModel = model<TTeamMember>('team_members', TeamMemberSchema);
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import { model, Schema, Types } from 'mongoose';
|
|
||||||
|
|
||||||
export type TUser = {
|
|
||||||
email: string,
|
|
||||||
name: string,
|
|
||||||
given_name: string,
|
|
||||||
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>({
|
|
||||||
email: { type: String, unique: true, index: 1 },
|
|
||||||
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
|
|
||||||
},
|
|
||||||
created_at: { type: Date, default: () => Date.now() }
|
|
||||||
})
|
|
||||||
|
|
||||||
export const UserModel = model<TUser>('users', UserSchema);
|
|
||||||
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { model, Schema, Types } from 'mongoose';
|
|
||||||
|
|
||||||
export type TUserSettings = {
|
|
||||||
user_id: Schema.Types.ObjectId,
|
|
||||||
max_projects: number,
|
|
||||||
active_project_id: Schema.Types.ObjectId
|
|
||||||
}
|
|
||||||
|
|
||||||
const UserSettingsSchema = new Schema<TUserSettings>({
|
|
||||||
user_id: { type: Types.ObjectId, unique: true, index: 1 },
|
|
||||||
max_projects: { type: Number, default: 3 },
|
|
||||||
active_project_id: Schema.Types.ObjectId,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const UserSettingsModel = model<TUserSettings>('user_settings', UserSettingsSchema);
|
|
||||||
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import { model, Schema } from 'mongoose';
|
|
||||||
|
|
||||||
export type TAiChatSchema = {
|
|
||||||
_id: Schema.Types.ObjectId,
|
|
||||||
project_id: Schema.Types.ObjectId,
|
|
||||||
messages: any[],
|
|
||||||
status: string,
|
|
||||||
completed: boolean,
|
|
||||||
title: string,
|
|
||||||
deleted: boolean,
|
|
||||||
created_at: Date,
|
|
||||||
updated_at: Date
|
|
||||||
}
|
|
||||||
|
|
||||||
const AiChatSchema = new Schema<TAiChatSchema>({
|
|
||||||
project_id: { type: Schema.Types.ObjectId, index: 1 },
|
|
||||||
status: { type: String },
|
|
||||||
completed: { type: Boolean },
|
|
||||||
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 AiChatModel = model<TAiChatSchema>('ai_chats', AiChatSchema);
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { model, Schema, Types } from 'mongoose';
|
|
||||||
|
|
||||||
|
|
||||||
export type TAnomalyDomain = {
|
|
||||||
project_id: Schema.Types.ObjectId
|
|
||||||
domain: string,
|
|
||||||
created_at: Date
|
|
||||||
}
|
|
||||||
|
|
||||||
const AnomalyDomainSchema = new Schema<TAnomalyDomain>({
|
|
||||||
project_id: { type: Types.ObjectId, required: true },
|
|
||||||
domain: { type: String, required: true },
|
|
||||||
created_at: { type: Date, required: true },
|
|
||||||
})
|
|
||||||
|
|
||||||
export const AnomalyDomainModel = model<TAnomalyDomain>('anomaly_domains', AnomalyDomainSchema);
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { model, Schema, Types } from 'mongoose';
|
|
||||||
|
|
||||||
|
|
||||||
export type TAnomalyEvents = {
|
|
||||||
project_id: Schema.Types.ObjectId
|
|
||||||
eventDate: Date,
|
|
||||||
created_at: Date
|
|
||||||
}
|
|
||||||
|
|
||||||
const AnomalyEventsSchema = new Schema<TAnomalyEvents>({
|
|
||||||
project_id: { type: Types.ObjectId, required: true },
|
|
||||||
eventDate: { type: Date, required: true },
|
|
||||||
created_at: { type: Date, required: true },
|
|
||||||
})
|
|
||||||
|
|
||||||
export const AnomalyEventsModel = model<TAnomalyEvents>('anomaly_events', AnomalyEventsSchema);
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { model, Schema, Types } from 'mongoose';
|
|
||||||
|
|
||||||
|
|
||||||
export type TAnomalyVisit = {
|
|
||||||
project_id: Schema.Types.ObjectId
|
|
||||||
visitDate: Date,
|
|
||||||
created_at: Date
|
|
||||||
}
|
|
||||||
|
|
||||||
const AnomalyVisitSchema = new Schema<TAnomalyVisit>({
|
|
||||||
project_id: { type: Types.ObjectId, required: true },
|
|
||||||
visitDate: { type: Date, required: true },
|
|
||||||
created_at: { type: Date, required: true },
|
|
||||||
})
|
|
||||||
|
|
||||||
export const AnomalyVisitModel = model<TAnomalyVisit>('anomaly_visits', AnomalyVisitSchema);
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { model, Schema, Types } from 'mongoose';
|
|
||||||
|
|
||||||
export type TAppsumoCode = {
|
|
||||||
_id: Schema.Types.ObjectId,
|
|
||||||
code: string,
|
|
||||||
used_at: Date,
|
|
||||||
created_at?: Date,
|
|
||||||
}
|
|
||||||
|
|
||||||
const AppsumoCodeSchema = new Schema<TAppsumoCode>({
|
|
||||||
code: { type: String, index: 1 },
|
|
||||||
created_at: { type: Date, default: () => Date.now() },
|
|
||||||
used_at: { type: Date, required: false },
|
|
||||||
});
|
|
||||||
|
|
||||||
export const AppsumoCodeModel = model<TAppsumoCode>('appsumo_codes', AppsumoCodeSchema);
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { model, Schema, Types } from 'mongoose';
|
|
||||||
|
|
||||||
export type TAppsumoCodeTry = {
|
|
||||||
project_id: Types.ObjectId,
|
|
||||||
codes: string[],
|
|
||||||
valid_codes: string[],
|
|
||||||
}
|
|
||||||
|
|
||||||
const AppsumoCodeTrySchema = new Schema<TAppsumoCodeTry>({
|
|
||||||
project_id: { type: Schema.Types.ObjectId, required: true, unique: true, index: 1 },
|
|
||||||
codes: [{ type: String }],
|
|
||||||
valid_codes: [{ type: String }]
|
|
||||||
});
|
|
||||||
|
|
||||||
export const AppsumoCodeTryModel = model<TAppsumoCodeTry>('appsumo_codes_tries', AppsumoCodeTrySchema);
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { model, Schema, Types } from 'mongoose';
|
|
||||||
|
|
||||||
export type TLimitNotify = {
|
|
||||||
_id: Schema.Types.ObjectId,
|
|
||||||
project_id: Schema.Types.ObjectId,
|
|
||||||
limit1: boolean,
|
|
||||||
limit2: boolean,
|
|
||||||
limit3: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const LimitNotifySchema = new Schema<TLimitNotify>({
|
|
||||||
project_id: { type: Types.ObjectId, index: 1 },
|
|
||||||
limit1: { type: Boolean },
|
|
||||||
limit2: { type: Boolean },
|
|
||||||
limit3: { type: Boolean }
|
|
||||||
});
|
|
||||||
|
|
||||||
export const LimitNotifyModel = model<TLimitNotify>('limit_notifies', LimitNotifySchema);
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { model, Schema, Types } from 'mongoose';
|
|
||||||
|
|
||||||
export type TEvent = {
|
|
||||||
project_id: Schema.Types.ObjectId,
|
|
||||||
name: string,
|
|
||||||
metadata: Record<string, string>,
|
|
||||||
session: string,
|
|
||||||
flowHash: string,
|
|
||||||
created_at: Date
|
|
||||||
}
|
|
||||||
|
|
||||||
const EventSchema = new Schema<TEvent>({
|
|
||||||
project_id: { type: Types.ObjectId, index: 1 },
|
|
||||||
name: { type: String, required: true, index: 1 },
|
|
||||||
metadata: Schema.Types.Mixed,
|
|
||||||
session: { type: String, index: 1 },
|
|
||||||
flowHash: { type: String },
|
|
||||||
created_at: { type: Date, default: () => Date.now(), index: true },
|
|
||||||
})
|
|
||||||
|
|
||||||
export const EventModel = model<TEvent>('events', EventSchema);
|
|
||||||
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { model, Schema, Types } from 'mongoose';
|
|
||||||
|
|
||||||
|
|
||||||
export type TSession = {
|
|
||||||
project_id: Schema.Types.ObjectId,
|
|
||||||
session: string,
|
|
||||||
flowHash: string,
|
|
||||||
duration: number,
|
|
||||||
updated_at: Date,
|
|
||||||
created_at: Date,
|
|
||||||
}
|
|
||||||
|
|
||||||
const SessionSchema = new Schema<TSession>({
|
|
||||||
project_id: { type: Types.ObjectId, index: 1 },
|
|
||||||
session: { type: String, required: true, index: 1 },
|
|
||||||
flowHash: { type: String },
|
|
||||||
duration: { type: Number, required: true, default: 0 },
|
|
||||||
updated_at: { type: Date, default: () => Date.now() },
|
|
||||||
created_at: { type: Date, default: () => Date.now(), index: true },
|
|
||||||
})
|
|
||||||
|
|
||||||
export const SessionModel = model<TSession>('sessions', SessionSchema);
|
|
||||||
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import { model, Schema } from 'mongoose';
|
|
||||||
|
|
||||||
export type TVisit = {
|
|
||||||
project_id: Schema.Types.ObjectId,
|
|
||||||
|
|
||||||
browser: string,
|
|
||||||
os: string,
|
|
||||||
|
|
||||||
continent: string,
|
|
||||||
country: string,
|
|
||||||
|
|
||||||
session: string,
|
|
||||||
flowHash: string,
|
|
||||||
device: string,
|
|
||||||
|
|
||||||
website: string,
|
|
||||||
page: string,
|
|
||||||
referrer: string,
|
|
||||||
|
|
||||||
created_at: Date
|
|
||||||
}
|
|
||||||
|
|
||||||
const VisitSchema = new Schema<TVisit>({
|
|
||||||
project_id: { type: Schema.Types.ObjectId, index: true },
|
|
||||||
|
|
||||||
browser: { type: String, required: true },
|
|
||||||
os: { type: String, required: true },
|
|
||||||
|
|
||||||
continent: { type: String },
|
|
||||||
country: { type: String },
|
|
||||||
|
|
||||||
session: { type: String, index: true },
|
|
||||||
flowHash: { type: String },
|
|
||||||
device: { type: String },
|
|
||||||
|
|
||||||
website: { type: String, required: true, index: 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 });
|
|
||||||
|
|
||||||
export const VisitModel = model<TVisit>('visits', VisitSchema);
|
|
||||||
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import { model, Schema, Types } from 'mongoose';
|
|
||||||
|
|
||||||
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() },
|
|
||||||
})
|
|
||||||
|
|
||||||
export const ProjectModel = model<TProject>('projects', ProjectSchema);
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { model, Schema, Types } from 'mongoose';
|
|
||||||
|
|
||||||
export type TProjectSnapshot = {
|
|
||||||
_id: Schema.Types.ObjectId,
|
|
||||||
project_id: Schema.Types.ObjectId,
|
|
||||||
name: string,
|
|
||||||
from: Date,
|
|
||||||
to: Date,
|
|
||||||
color: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const ProjectSnapshotSchema = new Schema<TProjectSnapshot>({
|
|
||||||
project_id: { type: Types.ObjectId, index: true },
|
|
||||||
name: { type: String, required: true },
|
|
||||||
from: { type: Date, required: true },
|
|
||||||
to: { type: Date, required: true },
|
|
||||||
color: { type: String, required: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ProjectSnapshotModel = model<TProjectSnapshot>('project_snapshots', ProjectSnapshotSchema);
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { model, Schema, Types } from 'mongoose';
|
|
||||||
|
|
||||||
export type TProjectCount = {
|
|
||||||
_id: Schema.Types.ObjectId,
|
|
||||||
project_id: Schema.Types.ObjectId,
|
|
||||||
events: number,
|
|
||||||
visits: number,
|
|
||||||
sessions: number,
|
|
||||||
lastRecheck?: Date,
|
|
||||||
updated_at: Date
|
|
||||||
}
|
|
||||||
|
|
||||||
const ProjectCountSchema = new Schema<TProjectCount>({
|
|
||||||
project_id: { type: Types.ObjectId, index: true, unique: true },
|
|
||||||
events: { type: Number, required: true, default: 0 },
|
|
||||||
visits: { type: Number, required: true, default: 0 },
|
|
||||||
sessions: { type: Number, required: true, default: 0 },
|
|
||||||
lastRecheck: { type: Date },
|
|
||||||
updated_at: { type: Date }
|
|
||||||
}, { timestamps: { updatedAt: 'updated_at' } });
|
|
||||||
|
|
||||||
export const ProjectCountModel = model<TProjectCount>('project_counts', ProjectCountSchema);
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import { model, Schema, Types } from 'mongoose';
|
|
||||||
|
|
||||||
export type TProjectLimit = {
|
|
||||||
_id: Schema.Types.ObjectId,
|
|
||||||
project_id: Schema.Types.ObjectId,
|
|
||||||
events: number,
|
|
||||||
visits: number,
|
|
||||||
ai_messages: number,
|
|
||||||
limit: number,
|
|
||||||
ai_limit: number,
|
|
||||||
billing_expire_at: Date,
|
|
||||||
billing_start_at: Date,
|
|
||||||
}
|
|
||||||
|
|
||||||
const ProjectLimitSchema = new Schema<TProjectLimit>({
|
|
||||||
project_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 },
|
|
||||||
limit: { type: Number, required: true },
|
|
||||||
ai_limit: { type: Number, required: true },
|
|
||||||
billing_start_at: { type: Date, required: true },
|
|
||||||
billing_expire_at: { type: Date, required: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ProjectLimitModel = model<TProjectLimit>('project_limits', ProjectLimitSchema);
|
|
||||||
@@ -1,224 +0,0 @@
|
|||||||
|
|
||||||
import dayjs from 'dayjs';
|
|
||||||
import * as fns from 'date-fns';
|
|
||||||
|
|
||||||
export type Slice = keyof typeof slicesData;
|
|
||||||
|
|
||||||
const slicesData = {
|
|
||||||
hour: {},
|
|
||||||
day: {},
|
|
||||||
week: {},
|
|
||||||
month: {},
|
|
||||||
year: {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const startOfFunctions: { [key in Slice]: (date: Date) => Date } = {
|
|
||||||
hour: fns.startOfHour,
|
|
||||||
day: fns.startOfDay,
|
|
||||||
week: fns.startOfWeek,
|
|
||||||
month: fns.startOfMonth,
|
|
||||||
year: fns.startOfYear
|
|
||||||
};
|
|
||||||
|
|
||||||
const endOfFunctions: { [key in Slice]: (date: Date) => Date } = {
|
|
||||||
hour: fns.endOfHour,
|
|
||||||
day: fns.endOfDay,
|
|
||||||
week: fns.endOfWeek,
|
|
||||||
month: fns.endOfMonth,
|
|
||||||
year: fns.endOfYear
|
|
||||||
};
|
|
||||||
|
|
||||||
class DateService {
|
|
||||||
|
|
||||||
public slicesData = slicesData;
|
|
||||||
|
|
||||||
getChartLabelFromISO(iso: string, offset: number, slice: Slice) {
|
|
||||||
const date = new Date(new Date(iso).getTime() + offset * 1000 * 60);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
canUseSlice(from: string | number | Date, to: string | number | Date, slice: Slice) {
|
|
||||||
|
|
||||||
const daysDiff = fns.differenceInDays(
|
|
||||||
new Date(new Date(to).getTime() + 1000),
|
|
||||||
new Date(from)
|
|
||||||
);
|
|
||||||
|
|
||||||
return this.canUseSliceFromDays(daysDiff, slice);
|
|
||||||
}
|
|
||||||
|
|
||||||
canUseSliceFromDays(days: number, slice: Slice): [false, string] | [true, number] {
|
|
||||||
|
|
||||||
// 3 Days
|
|
||||||
if (slice === 'hour' && (days > 3)) return [false, 'Date gap too big for this slice'];
|
|
||||||
// 3 Weeks
|
|
||||||
if (slice === 'day' && (days > 31)) return [false, 'Date gap too big for this slice'];
|
|
||||||
// 3 Years
|
|
||||||
if (slice === 'month' && (days > 365 * 3)) return [false, 'Date gap too big for this slice'];
|
|
||||||
|
|
||||||
// 2 days
|
|
||||||
if (slice === 'day' && (days < 2)) return [false, 'Date gap too small for this slice'];
|
|
||||||
// 2 month
|
|
||||||
if (slice === 'month' && (days < 31 * 2)) return [false, 'Date gap too small for this slice'];
|
|
||||||
|
|
||||||
return [true, days]
|
|
||||||
}
|
|
||||||
|
|
||||||
startOfSlice(date: Date, slice: Slice) {
|
|
||||||
const fn = startOfFunctions[slice];
|
|
||||||
if (!fn) throw Error(`startOfFunction of slice ${slice} not found`);
|
|
||||||
return fn(date);
|
|
||||||
}
|
|
||||||
|
|
||||||
endOfSlice(date: Date, slice: Slice) {
|
|
||||||
const fn = endOfFunctions[slice];
|
|
||||||
if (!fn) throw Error(`endOfFunction of slice ${slice} not found`);
|
|
||||||
return fn(date);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
getGranularityData(slice: Slice, dateField: string) {
|
|
||||||
|
|
||||||
const dateFromParts: Record<string, any> = {};
|
|
||||||
let granularity;
|
|
||||||
|
|
||||||
switch (slice) {
|
|
||||||
case 'hour':
|
|
||||||
dateFromParts.hour = { $hour: { date: dateField } }
|
|
||||||
granularity = granularity || 'hour';
|
|
||||||
case 'day':
|
|
||||||
dateFromParts.day = { $dayOfMonth: { date: dateField } }
|
|
||||||
granularity = granularity || 'day';
|
|
||||||
case 'month':
|
|
||||||
dateFromParts.month = { $month: { date: dateField } }
|
|
||||||
granularity = granularity || 'month';
|
|
||||||
case 'year':
|
|
||||||
dateFromParts.year = { $year: { date: dateField } }
|
|
||||||
granularity = granularity || 'year';
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
const addFunctions: { [key in Slice]: any } = { hour: fns.addHours, day: fns.addDays, week: fns.addWeeks, month: fns.addMonths, year: fns.addYears };
|
|
||||||
const addFunction = addFunctions[slice];
|
|
||||||
if (!addFunction) { throw new Error(`Invalid slice: ${slice}`); }
|
|
||||||
while (fns.isBefore(currentDate, toDate) || currentDate.getTime() === toDate.getTime()) {
|
|
||||||
slices.push(currentDate);
|
|
||||||
currentDate = addFunction(currentDate, 1);
|
|
||||||
}
|
|
||||||
return slices;
|
|
||||||
}
|
|
||||||
|
|
||||||
isSameDayUTC(a: Date, b: Date) {
|
|
||||||
return a.getUTCFullYear() === b.getUTCFullYear() && a.getUTCMonth() === b.getUTCMonth() && a.getUTCDate() === b.getUTCDate();
|
|
||||||
}
|
|
||||||
|
|
||||||
mergeDates(timeline: { _id: string, count: number }[], allDates: Date[], slice: Slice) {
|
|
||||||
|
|
||||||
const result: { _id: string, count: number }[] = [];
|
|
||||||
|
|
||||||
const isSames: { [key in Slice]: any } = { hour: fns.isSameHour, day: this.isSameDayUTC, week: fns.isSameWeek, month: fns.isSameMonth, year: fns.isSameYear, }
|
|
||||||
|
|
||||||
const isSame = isSames[slice];
|
|
||||||
|
|
||||||
if (!isSame) {
|
|
||||||
throw new Error(`Invalid slice: ${slice}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const date of allDates) {
|
|
||||||
result.push({ _id: date.toISOString(), count: 0 });
|
|
||||||
for (const element of timeline) {
|
|
||||||
const elementDate = new Date(element._id);
|
|
||||||
if (isSame(elementDate, date)) {
|
|
||||||
const existingEntry = result.find(item => isSame(date, new Date(item._id)));
|
|
||||||
if (!existingEntry) throw new Error('THIS CANNOT HAPPEN');
|
|
||||||
existingEntry.count += element.count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const dateServiceInstance = new DateService();
|
|
||||||
export default dateServiceInstance;
|
|
||||||
@@ -1,35 +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',
|
|
||||||
} 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 } };
|
|
||||||
|
|
||||||
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' }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user