new selfhosted version

This commit is contained in:
antonio
2025-11-28 14:11:51 +01:00
parent afda29997d
commit 951860f67e
1046 changed files with 72586 additions and 574750 deletions

View File

@@ -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;

View File

@@ -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' }
};
}
}

View File

@@ -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();