mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-10 15:58:38 +01:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7658dbe85c | ||
|
|
1f9ef5d18c | ||
|
|
94a28b31d3 | ||
|
|
87c9aca5c4 | ||
|
|
afda29997d |
9
dashboard/app.config.ts
Normal file
9
dashboard/app.config.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export default defineAppConfig({
|
||||||
|
ui: {
|
||||||
|
notifications: {
|
||||||
|
position: 'top-0 bottom-[unset]'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -69,6 +69,7 @@ const { drawerVisible, hideDrawer, drawerClasses } = useDrawer();
|
|||||||
|
|
||||||
|
|
||||||
<UModals />
|
<UModals />
|
||||||
|
<UNotifications />
|
||||||
|
|
||||||
<LazyOnboarding> </LazyOnboarding>
|
<LazyOnboarding> </LazyOnboarding>
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ onMounted(() => {
|
|||||||
<div class="flex overflow-x-auto hide-scrollbars">
|
<div class="flex overflow-x-auto hide-scrollbars">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div v-for="(tab, index) of items" @click="onChangeTab(index)"
|
<div v-for="(tab, index) of items" @click="onChangeTab(index)"
|
||||||
class="px-6 pb-3 poppins font-medium text-lyx-lightmode-text dark:text-lyx-text-darker border-b-[1px] border-lyx-text-darker"
|
class="px-6 whitespace-nowrap pb-3 poppins font-medium text-lyx-lightmode-text dark:text-lyx-text-darker border-b-[1px] border-lyx-text-darker"
|
||||||
:class="{
|
:class="{
|
||||||
'dark:!border-[#FFFFFF] dark:!text-[#FFFFFF] !border-lyx-primary !text-lyx-primary': activeTabIndex === index,
|
'dark:!border-[#FFFFFF] dark:!text-[#FFFFFF] !border-lyx-primary !text-lyx-primary': activeTabIndex === index,
|
||||||
'hover:border-lyx-lightmode-text-dark hover:text-lyx-lightmode-text-dark/60 dark:hover:border-lyx-text-dark dark:hover:text-lyx-text-dark cursor-pointer': activeTabIndex !== index
|
'hover:border-lyx-lightmode-text-dark hover:text-lyx-lightmode-text-dark/60 dark:hover:border-lyx-text-dark dark:hover:text-lyx-text-dark cursor-pointer': activeTabIndex !== index
|
||||||
|
|||||||
@@ -11,5 +11,5 @@ const widgetStyle = computed(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :style="widgetStyle" class="bg-lyx-widget-light"></div>
|
<div :style="widgetStyle" class="dark:bg-lyx-widget-light bg-lyx-lightmode-widget"></div>
|
||||||
</template>
|
</template>
|
||||||
@@ -9,7 +9,23 @@ const avgDuration = computed(() => {
|
|||||||
return (backendData.value.durations.durations.reduce((a: any, e: any) => a + parseInt(e[1]), 0) / backendData.value.durations.durations.length);
|
return (backendData.value.durations.durations.reduce((a: any, e: any) => a + parseInt(e[1]), 0) / backendData.value.durations.durations.length);
|
||||||
})
|
})
|
||||||
|
|
||||||
const labels = new Array(650).fill('-');
|
const labels = computed(() => {
|
||||||
|
if (!backendData?.value?.durations) return [];
|
||||||
|
|
||||||
|
const sizes = new Map<string, number>();
|
||||||
|
|
||||||
|
for (const e of backendData.value.durations.durations) {
|
||||||
|
if (!sizes.has(e[0])) {
|
||||||
|
sizes.set(e[0], 0);
|
||||||
|
} else {
|
||||||
|
const data = sizes.get(e[0]) ?? 0;
|
||||||
|
sizes.set(e[0], data + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const max = Array.from(sizes.values()).reduce((a, e) => a > e ? a : e, 0);
|
||||||
|
return new Array(max).fill('-');
|
||||||
|
});
|
||||||
|
|
||||||
const durationsDatasets = computed(() => {
|
const durationsDatasets = computed(() => {
|
||||||
if (!backendData?.value?.durations) return [];
|
if (!backendData?.value?.durations) return [];
|
||||||
@@ -26,7 +42,7 @@ const durationsDatasets = computed(() => {
|
|||||||
|
|
||||||
datasets.push({
|
datasets.push({
|
||||||
points: consumerDurations.map((e: any) => {
|
points: consumerDurations.map((e: any) => {
|
||||||
return 1000 / parseInt(e[1])
|
return 1000 / parseInt(e[1])
|
||||||
}),
|
}),
|
||||||
color: colors[i],
|
color: colors[i],
|
||||||
chartType: 'line',
|
chartType: 'line',
|
||||||
@@ -45,7 +61,7 @@ const durationsDatasets = computed(() => {
|
|||||||
|
|
||||||
<div class="cursor-default flex justify-center w-full">
|
<div class="cursor-default flex justify-center w-full">
|
||||||
|
|
||||||
<div v-if="backendData" class="flex flex-col mt-8 gap-6 px-20 items-center w-full">
|
<div v-if="backendData && !backendPending" class="flex flex-col mt-8 gap-6 px-20 items-center w-full">
|
||||||
|
|
||||||
<div class="flex gap-8">
|
<div class="flex gap-8">
|
||||||
<div> Queue size: {{ backendData.queue?.size || 'ERROR' }} </div>
|
<div> Queue size: {{ backendData.queue?.size || 'ERROR' }} </div>
|
||||||
|
|||||||
@@ -373,8 +373,9 @@ const legendClasses = ref<string[]>([
|
|||||||
</div>
|
</div>
|
||||||
<div class="mt-3 font-normal flex flex-col text-[.9rem] dark:text-lyx-text-dark text-lyx-lightmode-text-dark"
|
<div class="mt-3 font-normal flex flex-col text-[.9rem] dark:text-lyx-text-dark text-lyx-lightmode-text-dark"
|
||||||
v-if="(currentTooltipData as any).sessions > (currentTooltipData as any).visits">
|
v-if="(currentTooltipData as any).sessions > (currentTooltipData as any).visits">
|
||||||
<div> Unique visitors is greater than visits. </div>
|
<div> Unique visitors are higher than total visits </div>
|
||||||
<div> This can indicate bot traffic. </div>
|
<div> which often means bots (automated scripts or crawlers)</div>
|
||||||
|
<div> are inflating the numbers.</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="bg-lyx-background-lighter h-[2px] w-full my-2"> </div> -->
|
<!-- <div class="bg-lyx-background-lighter h-[2px] w-full my-2"> </div> -->
|
||||||
</LyxUiCard>
|
</LyxUiCard>
|
||||||
|
|||||||
@@ -68,21 +68,47 @@ const avgBouncingRate = computed(() => {
|
|||||||
return avg.toFixed(2) + ' %';
|
return avg.toFixed(2) + ' %';
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function weightedAverage(data: number[]): number {
|
||||||
|
if (data.length === 0) return 0;
|
||||||
|
|
||||||
|
// Compute median
|
||||||
|
const sortedData = [...data].sort((a, b) => a - b);
|
||||||
|
const middle = Math.floor(sortedData.length / 2);
|
||||||
|
const median = sortedData.length % 2 === 0
|
||||||
|
? (sortedData[middle - 1] + sortedData[middle]) / 2
|
||||||
|
: sortedData[middle];
|
||||||
|
|
||||||
|
// Define a threshold (e.g., 3 times the median) to filter out extreme values
|
||||||
|
const threshold = median * 3;
|
||||||
|
const filteredData = data.filter(num => num <= threshold);
|
||||||
|
|
||||||
|
if (filteredData.length === 0) return median; // Fallback to median if all are removed
|
||||||
|
|
||||||
|
// Compute weights based on inverse absolute deviation from median
|
||||||
|
const weights = filteredData.map(num => 1 / (1 + Math.abs(num - median)));
|
||||||
|
|
||||||
|
// Compute weighted sum and sum of weights
|
||||||
|
const weightedSum = filteredData.reduce((sum, num, i) => sum + num * weights[i], 0);
|
||||||
|
const sumOfWeights = weights.reduce((sum, weight) => sum + weight, 0);
|
||||||
|
|
||||||
|
return weightedSum / sumOfWeights;
|
||||||
|
}
|
||||||
const avgSessionDuration = computed(() => {
|
const avgSessionDuration = computed(() => {
|
||||||
if (!sessionsDurationData.data.value) return '0.00 %'
|
if (!sessionsDurationData.data.value) return '0.00 %'
|
||||||
|
|
||||||
const counts = sessionsDurationData.data.value.data
|
const counts = sessionsDurationData.data.value.data
|
||||||
.filter(e => e > 0)
|
// .filter(e => e > 0)
|
||||||
.reduce((a, e) => e + a, 0);
|
.reduce((a, e) => e + a, 0);
|
||||||
|
|
||||||
const avg = counts / (Math.max(sessionsDurationData.data.value.data.filter(e => e > 0).length, 1)) / 5;
|
const avg = weightedAverage(sessionsDurationData.data.value.data);
|
||||||
|
// counts / (Math.max(sessionsDurationData.data.value.data.length, 1));
|
||||||
|
|
||||||
let hours = 0;
|
let hours = 0;
|
||||||
let minutes = 0;
|
let minutes = 0;
|
||||||
let seconds = 0;
|
let seconds = 0;
|
||||||
seconds += avg * 60;
|
seconds += avg * 60;
|
||||||
while (seconds > 60) { seconds -= 60; minutes += 1; }
|
while (seconds >= 60) { seconds -= 60; minutes += 1; }
|
||||||
while (minutes > 60) { minutes -= 60; hours += 1; }
|
while (minutes >= 60) { minutes -= 60; hours += 1; }
|
||||||
return `${hours > 0 ? hours + 'h ' : ''}${minutes}m ${seconds.toFixed()}s`
|
return `${hours > 0 ? hours + 'h ' : ''}${minutes}m ${seconds.toFixed()}s`
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
80
dashboard/components/dialog/shields/AddAddress.vue
Normal file
80
dashboard/components/dialog/shields/AddAddress.vue
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
const emit = defineEmits(['success', 'cancel']);
|
||||||
|
|
||||||
|
const address = ref<string>('');
|
||||||
|
const description = ref<string>('');
|
||||||
|
|
||||||
|
|
||||||
|
const { data: currentIP } = useFetch<any>('https://api.ipify.org/?format=json');
|
||||||
|
|
||||||
|
|
||||||
|
const canAddAddress = computed(() => {
|
||||||
|
return address.value.trim().length > 0;
|
||||||
|
})
|
||||||
|
|
||||||
|
async function addAddress() {
|
||||||
|
if (!canAddAddress.value) return;
|
||||||
|
try {
|
||||||
|
const res = await $fetch('/api/shields/ip/add', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: useComputedHeaders({}).value,
|
||||||
|
body: JSON.stringify({ address: address.value, description: description.value })
|
||||||
|
});
|
||||||
|
address.value = '';
|
||||||
|
emit('success');
|
||||||
|
} catch (ex: any) {
|
||||||
|
alert(ex.message);
|
||||||
|
emit('cancel');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UModal :ui="{
|
||||||
|
strategy: 'override',
|
||||||
|
overlay: {
|
||||||
|
background: 'bg-lyx-background/85'
|
||||||
|
},
|
||||||
|
background: 'dark:bg-lyx-widget bg-lyx-lightmode-widget-light',
|
||||||
|
ring: 'border-solid border-[1px] border-[#262626]'
|
||||||
|
}">
|
||||||
|
<div class="h-full flex flex-col gap-2 p-4">
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
|
||||||
|
<div class="font-semibold text-[1.1rem]"> Add IP to Block List </div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-2 dark:text-lyx-text-dark text-lyx-lightmode-text-dark">
|
||||||
|
<div> Your current IP address is: {{ currentIP?.ip || '...' }} </div>
|
||||||
|
<div> Copy and Paste your IP address in the box below or enter a custom address </div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<div class="font-medium"> IP Address </div>
|
||||||
|
<LyxUiInput class="px-2 py-1" v-model="address" placeholder="127.0.0.1"></LyxUiInput>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<div class="font-medium"> Description (optional) </div>
|
||||||
|
<LyxUiInput class="px-2 py-1" v-model="description" placeholder="e.g. localhost or office">
|
||||||
|
</LyxUiInput>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-2 dark:text-lyx-text-dark text-lyx-lightmode-text-dark">
|
||||||
|
<div> Once added, we will start rejecting traffic from this IP within a few minutes.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex">
|
||||||
|
<LyxUiButton class="w-full text-center" :disabled="!canAddAddress" @click="addAddress()"
|
||||||
|
type="primary">
|
||||||
|
Add IP Address
|
||||||
|
</LyxUiButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</UModal>
|
||||||
|
|
||||||
|
</template>
|
||||||
67
dashboard/components/dialog/shields/AddDomain.vue
Normal file
67
dashboard/components/dialog/shields/AddDomain.vue
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
const emit = defineEmits(['success', 'cancel']);
|
||||||
|
|
||||||
|
const domain = ref<string>('');
|
||||||
|
|
||||||
|
const canAddDomain = computed(() => {
|
||||||
|
return domain.value.trim().length > 0;
|
||||||
|
})
|
||||||
|
|
||||||
|
async function addDomain() {
|
||||||
|
if (!canAddDomain.value) return;
|
||||||
|
try {
|
||||||
|
const res = await $fetch('/api/shields/domains/add', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: useComputedHeaders({}).value,
|
||||||
|
body: JSON.stringify({ domain: domain.value })
|
||||||
|
});
|
||||||
|
domain.value = '';
|
||||||
|
emit('success');
|
||||||
|
} catch (ex: any) {
|
||||||
|
alert(ex.message);
|
||||||
|
emit('cancel');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UModal :ui="{
|
||||||
|
strategy: 'override',
|
||||||
|
overlay: {
|
||||||
|
background: 'bg-lyx-background/85'
|
||||||
|
},
|
||||||
|
background: 'dark:bg-lyx-widget bg-lyx-lightmode-widget-light',
|
||||||
|
ring: 'border-solid border-[1px] border-[#262626]'
|
||||||
|
}">
|
||||||
|
<div class="h-full flex flex-col gap-2 p-4">
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
|
||||||
|
<div class="font-semibold text-[1.1rem]"> Add Domain to Allow List </div>
|
||||||
|
|
||||||
|
<LyxUiInput class="px-2 py-1" v-model="domain"></LyxUiInput>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-2 dark:text-lyx-text-dark text-lyx-lightmode-text-dark">
|
||||||
|
<div>
|
||||||
|
<div> You can use a wildcard (*) to match multiple hostnames. </div>
|
||||||
|
<div> For example, *.domain.com will only record traffic on the main domain and all the
|
||||||
|
subdomains.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div> NB: Once added, we will start allowing traffic only from matching hostnames within a few
|
||||||
|
minutes.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex">
|
||||||
|
<LyxUiButton class="w-full text-center" :disabled="!canAddDomain" @click="addDomain()" type="primary">
|
||||||
|
Add domain
|
||||||
|
</LyxUiButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</UModal>
|
||||||
|
|
||||||
|
</template>
|
||||||
56
dashboard/components/dialog/shields/DeleteAddress.vue
Normal file
56
dashboard/components/dialog/shields/DeleteAddress.vue
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
const emit = defineEmits(['success', 'cancel']);
|
||||||
|
|
||||||
|
const props = defineProps<{ address: string }>();
|
||||||
|
|
||||||
|
async function deleteAddress() {
|
||||||
|
if (!props.address) return;
|
||||||
|
try {
|
||||||
|
const res = await $fetch('/api/shields/ip/delete', {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: useComputedHeaders({}).value,
|
||||||
|
body: JSON.stringify({ address: props.address })
|
||||||
|
});
|
||||||
|
emit('success');
|
||||||
|
} catch (ex: any) {
|
||||||
|
alert(ex.message);
|
||||||
|
emit('cancel');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UModal :ui="{
|
||||||
|
strategy: 'override',
|
||||||
|
overlay: {
|
||||||
|
background: 'bg-lyx-background/85'
|
||||||
|
},
|
||||||
|
background: 'dark:bg-lyx-widget bg-lyx-lightmode-widget-light',
|
||||||
|
ring: 'border-solid border-[1px] border-[#262626]'
|
||||||
|
}">
|
||||||
|
<div class="h-full flex flex-col gap-2 p-4">
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
|
||||||
|
<div class="font-semibold text-[1.1rem]"> IP Address delete </div>
|
||||||
|
|
||||||
|
<div> Are you sure to delete the blacklisted IP Address
|
||||||
|
<span class="font-semibold">{{ props.address }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<LyxUiButton type="secondary" @click="emit('cancel')">
|
||||||
|
Cancel
|
||||||
|
</LyxUiButton>
|
||||||
|
<LyxUiButton @click="deleteAddress()" type="danger">
|
||||||
|
Delete
|
||||||
|
</LyxUiButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</UModal>
|
||||||
|
|
||||||
|
</template>
|
||||||
56
dashboard/components/dialog/shields/DeleteDomain.vue
Normal file
56
dashboard/components/dialog/shields/DeleteDomain.vue
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
const emit = defineEmits(['success', 'cancel']);
|
||||||
|
|
||||||
|
const props = defineProps<{ domain: string }>();
|
||||||
|
|
||||||
|
async function deleteDomain() {
|
||||||
|
if (!props.domain) return;
|
||||||
|
try {
|
||||||
|
const res = await $fetch('/api/shields/domains/delete', {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: useComputedHeaders({}).value,
|
||||||
|
body: JSON.stringify({ domain: props.domain })
|
||||||
|
});
|
||||||
|
emit('success');
|
||||||
|
} catch (ex: any) {
|
||||||
|
alert(ex.message);
|
||||||
|
emit('cancel');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UModal :ui="{
|
||||||
|
strategy: 'override',
|
||||||
|
overlay: {
|
||||||
|
background: 'bg-lyx-background/85'
|
||||||
|
},
|
||||||
|
background: 'dark:bg-lyx-widget bg-lyx-lightmode-widget-light',
|
||||||
|
ring: 'border-solid border-[1px] border-[#262626]'
|
||||||
|
}">
|
||||||
|
<div class="h-full flex flex-col gap-2 p-4">
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
|
||||||
|
<div class="font-semibold text-[1.1rem]"> Domain delete </div>
|
||||||
|
|
||||||
|
<div> Are you sure to delete the whitelisted domain
|
||||||
|
<span class="font-semibold">{{ props.domain }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<LyxUiButton type="secondary" @click="emit('cancel')">
|
||||||
|
Cancel
|
||||||
|
</LyxUiButton>
|
||||||
|
<LyxUiButton @click="deleteDomain()" type="danger">
|
||||||
|
Delete
|
||||||
|
</LyxUiButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</UModal>
|
||||||
|
|
||||||
|
</template>
|
||||||
101
dashboard/components/shields/Addresses.vue
Normal file
101
dashboard/components/shields/Addresses.vue
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { DialogShieldsDeleteAddress, DialogShieldsAddAddress } from '#components';
|
||||||
|
|
||||||
|
definePageMeta({ layout: 'dashboard' });
|
||||||
|
|
||||||
|
const { data: blackAddresses, refresh: refreshAddresses, pending: pendingAddresses } = useFetch('/api/shields/ip/list', {
|
||||||
|
headers: useComputedHeaders({})
|
||||||
|
});
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
const modal = useModal();
|
||||||
|
|
||||||
|
function showAddAddressModal() {
|
||||||
|
modal.open(DialogShieldsAddAddress, {
|
||||||
|
onSuccess: () => {
|
||||||
|
refreshAddresses();
|
||||||
|
modal.close();
|
||||||
|
|
||||||
|
toast.add({
|
||||||
|
id: 'shield_address_add_success',
|
||||||
|
title: 'Success',
|
||||||
|
description: 'Blacklist updated with the new address',
|
||||||
|
timeout: 5000
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
modal.close();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function showDeleteAddressModal(address: string) {
|
||||||
|
modal.open(DialogShieldsDeleteAddress, {
|
||||||
|
address,
|
||||||
|
onSuccess: () => {
|
||||||
|
refreshAddresses();
|
||||||
|
modal.close();
|
||||||
|
toast.add({
|
||||||
|
id: 'shield_address_remove_success',
|
||||||
|
title: 'Deleted',
|
||||||
|
description: 'Blacklist address deleted successfully',
|
||||||
|
timeout: 5000
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
modal.close();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<div class="py-4 flex">
|
||||||
|
<LyxUiCard class="w-full mx-2">
|
||||||
|
<div>
|
||||||
|
<div class="text-[1.2rem] font-semibold"> IP Block List </div>
|
||||||
|
<div class="dark:text-lyx-text-dark text-lyx-lightmode-text-dark">
|
||||||
|
Reject incoming traffic from specific IP addresses
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<LyxUiSeparator class="my-3"></LyxUiSeparator>
|
||||||
|
|
||||||
|
<div class="flex justify-end pb-3">
|
||||||
|
<LyxUiButton type="primary" @click="showAddAddressModal()"> Add IP Address </LyxUiButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-center pb-8 text-[1.2rem]" v-if="pendingAddresses">
|
||||||
|
<i class="fas fa-loader animate-spin"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="!pendingAddresses && blackAddresses && blackAddresses.length == 0"
|
||||||
|
class="flex flex-col items-center pb-8">
|
||||||
|
<div>
|
||||||
|
No domain rules configured for this project.
|
||||||
|
</div>
|
||||||
|
<div class="font-semibold">
|
||||||
|
Traffic from all domains is currently accepted.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="!pendingAddresses && blackAddresses && blackAddresses.length > 0"
|
||||||
|
class="grid grid-cols-[auto_auto_auto_auto] px-10">
|
||||||
|
<div> Domain </div>
|
||||||
|
<div class="col-span-2"> Description </div>
|
||||||
|
<div> Actions </div>
|
||||||
|
<LyxUiSeparator class="col-span-4 my-3"></LyxUiSeparator>
|
||||||
|
<template v-for="entry of blackAddresses">
|
||||||
|
<div class="mb-2"> {{ entry.address }} </div>
|
||||||
|
<div class="col-span-2">{{ entry.description || 'No description' }}</div>
|
||||||
|
<div> <i @click="showDeleteAddressModal(entry.address)"
|
||||||
|
class="far fa-trash cursor-pointer hover:text-lyx-text-dark"></i> </div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</LyxUiCard>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
47
dashboard/components/shields/Bots.vue
Normal file
47
dashboard/components/shields/Bots.vue
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
definePageMeta({ layout: 'dashboard' });
|
||||||
|
|
||||||
|
const { data: botOptions, refresh: refreshBotOptions, pending: pendingBotOptions } = useFetch('/api/shields/bots/options', {
|
||||||
|
headers: useComputedHeaders({})
|
||||||
|
});
|
||||||
|
|
||||||
|
async function onChange(newValue: boolean) {
|
||||||
|
await $fetch('/api/shields/bots/update_options', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: useComputedHeaders({ custom: { 'Content-Type': 'application/json' } }).value,
|
||||||
|
body: JSON.stringify({ block: newValue })
|
||||||
|
})
|
||||||
|
await refreshBotOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<div class="py-4 flex">
|
||||||
|
<LyxUiCard class="w-full mx-2">
|
||||||
|
<div>
|
||||||
|
<div class="text-[1.2rem] font-semibold"> Block bot traffic </div>
|
||||||
|
<div class="dark:text-lyx-text-dark text-lyx-lightmode-text-dark">
|
||||||
|
Automatically block unwanted bot and crawler traffic to protect your site from spam, scrapers, and
|
||||||
|
unnecessary server load.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<LyxUiSeparator class="my-3"></LyxUiSeparator>
|
||||||
|
|
||||||
|
<div class="flex justify-center pb-8 text-[1.2rem]" v-if="pendingBotOptions">
|
||||||
|
<i class="fas fa-loader animate-spin"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="!pendingBotOptions && botOptions">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<UToggle :modelValue="botOptions.block" @change="onChange"></UToggle>
|
||||||
|
<div> Enable bot protection </div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</LyxUiCard>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
99
dashboard/components/shields/Domains.vue
Normal file
99
dashboard/components/shields/Domains.vue
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { DialogShieldsAddDomain, DialogShieldsDeleteDomain } from '#components';
|
||||||
|
|
||||||
|
definePageMeta({ layout: 'dashboard' });
|
||||||
|
|
||||||
|
const { data: allowedDomains, refresh: refreshDomains, pending: pendingDomains } = useFetch('/api/shields/domains/list', {
|
||||||
|
headers: useComputedHeaders({})
|
||||||
|
});
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
const modal = useModal();
|
||||||
|
|
||||||
|
function showAddDomainModal() {
|
||||||
|
modal.open(DialogShieldsAddDomain, {
|
||||||
|
onSuccess: () => {
|
||||||
|
refreshDomains();
|
||||||
|
modal.close();
|
||||||
|
|
||||||
|
toast.add({
|
||||||
|
id: 'shield_domain_add_success',
|
||||||
|
title: 'Success',
|
||||||
|
description: 'Whitelist updated with the new domain',
|
||||||
|
timeout: 5000
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
modal.close();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function showDeleteDomainModal(domain: string) {
|
||||||
|
modal.open(DialogShieldsDeleteDomain, {
|
||||||
|
domain,
|
||||||
|
onSuccess: () => {
|
||||||
|
refreshDomains();
|
||||||
|
modal.close();
|
||||||
|
toast.add({
|
||||||
|
id: 'shield_domain_remove_success',
|
||||||
|
title: 'Deleted',
|
||||||
|
description: 'Whitelist domain deleted successfully',
|
||||||
|
timeout: 5000
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
modal.close();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<div class="py-4 flex">
|
||||||
|
<LyxUiCard class="w-full mx-2">
|
||||||
|
<div>
|
||||||
|
<div class="text-[1.2rem] font-semibold"> Domains allow list </div>
|
||||||
|
<div class="dark:text-lyx-text-dark text-lyx-lightmode-text-dark">
|
||||||
|
Accept incoming traffic only from familiar domains.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<LyxUiSeparator class="my-3"></LyxUiSeparator>
|
||||||
|
|
||||||
|
<div class="flex justify-end pb-3">
|
||||||
|
<LyxUiButton type="primary" @click="showAddDomainModal()"> Add Domain </LyxUiButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-center pb-8 text-[1.2rem]" v-if="pendingDomains">
|
||||||
|
<i class="fas fa-loader animate-spin"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="!pendingDomains && allowedDomains && allowedDomains.length == 0"
|
||||||
|
class="flex flex-col items-center pb-8">
|
||||||
|
<div>
|
||||||
|
No domain rules configured for this project.
|
||||||
|
</div>
|
||||||
|
<div class="font-semibold">
|
||||||
|
Traffic from all domains is currently accepted.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="!pendingDomains && allowedDomains && allowedDomains.length > 0"
|
||||||
|
class="grid grid-cols-[auto_auto_auto_auto] px-10">
|
||||||
|
<div class="col-span-3">Domain</div>
|
||||||
|
<div>Actions</div>
|
||||||
|
<LyxUiSeparator class="col-span-4 my-3"></LyxUiSeparator>
|
||||||
|
<template v-for="domain of allowedDomains">
|
||||||
|
<div class="col-span-3 mb-3">{{ domain }}</div>
|
||||||
|
<div> <i @click="showDeleteDomainModal(domain)"
|
||||||
|
class="far fa-trash cursor-pointer hover:text-lyx-text-dark"></i> </div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</LyxUiCard>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -33,9 +33,9 @@ const guestProjectList = computed(() => {
|
|||||||
return guestProjectsRequest.data.value;
|
return guestProjectsRequest.data.value;
|
||||||
})
|
})
|
||||||
|
|
||||||
const refreshProjectsList = () => {
|
const refreshProjectsList = async () => {
|
||||||
projectsRequest.refresh();
|
await projectsRequest.refresh();
|
||||||
guestProjectsRequest.refresh();
|
await guestProjectsRequest.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeProjectId = ref<string | undefined>();
|
const activeProjectId = ref<string | undefined>();
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const sections: Section[] = [
|
|||||||
{ label: 'Web Analytics', to: '/', icon: 'fal fa-table-layout' },
|
{ label: 'Web Analytics', to: '/', icon: 'fal fa-table-layout' },
|
||||||
{ label: 'Custom Events', to: '/events', icon: 'fal fa-square-bolt' },
|
{ label: 'Custom Events', to: '/events', icon: 'fal fa-square-bolt' },
|
||||||
{ label: 'Members', to: '/members', icon: 'fal fa-users' },
|
{ label: 'Members', to: '/members', icon: 'fal fa-users' },
|
||||||
|
{ label: 'Shields', to: '/shields', icon: 'fal fa-shield' },
|
||||||
{ label: 'Ask AI', to: '/analyst', icon: 'fal fa-sparkles' },
|
{ label: 'Ask AI', to: '/analyst', icon: 'fal fa-sparkles' },
|
||||||
|
|
||||||
// { label: 'Security', to: '/security', icon: 'fal fa-shield', disabled: selfhosted },
|
// { label: 'Security', to: '/security', icon: 'fal fa-shield', disabled: selfhosted },
|
||||||
|
|||||||
@@ -40,8 +40,10 @@ async function createProject() {
|
|||||||
await actions.refreshProjectsList();
|
await actions.refreshProjectsList();
|
||||||
|
|
||||||
const newActiveProjectId = projectList.value?.[projectList.value?.length - 1]._id.toString();
|
const newActiveProjectId = projectList.value?.[projectList.value?.length - 1]._id.toString();
|
||||||
|
|
||||||
if (newActiveProjectId) {
|
if (newActiveProjectId) {
|
||||||
await actions.setActiveProject(newActiveProjectId);
|
await actions.setActiveProject(newActiveProjectId);
|
||||||
|
console.log('Set active project', newActiveProjectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
setPageLayout('dashboard');
|
setPageLayout('dashboard');
|
||||||
|
|||||||
35
dashboard/pages/shields.vue
Normal file
35
dashboard/pages/shields.vue
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
definePageMeta({ layout: 'dashboard' });
|
||||||
|
|
||||||
|
const selfhosted = useSelfhosted();
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{ label: 'Domains', slot: 'domains', tab: 'domains' },
|
||||||
|
{ label: 'IP Addresses', slot: 'ipaddresses', tab: 'ipaddresses' },
|
||||||
|
{ label: 'Bot traffic', slot: 'bots', tab: 'bots' },
|
||||||
|
// { label: 'Countries', slot: 'countries', tab: 'countries' },
|
||||||
|
// { label: 'Pages', slot: 'pages', tab: 'pages' },
|
||||||
|
]
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="lg:px-10 h-full lg:py-8 overflow-hidden hide-scrollbars">
|
||||||
|
|
||||||
|
<div class="poppins font-semibold text-[1.3rem] lg:px-0 px-4 lg:py-0 py-4"> Shields </div>
|
||||||
|
|
||||||
|
<CustomTab :items="items" :route="true" class="mt-8">
|
||||||
|
<template #domains>
|
||||||
|
<ShieldsDomains></ShieldsDomains>
|
||||||
|
</template>
|
||||||
|
<template #ipaddresses>
|
||||||
|
<ShieldsAddresses></ShieldsAddresses>
|
||||||
|
</template>
|
||||||
|
<template #bots>
|
||||||
|
<ShieldsBots></ShieldsBots>
|
||||||
|
</template>
|
||||||
|
</CustomTab>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -14,7 +14,7 @@ export async function getUserProjectFromId(project_id: string, user: AuthContext
|
|||||||
const project = await ProjectModel.findById(project_id);
|
const project = await ProjectModel.findById(project_id);
|
||||||
if (!project) return;
|
if (!project) return;
|
||||||
|
|
||||||
const [hasAccess, role] = await hasAccessToProject(user.id, project_id, project);
|
const [hasAccess, role] = await hasAccessToProject(user.id, project_id, user.user.email, project);
|
||||||
if (!hasAccess) return;
|
if (!hasAccess) return;
|
||||||
|
|
||||||
if (role === 'GUEST' && !allowGuest) return false;
|
if (role === 'GUEST' && !allowGuest) return false;
|
||||||
|
|||||||
@@ -59,6 +59,13 @@ export default defineEventHandler(async event => {
|
|||||||
|
|
||||||
const savedUser = await newUser.save();
|
const savedUser = await newUser.save();
|
||||||
|
|
||||||
|
|
||||||
|
setImmediate(() => {
|
||||||
|
const emailData = EmailService.getEmailServerInfo('brevolist_add', { email: payload.email as string });
|
||||||
|
EmailServiceHelper.sendEmail(emailData);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
console.log('SENDING WELCOME EMAIL TO', payload.email);
|
console.log('SENDING WELCOME EMAIL TO', payload.email);
|
||||||
if (!payload.email) return;
|
if (!payload.email) return;
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ export default defineEventHandler(async event => {
|
|||||||
|
|
||||||
await RegisterModel.create({ email, password: hashedPassword });
|
await RegisterModel.create({ email, password: hashedPassword });
|
||||||
|
|
||||||
|
setImmediate(() => {
|
||||||
|
const emailData = EmailService.getEmailServerInfo('brevolist_add', { email });
|
||||||
|
EmailServiceHelper.sendEmail(emailData);
|
||||||
|
});
|
||||||
|
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
const emailData = EmailService.getEmailServerInfo('confirm', { target: email, link: `https://dashboard.litlyx.com/api/auth/confirm_email?register_code=${jwt}` });
|
const emailData = EmailService.getEmailServerInfo('confirm', { target: email, link: `https://dashboard.litlyx.com/api/auth/confirm_email?register_code=${jwt}` });
|
||||||
EmailServiceHelper.sendEmail(emailData);
|
EmailServiceHelper.sendEmail(emailData);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export default defineEventHandler(async event => {
|
|||||||
...result
|
...result
|
||||||
]
|
]
|
||||||
|
|
||||||
const member = await TeamMemberModel.findOne({ project_id, user_id: data.user.id, pending: false });
|
const member = await TeamMemberModel.findOne({ project_id, $or: [{ user_id: user.id }, { email: user.user.email }], pending: false });
|
||||||
if (!member) return setResponseStatus(event, 400, 'Not a member');
|
if (!member) return setResponseStatus(event, 400, 'Not a member');
|
||||||
if (!member.permission) return setResponseStatus(event, 400, 'No permission');
|
if (!member.permission) return setResponseStatus(event, 400, 'No permission');
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export default defineEventHandler(async event => {
|
|||||||
const project = await ProjectModel.findOne({ _id: project_id });
|
const project = await ProjectModel.findOne({ _id: project_id });
|
||||||
if (!project) return;
|
if (!project) return;
|
||||||
|
|
||||||
const [hasAccess] = await hasAccessToProject(user.id, project_id, project)
|
const [hasAccess] = await hasAccessToProject(user.id, project_id, user.user.email, project)
|
||||||
if (!hasAccess) return;
|
if (!hasAccess) return;
|
||||||
|
|
||||||
const query = getQuery(event);
|
const query = getQuery(event);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export default defineEventHandler(async event => {
|
|||||||
|
|
||||||
const { name } = await readBody(event);
|
const { name } = await readBody(event);
|
||||||
|
|
||||||
if (name.trim()) return setResponseStatus(event, 400, 'name is required');
|
if (name.trim().length == 0) return setResponseStatus(event, 400, 'name is required');
|
||||||
if (name.trim().length < 2) return setResponseStatus(event, 400, 'name too short');
|
if (name.trim().length < 2) return setResponseStatus(event, 400, 'name too short');
|
||||||
if (name.trim().length > 32) return setResponseStatus(event, 400, 'name too long');
|
if (name.trim().length > 32) return setResponseStatus(event, 400, 'name too long');
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,13 @@ export default defineEventHandler(async event => {
|
|||||||
if (!userData?.logged) return [];
|
if (!userData?.logged) return [];
|
||||||
|
|
||||||
|
|
||||||
const members = await TeamMemberModel.find({ user_id: userData.id, pending: false });
|
const members = await TeamMemberModel.find({
|
||||||
|
$or: [
|
||||||
|
{ user_id: userData.id },
|
||||||
|
{ email: userData.user.email }
|
||||||
|
],
|
||||||
|
pending: false
|
||||||
|
});
|
||||||
|
|
||||||
const projects: TProject[] = [];
|
const projects: TProject[] = [];
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,12 @@ export default defineEventHandler(async event => {
|
|||||||
|
|
||||||
console.log({ project_id, user_id: data.user.id });
|
console.log({ project_id, user_id: data.user.id });
|
||||||
|
|
||||||
const member = await TeamMemberModel.findOne({ project_id, user_id: data.user.id });
|
const member = await TeamMemberModel.findOne({
|
||||||
|
project_id, $or: [
|
||||||
|
{ user_id: data.user.id },
|
||||||
|
{ email: data.user.user.email }
|
||||||
|
]
|
||||||
|
});
|
||||||
if (!member) return setResponseStatus(event, 400, 'member not found');
|
if (!member) return setResponseStatus(event, 400, 'member not found');
|
||||||
|
|
||||||
member.pending = false;
|
member.pending = false;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export default defineEventHandler(async event => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const link = `http://127.0.0.1:3000/accept_invite?project_id=${project_id.toString()}`;
|
const link = `https://dashboard.litlyx.com/accept_invite?project_id=${project_id.toString()}`;
|
||||||
|
|
||||||
if (!targetUser) {
|
if (!targetUser) {
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export default defineEventHandler(async event => {
|
|||||||
if (!user) return setResponseStatus(event, 400, 'Email not found');
|
if (!user) return setResponseStatus(event, 400, 'Email not found');
|
||||||
|
|
||||||
await TeamMemberModel.deleteOne({ project_id, user_id: user.id });
|
await TeamMemberModel.deleteOne({ project_id, user_id: user.id });
|
||||||
|
await TeamMemberModel.deleteOne({ project_id, email: email });
|
||||||
|
|
||||||
return { ok: true }
|
return { ok: true }
|
||||||
|
|
||||||
|
|||||||
@@ -42,8 +42,15 @@ export default defineEventHandler(async event => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
for (const member of members) {
|
for (const member of members) {
|
||||||
const userMember = await UserModel.findById(member.user_id);
|
|
||||||
if (!userMember) continue;
|
let userMember;
|
||||||
|
|
||||||
|
if (member.user_id) {
|
||||||
|
userMember = await UserModel.findById(member.user_id);
|
||||||
|
} else {
|
||||||
|
userMember = await UserModel.findOne({ email: member.email });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const permission: TPermission = {
|
const permission: TPermission = {
|
||||||
webAnalytics: member.permission?.webAnalytics || false,
|
webAnalytics: member.permission?.webAnalytics || false,
|
||||||
@@ -54,11 +61,11 @@ export default defineEventHandler(async event => {
|
|||||||
|
|
||||||
result.push({
|
result.push({
|
||||||
id: member.id,
|
id: member.id,
|
||||||
email: userMember.email,
|
email: userMember?.email || member.email || 'NO_EMAIL',
|
||||||
name: userMember.name,
|
name: userMember?.name || 'NO_NAME',
|
||||||
role: member.role,
|
role: member.role,
|
||||||
pending: member.pending,
|
pending: member.pending,
|
||||||
me: user.id === userMember.id,
|
me: user.id === (userMember?.id || member.user_id || 'NO_ID'),
|
||||||
permission
|
permission
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,18 @@ export default defineEventHandler(async event => {
|
|||||||
webAnalytics: true
|
webAnalytics: true
|
||||||
}
|
}
|
||||||
|
|
||||||
const member = await TeamMemberModel.findOne({ project_id, user_id: user.id });
|
const member = await TeamMemberModel.findOne({
|
||||||
|
project_id,
|
||||||
|
$or: [
|
||||||
|
{ user_id: user.id }, { email: user.user.email }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
if (!member) return {
|
if (!member) return {
|
||||||
ai: true,
|
ai: false,
|
||||||
domains: ['All domains'],
|
domains: [],
|
||||||
events: true,
|
events: false,
|
||||||
webAnalytics: true
|
webAnalytics: false
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
9
dashboard/server/api/shields/bots/options.ts
Normal file
9
dashboard/server/api/shields/bots/options.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { BotTrafficOptionModel } from "~/shared/schema/shields/BotTrafficOptionSchema";
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
const data = await getRequestData(event, [], ['OWNER']);
|
||||||
|
if (!data) return;
|
||||||
|
const result = await BotTrafficOptionModel.findOne({ project_id: data.project_id });
|
||||||
|
if (!result) return { block: false };
|
||||||
|
return { block: result.block }
|
||||||
|
});
|
||||||
14
dashboard/server/api/shields/bots/update_options.post.ts
Normal file
14
dashboard/server/api/shields/bots/update_options.post.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { BotTrafficOptionModel } from "~/shared/schema/shields/BotTrafficOptionSchema";
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
const data = await getRequestData(event, [], ['OWNER']);
|
||||||
|
if (!data) return;
|
||||||
|
const body = await readBody(event);
|
||||||
|
const { block } = body;
|
||||||
|
|
||||||
|
if (block != true && block != false)
|
||||||
|
return setResponseStatus(event, 400, 'block is required and must be true or false');
|
||||||
|
|
||||||
|
const result = await BotTrafficOptionModel.updateOne({ project_id: data.project_id }, { block }, { upsert: true });
|
||||||
|
return { ok: result.acknowledged };
|
||||||
|
});
|
||||||
11
dashboard/server/api/shields/countries/add.post.ts
Normal file
11
dashboard/server/api/shields/countries/add.post.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { CountryBlacklistModel } from "~/shared/schema/shields/CountryBlacklistSchema";
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
const data = await getRequestData(event, [], ['OWNER']);
|
||||||
|
if (!data) return;
|
||||||
|
const body = await readBody(event);
|
||||||
|
const { country, description } = body;
|
||||||
|
if (country.trim().length == 0) return setResponseStatus(event, 400, 'Country is required');
|
||||||
|
const result = await CountryBlacklistModel.updateOne({ project_id: data.project_id, country }, { description }, { upsert: true });
|
||||||
|
return { ok: result.acknowledged };
|
||||||
|
});
|
||||||
14
dashboard/server/api/shields/countries/delete.delete.ts
Normal file
14
dashboard/server/api/shields/countries/delete.delete.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { CountryBlacklistModel } from "~/shared/schema/shields/CountryBlacklistSchema";
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
|
const data = await getRequestData(event, [], ['OWNER']);
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
const body = await readBody(event);
|
||||||
|
const { country } = body;
|
||||||
|
|
||||||
|
const removal = await CountryBlacklistModel.deleteOne({ project_id: data.project_id, country });
|
||||||
|
|
||||||
|
return { ok: removal.deletedCount == 1 };
|
||||||
|
});
|
||||||
8
dashboard/server/api/shields/countries/list.ts
Normal file
8
dashboard/server/api/shields/countries/list.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { CountryBlacklistModel } from "~/shared/schema/shields/CountryBlacklistSchema";
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
const data = await getRequestData(event, [], ['OWNER']);
|
||||||
|
if (!data) return;
|
||||||
|
const blacklist = await CountryBlacklistModel.find({ project_id: data.project_id });
|
||||||
|
return blacklist.map(e => e.toJSON());
|
||||||
|
});
|
||||||
21
dashboard/server/api/shields/domains/add.post.ts
Normal file
21
dashboard/server/api/shields/domains/add.post.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { DomainWhitelistModel } from "~/shared/schema/shields/DomainWhitelistSchema";
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
|
const data = await getRequestData(event, [], ['OWNER']);
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
const body = await readBody(event);
|
||||||
|
const { domain } = body;
|
||||||
|
|
||||||
|
if (domain.trim().length == 0) return setResponseStatus(event, 400, 'Domain is required');
|
||||||
|
|
||||||
|
const whitelist = await DomainWhitelistModel.updateOne({
|
||||||
|
project_id: data.project_id
|
||||||
|
},
|
||||||
|
{ $push: { domains: domain } },
|
||||||
|
{ upsert: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
return { ok: true };
|
||||||
|
});
|
||||||
18
dashboard/server/api/shields/domains/delete.delete.ts
Normal file
18
dashboard/server/api/shields/domains/delete.delete.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { DomainWhitelistModel } from "~/shared/schema/shields/DomainWhitelistSchema";
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
|
const data = await getRequestData(event, [], ['OWNER']);
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
const body = await readBody(event);
|
||||||
|
const { domain } = body;
|
||||||
|
|
||||||
|
const removal = await DomainWhitelistModel.updateOne({
|
||||||
|
project_id: data.project_id
|
||||||
|
},
|
||||||
|
{ $pull: { domains: domain } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return { ok: removal.modifiedCount == 1 };
|
||||||
|
});
|
||||||
10
dashboard/server/api/shields/domains/list.ts
Normal file
10
dashboard/server/api/shields/domains/list.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { DomainWhitelistModel } from "~/shared/schema/shields/DomainWhitelistSchema";
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
const data = await getRequestData(event, [], ['OWNER']);
|
||||||
|
if (!data) return;
|
||||||
|
const whitelist = await DomainWhitelistModel.findOne({ project_id: data.project_id });
|
||||||
|
if (!whitelist) return [];
|
||||||
|
const domains = whitelist.domains;
|
||||||
|
return domains;
|
||||||
|
});
|
||||||
11
dashboard/server/api/shields/ip/add.post.ts
Normal file
11
dashboard/server/api/shields/ip/add.post.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { AddressBlacklistModel } from "~/shared/schema/shields/AddressBlacklistSchema";
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
const data = await getRequestData(event, [], ['OWNER']);
|
||||||
|
if (!data) return;
|
||||||
|
const body = await readBody(event);
|
||||||
|
const { address, description } = body;
|
||||||
|
if (address.trim().length == 0) return setResponseStatus(event, 400, 'Address is required');
|
||||||
|
const result = await AddressBlacklistModel.updateOne({ project_id: data.project_id, address }, { description }, { upsert: true });
|
||||||
|
return { ok: result.acknowledged };
|
||||||
|
});
|
||||||
14
dashboard/server/api/shields/ip/delete.delete.ts
Normal file
14
dashboard/server/api/shields/ip/delete.delete.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { AddressBlacklistModel } from "~/shared/schema/shields/AddressBlacklistSchema";
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
|
const data = await getRequestData(event, [], ['OWNER']);
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
const body = await readBody(event);
|
||||||
|
const { address } = body;
|
||||||
|
|
||||||
|
const removal = await AddressBlacklistModel.deleteOne({ project_id: data.project_id, address });
|
||||||
|
|
||||||
|
return { ok: removal.deletedCount == 1 };
|
||||||
|
});
|
||||||
8
dashboard/server/api/shields/ip/list.ts
Normal file
8
dashboard/server/api/shields/ip/list.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { AddressBlacklistModel } from "~/shared/schema/shields/AddressBlacklistSchema";
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
const data = await getRequestData(event, [], ['OWNER']);
|
||||||
|
if (!data) return;
|
||||||
|
const blacklist = await AddressBlacklistModel.find({ project_id: data.project_id });
|
||||||
|
return blacklist.map(e => e.toJSON());
|
||||||
|
});
|
||||||
@@ -24,6 +24,28 @@ export default defineEventHandler(async event => {
|
|||||||
customProjection: {
|
customProjection: {
|
||||||
count: { $divide: ["$duration", "$count"] }
|
count: { $divide: ["$duration", "$count"] }
|
||||||
},
|
},
|
||||||
|
customQueries: [
|
||||||
|
{
|
||||||
|
index: 1,
|
||||||
|
query: {
|
||||||
|
"$lookup": {
|
||||||
|
"from": "visits",
|
||||||
|
"localField": "session",
|
||||||
|
"foreignField": "session",
|
||||||
|
"as": "visits",
|
||||||
|
"pipeline": [{ "$limit": 1 }]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 2,
|
||||||
|
query: {
|
||||||
|
"$match": {
|
||||||
|
"visits.0": { "$exists": true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
const timelineFilledMerged = fillAndMergeTimelineAggregationV2(timelineData, slice, from, to);
|
const timelineFilledMerged = fillAndMergeTimelineAggregationV2(timelineData, slice, from, to);
|
||||||
return timelineFilledMerged;
|
return timelineFilledMerged;
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ import { LimitNotifyModel } from "@schema/broker/LimitNotifySchema";
|
|||||||
import { SessionModel } from "@schema/metrics/SessionSchema";
|
import { SessionModel } from "@schema/metrics/SessionSchema";
|
||||||
import StripeService from "~/server/services/StripeService";
|
import StripeService from "~/server/services/StripeService";
|
||||||
import { UserModel } from "@schema/UserSchema";
|
import { UserModel } from "@schema/UserSchema";
|
||||||
|
import { AddressBlacklistModel } from "~/shared/schema/shields/AddressBlacklistSchema";
|
||||||
|
import { DomainWhitelistModel } from "~/shared/schema/shields/DomainWhitelistSchema";
|
||||||
|
import { CountryBlacklistModel } from "~/shared/schema/shields/CountryBlacklistSchema";
|
||||||
|
import { BotTrafficOptionModel } from "~/shared/schema/shields/BotTrafficOptionSchema";
|
||||||
|
import { TeamMemberModel } from "~/shared/schema/TeamMemberSchema";
|
||||||
|
import { PasswordModel } from "~/shared/schema/PasswordSchema";
|
||||||
|
|
||||||
export default defineEventHandler(async event => {
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
@@ -19,6 +25,11 @@ export default defineEventHandler(async event => {
|
|||||||
const premiumProjects = projects.filter(e => { return e.premium && e.premium_type != 0 }).length;
|
const premiumProjects = projects.filter(e => { return e.premium && e.premium_type != 0 }).length;
|
||||||
if (premiumProjects > 0) return setResponseStatus(event, 400, 'Cannot delete an account with a premium project');
|
if (premiumProjects > 0) return setResponseStatus(event, 400, 'Cannot delete an account with a premium project');
|
||||||
|
|
||||||
|
const membersDeletation = await TeamMemberModel.deleteMany({ user_id: userData.id });
|
||||||
|
const membersEmailDeletation = await TeamMemberModel.deleteMany({ email: userData.user.email });
|
||||||
|
|
||||||
|
const passwordDeletation = await PasswordModel.deleteMany({ user_id: userData.id });
|
||||||
|
|
||||||
for (const project of projects) {
|
for (const project of projects) {
|
||||||
const project_id = project._id;
|
const project_id = project._id;
|
||||||
await StripeService.deleteCustomer(project.customer_id);
|
await StripeService.deleteCustomer(project.customer_id);
|
||||||
@@ -28,9 +39,16 @@ export default defineEventHandler(async event => {
|
|||||||
const limitdeletation = await ProjectLimitModel.deleteMany({ project_id });
|
const limitdeletation = await ProjectLimitModel.deleteMany({ project_id });
|
||||||
const sessionsDeletation = await SessionModel.deleteMany({ project_id });
|
const sessionsDeletation = await SessionModel.deleteMany({ project_id });
|
||||||
const notifiesDeletation = await LimitNotifyModel.deleteMany({ project_id });
|
const notifiesDeletation = await LimitNotifyModel.deleteMany({ project_id });
|
||||||
const aiChatsDeletation = await AiChatModel.deleteMany({ project_id });
|
const aiChatsDeletation = await AiChatModel.deleteMany({ project_id });
|
||||||
|
|
||||||
const userDeletation = await UserModel.deleteOne({ _id: userData.id });
|
//Shields
|
||||||
|
const addressBlacklistDeletation = await AddressBlacklistModel.deleteMany({ project_id });
|
||||||
|
const botTrafficOptionsDeletation = await BotTrafficOptionModel.deleteMany({ project_id });
|
||||||
|
const countryBlacklistDeletation = await CountryBlacklistModel.deleteMany({ project_id });
|
||||||
|
const domainWhitelistDeletation = await DomainWhitelistModel.deleteMany({ project_id });
|
||||||
|
|
||||||
|
|
||||||
|
const userDeletation = await UserModel.deleteOne({ _id: userData.id });
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ export type AdvancedTimelineAggregationOptions = TimelineAggregationOptions & {
|
|||||||
customGroup?: Record<string, any>,
|
customGroup?: Record<string, any>,
|
||||||
customProjection?: Record<string, any>,
|
customProjection?: Record<string, any>,
|
||||||
customIdGroup?: Record<string, any>,
|
customIdGroup?: Record<string, any>,
|
||||||
customAfterMatch?: Record<string, any>
|
customAfterMatch?: Record<string, any>,
|
||||||
|
customQueries?: { index: number, query: Record<string, any> }[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function executeAdvancedTimelineAggregation<T = {}>(options: AdvancedTimelineAggregationOptions) {
|
export async function executeAdvancedTimelineAggregation<T = {}>(options: AdvancedTimelineAggregationOptions) {
|
||||||
@@ -29,6 +30,7 @@ export async function executeAdvancedTimelineAggregation<T = {}>(options: Advanc
|
|||||||
options.customGroup = options.customGroup || {};
|
options.customGroup = options.customGroup || {};
|
||||||
options.customProjection = options.customProjection || {};
|
options.customProjection = options.customProjection || {};
|
||||||
options.customIdGroup = options.customIdGroup || {};
|
options.customIdGroup = options.customIdGroup || {};
|
||||||
|
options.customQueries = options.customQueries || [];
|
||||||
|
|
||||||
const { dateFromParts, granularity } = DateService.getGranularityData(options.slice, '$tmpDate');
|
const { dateFromParts, granularity } = DateService.getGranularityData(options.slice, '$tmpDate');
|
||||||
if (!dateFromParts) throw Error('Slice is probably not correct');
|
if (!dateFromParts) throw Error('Slice is probably not correct');
|
||||||
@@ -103,6 +105,9 @@ export async function executeAdvancedTimelineAggregation<T = {}>(options: Advanc
|
|||||||
}
|
}
|
||||||
] as any[];
|
] as any[];
|
||||||
|
|
||||||
|
for (const customQuery of options.customQueries) {
|
||||||
|
aggregation.splice(customQuery.index, 0, customQuery.query);
|
||||||
|
}
|
||||||
|
|
||||||
if (options.customAfterMatch) aggregation.splice(1, 0, options.customAfterMatch);
|
if (options.customAfterMatch) aggregation.splice(1, 0, options.customAfterMatch);
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export type GetRequestDataOptions = {
|
|||||||
export type RequestDataScope = 'SCHEMA' | 'ANON' | 'SLICE' | 'RANGE' | 'OFFSET' | 'DOMAIN';
|
export type RequestDataScope = 'SCHEMA' | 'ANON' | 'SLICE' | 'RANGE' | 'OFFSET' | 'DOMAIN';
|
||||||
export type RequestDataPermissions = 'WEB' | 'EVENTS' | 'AI' | 'OWNER';
|
export type RequestDataPermissions = 'WEB' | 'EVENTS' | 'AI' | 'OWNER';
|
||||||
|
|
||||||
async function getAccessPermission(user_id: string, project: TProject): Promise<TPermission> {
|
async function getAccessPermission(user_id: string, project: TProject, email: string): Promise<TPermission> {
|
||||||
if (!project) return { ai: false, domains: [], events: false, webAnalytics: false }
|
if (!project) return { ai: false, domains: [], events: false, webAnalytics: false }
|
||||||
|
|
||||||
//TODO: Create table with admins
|
//TODO: Create table with admins
|
||||||
@@ -44,17 +44,17 @@ async function getAccessPermission(user_id: string, project: TProject): Promise<
|
|||||||
const owner = project.owner.toString();
|
const owner = project.owner.toString();
|
||||||
const project_id = project._id;
|
const project_id = project._id;
|
||||||
if (owner === user_id) return { ai: true, domains: ['All domains'], events: true, webAnalytics: true }
|
if (owner === user_id) return { ai: true, domains: ['All domains'], events: true, webAnalytics: true }
|
||||||
const member = await TeamMemberModel.findOne({ project_id, user_id }, { permission: 1 });
|
const member = await TeamMemberModel.findOne({ project_id, $or: [{ user_id }, { email }] }, { permission: 1 });
|
||||||
if (!member) return { ai: false, domains: [], events: false, webAnalytics: false }
|
if (!member) return { ai: false, domains: [], events: false, webAnalytics: false }
|
||||||
return { ai: false, domains: [], events: false, webAnalytics: false, ...member.permission as any }
|
return { ai: false, domains: [], events: false, webAnalytics: false, ...member.permission as any }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function hasAccessToProject(user_id: string, project: TProject) {
|
async function hasAccessToProject(user_id: string, project: TProject, email: string) {
|
||||||
if (!project) return [false, 'NONE'];
|
if (!project) return [false, 'NONE'];
|
||||||
const owner = project.owner.toString();
|
const owner = project.owner.toString();
|
||||||
const project_id = project._id;
|
const project_id = project._id;
|
||||||
if (owner === user_id) return [true, 'OWNER'];
|
if (owner === user_id) return [true, 'OWNER'];
|
||||||
const isGuest = await TeamMemberModel.exists({ project_id, user_id });
|
const isGuest = await TeamMemberModel.exists({ project_id, $or: [{ user_id }, { email }] });
|
||||||
if (isGuest) return [true, 'GUEST'];
|
if (isGuest) return [true, 'GUEST'];
|
||||||
|
|
||||||
//TODO: Create table with admins
|
//TODO: Create table with admins
|
||||||
@@ -128,14 +128,14 @@ export async function getRequestData(event: H3Event<EventHandlerRequest>, requir
|
|||||||
|
|
||||||
if (user.id != project.owner.toString()) {
|
if (user.id != project.owner.toString()) {
|
||||||
if (required_permissions.includes('OWNER')) return setResponseStatus(event, 403, 'ADMIN permission required');
|
if (required_permissions.includes('OWNER')) return setResponseStatus(event, 403, 'ADMIN permission required');
|
||||||
const hasAccess = await TeamMemberModel.findOne({ project_id, user_id: user.id });
|
const hasAccess = await TeamMemberModel.findOne({ project_id, $or: [{ user_id: user.id }, { email: user.user.email }] });
|
||||||
if (!hasAccess) return setResponseStatus(event, 403, 'No permissions');
|
if (!hasAccess) return setResponseStatus(event, 403, 'No permissions');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (required_permissions.length > 0 || requireDomain) {
|
if (required_permissions.length > 0 || requireDomain) {
|
||||||
|
|
||||||
const permission = await getAccessPermission(user.id, project);
|
const permission = await getAccessPermission(user.id, project, user.user.email);
|
||||||
|
|
||||||
if (required_permissions.includes('WEB') && permission.webAnalytics === false) {
|
if (required_permissions.includes('WEB') && permission.webAnalytics === false) {
|
||||||
return setResponseStatus(event, 403, 'WEB permission required');
|
return setResponseStatus(event, 403, 'WEB permission required');
|
||||||
@@ -220,7 +220,7 @@ export async function getRequestDataOld(event: H3Event<EventHandlerRequest>, opt
|
|||||||
|
|
||||||
|
|
||||||
if (pid !== "6643cd08a1854e3b81722ab5") {
|
if (pid !== "6643cd08a1854e3b81722ab5") {
|
||||||
const [hasAccess, role] = await hasAccessToProject(user.id, project);
|
const [hasAccess, role] = await hasAccessToProject(user.id, project, user.user.email);
|
||||||
if (!hasAccess) return setResponseStatus(event, 400, 'no access to project');
|
if (!hasAccess) return setResponseStatus(event, 400, 'no access to project');
|
||||||
if (role === 'GUEST' && !allowGuests) return setResponseStatus(event, 403, 'only owner can access this');
|
if (role === 'GUEST' && !allowGuests) return setResponseStatus(event, 403, 'only owner can access this');
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { ProjectModel, TProject } from "@schema/project/ProjectSchema";
|
import { ProjectModel, TProject } from "@schema/project/ProjectSchema";
|
||||||
import { TeamMemberModel } from "@schema/TeamMemberSchema";
|
import { TeamMemberModel } from "@schema/TeamMemberSchema";
|
||||||
|
|
||||||
export async function hasAccessToProject(user_id: string, project_id: string, project?: TProject) {
|
export async function hasAccessToProject(user_id: string, project_id: string, email: string, project?: TProject) {
|
||||||
const targetProject = project ?? await ProjectModel.findById(project_id, { owner: true });
|
const targetProject = project ?? await ProjectModel.findById(project_id, { owner: true });
|
||||||
if (!targetProject) return [false, 'NONE'];
|
if (!targetProject) return [false, 'NONE'];
|
||||||
if (targetProject.owner.toString() === user_id) return [true, 'OWNER'];
|
if (targetProject.owner.toString() === user_id) return [true, 'OWNER'];
|
||||||
const isGuest = await TeamMemberModel.exists({ project_id, user_id });
|
const isGuest = await TeamMemberModel.exists({ project_id, $or: [{ user_id }, { email }] });
|
||||||
if (isGuest) return [true, 'GUEST'];
|
if (isGuest) return [true, 'GUEST'];
|
||||||
return [false, 'NONE'];
|
return [false, 'NONE'];
|
||||||
}
|
}
|
||||||
@@ -34,7 +34,7 @@ export class EmailService {
|
|||||||
static async sendInviteEmailNoAccount(target: string, projectName: string, link: string) {
|
static async sendInviteEmailNoAccount(target: string, projectName: string, link: string) {
|
||||||
try {
|
try {
|
||||||
const sendSmtpEmail = new SendSmtpEmail();
|
const sendSmtpEmail = new SendSmtpEmail();
|
||||||
sendSmtpEmail.subject = "⚡ Invite - No account";
|
sendSmtpEmail.subject = "⚡ Invite on a Litlyx project";
|
||||||
sendSmtpEmail.sender = { "name": "Litlyx", "email": "help@litlyx.com" };
|
sendSmtpEmail.sender = { "name": "Litlyx", "email": "help@litlyx.com" };
|
||||||
sendSmtpEmail.to = [{ "email": target }];
|
sendSmtpEmail.to = [{ "email": target }];
|
||||||
|
|
||||||
@@ -55,6 +55,7 @@ export class EmailService {
|
|||||||
try {
|
try {
|
||||||
await this.apiContacts.createContact({ email });
|
await this.apiContacts.createContact({ email });
|
||||||
await this.apiContacts.addContactToList(12, { emails: [email] })
|
await this.apiContacts.addContactToList(12, { emails: [email] })
|
||||||
|
return true;
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
console.error('ERROR ADDING CONTACT', ex);
|
console.error('ERROR ADDING CONTACT', ex);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ app.post('/send/invite/noaccount', express.json(), async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/brevolist/add', express.json(), async (req, res) => {
|
app.post('/send/brevolist/add', express.json(), async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { email } = req.body;
|
const { email } = req.body;
|
||||||
const ok = await EmailService.createContact(email);
|
const ok = await EmailService.createContact(email);
|
||||||
|
|||||||
@@ -1,21 +1,102 @@
|
|||||||
export const PROJECT_INVITE_EMAIL = `<!DOCTYPE html>
|
export const PROJECT_INVITE_EMAIL = `<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Thank You for Upgrading Your Litlyx Plan!</title>
|
<title>Email Confirmation</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
text-align: center;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-btn {
|
||||||
|
background: black;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 12px 20px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-btn:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
margin-top: 20px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #facc15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
margin-top: 15px;
|
||||||
|
font-size: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
|
||||||
|
|
||||||
<!-- Email Content -->
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<img class="icon"
|
||||||
|
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAP0SURBVHgB7Zy9ixNBGMZfPwor8xecf8EFrE3Awi6CfQ7sFay9wvoOzlpBa8XrD7QPxM5COK0Pzj5gaec+ZF7yZt3NzmY/ZmZ9fjDsxsyeME/ej5l5Z0UIIYQQQgghhBBCCCGEEEIIIYQQMmQusnZPSDSsXDsWEgUr075LwtZyU9JnlPsMMeDCHgsJAgSw1mGt5Zkkxi1Jn3HW5u7+LGvfsjZ1nx+561chvTGTjUWomzqWxC0lZTDYOvBT8+9Hsi3KkZBesNaQz66em++uZO3eSMe8lc2gF2EFSzolTgWkuDrYZZzKRpQLIZ2yEL+BVuEY5DtGB/m0oh9c1ZXpH2U8SX2mbgf1uqIvvn9qPr+RCEldkANz/8Oj/zJr79w9xORiZMvYYD3yfAb91HXhGlXWlbqFHLrrZdZ+ez6Dfq/cPcR5KaQ19Jf+Uepjsy5OGFsAg9hkWWQqEc5NUnZZE3N/KfVZugYgThSxJGVBZu6KdHYfQcBrcz8X0gh1N/vED4vGEsQj30ytM1K1kJm5/yLN0OchRnArGYIgS2nGuWxS5pmQvdC984W0g02Bg7qtFC0Ev2LNiM6lHezfCeq2UhVEaRo/iv7OVIg3tuSnLXel2GwrGLclLexk8L3UB4KOZDtOXLuGUqGp+w6rAPvObRqRmiC6XI4B/OTRHwM8cQ2DvCtg28VJ9KcgFSDY+gbzuWt14oEVK9hi4w1JA63XxRXWcb+kHwQ4kX8HFL/+z1n76Z631gAhDl0bu/8D1vFQSCnYbq1a2bWbVdpQIlQ3a+JOYgW2rqpomXwk2xM7Xd9i/VUH2LrdoiI3iLGQ7epEHkPoCLgOW7ZT5KqsGKxK7BAUslkxiny6jRkQJvjS+RBBAM7HgyIxbMX7oMQImfaqC7or65Qzv42K1PSFrNNVC/qoCEhhn0h1kRzxYFXS4KpgFWW/ep8UmOxBXgS4Kpzn2OV+8ucJSYvoJlOdPXE7JxmkdYTcD9Hli4Maz+jmke/iYnKEFESLo33nDmPTt62NqegIKYgubyNm+Kw3HZp7CtIB9vjAxKO/XcFtWmlCSrDHAqrQCWPbW7dREbrIQQ/P+LgtjR++xw6SJAZBdICjPGLWN6EFgRhqJbAAno6NALgrnSTuettC29WKZAf28EzZvsYHiaBuqmtiqVxEGnvm7m1Bg+WXu46EG1G9YVdyYSk285qb73i4pkesKLpBpdWG0Z0J/F/Iv4AM1gKriOq0UxfE+oo/1NkiZmD9Sq0D1SQaO+5k7Y/w1X29AwHy1mI3tVjYEAgIgw2p/FtHT2RgpFLba8HEEdnXA1kLhUKIIJXqhBBCCCGEEEIIIYQQQgghhBBCCOmPv8OsFHqaHzwyAAAAAElFTkSuQmCC"
|
||||||
|
alt="litlyx-logo">
|
||||||
|
|
||||||
<p>Invited by <strong>[Project Name]</strong> on <strong>Litlyx</strong></p>
|
<h2>You're invited to the Litlyx project [Project Name]!</h2>
|
||||||
|
<p>Join now by clicking the button below.</p>
|
||||||
<p>Best regards,</p>
|
<a href="[Link]" class="confirm-btn">
|
||||||
|
Join the Project
|
||||||
|
</a>
|
||||||
|
<p class="footer">See you there,<br> The <span class="brand">Litlyx</span> Team</p>
|
||||||
|
<p class="footer">If you need any help feel free to reach out at help@litlyx.com.</p>
|
||||||
|
<p class="footer">Litlyx Srl<br>Viale Tirreno 187<br>Rome, 00141</p>
|
||||||
|
|
||||||
<p>Antonio,</p>
|
</div>
|
||||||
<p>CEO @ Litlyx</p>
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function confirmEmail() {
|
||||||
|
alert("Email confirmed! Thank you for joining the waitlist.");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>`
|
</html>`
|
||||||
@@ -1,21 +1,102 @@
|
|||||||
export const PROJECT_INVITE_EMAIL_NO_ACCOUNT = `<!DOCTYPE html>
|
export const PROJECT_INVITE_EMAIL_NO_ACCOUNT = `<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Thank You for Upgrading Your Litlyx Plan!</title>
|
<title>Email Confirmation</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
text-align: center;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-btn {
|
||||||
|
background: black;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 12px 20px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-btn:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
margin-top: 20px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #facc15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
margin-top: 15px;
|
||||||
|
font-size: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
|
||||||
|
|
||||||
<!-- Email Content -->
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<img class="icon"
|
||||||
|
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAP0SURBVHgB7Zy9ixNBGMZfPwor8xecf8EFrE3Awi6CfQ7sFay9wvoOzlpBa8XrD7QPxM5COK0Pzj5gaec+ZF7yZt3NzmY/ZmZ9fjDsxsyeME/ej5l5Z0UIIYQQQgghhBBCCCGEEEIIIYQQMmQusnZPSDSsXDsWEgUr075LwtZyU9JnlPsMMeDCHgsJAgSw1mGt5Zkkxi1Jn3HW5u7+LGvfsjZ1nx+561chvTGTjUWomzqWxC0lZTDYOvBT8+9Hsi3KkZBesNaQz66em++uZO3eSMe8lc2gF2EFSzolTgWkuDrYZZzKRpQLIZ2yEL+BVuEY5DtGB/m0oh9c1ZXpH2U8SX2mbgf1uqIvvn9qPr+RCEldkANz/8Oj/zJr79w9xORiZMvYYD3yfAb91HXhGlXWlbqFHLrrZdZ+ez6Dfq/cPcR5KaQ19Jf+Uepjsy5OGFsAg9hkWWQqEc5NUnZZE3N/KfVZugYgThSxJGVBZu6KdHYfQcBrcz8X0gh1N/vED4vGEsQj30ytM1K1kJm5/yLN0OchRnArGYIgS2nGuWxS5pmQvdC984W0g02Bg7qtFC0Ev2LNiM6lHezfCeq2UhVEaRo/iv7OVIg3tuSnLXel2GwrGLclLexk8L3UB4KOZDtOXLuGUqGp+w6rAPvObRqRmiC6XI4B/OTRHwM8cQ2DvCtg28VJ9KcgFSDY+gbzuWt14oEVK9hi4w1JA63XxRXWcb+kHwQ4kX8HFL/+z1n76Z631gAhDl0bu/8D1vFQSCnYbq1a2bWbVdpQIlQ3a+JOYgW2rqpomXwk2xM7Xd9i/VUH2LrdoiI3iLGQ7epEHkPoCLgOW7ZT5KqsGKxK7BAUslkxiny6jRkQJvjS+RBBAM7HgyIxbMX7oMQImfaqC7or65Qzv42K1PSFrNNVC/qoCEhhn0h1kRzxYFXS4KpgFWW/ep8UmOxBXgS4Kpzn2OV+8ucJSYvoJlOdPXE7JxmkdYTcD9Hli4Maz+jmke/iYnKEFESLo33nDmPTt62NqegIKYgubyNm+Kw3HZp7CtIB9vjAxKO/XcFtWmlCSrDHAqrQCWPbW7dREbrIQQ/P+LgtjR++xw6SJAZBdICjPGLWN6EFgRhqJbAAno6NALgrnSTuettC29WKZAf28EzZvsYHiaBuqmtiqVxEGnvm7m1Bg+WXu46EG1G9YVdyYSk285qb73i4pkesKLpBpdWG0Z0J/F/Iv4AM1gKriOq0UxfE+oo/1NkiZmD9Sq0D1SQaO+5k7Y/w1X29AwHy1mI3tVjYEAgIgw2p/FtHT2RgpFLba8HEEdnXA1kLhUKIIJXqhBBCCCGEEEIIIYQQQgghhBBCCOmPv8OsFHqaHzwyAAAAAElFTkSuQmCC"
|
||||||
|
alt="litlyx-logo">
|
||||||
|
|
||||||
<p>Invited by <strong>[Project Name]</strong> on <strong>Litlyx</strong> NO_ACCOUNT</p>
|
<h2>You're invited to the Litlyx project [Project Name]!</h2>
|
||||||
|
<p>Join now by clicking the button below.</p>
|
||||||
<p>Best regards,</p>
|
<a href="[Link]" class="confirm-btn">
|
||||||
|
Join the Project
|
||||||
|
</a>
|
||||||
|
<p class="footer">See you there,<br> The <span class="brand">Litlyx</span> Team</p>
|
||||||
|
<p class="footer">If you need any help feel free to reach out at help@litlyx.com.</p>
|
||||||
|
<p class="footer">Litlyx Srl<br>Viale Tirreno 187<br>Rome, 00141</p>
|
||||||
|
|
||||||
<p>Antonio,</p>
|
</div>
|
||||||
<p>CEO @ Litlyx</p>
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function confirmEmail() {
|
||||||
|
alert("Email confirmed! Thank you for joining the waitlist.");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>`
|
</html>`
|
||||||
@@ -14,6 +14,9 @@
|
|||||||
"consumer:shared": "ts-node scripts/consumer/shared.ts",
|
"consumer:shared": "ts-node scripts/consumer/shared.ts",
|
||||||
"consumer:deploy": "ts-node scripts/consumer/deploy.ts",
|
"consumer:deploy": "ts-node scripts/consumer/deploy.ts",
|
||||||
|
|
||||||
|
"payments:shared": "ts-node scripts/payments/shared.ts",
|
||||||
|
"payments:deploy": "ts-node scripts/payments/deploy.ts",
|
||||||
|
|
||||||
"email:deploy": "ts-node scripts/email/deploy.ts"
|
"email:deploy": "ts-node scripts/email/deploy.ts"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
|
|||||||
9
payments/.gitignore
vendored
Normal file
9
payments/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
node_modules
|
||||||
|
static
|
||||||
|
ecosystem.config.cjs
|
||||||
|
ecosystem.config.js
|
||||||
|
dist
|
||||||
|
start_dev.js
|
||||||
|
package-lock.json
|
||||||
|
build_all.bat
|
||||||
|
src/shared
|
||||||
28
payments/package.json
Normal file
28
payments/package.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "payments",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "node scripts/start_dev.js",
|
||||||
|
"dev_prod": "node scripts/start_dev_prod.js",
|
||||||
|
"compile": "tsc",
|
||||||
|
"build": "npm run compile",
|
||||||
|
"workspace:shared": "ts-node ../scripts/payments/shared.ts",
|
||||||
|
"workspace:deploy": "ts-node ../scripts/payments/deploy.ts"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "Emily",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/express": "^5.0.1",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"express": "^4.21.2",
|
||||||
|
"mongoose": "^8.13.0",
|
||||||
|
"stripe": "^17.7.0",
|
||||||
|
"zod": "^3.24.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/cors": "^2.8.17"
|
||||||
|
}
|
||||||
|
}
|
||||||
901
payments/pnpm-lock.yaml
generated
Normal file
901
payments/pnpm-lock.yaml
generated
Normal file
@@ -0,0 +1,901 @@
|
|||||||
|
lockfileVersion: '9.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
importers:
|
||||||
|
|
||||||
|
.:
|
||||||
|
dependencies:
|
||||||
|
'@types/express':
|
||||||
|
specifier: ^5.0.1
|
||||||
|
version: 5.0.1
|
||||||
|
cors:
|
||||||
|
specifier: ^2.8.5
|
||||||
|
version: 2.8.5
|
||||||
|
express:
|
||||||
|
specifier: ^4.21.2
|
||||||
|
version: 4.21.2
|
||||||
|
mongoose:
|
||||||
|
specifier: ^8.13.0
|
||||||
|
version: 8.13.0
|
||||||
|
stripe:
|
||||||
|
specifier: ^17.7.0
|
||||||
|
version: 17.7.0
|
||||||
|
zod:
|
||||||
|
specifier: ^3.24.2
|
||||||
|
version: 3.24.2
|
||||||
|
devDependencies:
|
||||||
|
'@types/cors':
|
||||||
|
specifier: ^2.8.17
|
||||||
|
version: 2.8.17
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
'@mongodb-js/saslprep@1.2.0':
|
||||||
|
resolution: {integrity: sha512-+ywrb0AqkfaYuhHs6LxKWgqbh3I72EpEgESCw37o+9qPx9WTCkgDm2B+eMrwehGtHBWHFU4GXvnSCNiFhhausg==}
|
||||||
|
|
||||||
|
'@types/body-parser@1.19.5':
|
||||||
|
resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
|
||||||
|
|
||||||
|
'@types/connect@3.4.38':
|
||||||
|
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
|
||||||
|
|
||||||
|
'@types/cors@2.8.17':
|
||||||
|
resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==}
|
||||||
|
|
||||||
|
'@types/express-serve-static-core@5.0.6':
|
||||||
|
resolution: {integrity: sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==}
|
||||||
|
|
||||||
|
'@types/express@5.0.1':
|
||||||
|
resolution: {integrity: sha512-UZUw8vjpWFXuDnjFTh7/5c2TWDlQqeXHi6hcN7F2XSVT5P+WmUnnbFS3KA6Jnc6IsEqI2qCVu2bK0R0J4A8ZQQ==}
|
||||||
|
|
||||||
|
'@types/http-errors@2.0.4':
|
||||||
|
resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==}
|
||||||
|
|
||||||
|
'@types/mime@1.3.5':
|
||||||
|
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
|
||||||
|
|
||||||
|
'@types/node@22.13.13':
|
||||||
|
resolution: {integrity: sha512-ClsL5nMwKaBRwPcCvH8E7+nU4GxHVx1axNvMZTFHMEfNI7oahimt26P5zjVCRrjiIWj6YFXfE1v3dEp94wLcGQ==}
|
||||||
|
|
||||||
|
'@types/qs@6.9.18':
|
||||||
|
resolution: {integrity: sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==}
|
||||||
|
|
||||||
|
'@types/range-parser@1.2.7':
|
||||||
|
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
|
||||||
|
|
||||||
|
'@types/send@0.17.4':
|
||||||
|
resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==}
|
||||||
|
|
||||||
|
'@types/serve-static@1.15.7':
|
||||||
|
resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==}
|
||||||
|
|
||||||
|
'@types/webidl-conversions@7.0.3':
|
||||||
|
resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==}
|
||||||
|
|
||||||
|
'@types/whatwg-url@11.0.5':
|
||||||
|
resolution: {integrity: sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==}
|
||||||
|
|
||||||
|
accepts@1.3.8:
|
||||||
|
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
array-flatten@1.1.1:
|
||||||
|
resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
|
||||||
|
|
||||||
|
body-parser@1.20.3:
|
||||||
|
resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==}
|
||||||
|
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
||||||
|
|
||||||
|
bson@6.10.3:
|
||||||
|
resolution: {integrity: sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ==}
|
||||||
|
engines: {node: '>=16.20.1'}
|
||||||
|
|
||||||
|
bytes@3.1.2:
|
||||||
|
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
call-bind-apply-helpers@1.0.2:
|
||||||
|
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
call-bound@1.0.4:
|
||||||
|
resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
content-disposition@0.5.4:
|
||||||
|
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
content-type@1.0.5:
|
||||||
|
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
cookie-signature@1.0.6:
|
||||||
|
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
|
||||||
|
|
||||||
|
cookie@0.7.1:
|
||||||
|
resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
cors@2.8.5:
|
||||||
|
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
|
||||||
|
engines: {node: '>= 0.10'}
|
||||||
|
|
||||||
|
debug@2.6.9:
|
||||||
|
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
|
||||||
|
peerDependencies:
|
||||||
|
supports-color: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
supports-color:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
debug@4.4.0:
|
||||||
|
resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
|
||||||
|
engines: {node: '>=6.0'}
|
||||||
|
peerDependencies:
|
||||||
|
supports-color: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
supports-color:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
depd@2.0.0:
|
||||||
|
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
destroy@1.2.0:
|
||||||
|
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
|
||||||
|
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
||||||
|
|
||||||
|
dunder-proto@1.0.1:
|
||||||
|
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
ee-first@1.1.1:
|
||||||
|
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
|
||||||
|
|
||||||
|
encodeurl@1.0.2:
|
||||||
|
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
encodeurl@2.0.0:
|
||||||
|
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
es-define-property@1.0.1:
|
||||||
|
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-errors@1.3.0:
|
||||||
|
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-object-atoms@1.1.1:
|
||||||
|
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
escape-html@1.0.3:
|
||||||
|
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
||||||
|
|
||||||
|
etag@1.8.1:
|
||||||
|
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
express@4.21.2:
|
||||||
|
resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==}
|
||||||
|
engines: {node: '>= 0.10.0'}
|
||||||
|
|
||||||
|
finalhandler@1.3.1:
|
||||||
|
resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
forwarded@0.2.0:
|
||||||
|
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
fresh@0.5.2:
|
||||||
|
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
function-bind@1.1.2:
|
||||||
|
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||||
|
|
||||||
|
get-intrinsic@1.3.0:
|
||||||
|
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
get-proto@1.0.1:
|
||||||
|
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
gopd@1.2.0:
|
||||||
|
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
has-symbols@1.1.0:
|
||||||
|
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
hasown@2.0.2:
|
||||||
|
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
http-errors@2.0.0:
|
||||||
|
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
iconv-lite@0.4.24:
|
||||||
|
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
inherits@2.0.4:
|
||||||
|
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||||
|
|
||||||
|
ipaddr.js@1.9.1:
|
||||||
|
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
|
||||||
|
engines: {node: '>= 0.10'}
|
||||||
|
|
||||||
|
kareem@2.6.3:
|
||||||
|
resolution: {integrity: sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
|
math-intrinsics@1.1.0:
|
||||||
|
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
media-typer@0.3.0:
|
||||||
|
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
memory-pager@1.5.0:
|
||||||
|
resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==}
|
||||||
|
|
||||||
|
merge-descriptors@1.0.3:
|
||||||
|
resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
|
||||||
|
|
||||||
|
methods@1.1.2:
|
||||||
|
resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
mime-db@1.52.0:
|
||||||
|
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
mime-types@2.1.35:
|
||||||
|
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
mime@1.6.0:
|
||||||
|
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
mongodb-connection-string-url@3.0.2:
|
||||||
|
resolution: {integrity: sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==}
|
||||||
|
|
||||||
|
mongodb@6.15.0:
|
||||||
|
resolution: {integrity: sha512-ifBhQ0rRzHDzqp9jAQP6OwHSH7dbYIQjD3SbJs9YYk9AikKEettW/9s/tbSFDTpXcRbF+u1aLrhHxDFaYtZpFQ==}
|
||||||
|
engines: {node: '>=16.20.1'}
|
||||||
|
peerDependencies:
|
||||||
|
'@aws-sdk/credential-providers': ^3.188.0
|
||||||
|
'@mongodb-js/zstd': ^1.1.0 || ^2.0.0
|
||||||
|
gcp-metadata: ^5.2.0
|
||||||
|
kerberos: ^2.0.1
|
||||||
|
mongodb-client-encryption: '>=6.0.0 <7'
|
||||||
|
snappy: ^7.2.2
|
||||||
|
socks: ^2.7.1
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@aws-sdk/credential-providers':
|
||||||
|
optional: true
|
||||||
|
'@mongodb-js/zstd':
|
||||||
|
optional: true
|
||||||
|
gcp-metadata:
|
||||||
|
optional: true
|
||||||
|
kerberos:
|
||||||
|
optional: true
|
||||||
|
mongodb-client-encryption:
|
||||||
|
optional: true
|
||||||
|
snappy:
|
||||||
|
optional: true
|
||||||
|
socks:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
mongoose@8.13.0:
|
||||||
|
resolution: {integrity: sha512-e/iYV1mPeOkg+SWAMHzt3t42/EZyER3OB1H2pjP9C3vQ+Qb5DMeV9Kb+YCUycKgScA3fbwL7dKG4EpinGlg21g==}
|
||||||
|
engines: {node: '>=16.20.1'}
|
||||||
|
|
||||||
|
mpath@0.9.0:
|
||||||
|
resolution: {integrity: sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==}
|
||||||
|
engines: {node: '>=4.0.0'}
|
||||||
|
|
||||||
|
mquery@5.0.0:
|
||||||
|
resolution: {integrity: sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
|
||||||
|
ms@2.0.0:
|
||||||
|
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
|
||||||
|
|
||||||
|
ms@2.1.3:
|
||||||
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
|
||||||
|
negotiator@0.6.3:
|
||||||
|
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
object-assign@4.1.1:
|
||||||
|
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
object-inspect@1.13.4:
|
||||||
|
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
on-finished@2.4.1:
|
||||||
|
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
parseurl@1.3.3:
|
||||||
|
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
path-to-regexp@0.1.12:
|
||||||
|
resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==}
|
||||||
|
|
||||||
|
proxy-addr@2.0.7:
|
||||||
|
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
|
||||||
|
engines: {node: '>= 0.10'}
|
||||||
|
|
||||||
|
punycode@2.3.1:
|
||||||
|
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
qs@6.13.0:
|
||||||
|
resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
|
||||||
|
engines: {node: '>=0.6'}
|
||||||
|
|
||||||
|
qs@6.14.0:
|
||||||
|
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
|
||||||
|
engines: {node: '>=0.6'}
|
||||||
|
|
||||||
|
range-parser@1.2.1:
|
||||||
|
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
raw-body@2.5.2:
|
||||||
|
resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
safe-buffer@5.2.1:
|
||||||
|
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||||
|
|
||||||
|
safer-buffer@2.1.2:
|
||||||
|
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||||
|
|
||||||
|
send@0.19.0:
|
||||||
|
resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
|
||||||
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
|
serve-static@1.16.2:
|
||||||
|
resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
|
||||||
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
|
setprototypeof@1.2.0:
|
||||||
|
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
|
||||||
|
|
||||||
|
side-channel-list@1.0.0:
|
||||||
|
resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
side-channel-map@1.0.1:
|
||||||
|
resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
side-channel-weakmap@1.0.2:
|
||||||
|
resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
side-channel@1.1.0:
|
||||||
|
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
sift@17.1.3:
|
||||||
|
resolution: {integrity: sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==}
|
||||||
|
|
||||||
|
sparse-bitfield@3.0.3:
|
||||||
|
resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==}
|
||||||
|
|
||||||
|
statuses@2.0.1:
|
||||||
|
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
stripe@17.7.0:
|
||||||
|
resolution: {integrity: sha512-aT2BU9KkizY9SATf14WhhYVv2uOapBWX0OFWF4xvcj1mPaNotlSc2CsxpS4DS46ZueSppmCF5BX1sNYBtwBvfw==}
|
||||||
|
engines: {node: '>=12.*'}
|
||||||
|
|
||||||
|
toidentifier@1.0.1:
|
||||||
|
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
||||||
|
engines: {node: '>=0.6'}
|
||||||
|
|
||||||
|
tr46@5.1.0:
|
||||||
|
resolution: {integrity: sha512-IUWnUK7ADYR5Sl1fZlO1INDUhVhatWl7BtJWsIhwJ0UAK7ilzzIa8uIqOO/aYVWHZPJkKbEL+362wrzoeRF7bw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
type-is@1.6.18:
|
||||||
|
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
undici-types@6.20.0:
|
||||||
|
resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
|
||||||
|
|
||||||
|
unpipe@1.0.0:
|
||||||
|
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
utils-merge@1.0.1:
|
||||||
|
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
|
||||||
|
engines: {node: '>= 0.4.0'}
|
||||||
|
|
||||||
|
vary@1.1.2:
|
||||||
|
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
webidl-conversions@7.0.0:
|
||||||
|
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
whatwg-url@14.2.0:
|
||||||
|
resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
zod@3.24.2:
|
||||||
|
resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==}
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
'@mongodb-js/saslprep@1.2.0':
|
||||||
|
dependencies:
|
||||||
|
sparse-bitfield: 3.0.3
|
||||||
|
|
||||||
|
'@types/body-parser@1.19.5':
|
||||||
|
dependencies:
|
||||||
|
'@types/connect': 3.4.38
|
||||||
|
'@types/node': 22.13.13
|
||||||
|
|
||||||
|
'@types/connect@3.4.38':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 22.13.13
|
||||||
|
|
||||||
|
'@types/cors@2.8.17':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 22.13.13
|
||||||
|
|
||||||
|
'@types/express-serve-static-core@5.0.6':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 22.13.13
|
||||||
|
'@types/qs': 6.9.18
|
||||||
|
'@types/range-parser': 1.2.7
|
||||||
|
'@types/send': 0.17.4
|
||||||
|
|
||||||
|
'@types/express@5.0.1':
|
||||||
|
dependencies:
|
||||||
|
'@types/body-parser': 1.19.5
|
||||||
|
'@types/express-serve-static-core': 5.0.6
|
||||||
|
'@types/serve-static': 1.15.7
|
||||||
|
|
||||||
|
'@types/http-errors@2.0.4': {}
|
||||||
|
|
||||||
|
'@types/mime@1.3.5': {}
|
||||||
|
|
||||||
|
'@types/node@22.13.13':
|
||||||
|
dependencies:
|
||||||
|
undici-types: 6.20.0
|
||||||
|
|
||||||
|
'@types/qs@6.9.18': {}
|
||||||
|
|
||||||
|
'@types/range-parser@1.2.7': {}
|
||||||
|
|
||||||
|
'@types/send@0.17.4':
|
||||||
|
dependencies:
|
||||||
|
'@types/mime': 1.3.5
|
||||||
|
'@types/node': 22.13.13
|
||||||
|
|
||||||
|
'@types/serve-static@1.15.7':
|
||||||
|
dependencies:
|
||||||
|
'@types/http-errors': 2.0.4
|
||||||
|
'@types/node': 22.13.13
|
||||||
|
'@types/send': 0.17.4
|
||||||
|
|
||||||
|
'@types/webidl-conversions@7.0.3': {}
|
||||||
|
|
||||||
|
'@types/whatwg-url@11.0.5':
|
||||||
|
dependencies:
|
||||||
|
'@types/webidl-conversions': 7.0.3
|
||||||
|
|
||||||
|
accepts@1.3.8:
|
||||||
|
dependencies:
|
||||||
|
mime-types: 2.1.35
|
||||||
|
negotiator: 0.6.3
|
||||||
|
|
||||||
|
array-flatten@1.1.1: {}
|
||||||
|
|
||||||
|
body-parser@1.20.3:
|
||||||
|
dependencies:
|
||||||
|
bytes: 3.1.2
|
||||||
|
content-type: 1.0.5
|
||||||
|
debug: 2.6.9
|
||||||
|
depd: 2.0.0
|
||||||
|
destroy: 1.2.0
|
||||||
|
http-errors: 2.0.0
|
||||||
|
iconv-lite: 0.4.24
|
||||||
|
on-finished: 2.4.1
|
||||||
|
qs: 6.13.0
|
||||||
|
raw-body: 2.5.2
|
||||||
|
type-is: 1.6.18
|
||||||
|
unpipe: 1.0.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
bson@6.10.3: {}
|
||||||
|
|
||||||
|
bytes@3.1.2: {}
|
||||||
|
|
||||||
|
call-bind-apply-helpers@1.0.2:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
function-bind: 1.1.2
|
||||||
|
|
||||||
|
call-bound@1.0.4:
|
||||||
|
dependencies:
|
||||||
|
call-bind-apply-helpers: 1.0.2
|
||||||
|
get-intrinsic: 1.3.0
|
||||||
|
|
||||||
|
content-disposition@0.5.4:
|
||||||
|
dependencies:
|
||||||
|
safe-buffer: 5.2.1
|
||||||
|
|
||||||
|
content-type@1.0.5: {}
|
||||||
|
|
||||||
|
cookie-signature@1.0.6: {}
|
||||||
|
|
||||||
|
cookie@0.7.1: {}
|
||||||
|
|
||||||
|
cors@2.8.5:
|
||||||
|
dependencies:
|
||||||
|
object-assign: 4.1.1
|
||||||
|
vary: 1.1.2
|
||||||
|
|
||||||
|
debug@2.6.9:
|
||||||
|
dependencies:
|
||||||
|
ms: 2.0.0
|
||||||
|
|
||||||
|
debug@4.4.0:
|
||||||
|
dependencies:
|
||||||
|
ms: 2.1.3
|
||||||
|
|
||||||
|
depd@2.0.0: {}
|
||||||
|
|
||||||
|
destroy@1.2.0: {}
|
||||||
|
|
||||||
|
dunder-proto@1.0.1:
|
||||||
|
dependencies:
|
||||||
|
call-bind-apply-helpers: 1.0.2
|
||||||
|
es-errors: 1.3.0
|
||||||
|
gopd: 1.2.0
|
||||||
|
|
||||||
|
ee-first@1.1.1: {}
|
||||||
|
|
||||||
|
encodeurl@1.0.2: {}
|
||||||
|
|
||||||
|
encodeurl@2.0.0: {}
|
||||||
|
|
||||||
|
es-define-property@1.0.1: {}
|
||||||
|
|
||||||
|
es-errors@1.3.0: {}
|
||||||
|
|
||||||
|
es-object-atoms@1.1.1:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
|
||||||
|
escape-html@1.0.3: {}
|
||||||
|
|
||||||
|
etag@1.8.1: {}
|
||||||
|
|
||||||
|
express@4.21.2:
|
||||||
|
dependencies:
|
||||||
|
accepts: 1.3.8
|
||||||
|
array-flatten: 1.1.1
|
||||||
|
body-parser: 1.20.3
|
||||||
|
content-disposition: 0.5.4
|
||||||
|
content-type: 1.0.5
|
||||||
|
cookie: 0.7.1
|
||||||
|
cookie-signature: 1.0.6
|
||||||
|
debug: 2.6.9
|
||||||
|
depd: 2.0.0
|
||||||
|
encodeurl: 2.0.0
|
||||||
|
escape-html: 1.0.3
|
||||||
|
etag: 1.8.1
|
||||||
|
finalhandler: 1.3.1
|
||||||
|
fresh: 0.5.2
|
||||||
|
http-errors: 2.0.0
|
||||||
|
merge-descriptors: 1.0.3
|
||||||
|
methods: 1.1.2
|
||||||
|
on-finished: 2.4.1
|
||||||
|
parseurl: 1.3.3
|
||||||
|
path-to-regexp: 0.1.12
|
||||||
|
proxy-addr: 2.0.7
|
||||||
|
qs: 6.13.0
|
||||||
|
range-parser: 1.2.1
|
||||||
|
safe-buffer: 5.2.1
|
||||||
|
send: 0.19.0
|
||||||
|
serve-static: 1.16.2
|
||||||
|
setprototypeof: 1.2.0
|
||||||
|
statuses: 2.0.1
|
||||||
|
type-is: 1.6.18
|
||||||
|
utils-merge: 1.0.1
|
||||||
|
vary: 1.1.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
finalhandler@1.3.1:
|
||||||
|
dependencies:
|
||||||
|
debug: 2.6.9
|
||||||
|
encodeurl: 2.0.0
|
||||||
|
escape-html: 1.0.3
|
||||||
|
on-finished: 2.4.1
|
||||||
|
parseurl: 1.3.3
|
||||||
|
statuses: 2.0.1
|
||||||
|
unpipe: 1.0.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
forwarded@0.2.0: {}
|
||||||
|
|
||||||
|
fresh@0.5.2: {}
|
||||||
|
|
||||||
|
function-bind@1.1.2: {}
|
||||||
|
|
||||||
|
get-intrinsic@1.3.0:
|
||||||
|
dependencies:
|
||||||
|
call-bind-apply-helpers: 1.0.2
|
||||||
|
es-define-property: 1.0.1
|
||||||
|
es-errors: 1.3.0
|
||||||
|
es-object-atoms: 1.1.1
|
||||||
|
function-bind: 1.1.2
|
||||||
|
get-proto: 1.0.1
|
||||||
|
gopd: 1.2.0
|
||||||
|
has-symbols: 1.1.0
|
||||||
|
hasown: 2.0.2
|
||||||
|
math-intrinsics: 1.1.0
|
||||||
|
|
||||||
|
get-proto@1.0.1:
|
||||||
|
dependencies:
|
||||||
|
dunder-proto: 1.0.1
|
||||||
|
es-object-atoms: 1.1.1
|
||||||
|
|
||||||
|
gopd@1.2.0: {}
|
||||||
|
|
||||||
|
has-symbols@1.1.0: {}
|
||||||
|
|
||||||
|
hasown@2.0.2:
|
||||||
|
dependencies:
|
||||||
|
function-bind: 1.1.2
|
||||||
|
|
||||||
|
http-errors@2.0.0:
|
||||||
|
dependencies:
|
||||||
|
depd: 2.0.0
|
||||||
|
inherits: 2.0.4
|
||||||
|
setprototypeof: 1.2.0
|
||||||
|
statuses: 2.0.1
|
||||||
|
toidentifier: 1.0.1
|
||||||
|
|
||||||
|
iconv-lite@0.4.24:
|
||||||
|
dependencies:
|
||||||
|
safer-buffer: 2.1.2
|
||||||
|
|
||||||
|
inherits@2.0.4: {}
|
||||||
|
|
||||||
|
ipaddr.js@1.9.1: {}
|
||||||
|
|
||||||
|
kareem@2.6.3: {}
|
||||||
|
|
||||||
|
math-intrinsics@1.1.0: {}
|
||||||
|
|
||||||
|
media-typer@0.3.0: {}
|
||||||
|
|
||||||
|
memory-pager@1.5.0: {}
|
||||||
|
|
||||||
|
merge-descriptors@1.0.3: {}
|
||||||
|
|
||||||
|
methods@1.1.2: {}
|
||||||
|
|
||||||
|
mime-db@1.52.0: {}
|
||||||
|
|
||||||
|
mime-types@2.1.35:
|
||||||
|
dependencies:
|
||||||
|
mime-db: 1.52.0
|
||||||
|
|
||||||
|
mime@1.6.0: {}
|
||||||
|
|
||||||
|
mongodb-connection-string-url@3.0.2:
|
||||||
|
dependencies:
|
||||||
|
'@types/whatwg-url': 11.0.5
|
||||||
|
whatwg-url: 14.2.0
|
||||||
|
|
||||||
|
mongodb@6.15.0:
|
||||||
|
dependencies:
|
||||||
|
'@mongodb-js/saslprep': 1.2.0
|
||||||
|
bson: 6.10.3
|
||||||
|
mongodb-connection-string-url: 3.0.2
|
||||||
|
|
||||||
|
mongoose@8.13.0:
|
||||||
|
dependencies:
|
||||||
|
bson: 6.10.3
|
||||||
|
kareem: 2.6.3
|
||||||
|
mongodb: 6.15.0
|
||||||
|
mpath: 0.9.0
|
||||||
|
mquery: 5.0.0
|
||||||
|
ms: 2.1.3
|
||||||
|
sift: 17.1.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@aws-sdk/credential-providers'
|
||||||
|
- '@mongodb-js/zstd'
|
||||||
|
- gcp-metadata
|
||||||
|
- kerberos
|
||||||
|
- mongodb-client-encryption
|
||||||
|
- snappy
|
||||||
|
- socks
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
mpath@0.9.0: {}
|
||||||
|
|
||||||
|
mquery@5.0.0:
|
||||||
|
dependencies:
|
||||||
|
debug: 4.4.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
ms@2.0.0: {}
|
||||||
|
|
||||||
|
ms@2.1.3: {}
|
||||||
|
|
||||||
|
negotiator@0.6.3: {}
|
||||||
|
|
||||||
|
object-assign@4.1.1: {}
|
||||||
|
|
||||||
|
object-inspect@1.13.4: {}
|
||||||
|
|
||||||
|
on-finished@2.4.1:
|
||||||
|
dependencies:
|
||||||
|
ee-first: 1.1.1
|
||||||
|
|
||||||
|
parseurl@1.3.3: {}
|
||||||
|
|
||||||
|
path-to-regexp@0.1.12: {}
|
||||||
|
|
||||||
|
proxy-addr@2.0.7:
|
||||||
|
dependencies:
|
||||||
|
forwarded: 0.2.0
|
||||||
|
ipaddr.js: 1.9.1
|
||||||
|
|
||||||
|
punycode@2.3.1: {}
|
||||||
|
|
||||||
|
qs@6.13.0:
|
||||||
|
dependencies:
|
||||||
|
side-channel: 1.1.0
|
||||||
|
|
||||||
|
qs@6.14.0:
|
||||||
|
dependencies:
|
||||||
|
side-channel: 1.1.0
|
||||||
|
|
||||||
|
range-parser@1.2.1: {}
|
||||||
|
|
||||||
|
raw-body@2.5.2:
|
||||||
|
dependencies:
|
||||||
|
bytes: 3.1.2
|
||||||
|
http-errors: 2.0.0
|
||||||
|
iconv-lite: 0.4.24
|
||||||
|
unpipe: 1.0.0
|
||||||
|
|
||||||
|
safe-buffer@5.2.1: {}
|
||||||
|
|
||||||
|
safer-buffer@2.1.2: {}
|
||||||
|
|
||||||
|
send@0.19.0:
|
||||||
|
dependencies:
|
||||||
|
debug: 2.6.9
|
||||||
|
depd: 2.0.0
|
||||||
|
destroy: 1.2.0
|
||||||
|
encodeurl: 1.0.2
|
||||||
|
escape-html: 1.0.3
|
||||||
|
etag: 1.8.1
|
||||||
|
fresh: 0.5.2
|
||||||
|
http-errors: 2.0.0
|
||||||
|
mime: 1.6.0
|
||||||
|
ms: 2.1.3
|
||||||
|
on-finished: 2.4.1
|
||||||
|
range-parser: 1.2.1
|
||||||
|
statuses: 2.0.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
serve-static@1.16.2:
|
||||||
|
dependencies:
|
||||||
|
encodeurl: 2.0.0
|
||||||
|
escape-html: 1.0.3
|
||||||
|
parseurl: 1.3.3
|
||||||
|
send: 0.19.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
setprototypeof@1.2.0: {}
|
||||||
|
|
||||||
|
side-channel-list@1.0.0:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
object-inspect: 1.13.4
|
||||||
|
|
||||||
|
side-channel-map@1.0.1:
|
||||||
|
dependencies:
|
||||||
|
call-bound: 1.0.4
|
||||||
|
es-errors: 1.3.0
|
||||||
|
get-intrinsic: 1.3.0
|
||||||
|
object-inspect: 1.13.4
|
||||||
|
|
||||||
|
side-channel-weakmap@1.0.2:
|
||||||
|
dependencies:
|
||||||
|
call-bound: 1.0.4
|
||||||
|
es-errors: 1.3.0
|
||||||
|
get-intrinsic: 1.3.0
|
||||||
|
object-inspect: 1.13.4
|
||||||
|
side-channel-map: 1.0.1
|
||||||
|
|
||||||
|
side-channel@1.1.0:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
object-inspect: 1.13.4
|
||||||
|
side-channel-list: 1.0.0
|
||||||
|
side-channel-map: 1.0.1
|
||||||
|
side-channel-weakmap: 1.0.2
|
||||||
|
|
||||||
|
sift@17.1.3: {}
|
||||||
|
|
||||||
|
sparse-bitfield@3.0.3:
|
||||||
|
dependencies:
|
||||||
|
memory-pager: 1.5.0
|
||||||
|
|
||||||
|
statuses@2.0.1: {}
|
||||||
|
|
||||||
|
stripe@17.7.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 22.13.13
|
||||||
|
qs: 6.14.0
|
||||||
|
|
||||||
|
toidentifier@1.0.1: {}
|
||||||
|
|
||||||
|
tr46@5.1.0:
|
||||||
|
dependencies:
|
||||||
|
punycode: 2.3.1
|
||||||
|
|
||||||
|
type-is@1.6.18:
|
||||||
|
dependencies:
|
||||||
|
media-typer: 0.3.0
|
||||||
|
mime-types: 2.1.35
|
||||||
|
|
||||||
|
undici-types@6.20.0: {}
|
||||||
|
|
||||||
|
unpipe@1.0.0: {}
|
||||||
|
|
||||||
|
utils-merge@1.0.1: {}
|
||||||
|
|
||||||
|
vary@1.1.2: {}
|
||||||
|
|
||||||
|
webidl-conversions@7.0.0: {}
|
||||||
|
|
||||||
|
whatwg-url@14.2.0:
|
||||||
|
dependencies:
|
||||||
|
tr46: 5.1.0
|
||||||
|
webidl-conversions: 7.0.0
|
||||||
|
|
||||||
|
zod@3.24.2: {}
|
||||||
6
payments/src/Utils.ts
Normal file
6
payments/src/Utils.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import type { Response } from "express";
|
||||||
|
|
||||||
|
|
||||||
|
export function sendJson(res: Response, status: number, data: Record<string, any>): void {
|
||||||
|
res.status(status).json(data);
|
||||||
|
}
|
||||||
0
payments/src/index.ts
Normal file
0
payments/src/index.ts
Normal file
43
payments/src/routers/PaymentRouter.ts
Normal file
43
payments/src/routers/PaymentRouter.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { json, Router } from 'express';
|
||||||
|
import z from 'zod';
|
||||||
|
import { getPlanFromId } from '../shared/data/PLANS';
|
||||||
|
import StripeService from '../services/StripeService';
|
||||||
|
import { sendJson } from '../Utils';
|
||||||
|
import { ProjectModel } from '../shared/schema/project/ProjectSchema';
|
||||||
|
|
||||||
|
export const paymentRouter = Router();
|
||||||
|
|
||||||
|
|
||||||
|
export const ZBodyCreatePayment = z.object({
|
||||||
|
pid: z.string(),
|
||||||
|
plan_id: z.number()
|
||||||
|
})
|
||||||
|
|
||||||
|
paymentRouter.post('/create', json(), async (req, res) => {
|
||||||
|
try {
|
||||||
|
const createPaymentData = ZBodyCreatePayment.parse(req.body);
|
||||||
|
|
||||||
|
const plan = getPlanFromId(createPaymentData.plan_id);
|
||||||
|
if (!plan) return sendJson(res, 400, { error: 'plan not found' });
|
||||||
|
|
||||||
|
const project = await ProjectModel.findById(createPaymentData.pid);
|
||||||
|
if (!project) return sendJson(res, 400, { error: 'project not found' });
|
||||||
|
if (!project.customer_id) return sendJson(res, 400, { error: 'project have no customer_id' });
|
||||||
|
|
||||||
|
const price = StripeService.testMode ? plan.PRICE_TEST : plan.PRICE;
|
||||||
|
|
||||||
|
const checkout = await StripeService.createPayment(
|
||||||
|
price,
|
||||||
|
'https://dashboard.litlyx.com/payment_ok',
|
||||||
|
createPaymentData.pid,
|
||||||
|
project.customer_id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!checkout) return sendJson(res, 400, { error: 'cannot create payment' });
|
||||||
|
|
||||||
|
return sendJson(res, 200, { url: checkout.url });
|
||||||
|
|
||||||
|
} catch (ex) {
|
||||||
|
res.status(500).json({ error: ex.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
19
payments/src/routers/WebhookRouter.ts
Normal file
19
payments/src/routers/WebhookRouter.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
import { json, Router } from 'express';
|
||||||
|
|
||||||
|
export const webhookRouter = Router();
|
||||||
|
|
||||||
|
|
||||||
|
webhookRouter.get('/', json(), async (req, res) => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
const signature = req.header('stripe-signature');
|
||||||
|
if (!signature) {
|
||||||
|
console.error('No signature on the webhook')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} catch (ex) {
|
||||||
|
res.status(500).json({ error: ex.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
209
payments/src/services/StripeService.ts
Normal file
209
payments/src/services/StripeService.ts
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
|
||||||
|
import Stripe from "stripe";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class StripeService {
|
||||||
|
private stripe?: Stripe;
|
||||||
|
private privateKey?: string;
|
||||||
|
private webhookSecret?: string;
|
||||||
|
public testMode?: boolean;
|
||||||
|
|
||||||
|
init(privateKey: string, webhookSecret: string, testMode: boolean = false) {
|
||||||
|
this.privateKey = privateKey;
|
||||||
|
this.webhookSecret = webhookSecret;
|
||||||
|
this.stripe = new Stripe(this.privateKey);
|
||||||
|
this.testMode = testMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
parseWebhook(body: any, sig: string) {
|
||||||
|
if (!this.stripe) throw Error('Stripe not initialized');
|
||||||
|
if (!this.webhookSecret) {
|
||||||
|
console.error('Stripe not initialized')
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return this.stripe.webhooks.constructEvent(body, sig, this.webhookSecret);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async createOnetimePayment(price: string, success_url: string, pid: string, customer?: string) {
|
||||||
|
if (!this.stripe) throw Error('Stripe not initialized');
|
||||||
|
|
||||||
|
const checkout = await this.stripe.checkout.sessions.create({
|
||||||
|
allow_promotion_codes: true,
|
||||||
|
payment_method_types: ['card'],
|
||||||
|
invoice_creation: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
line_items: [
|
||||||
|
{ price, quantity: 1 }
|
||||||
|
],
|
||||||
|
payment_intent_data: {
|
||||||
|
metadata: {
|
||||||
|
pid, price
|
||||||
|
}
|
||||||
|
},
|
||||||
|
customer,
|
||||||
|
success_url,
|
||||||
|
mode: 'payment'
|
||||||
|
});
|
||||||
|
|
||||||
|
return checkout;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createPayment(price: string, success_url: string, pid: string, customer: string) {
|
||||||
|
if (!this.stripe) throw Error('Stripe not initialized');
|
||||||
|
|
||||||
|
const checkout = await this.stripe.checkout.sessions.create({
|
||||||
|
allow_promotion_codes: true,
|
||||||
|
payment_method_types: ['card'],
|
||||||
|
line_items: [
|
||||||
|
{ price, quantity: 1 }
|
||||||
|
],
|
||||||
|
subscription_data: {
|
||||||
|
metadata: { pid },
|
||||||
|
},
|
||||||
|
customer,
|
||||||
|
success_url,
|
||||||
|
mode: 'subscription'
|
||||||
|
});
|
||||||
|
|
||||||
|
return checkout;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPriceData(priceId: string) {
|
||||||
|
if (!this.stripe) throw Error('Stripe not initialized');
|
||||||
|
const priceData = await this.stripe.prices.retrieve(priceId);
|
||||||
|
return priceData;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteSubscription(subscriptionId: string) {
|
||||||
|
if (!this.stripe) throw Error('Stripe not initialized');
|
||||||
|
const subscription = await this.stripe.subscriptions.cancel(subscriptionId);
|
||||||
|
return subscription;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSubscription(subscriptionId: string) {
|
||||||
|
if (!this.stripe) throw Error('Stripe not initialized');
|
||||||
|
const subscription = await this.stripe.subscriptions.retrieve(subscriptionId);
|
||||||
|
return subscription;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllSubscriptions(customer_id: string) {
|
||||||
|
if (!this.stripe) throw Error('Stripe not initialized');
|
||||||
|
const subscriptions = await this.stripe.subscriptions.list({ customer: customer_id });
|
||||||
|
return subscriptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getInvoices(customer_id: string) {
|
||||||
|
if (!this.stripe) throw Error('Stripe not initialized');
|
||||||
|
const invoices = await this.stripe?.invoices.list({ customer: customer_id });
|
||||||
|
return invoices;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCustomer(customer_id: string) {
|
||||||
|
if (!this.stripe) throw Error('Stripe not initialized');
|
||||||
|
const customer = await this.stripe.customers.retrieve(customer_id, { expand: [] })
|
||||||
|
return customer;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createCustomer(email: string) {
|
||||||
|
if (!this.stripe) throw Error('Stripe not initialized');
|
||||||
|
const customer = await this.stripe.customers.create({ email });
|
||||||
|
return customer;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setCustomerInfo(customer_id: string, address: { line1: string, line2: string, city: string, country: string, postal_code: string, state: string }) {
|
||||||
|
if (!this.stripe) throw Error('Stripe not initialized');
|
||||||
|
const customer = await this.stripe.customers.update(customer_id, {
|
||||||
|
address: {
|
||||||
|
line1: address.line1,
|
||||||
|
line2: address.line2,
|
||||||
|
city: address.city,
|
||||||
|
country: address.country,
|
||||||
|
postal_code: address.postal_code,
|
||||||
|
state: address.state
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return customer.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteCustomer(customer_id: string) {
|
||||||
|
if (!this.stripe) throw Error('Stripe not initialized');
|
||||||
|
const { deleted } = await this.stripe.customers.del(customer_id);
|
||||||
|
return deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// async createStripeCode(plan: PREMIUM_TAG) {
|
||||||
|
// if (!this.stripe) throw Error('Stripe not initialized');
|
||||||
|
|
||||||
|
// const INCUBATION_COUPON = 'sDD7Weh3';
|
||||||
|
|
||||||
|
// if (plan === 'INCUBATION') {
|
||||||
|
// await this.stripe.promotionCodes.create({
|
||||||
|
// coupon: INCUBATION_COUPON,
|
||||||
|
// active: true,
|
||||||
|
// code: 'TESTCACCA1',
|
||||||
|
// max_redemptions: 1,
|
||||||
|
// })
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// async createSubscription(customer_id: string, planId: number) {
|
||||||
|
// if (this.disabledMode) return;
|
||||||
|
// if (!this.stripe) throw Error('Stripe not initialized');
|
||||||
|
|
||||||
|
// const PLAN = getPlanFromId(planId);
|
||||||
|
// if (!PLAN) throw Error('Plan not found');
|
||||||
|
|
||||||
|
// const subscription = await this.stripe.subscriptions.create({
|
||||||
|
// customer: customer_id,
|
||||||
|
// items: [
|
||||||
|
// { price: this.testMode ? PLAN.PRICE_TEST : PLAN.PRICE, quantity: 1 }
|
||||||
|
// ],
|
||||||
|
// });
|
||||||
|
|
||||||
|
// return subscription;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// async createOneTimeSubscriptionDummy(customer_id: string, planId: number) {
|
||||||
|
// if (this.disabledMode) return;
|
||||||
|
// if (!this.stripe) throw Error('Stripe not initialized');
|
||||||
|
|
||||||
|
// const PLAN = getPlanFromId(planId);
|
||||||
|
// if (!PLAN) throw Error('Plan not found');
|
||||||
|
|
||||||
|
// const subscription = await this.stripe.subscriptions.create({
|
||||||
|
// customer: customer_id,
|
||||||
|
// items: [
|
||||||
|
// { price: this.testMode ? PLAN.PRICE_TEST : PLAN.PRICE, quantity: 1 }
|
||||||
|
// ],
|
||||||
|
// });
|
||||||
|
|
||||||
|
// return subscription;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// async createFreeSubscription(customer_id: string) {
|
||||||
|
// if (this.disabledMode) return;
|
||||||
|
// if (!this.stripe) throw Error('Stripe not initialized');
|
||||||
|
|
||||||
|
// const FREE_PLAN = getPlanFromTag('FREE');
|
||||||
|
|
||||||
|
// const subscription = await this.stripe.subscriptions.create({
|
||||||
|
// customer: customer_id,
|
||||||
|
// items: [
|
||||||
|
// { price: this.testMode ? FREE_PLAN.PRICE_TEST : FREE_PLAN.PRICE, quantity: 1 }
|
||||||
|
// ]
|
||||||
|
// });
|
||||||
|
|
||||||
|
// return subscription;
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = new StripeService();
|
||||||
|
export default instance;
|
||||||
13
payments/tsconfig.json
Normal file
13
payments/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "NodeNext",
|
||||||
|
"target": "ESNext",
|
||||||
|
"outDir": "dist"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
|
"mongoose": "^8.12.1",
|
||||||
"redis": "^4.7.0"
|
"redis": "^4.7.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
173
producer/pnpm-lock.yaml
generated
173
producer/pnpm-lock.yaml
generated
@@ -14,6 +14,9 @@ importers:
|
|||||||
express:
|
express:
|
||||||
specifier: ^4.19.2
|
specifier: ^4.19.2
|
||||||
version: 4.19.2
|
version: 4.19.2
|
||||||
|
mongoose:
|
||||||
|
specifier: ^8.12.1
|
||||||
|
version: 8.12.1
|
||||||
redis:
|
redis:
|
||||||
specifier: ^4.7.0
|
specifier: ^4.7.0
|
||||||
version: 4.7.0
|
version: 4.7.0
|
||||||
@@ -50,6 +53,9 @@ packages:
|
|||||||
'@jridgewell/trace-mapping@0.3.9':
|
'@jridgewell/trace-mapping@0.3.9':
|
||||||
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
|
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
|
||||||
|
|
||||||
|
'@mongodb-js/saslprep@1.2.0':
|
||||||
|
resolution: {integrity: sha512-+ywrb0AqkfaYuhHs6LxKWgqbh3I72EpEgESCw37o+9qPx9WTCkgDm2B+eMrwehGtHBWHFU4GXvnSCNiFhhausg==}
|
||||||
|
|
||||||
'@redis/bloom@1.2.0':
|
'@redis/bloom@1.2.0':
|
||||||
resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==}
|
resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -127,6 +133,12 @@ packages:
|
|||||||
'@types/serve-static@1.15.7':
|
'@types/serve-static@1.15.7':
|
||||||
resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==}
|
resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==}
|
||||||
|
|
||||||
|
'@types/webidl-conversions@7.0.3':
|
||||||
|
resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==}
|
||||||
|
|
||||||
|
'@types/whatwg-url@11.0.5':
|
||||||
|
resolution: {integrity: sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==}
|
||||||
|
|
||||||
accepts@1.3.8:
|
accepts@1.3.8:
|
||||||
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
|
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
@@ -150,6 +162,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==}
|
resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==}
|
||||||
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
||||||
|
|
||||||
|
bson@6.10.3:
|
||||||
|
resolution: {integrity: sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ==}
|
||||||
|
engines: {node: '>=16.20.1'}
|
||||||
|
|
||||||
bytes@3.1.2:
|
bytes@3.1.2:
|
||||||
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
|
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@@ -192,6 +208,15 @@ packages:
|
|||||||
supports-color:
|
supports-color:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
debug@4.4.0:
|
||||||
|
resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
|
||||||
|
engines: {node: '>=6.0'}
|
||||||
|
peerDependencies:
|
||||||
|
supports-color: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
supports-color:
|
||||||
|
optional: true
|
||||||
|
|
||||||
define-data-property@1.1.4:
|
define-data-property@1.1.4:
|
||||||
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
|
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -290,6 +315,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
|
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
|
||||||
engines: {node: '>= 0.10'}
|
engines: {node: '>= 0.10'}
|
||||||
|
|
||||||
|
kareem@2.6.3:
|
||||||
|
resolution: {integrity: sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
make-error@1.3.6:
|
make-error@1.3.6:
|
||||||
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
|
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
|
||||||
|
|
||||||
@@ -297,6 +326,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
|
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
memory-pager@1.5.0:
|
||||||
|
resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==}
|
||||||
|
|
||||||
merge-descriptors@1.0.1:
|
merge-descriptors@1.0.1:
|
||||||
resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==}
|
resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==}
|
||||||
|
|
||||||
@@ -317,6 +349,48 @@ packages:
|
|||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
mongodb-connection-string-url@3.0.2:
|
||||||
|
resolution: {integrity: sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==}
|
||||||
|
|
||||||
|
mongodb@6.14.2:
|
||||||
|
resolution: {integrity: sha512-kMEHNo0F3P6QKDq17zcDuPeaywK/YaJVCEQRzPF3TOM/Bl9MFg64YE5Tu7ifj37qZJMhwU1tl2Ioivws5gRG5Q==}
|
||||||
|
engines: {node: '>=16.20.1'}
|
||||||
|
peerDependencies:
|
||||||
|
'@aws-sdk/credential-providers': ^3.188.0
|
||||||
|
'@mongodb-js/zstd': ^1.1.0 || ^2.0.0
|
||||||
|
gcp-metadata: ^5.2.0
|
||||||
|
kerberos: ^2.0.1
|
||||||
|
mongodb-client-encryption: '>=6.0.0 <7'
|
||||||
|
snappy: ^7.2.2
|
||||||
|
socks: ^2.7.1
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@aws-sdk/credential-providers':
|
||||||
|
optional: true
|
||||||
|
'@mongodb-js/zstd':
|
||||||
|
optional: true
|
||||||
|
gcp-metadata:
|
||||||
|
optional: true
|
||||||
|
kerberos:
|
||||||
|
optional: true
|
||||||
|
mongodb-client-encryption:
|
||||||
|
optional: true
|
||||||
|
snappy:
|
||||||
|
optional: true
|
||||||
|
socks:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
mongoose@8.12.1:
|
||||||
|
resolution: {integrity: sha512-UW22y8QFVYmrb36hm8cGncfn4ARc/XsYWQwRTaj0gxtQk1rDuhzDO1eBantS+hTTatfAIS96LlRCJrcNHvW5+Q==}
|
||||||
|
engines: {node: '>=16.20.1'}
|
||||||
|
|
||||||
|
mpath@0.9.0:
|
||||||
|
resolution: {integrity: sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==}
|
||||||
|
engines: {node: '>=4.0.0'}
|
||||||
|
|
||||||
|
mquery@5.0.0:
|
||||||
|
resolution: {integrity: sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
|
||||||
ms@2.0.0:
|
ms@2.0.0:
|
||||||
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
|
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
|
||||||
|
|
||||||
@@ -349,6 +423,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
|
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
|
||||||
engines: {node: '>= 0.10'}
|
engines: {node: '>= 0.10'}
|
||||||
|
|
||||||
|
punycode@2.3.1:
|
||||||
|
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
qs@6.11.0:
|
qs@6.11.0:
|
||||||
resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==}
|
resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==}
|
||||||
engines: {node: '>=0.6'}
|
engines: {node: '>=0.6'}
|
||||||
@@ -389,6 +467,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==}
|
resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
sift@17.1.3:
|
||||||
|
resolution: {integrity: sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==}
|
||||||
|
|
||||||
|
sparse-bitfield@3.0.3:
|
||||||
|
resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==}
|
||||||
|
|
||||||
statuses@2.0.1:
|
statuses@2.0.1:
|
||||||
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@@ -397,6 +481,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
||||||
engines: {node: '>=0.6'}
|
engines: {node: '>=0.6'}
|
||||||
|
|
||||||
|
tr46@5.1.0:
|
||||||
|
resolution: {integrity: sha512-IUWnUK7ADYR5Sl1fZlO1INDUhVhatWl7BtJWsIhwJ0UAK7ilzzIa8uIqOO/aYVWHZPJkKbEL+362wrzoeRF7bw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
ts-node@10.9.2:
|
ts-node@10.9.2:
|
||||||
resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
|
resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -438,6 +526,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
webidl-conversions@7.0.0:
|
||||||
|
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
whatwg-url@14.2.0:
|
||||||
|
resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
yallist@4.0.0:
|
yallist@4.0.0:
|
||||||
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
||||||
|
|
||||||
@@ -460,6 +556,10 @@ snapshots:
|
|||||||
'@jridgewell/resolve-uri': 3.1.2
|
'@jridgewell/resolve-uri': 3.1.2
|
||||||
'@jridgewell/sourcemap-codec': 1.4.15
|
'@jridgewell/sourcemap-codec': 1.4.15
|
||||||
|
|
||||||
|
'@mongodb-js/saslprep@1.2.0':
|
||||||
|
dependencies:
|
||||||
|
sparse-bitfield: 3.0.3
|
||||||
|
|
||||||
'@redis/bloom@1.2.0(@redis/client@1.6.0)':
|
'@redis/bloom@1.2.0(@redis/client@1.6.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@redis/client': 1.6.0
|
'@redis/client': 1.6.0
|
||||||
@@ -544,6 +644,12 @@ snapshots:
|
|||||||
'@types/node': 20.14.2
|
'@types/node': 20.14.2
|
||||||
'@types/send': 0.17.4
|
'@types/send': 0.17.4
|
||||||
|
|
||||||
|
'@types/webidl-conversions@7.0.3': {}
|
||||||
|
|
||||||
|
'@types/whatwg-url@11.0.5':
|
||||||
|
dependencies:
|
||||||
|
'@types/webidl-conversions': 7.0.3
|
||||||
|
|
||||||
accepts@1.3.8:
|
accepts@1.3.8:
|
||||||
dependencies:
|
dependencies:
|
||||||
mime-types: 2.1.35
|
mime-types: 2.1.35
|
||||||
@@ -576,6 +682,8 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
bson@6.10.3: {}
|
||||||
|
|
||||||
bytes@3.1.2: {}
|
bytes@3.1.2: {}
|
||||||
|
|
||||||
call-bind@1.0.7:
|
call-bind@1.0.7:
|
||||||
@@ -609,6 +717,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.0.0
|
ms: 2.0.0
|
||||||
|
|
||||||
|
debug@4.4.0:
|
||||||
|
dependencies:
|
||||||
|
ms: 2.1.3
|
||||||
|
|
||||||
define-data-property@1.1.4:
|
define-data-property@1.1.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
es-define-property: 1.0.0
|
es-define-property: 1.0.0
|
||||||
@@ -731,10 +843,14 @@ snapshots:
|
|||||||
|
|
||||||
ipaddr.js@1.9.1: {}
|
ipaddr.js@1.9.1: {}
|
||||||
|
|
||||||
|
kareem@2.6.3: {}
|
||||||
|
|
||||||
make-error@1.3.6: {}
|
make-error@1.3.6: {}
|
||||||
|
|
||||||
media-typer@0.3.0: {}
|
media-typer@0.3.0: {}
|
||||||
|
|
||||||
|
memory-pager@1.5.0: {}
|
||||||
|
|
||||||
merge-descriptors@1.0.1: {}
|
merge-descriptors@1.0.1: {}
|
||||||
|
|
||||||
methods@1.1.2: {}
|
methods@1.1.2: {}
|
||||||
@@ -747,6 +863,44 @@ snapshots:
|
|||||||
|
|
||||||
mime@1.6.0: {}
|
mime@1.6.0: {}
|
||||||
|
|
||||||
|
mongodb-connection-string-url@3.0.2:
|
||||||
|
dependencies:
|
||||||
|
'@types/whatwg-url': 11.0.5
|
||||||
|
whatwg-url: 14.2.0
|
||||||
|
|
||||||
|
mongodb@6.14.2:
|
||||||
|
dependencies:
|
||||||
|
'@mongodb-js/saslprep': 1.2.0
|
||||||
|
bson: 6.10.3
|
||||||
|
mongodb-connection-string-url: 3.0.2
|
||||||
|
|
||||||
|
mongoose@8.12.1:
|
||||||
|
dependencies:
|
||||||
|
bson: 6.10.3
|
||||||
|
kareem: 2.6.3
|
||||||
|
mongodb: 6.14.2
|
||||||
|
mpath: 0.9.0
|
||||||
|
mquery: 5.0.0
|
||||||
|
ms: 2.1.3
|
||||||
|
sift: 17.1.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@aws-sdk/credential-providers'
|
||||||
|
- '@mongodb-js/zstd'
|
||||||
|
- gcp-metadata
|
||||||
|
- kerberos
|
||||||
|
- mongodb-client-encryption
|
||||||
|
- snappy
|
||||||
|
- socks
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
mpath@0.9.0: {}
|
||||||
|
|
||||||
|
mquery@5.0.0:
|
||||||
|
dependencies:
|
||||||
|
debug: 4.4.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
ms@2.0.0: {}
|
ms@2.0.0: {}
|
||||||
|
|
||||||
ms@2.1.3: {}
|
ms@2.1.3: {}
|
||||||
@@ -770,6 +924,8 @@ snapshots:
|
|||||||
forwarded: 0.2.0
|
forwarded: 0.2.0
|
||||||
ipaddr.js: 1.9.1
|
ipaddr.js: 1.9.1
|
||||||
|
|
||||||
|
punycode@2.3.1: {}
|
||||||
|
|
||||||
qs@6.11.0:
|
qs@6.11.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
side-channel: 1.0.6
|
side-channel: 1.0.6
|
||||||
@@ -841,10 +997,20 @@ snapshots:
|
|||||||
get-intrinsic: 1.2.4
|
get-intrinsic: 1.2.4
|
||||||
object-inspect: 1.13.1
|
object-inspect: 1.13.1
|
||||||
|
|
||||||
|
sift@17.1.3: {}
|
||||||
|
|
||||||
|
sparse-bitfield@3.0.3:
|
||||||
|
dependencies:
|
||||||
|
memory-pager: 1.5.0
|
||||||
|
|
||||||
statuses@2.0.1: {}
|
statuses@2.0.1: {}
|
||||||
|
|
||||||
toidentifier@1.0.1: {}
|
toidentifier@1.0.1: {}
|
||||||
|
|
||||||
|
tr46@5.1.0:
|
||||||
|
dependencies:
|
||||||
|
punycode: 2.3.1
|
||||||
|
|
||||||
ts-node@10.9.2(@types/node@20.14.2)(typescript@5.4.5):
|
ts-node@10.9.2(@types/node@20.14.2)(typescript@5.4.5):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@cspotcode/source-map-support': 0.8.1
|
'@cspotcode/source-map-support': 0.8.1
|
||||||
@@ -880,6 +1046,13 @@ snapshots:
|
|||||||
|
|
||||||
vary@1.1.2: {}
|
vary@1.1.2: {}
|
||||||
|
|
||||||
|
webidl-conversions@7.0.0: {}
|
||||||
|
|
||||||
|
whatwg-url@14.2.0:
|
||||||
|
dependencies:
|
||||||
|
tr46: 5.1.0
|
||||||
|
webidl-conversions: 7.0.0
|
||||||
|
|
||||||
yallist@4.0.0: {}
|
yallist@4.0.0: {}
|
||||||
|
|
||||||
yn@3.1.1: {}
|
yn@3.1.1: {}
|
||||||
|
|||||||
637
producer/src/controller.ts
Normal file
637
producer/src/controller.ts
Normal file
@@ -0,0 +1,637 @@
|
|||||||
|
import { DomainWhitelistModel } from "./shared/schema/shields/DomainWhitelistSchema";
|
||||||
|
import { AddressBlacklistModel } from "./shared/schema/shields/AddressBlacklistSchema";
|
||||||
|
import { BotTrafficOptionModel } from "./shared/schema/shields/BotTrafficOptionSchema";
|
||||||
|
|
||||||
|
|
||||||
|
const BOT_PATTERNS = [
|
||||||
|
"Googlebot\\/",
|
||||||
|
"Googlebot-Mobile",
|
||||||
|
"Googlebot-Image",
|
||||||
|
"Googlebot-News",
|
||||||
|
"Googlebot-Video",
|
||||||
|
"AdsBot-Google([^-]|$)",
|
||||||
|
"AdsBot-Google-Mobile",
|
||||||
|
"Feedfetcher-Google",
|
||||||
|
"Mediapartners-Google",
|
||||||
|
"Mediapartners \\(Googlebot\\)",
|
||||||
|
"APIs-Google",
|
||||||
|
"Google-InspectionTool",
|
||||||
|
"Storebot-Google",
|
||||||
|
"GoogleOther",
|
||||||
|
"bingbot",
|
||||||
|
"Slurp",
|
||||||
|
"[wW]get",
|
||||||
|
"LinkedInBot",
|
||||||
|
"Python-urllib",
|
||||||
|
"python-requests",
|
||||||
|
"aiohttp",
|
||||||
|
"httpx",
|
||||||
|
"libwww-perl",
|
||||||
|
"httpunit",
|
||||||
|
"Nutch",
|
||||||
|
"Go-http-client",
|
||||||
|
"phpcrawl",
|
||||||
|
"msnbot",
|
||||||
|
"jyxobot",
|
||||||
|
"FAST-WebCrawler",
|
||||||
|
"FAST Enterprise Crawler",
|
||||||
|
"BIGLOTRON",
|
||||||
|
"Teoma",
|
||||||
|
"convera",
|
||||||
|
"seekbot",
|
||||||
|
"Gigabot",
|
||||||
|
"Gigablast",
|
||||||
|
"exabot",
|
||||||
|
"ia_archiver",
|
||||||
|
"GingerCrawler",
|
||||||
|
"webmon ",
|
||||||
|
"HTTrack",
|
||||||
|
"grub\\.org",
|
||||||
|
"UsineNouvelleCrawler",
|
||||||
|
"antibot",
|
||||||
|
"netresearchserver",
|
||||||
|
"speedy",
|
||||||
|
"fluffy",
|
||||||
|
"findlink",
|
||||||
|
"msrbot",
|
||||||
|
"panscient",
|
||||||
|
"yacybot",
|
||||||
|
"AISearchBot",
|
||||||
|
"ips-agent",
|
||||||
|
"tagoobot",
|
||||||
|
"MJ12bot",
|
||||||
|
"woriobot",
|
||||||
|
"yanga",
|
||||||
|
"buzzbot",
|
||||||
|
"mlbot",
|
||||||
|
"yandex\\.com\\/bots",
|
||||||
|
"purebot",
|
||||||
|
"Linguee Bot",
|
||||||
|
"CyberPatrol",
|
||||||
|
"voilabot",
|
||||||
|
"Baiduspider",
|
||||||
|
"citeseerxbot",
|
||||||
|
"spbot",
|
||||||
|
"twengabot",
|
||||||
|
"postrank",
|
||||||
|
"Turnitin",
|
||||||
|
"scribdbot",
|
||||||
|
"page2rss",
|
||||||
|
"sitebot",
|
||||||
|
"linkdex",
|
||||||
|
"Adidxbot",
|
||||||
|
"ezooms",
|
||||||
|
"dotbot",
|
||||||
|
"Mail\\.RU_Bot",
|
||||||
|
"discobot",
|
||||||
|
"heritrix",
|
||||||
|
"findthatfile",
|
||||||
|
"europarchive\\.org",
|
||||||
|
"NerdByNature\\.Bot",
|
||||||
|
"(sistrix|SISTRIX) [cC]rawler",
|
||||||
|
"Ahrefs(Bot|SiteAudit)",
|
||||||
|
"fuelbot",
|
||||||
|
"CrunchBot",
|
||||||
|
"IndeedBot",
|
||||||
|
"mappydata",
|
||||||
|
"woobot",
|
||||||
|
"ZoominfoBot",
|
||||||
|
"PrivacyAwareBot",
|
||||||
|
"Multiviewbot",
|
||||||
|
"SWIMGBot",
|
||||||
|
"Grobbot",
|
||||||
|
"eright",
|
||||||
|
"Apercite",
|
||||||
|
"semanticbot",
|
||||||
|
"Aboundex",
|
||||||
|
"domaincrawler",
|
||||||
|
"wbsearchbot",
|
||||||
|
"summify",
|
||||||
|
"CCBot",
|
||||||
|
"edisterbot",
|
||||||
|
"SeznamBot",
|
||||||
|
"ec2linkfinder",
|
||||||
|
"gslfbot",
|
||||||
|
"aiHitBot",
|
||||||
|
"intelium_bot",
|
||||||
|
"facebookexternalhit",
|
||||||
|
"Yeti",
|
||||||
|
"RetrevoPageAnalyzer",
|
||||||
|
"lb-spider",
|
||||||
|
"Sogou",
|
||||||
|
"lssbot",
|
||||||
|
"careerbot",
|
||||||
|
"wotbox",
|
||||||
|
"wocbot",
|
||||||
|
"ichiro",
|
||||||
|
"DuckDuckBot",
|
||||||
|
"lssrocketcrawler",
|
||||||
|
"drupact",
|
||||||
|
"webcompanycrawler",
|
||||||
|
"acoonbot",
|
||||||
|
"openindexspider",
|
||||||
|
"gnam gnam spider",
|
||||||
|
"web-archive-net\\.com\\.bot",
|
||||||
|
"backlinkcrawler",
|
||||||
|
"coccoc",
|
||||||
|
"integromedb",
|
||||||
|
"content crawler spider",
|
||||||
|
"toplistbot",
|
||||||
|
"it2media-domain-crawler",
|
||||||
|
"ip-web-crawler\\.com",
|
||||||
|
"siteexplorer\\.info",
|
||||||
|
"elisabot",
|
||||||
|
"proximic",
|
||||||
|
"changedetection",
|
||||||
|
"arabot",
|
||||||
|
"WeSEE:Search",
|
||||||
|
"niki-bot",
|
||||||
|
"CrystalSemanticsBot",
|
||||||
|
"rogerbot",
|
||||||
|
"360Spider",
|
||||||
|
"psbot",
|
||||||
|
"InterfaxScanBot",
|
||||||
|
"CC Metadata Scaper",
|
||||||
|
"g00g1e\\.net",
|
||||||
|
"GrapeshotCrawler",
|
||||||
|
"urlappendbot",
|
||||||
|
"brainobot",
|
||||||
|
"fr-crawler",
|
||||||
|
"binlar",
|
||||||
|
"SimpleCrawler",
|
||||||
|
"Twitterbot",
|
||||||
|
"cXensebot",
|
||||||
|
"smtbot",
|
||||||
|
"bnf\\.fr_bot",
|
||||||
|
"A6-Indexer",
|
||||||
|
"ADmantX",
|
||||||
|
"Facebot",
|
||||||
|
"OrangeBot\\/",
|
||||||
|
"memorybot",
|
||||||
|
"AdvBot",
|
||||||
|
"MegaIndex",
|
||||||
|
"SemanticScholarBot",
|
||||||
|
"ltx71",
|
||||||
|
"nerdybot",
|
||||||
|
"xovibot",
|
||||||
|
"BUbiNG",
|
||||||
|
"Qwantify",
|
||||||
|
"archive\\.org_bot",
|
||||||
|
"Applebot",
|
||||||
|
"TweetmemeBot",
|
||||||
|
"crawler4j",
|
||||||
|
"findxbot",
|
||||||
|
"S[eE][mM]rushBot",
|
||||||
|
"yoozBot",
|
||||||
|
"lipperhey",
|
||||||
|
"Y!J",
|
||||||
|
"Domain Re-Animator Bot",
|
||||||
|
"AddThis",
|
||||||
|
"Screaming Frog SEO Spider",
|
||||||
|
"MetaURI",
|
||||||
|
"Scrapy",
|
||||||
|
"Livelap[bB]ot",
|
||||||
|
"OpenHoseBot",
|
||||||
|
"CapsuleChecker",
|
||||||
|
"collection@infegy\\.com",
|
||||||
|
"IstellaBot",
|
||||||
|
"DeuSu\\/",
|
||||||
|
"betaBot",
|
||||||
|
"Cliqzbot\\/",
|
||||||
|
"MojeekBot\\/",
|
||||||
|
"netEstate NE Crawler",
|
||||||
|
"SafeSearch microdata crawler",
|
||||||
|
"Gluten Free Crawler\\/",
|
||||||
|
"Sonic",
|
||||||
|
"Sysomos",
|
||||||
|
"Trove",
|
||||||
|
"deadlinkchecker",
|
||||||
|
"Slack-ImgProxy",
|
||||||
|
"Embedly",
|
||||||
|
"RankActiveLinkBot",
|
||||||
|
"iskanie",
|
||||||
|
"SafeDNSBot",
|
||||||
|
"SkypeUriPreview",
|
||||||
|
"Veoozbot",
|
||||||
|
"Slackbot",
|
||||||
|
"redditbot",
|
||||||
|
"datagnionbot",
|
||||||
|
"Google-Adwords-Instant",
|
||||||
|
"adbeat_bot",
|
||||||
|
"WhatsApp",
|
||||||
|
"contxbot",
|
||||||
|
"pinterest\\.com\\/bot",
|
||||||
|
"electricmonk",
|
||||||
|
"GarlikCrawler",
|
||||||
|
"BingPreview\\/",
|
||||||
|
"vebidoobot",
|
||||||
|
"FemtosearchBot",
|
||||||
|
"Yahoo Link Preview",
|
||||||
|
"MetaJobBot",
|
||||||
|
"DomainStatsBot",
|
||||||
|
"mindUpBot",
|
||||||
|
"Daum\\/",
|
||||||
|
"Jugendschutzprogramm-Crawler",
|
||||||
|
"Xenu Link Sleuth",
|
||||||
|
"Pcore-HTTP",
|
||||||
|
"moatbot",
|
||||||
|
"KosmioBot",
|
||||||
|
"[pP]ingdom",
|
||||||
|
"AppInsights",
|
||||||
|
"PhantomJS",
|
||||||
|
"Gowikibot",
|
||||||
|
"PiplBot",
|
||||||
|
"Discordbot",
|
||||||
|
"TelegramBot",
|
||||||
|
"Jetslide",
|
||||||
|
"newsharecounts",
|
||||||
|
"James BOT",
|
||||||
|
"Bark[rR]owler",
|
||||||
|
"TinEye",
|
||||||
|
"SocialRankIOBot",
|
||||||
|
"trendictionbot",
|
||||||
|
"Ocarinabot",
|
||||||
|
"epicbot",
|
||||||
|
"Primalbot",
|
||||||
|
"DuckDuckGo-Favicons-Bot",
|
||||||
|
"GnowitNewsbot",
|
||||||
|
"Leikibot",
|
||||||
|
"LinkArchiver",
|
||||||
|
"YaK\\/",
|
||||||
|
"PaperLiBot",
|
||||||
|
"Digg Deeper",
|
||||||
|
"dcrawl",
|
||||||
|
"Snacktory",
|
||||||
|
"AndersPinkBot",
|
||||||
|
"Fyrebot",
|
||||||
|
"EveryoneSocialBot",
|
||||||
|
"Mediatoolkitbot",
|
||||||
|
"Luminator-robots",
|
||||||
|
"ExtLinksBot",
|
||||||
|
"SurveyBot",
|
||||||
|
"NING\\/",
|
||||||
|
"okhttp",
|
||||||
|
"Nuzzel",
|
||||||
|
"omgili",
|
||||||
|
"PocketParser",
|
||||||
|
"YisouSpider",
|
||||||
|
"um-LN",
|
||||||
|
"ToutiaoSpider",
|
||||||
|
"MuckRack",
|
||||||
|
"Jamie's Spider",
|
||||||
|
"AHC\\/",
|
||||||
|
"NetcraftSurveyAgent",
|
||||||
|
"Laserlikebot",
|
||||||
|
"^Apache-HttpClient",
|
||||||
|
"AppEngine-Google",
|
||||||
|
"Jetty",
|
||||||
|
"Upflow",
|
||||||
|
"Thinklab",
|
||||||
|
"Traackr\\.com",
|
||||||
|
"Twurly",
|
||||||
|
"Mastodon",
|
||||||
|
"http_get",
|
||||||
|
"DnyzBot",
|
||||||
|
"botify",
|
||||||
|
"007ac9 Crawler",
|
||||||
|
"BehloolBot",
|
||||||
|
"BrandVerity",
|
||||||
|
"check_http",
|
||||||
|
"BDCbot",
|
||||||
|
"ZumBot",
|
||||||
|
"EZID",
|
||||||
|
"ICC-Crawler",
|
||||||
|
"ArchiveBot",
|
||||||
|
"^LCC ",
|
||||||
|
"filterdb\\.iss\\.net\\/crawler",
|
||||||
|
"BLP_bbot",
|
||||||
|
"BomboraBot",
|
||||||
|
"Buck\\/",
|
||||||
|
"Companybook-Crawler",
|
||||||
|
"Genieo",
|
||||||
|
"magpie-crawler",
|
||||||
|
"MeltwaterNews",
|
||||||
|
"Moreover",
|
||||||
|
"newspaper\\/",
|
||||||
|
"ScoutJet",
|
||||||
|
"(^| )sentry\\/",
|
||||||
|
"StorygizeBot",
|
||||||
|
"UptimeRobot",
|
||||||
|
"OutclicksBot",
|
||||||
|
"seoscanners",
|
||||||
|
"Hatena",
|
||||||
|
"Google Web Preview",
|
||||||
|
"MauiBot",
|
||||||
|
"AlphaBot",
|
||||||
|
"SBL-BOT",
|
||||||
|
"IAS crawler",
|
||||||
|
"adscanner",
|
||||||
|
"Netvibes",
|
||||||
|
"acapbot",
|
||||||
|
"Baidu-YunGuanCe",
|
||||||
|
"bitlybot",
|
||||||
|
"blogmuraBot",
|
||||||
|
"Bot\\.AraTurka\\.com",
|
||||||
|
"bot-pge\\.chlooe\\.com",
|
||||||
|
"BoxcarBot",
|
||||||
|
"BTWebClient",
|
||||||
|
"ContextAd Bot",
|
||||||
|
"Digincore bot",
|
||||||
|
"Disqus",
|
||||||
|
"Feedly",
|
||||||
|
"Fetch\\/",
|
||||||
|
"Fever",
|
||||||
|
"Flamingo_SearchEngine",
|
||||||
|
"FlipboardProxy",
|
||||||
|
"g2reader-bot",
|
||||||
|
"G2 Web Services",
|
||||||
|
"imrbot",
|
||||||
|
"K7MLWCBot",
|
||||||
|
"Kemvibot",
|
||||||
|
"Landau-Media-Spider",
|
||||||
|
"linkapediabot",
|
||||||
|
"vkShare",
|
||||||
|
"Siteimprove\\.com",
|
||||||
|
"BLEXBot\\/",
|
||||||
|
"DareBoost",
|
||||||
|
"ZuperlistBot\\/",
|
||||||
|
"Miniflux\\/",
|
||||||
|
"Feedspot",
|
||||||
|
"Diffbot\\/",
|
||||||
|
"SEOkicks",
|
||||||
|
"tracemyfile",
|
||||||
|
"Nimbostratus-Bot",
|
||||||
|
"zgrab",
|
||||||
|
"PR-CY\\.RU",
|
||||||
|
"AdsTxtCrawler",
|
||||||
|
"Datafeedwatch",
|
||||||
|
"Zabbix",
|
||||||
|
"TangibleeBot",
|
||||||
|
"google-xrawler",
|
||||||
|
"axios",
|
||||||
|
"Amazon CloudFront",
|
||||||
|
"Pulsepoint",
|
||||||
|
"CloudFlare-AlwaysOnline",
|
||||||
|
"Cloudflare-Healthchecks",
|
||||||
|
"Cloudflare-Traffic-Manager",
|
||||||
|
"CloudFlare-Prefetch",
|
||||||
|
"Cloudflare-SSLDetector",
|
||||||
|
"https:\\/\\/developers\\.cloudflare\\.com\\/security-center\\/",
|
||||||
|
"Google-Structured-Data-Testing-Tool",
|
||||||
|
"WordupInfoSearch",
|
||||||
|
"WebDataStats",
|
||||||
|
"HttpUrlConnection",
|
||||||
|
"ZoomBot",
|
||||||
|
"VelenPublicWebCrawler",
|
||||||
|
"MoodleBot",
|
||||||
|
"jpg-newsbot",
|
||||||
|
"outbrain",
|
||||||
|
"W3C_Validator",
|
||||||
|
"Validator\\.nu",
|
||||||
|
"W3C-checklink",
|
||||||
|
"W3C-mobileOK",
|
||||||
|
"W3C_I18n-Checker",
|
||||||
|
"FeedValidator",
|
||||||
|
"W3C_CSS_Validator",
|
||||||
|
"W3C_Unicorn",
|
||||||
|
"Google-PhysicalWeb",
|
||||||
|
"Blackboard",
|
||||||
|
"ICBot\\/",
|
||||||
|
"BazQux",
|
||||||
|
"Twingly",
|
||||||
|
"Rivva",
|
||||||
|
"Experibot",
|
||||||
|
"awesomecrawler",
|
||||||
|
"Dataprovider\\.com",
|
||||||
|
"GroupHigh\\/",
|
||||||
|
"theoldreader\\.com",
|
||||||
|
"AnyEvent",
|
||||||
|
"Uptimebot\\.org",
|
||||||
|
"Nmap Scripting Engine",
|
||||||
|
"2ip\\.ru",
|
||||||
|
"Clickagy",
|
||||||
|
"Caliperbot",
|
||||||
|
"MBCrawler",
|
||||||
|
"online-webceo-bot",
|
||||||
|
"B2B Bot",
|
||||||
|
"AddSearchBot",
|
||||||
|
"Google Favicon",
|
||||||
|
"HubSpot",
|
||||||
|
"Chrome-Lighthouse",
|
||||||
|
"HeadlessChrome",
|
||||||
|
"CheckMarkNetwork\\/",
|
||||||
|
"www\\.uptime\\.com",
|
||||||
|
"Streamline3Bot\\/",
|
||||||
|
"serpstatbot\\/",
|
||||||
|
"MixnodeCache\\/",
|
||||||
|
"^curl",
|
||||||
|
"SimpleScraper",
|
||||||
|
"RSSingBot",
|
||||||
|
"Jooblebot",
|
||||||
|
"fedoraplanet",
|
||||||
|
"Friendica",
|
||||||
|
"NextCloud",
|
||||||
|
"Tiny Tiny RSS",
|
||||||
|
"RegionStuttgartBot",
|
||||||
|
"Bytespider",
|
||||||
|
"Datanyze",
|
||||||
|
"Google-Site-Verification",
|
||||||
|
"TrendsmapResolver",
|
||||||
|
"tweetedtimes",
|
||||||
|
"NTENTbot",
|
||||||
|
"Gwene",
|
||||||
|
"SimplePie",
|
||||||
|
"SearchAtlas",
|
||||||
|
"Superfeedr",
|
||||||
|
"feedbot",
|
||||||
|
"UT-Dorkbot",
|
||||||
|
"Amazonbot",
|
||||||
|
"SerendeputyBot",
|
||||||
|
"Eyeotabot",
|
||||||
|
"officestorebot",
|
||||||
|
"Neticle Crawler",
|
||||||
|
"SurdotlyBot",
|
||||||
|
"LinkisBot",
|
||||||
|
"AwarioSmartBot",
|
||||||
|
"AwarioRssBot",
|
||||||
|
"RyteBot",
|
||||||
|
"FreeWebMonitoring SiteChecker",
|
||||||
|
"AspiegelBot",
|
||||||
|
"NAVER Blog Rssbot",
|
||||||
|
"zenback bot",
|
||||||
|
"SentiBot",
|
||||||
|
"Domains Project\\/",
|
||||||
|
"Pandalytics",
|
||||||
|
"VKRobot",
|
||||||
|
"bidswitchbot",
|
||||||
|
"tigerbot",
|
||||||
|
"NIXStatsbot",
|
||||||
|
"Atom Feed Robot",
|
||||||
|
"[Cc]urebot",
|
||||||
|
"PagePeeker\\/",
|
||||||
|
"Vigil\\/",
|
||||||
|
"rssbot\\/",
|
||||||
|
"startmebot\\/",
|
||||||
|
"JobboerseBot",
|
||||||
|
"seewithkids",
|
||||||
|
"NINJA bot",
|
||||||
|
"Cutbot",
|
||||||
|
"BublupBot",
|
||||||
|
"BrandONbot",
|
||||||
|
"RidderBot",
|
||||||
|
"Taboolabot",
|
||||||
|
"Dubbotbot",
|
||||||
|
"FindITAnswersbot",
|
||||||
|
"infoobot",
|
||||||
|
"Refindbot",
|
||||||
|
"BlogTraffic\\/\\d\\.\\d+ Feed-Fetcher",
|
||||||
|
"SeobilityBot",
|
||||||
|
"Cincraw",
|
||||||
|
"Dragonbot",
|
||||||
|
"VoluumDSP-content-bot",
|
||||||
|
"FreshRSS",
|
||||||
|
"BitBot",
|
||||||
|
"^PHP-Curl-Class",
|
||||||
|
"Google-Certificates-Bridge",
|
||||||
|
"centurybot",
|
||||||
|
"Viber",
|
||||||
|
"e\\.ventures Investment Crawler",
|
||||||
|
"evc-batch",
|
||||||
|
"PetalBot",
|
||||||
|
"virustotal",
|
||||||
|
"(^| )PTST\\/",
|
||||||
|
"minicrawler",
|
||||||
|
"Cookiebot",
|
||||||
|
"trovitBot",
|
||||||
|
"seostar\\.co",
|
||||||
|
"IonCrawl",
|
||||||
|
"Uptime-Kuma",
|
||||||
|
"Seekport",
|
||||||
|
"FreshpingBot",
|
||||||
|
"Feedbin",
|
||||||
|
"CriteoBot",
|
||||||
|
"Snap URL Preview Service",
|
||||||
|
"Better Uptime Bot",
|
||||||
|
"RuxitSynthetic",
|
||||||
|
"Google-Read-Aloud",
|
||||||
|
"Valve\\/Steam",
|
||||||
|
"OdklBot\\/",
|
||||||
|
"GPTBot",
|
||||||
|
"ChatGPT-User",
|
||||||
|
"OAI-SearchBot",
|
||||||
|
"YandexRenderResourcesBot\\/",
|
||||||
|
"LightspeedSystemsCrawler",
|
||||||
|
"ev-crawler\\/",
|
||||||
|
"BitSightBot\\/",
|
||||||
|
"woorankreview\\/",
|
||||||
|
"Google-Safety",
|
||||||
|
"AwarioBot",
|
||||||
|
"DataForSeoBot",
|
||||||
|
"Linespider",
|
||||||
|
"WellKnownBot",
|
||||||
|
"A Patent Crawler",
|
||||||
|
"StractBot",
|
||||||
|
"search\\.marginalia\\.nu",
|
||||||
|
"YouBot",
|
||||||
|
"Nicecrawler",
|
||||||
|
"Neevabot",
|
||||||
|
"BrightEdge Crawler",
|
||||||
|
"SiteCheckerBotCrawler",
|
||||||
|
"TombaPublicWebCrawler",
|
||||||
|
"CrawlyProjectCrawler",
|
||||||
|
"KomodiaBot",
|
||||||
|
"KStandBot",
|
||||||
|
"CISPA Webcrawler",
|
||||||
|
"MTRobot",
|
||||||
|
"hyscore\\.io",
|
||||||
|
"AlexandriaOrgBot",
|
||||||
|
"2ip bot",
|
||||||
|
"Yellowbrandprotectionbot",
|
||||||
|
"SEOlizer",
|
||||||
|
"vuhuvBot",
|
||||||
|
"INETDEX-BOT",
|
||||||
|
"Synapse",
|
||||||
|
"t3versionsBot",
|
||||||
|
"deepnoc",
|
||||||
|
"Cocolyzebot",
|
||||||
|
"hypestat",
|
||||||
|
"ReverseEngineeringBot",
|
||||||
|
"sempi\\.tech",
|
||||||
|
"Iframely",
|
||||||
|
"MetaInspector",
|
||||||
|
"node-fetch",
|
||||||
|
"l9explore",
|
||||||
|
"python-opengraph",
|
||||||
|
"OpenGraphCheck",
|
||||||
|
"developers\\.google\\.com\\/\\+\\/web\\/snippet",
|
||||||
|
"SenutoBot",
|
||||||
|
"MaCoCu",
|
||||||
|
"NewsBlur",
|
||||||
|
"inoreader",
|
||||||
|
"NetSystemsResearch",
|
||||||
|
"PageThing",
|
||||||
|
"WordPress\\/",
|
||||||
|
"PhxBot",
|
||||||
|
"ImagesiftBot",
|
||||||
|
"Expanse",
|
||||||
|
"InternetMeasurement",
|
||||||
|
"^BW\\/",
|
||||||
|
"GeedoBot",
|
||||||
|
"Audisto Crawler",
|
||||||
|
"PerplexityBot\\/",
|
||||||
|
"[cC]laude[bB]ot",
|
||||||
|
"Monsidobot",
|
||||||
|
"GroupMeBot",
|
||||||
|
"Vercelbot",
|
||||||
|
"vercel-screenshot",
|
||||||
|
"facebookcatalog\\/",
|
||||||
|
"meta-externalagent\\/",
|
||||||
|
"meta-externalfetcher\\/",
|
||||||
|
"AcademicBotRTU",
|
||||||
|
"KeybaseBot",
|
||||||
|
"Lemmy",
|
||||||
|
"CookieHubScan",
|
||||||
|
"Hydrozen\\.io",
|
||||||
|
"HTTP Banner Detection",
|
||||||
|
"SummalyBot",
|
||||||
|
"MicrosoftPreview\\/",
|
||||||
|
"GeedoProductSearch",
|
||||||
|
"TikTokSpider"
|
||||||
|
]
|
||||||
|
|
||||||
|
function isBot(userAgent: string) {
|
||||||
|
for (const pattern of BOT_PATTERNS) {
|
||||||
|
const regexp = new RegExp(pattern);
|
||||||
|
const result = userAgent.match(regexp);
|
||||||
|
if (result != null) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function isAllowedToLog(project_id: string, website: string, ip: string, userAgent: string) {
|
||||||
|
|
||||||
|
const blacklistData = await AddressBlacklistModel.find({ project_id }, { address: 1 });
|
||||||
|
for (const blacklistedData of blacklistData) {
|
||||||
|
if (blacklistedData.address == ip) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const botOptions = await BotTrafficOptionModel.findOne({ project_id }, { block: 1 });
|
||||||
|
if (botOptions && botOptions.block) {
|
||||||
|
const isbot = isBot(userAgent);
|
||||||
|
if (isbot) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const whitelist = await DomainWhitelistModel.findOne({ project_id }, { domains: 1 });
|
||||||
|
if (!whitelist) return true;
|
||||||
|
if (!whitelist.domains) return true;
|
||||||
|
if (whitelist.domains.length == 0) return true;
|
||||||
|
|
||||||
|
const allowedDomains = whitelist.domains;
|
||||||
|
for (const allowedDomain of allowedDomains) {
|
||||||
|
const regexpDomain = new RegExp(allowedDomain.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*'));
|
||||||
|
const result = website.match(regexpDomain);
|
||||||
|
if (result != null) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import { Router, json } from "express";
|
|||||||
import { createSessionHash, getIPFromRequest } from "./utils";
|
import { createSessionHash, getIPFromRequest } from "./utils";
|
||||||
import { requireEnv } from "./shared/utils/requireEnv";
|
import { requireEnv } from "./shared/utils/requireEnv";
|
||||||
import { RedisStreamService } from "./shared/services/RedisStreamService";
|
import { RedisStreamService } from "./shared/services/RedisStreamService";
|
||||||
|
import { isAllowedToLog } from "./controller";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@@ -14,6 +15,10 @@ router.post('/keep_alive', json(jsonOptions), async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const ip = getIPFromRequest(req);
|
const ip = getIPFromRequest(req);
|
||||||
const sessionHash = createSessionHash(req.body.website, ip, req.body.userAgent);
|
const sessionHash = createSessionHash(req.body.website, ip, req.body.userAgent);
|
||||||
|
|
||||||
|
const allowed = await isAllowedToLog(req.body.pid, req.body.website, ip, req.body.userAgent);
|
||||||
|
if (!allowed) return res.sendStatus(400);
|
||||||
|
|
||||||
await RedisStreamService.addToStream(streamName, {
|
await RedisStreamService.addToStream(streamName, {
|
||||||
...req.body, _type: 'keep_alive', sessionHash, ip,
|
...req.body, _type: 'keep_alive', sessionHash, ip,
|
||||||
instant: req.body.instant + '',
|
instant: req.body.instant + '',
|
||||||
@@ -32,6 +37,9 @@ router.post('/metrics/push', json(jsonOptions), async (req, res) => {
|
|||||||
const ip = getIPFromRequest(req);
|
const ip = getIPFromRequest(req);
|
||||||
const sessionHash = createSessionHash(req.body.website, ip, req.body.userAgent);
|
const sessionHash = createSessionHash(req.body.website, ip, req.body.userAgent);
|
||||||
|
|
||||||
|
const allowed = await isAllowedToLog(req.body.pid, req.body.website, ip, req.body.userAgent);
|
||||||
|
if (!allowed) return res.sendStatus(400);
|
||||||
|
|
||||||
const { type } = req.body;
|
const { type } = req.body;
|
||||||
|
|
||||||
if (type === 0) {
|
if (type === 0) {
|
||||||
|
|||||||
@@ -14,17 +14,31 @@ const jsonOptions = { limit: '25kb', type: allowAnyType }
|
|||||||
const streamName = requireEnv('STREAM_NAME');
|
const streamName = requireEnv('STREAM_NAME');
|
||||||
|
|
||||||
import DeprecatedRouter from "./deprecated";
|
import DeprecatedRouter from "./deprecated";
|
||||||
|
import { isAllowedToLog } from "./controller";
|
||||||
|
import { connectDatabase } from "./shared/services/DatabaseService";
|
||||||
app.use('/v1', DeprecatedRouter);
|
app.use('/v1', DeprecatedRouter);
|
||||||
|
|
||||||
app.post('/event', express.json(jsonOptions), async (req, res) => {
|
app.post('/event', express.json(jsonOptions), async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
const ip = getIPFromRequest(req);
|
const ip = getIPFromRequest(req);
|
||||||
const sessionHash = createSessionHash(req.body.website, ip, req.body.userAgent);
|
const sessionHash = createSessionHash(req.body.website, ip, req.body.userAgent);
|
||||||
const flowHash = createFlowSessionHash(req.body.pid, ip, req.body.userAgent);
|
const flowHash = createFlowSessionHash(req.body.pid, ip, req.body.userAgent);
|
||||||
|
|
||||||
|
const allowed = await isAllowedToLog(req.body.pid, req.body.website, ip, req.body.userAgent);
|
||||||
|
if (!allowed) return res.sendStatus(400);
|
||||||
|
|
||||||
await RedisStreamService.addToStream(streamName, {
|
await RedisStreamService.addToStream(streamName, {
|
||||||
...req.body, _type: 'event', sessionHash, ip, flowHash,
|
...req.body, _type: 'event', sessionHash, ip, flowHash,
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const duration = Date.now() - startTime;
|
||||||
|
|
||||||
|
await RedisStreamService.METRICS_PRODUCER_onProcess(process.env.NODE_APP_INSTANCE, duration);
|
||||||
|
|
||||||
return res.sendStatus(200);
|
return res.sendStatus(200);
|
||||||
} catch (ex: any) {
|
} catch (ex: any) {
|
||||||
return res.status(500).json({ error: ex.message });
|
return res.status(500).json({ error: ex.message });
|
||||||
@@ -33,10 +47,22 @@ app.post('/event', express.json(jsonOptions), async (req, res) => {
|
|||||||
|
|
||||||
app.post('/visit', express.json(jsonOptions), async (req, res) => {
|
app.post('/visit', express.json(jsonOptions), async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
const ip = getIPFromRequest(req);
|
const ip = getIPFromRequest(req);
|
||||||
const sessionHash = createSessionHash(req.body.website, ip, req.body.userAgent);
|
const sessionHash = createSessionHash(req.body.website, ip, req.body.userAgent);
|
||||||
const flowHash = createFlowSessionHash(req.body.pid, ip, req.body.userAgent);
|
const flowHash = createFlowSessionHash(req.body.pid, ip, req.body.userAgent);
|
||||||
|
|
||||||
|
const allowed = await isAllowedToLog(req.body.pid, req.body.website, ip, req.body.userAgent);
|
||||||
|
if (!allowed) return res.sendStatus(400);
|
||||||
|
|
||||||
await RedisStreamService.addToStream(streamName, { ...req.body, _type: 'visit', sessionHash, ip, flowHash, timestamp: Date.now() });
|
await RedisStreamService.addToStream(streamName, { ...req.body, _type: 'visit', sessionHash, ip, flowHash, timestamp: Date.now() });
|
||||||
|
|
||||||
|
const duration = Date.now() - startTime;
|
||||||
|
|
||||||
|
await RedisStreamService.METRICS_PRODUCER_onProcess(process.env.NODE_APP_INSTANCE, duration);
|
||||||
|
|
||||||
return res.sendStatus(200);
|
return res.sendStatus(200);
|
||||||
} catch (ex: any) {
|
} catch (ex: any) {
|
||||||
return res.status(500).json({ error: ex.message });
|
return res.status(500).json({ error: ex.message });
|
||||||
@@ -45,14 +71,26 @@ app.post('/visit', express.json(jsonOptions), async (req, res) => {
|
|||||||
|
|
||||||
app.post('/keep_alive', express.json(jsonOptions), async (req, res) => {
|
app.post('/keep_alive', express.json(jsonOptions), async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
const ip = getIPFromRequest(req);
|
const ip = getIPFromRequest(req);
|
||||||
const sessionHash = createSessionHash(req.body.website, ip, req.body.userAgent);
|
const sessionHash = createSessionHash(req.body.website, ip, req.body.userAgent);
|
||||||
const flowHash = createFlowSessionHash(req.body.pid, ip, req.body.userAgent);
|
const flowHash = createFlowSessionHash(req.body.pid, ip, req.body.userAgent);
|
||||||
|
|
||||||
|
const allowed = await isAllowedToLog(req.body.pid, req.body.website, ip, req.body.userAgent);
|
||||||
|
if (!allowed) return res.sendStatus(400);
|
||||||
|
|
||||||
await RedisStreamService.addToStream(streamName, {
|
await RedisStreamService.addToStream(streamName, {
|
||||||
...req.body, _type: 'keep_alive', sessionHash, ip,
|
...req.body, _type: 'keep_alive', sessionHash, ip,
|
||||||
instant: req.body.instant + '',
|
instant: req.body.instant + '',
|
||||||
flowHash, timestamp: Date.now()
|
flowHash, timestamp: Date.now()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const duration = Date.now() - startTime;
|
||||||
|
|
||||||
|
await RedisStreamService.METRICS_PRODUCER_onProcess(process.env.NODE_APP_INSTANCE, duration);
|
||||||
|
|
||||||
return res.sendStatus(200);
|
return res.sendStatus(200);
|
||||||
} catch (ex: any) {
|
} catch (ex: any) {
|
||||||
return res.status(500).json({ error: ex.message });
|
return res.status(500).json({ error: ex.message });
|
||||||
@@ -61,6 +99,7 @@ app.post('/keep_alive', express.json(jsonOptions), async (req, res) => {
|
|||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const PORT = requireEnv("PORT");
|
const PORT = requireEnv("PORT");
|
||||||
|
await connectDatabase(process.env.MONGO_CONNECTION_STRING);
|
||||||
await RedisStreamService.connect();
|
await RedisStreamService.connect();
|
||||||
app.listen(PORT, () => console.log(`Listening on port ${PORT}`));
|
app.listen(PORT, () => console.log(`Listening on port ${PORT}`));
|
||||||
}
|
}
|
||||||
|
|||||||
87
scripts/payments/deploy.ts
Normal file
87
scripts/payments/deploy.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
|
||||||
|
// import fs from 'fs-extra';
|
||||||
|
// import path from 'path';
|
||||||
|
// import child from 'child_process';
|
||||||
|
// import { createZip } from '../helpers/zip-helper';
|
||||||
|
// import { DeployHelper } from '../helpers/deploy-helper';
|
||||||
|
// import { DATABASE_CONNECTION_STRING_PRODUCTION, DATABASE_CONNECTION_STRING_TESTMODE, REMOTE_HOST_TESTMODE } from '../.config';
|
||||||
|
|
||||||
|
// const TMP_PATH = path.join(__dirname, '../../tmp');
|
||||||
|
// const LOCAL_PATH = path.join(__dirname, '../../consumer');
|
||||||
|
// const REMOTE_PATH = '/home/litlyx/consumer';
|
||||||
|
// const ZIP_NAME = 'consumer.zip';
|
||||||
|
|
||||||
|
// const MODE = DeployHelper.getMode();
|
||||||
|
// const SKIP_BUILD = DeployHelper.getArgAt(0) == '--no-build';
|
||||||
|
|
||||||
|
// console.log('Deploying consumer in mode:', MODE);
|
||||||
|
|
||||||
|
// setTimeout(() => { main(); }, 3000);
|
||||||
|
|
||||||
|
// async function main() {
|
||||||
|
|
||||||
|
// if (fs.existsSync(TMP_PATH)) fs.rmSync(TMP_PATH, { force: true, recursive: true });
|
||||||
|
// fs.ensureDirSync(TMP_PATH);
|
||||||
|
|
||||||
|
|
||||||
|
// if (!SKIP_BUILD) {
|
||||||
|
// console.log('Building');
|
||||||
|
// child.execSync(`cd ${LOCAL_PATH} && pnpm run build`);
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// console.log('Creting zip file');
|
||||||
|
// const archive = createZip(TMP_PATH + '/' + ZIP_NAME);
|
||||||
|
// archive.directory(LOCAL_PATH + '/dist', '/dist');
|
||||||
|
|
||||||
|
// if (MODE === 'testmode') {
|
||||||
|
// const ecosystemContent = fs.readFileSync(LOCAL_PATH + '/ecosystem.config.js', 'utf8');
|
||||||
|
// const REDIS_URL = ecosystemContent.match(/REDIS_URL: ["'](.*?)["']/)[1];
|
||||||
|
// const devContent = ecosystemContent
|
||||||
|
// .replace(REDIS_URL, `redis://${REMOTE_HOST_TESTMODE}`)
|
||||||
|
// .replace(DATABASE_CONNECTION_STRING_PRODUCTION, `redis://${DATABASE_CONNECTION_STRING_TESTMODE}`);
|
||||||
|
// archive.append(Buffer.from(devContent), { name: '/ecosystem.config.js' });
|
||||||
|
// } else {
|
||||||
|
// archive.file(LOCAL_PATH + '/ecosystem.config.js', { name: '/ecosystem.config.js' })
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// archive.file(LOCAL_PATH + '/package.json', { name: '/package.json' });
|
||||||
|
// archive.file(LOCAL_PATH + '/pnpm-lock.yaml', { name: '/pnpm-lock.yaml' });
|
||||||
|
// await archive.finalize();
|
||||||
|
|
||||||
|
// await DeployHelper.connect();
|
||||||
|
|
||||||
|
// const { scp, ssh } = DeployHelper.instances();
|
||||||
|
|
||||||
|
// console.log('Creating remote structure');
|
||||||
|
// console.log('Check existing');
|
||||||
|
// const remoteExist = await scp.exists(REMOTE_PATH);
|
||||||
|
// console.log('Exist', remoteExist);
|
||||||
|
// if (remoteExist) {
|
||||||
|
// console.log('Deleting');
|
||||||
|
// await DeployHelper.execute(`rm -r ${REMOTE_PATH}`);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// console.log('Creating folder');
|
||||||
|
// await scp.mkdir(REMOTE_PATH);
|
||||||
|
|
||||||
|
// console.log('Uploading zip file');
|
||||||
|
// await scp.uploadFile(TMP_PATH + '/' + ZIP_NAME, REMOTE_PATH + '/' + ZIP_NAME);
|
||||||
|
// scp.close();
|
||||||
|
|
||||||
|
// console.log('Cleaning local');
|
||||||
|
// fs.rmSync(TMP_PATH + '/' + ZIP_NAME, { force: true, recursive: true });
|
||||||
|
|
||||||
|
// console.log('Extracting remote');
|
||||||
|
// await DeployHelper.execute(`cd ${REMOTE_PATH} && unzip ${ZIP_NAME} && rm -r ${ZIP_NAME}`);
|
||||||
|
|
||||||
|
// console.log('Installing remote');
|
||||||
|
// await DeployHelper.execute(`cd ${REMOTE_PATH} && /root/.nvm/versions/node/v21.2.0/bin/pnpm i`);
|
||||||
|
|
||||||
|
// console.log('Executing remote');
|
||||||
|
// await DeployHelper.execute(`cd ${REMOTE_PATH} && /root/.nvm/versions/node/v21.2.0/bin/pm2 start ecosystem.config.js`);
|
||||||
|
|
||||||
|
// ssh.dispose();
|
||||||
|
|
||||||
|
// }
|
||||||
23
scripts/payments/shared.ts
Normal file
23
scripts/payments/shared.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
import { SharedHelper } from "../helpers/shared-helper";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
const helper = new SharedHelper(path.join(__dirname, '../../payments/src/shared'))
|
||||||
|
|
||||||
|
helper.clear();
|
||||||
|
|
||||||
|
helper.create('utils');
|
||||||
|
helper.copy('utils/requireEnv.ts');
|
||||||
|
|
||||||
|
helper.create('services');
|
||||||
|
helper.copy('services/DatabaseService.ts');
|
||||||
|
helper.copy('services/EmailService.ts');
|
||||||
|
|
||||||
|
helper.create('schema');
|
||||||
|
helper.copy('schema/UserSchema.ts');
|
||||||
|
|
||||||
|
helper.create('schema/project');
|
||||||
|
helper.copy('schema/project/ProjectSchema.ts');
|
||||||
|
|
||||||
|
helper.create('data');
|
||||||
|
helper.copy('data/PLANS.ts');
|
||||||
@@ -4,7 +4,7 @@ import path from 'path';
|
|||||||
import child from 'child_process';
|
import child from 'child_process';
|
||||||
import { createZip } from '../helpers/zip-helper';
|
import { createZip } from '../helpers/zip-helper';
|
||||||
import { DeployHelper } from '../helpers/deploy-helper';
|
import { DeployHelper } from '../helpers/deploy-helper';
|
||||||
import { REMOTE_HOST_TESTMODE } from '../.config';
|
import { DATABASE_CONNECTION_STRING_PRODUCTION, DATABASE_CONNECTION_STRING_TESTMODE, REMOTE_HOST_TESTMODE } from '../.config';
|
||||||
|
|
||||||
const TMP_PATH = path.join(__dirname, '../../tmp');
|
const TMP_PATH = path.join(__dirname, '../../tmp');
|
||||||
const LOCAL_PATH = path.join(__dirname, '../../producer');
|
const LOCAL_PATH = path.join(__dirname, '../../producer');
|
||||||
@@ -37,7 +37,9 @@ async function main() {
|
|||||||
if (MODE === 'testmode') {
|
if (MODE === 'testmode') {
|
||||||
const ecosystemContent = fs.readFileSync(LOCAL_PATH + '/ecosystem.config.js', 'utf8');
|
const ecosystemContent = fs.readFileSync(LOCAL_PATH + '/ecosystem.config.js', 'utf8');
|
||||||
const REDIS_URL = ecosystemContent.match(/REDIS_URL: ["'](.*?)["']/)[1];
|
const REDIS_URL = ecosystemContent.match(/REDIS_URL: ["'](.*?)["']/)[1];
|
||||||
const devContent = ecosystemContent.replace(REDIS_URL, `redis://${REMOTE_HOST_TESTMODE}`);
|
const devContent = ecosystemContent
|
||||||
|
.replace(REDIS_URL, `redis://${REMOTE_HOST_TESTMODE}`)
|
||||||
|
.replace(DATABASE_CONNECTION_STRING_PRODUCTION, `redis://${DATABASE_CONNECTION_STRING_TESTMODE}`);
|
||||||
archive.append(Buffer.from(devContent), { name: '/ecosystem.config.js' });
|
archive.append(Buffer.from(devContent), { name: '/ecosystem.config.js' });
|
||||||
} else {
|
} else {
|
||||||
archive.file(LOCAL_PATH + '/ecosystem.config.js', { name: '/ecosystem.config.js' })
|
archive.file(LOCAL_PATH + '/ecosystem.config.js', { name: '/ecosystem.config.js' })
|
||||||
|
|||||||
@@ -11,3 +11,12 @@ helper.copy('utils/requireEnv.ts');
|
|||||||
|
|
||||||
helper.create('services');
|
helper.create('services');
|
||||||
helper.copy('services/RedisStreamService.ts');
|
helper.copy('services/RedisStreamService.ts');
|
||||||
|
helper.copy('services/DatabaseService.ts');
|
||||||
|
|
||||||
|
helper.create('schema');
|
||||||
|
helper.create('schema/shields');
|
||||||
|
helper.copy('schema/shields/AddressBlacklistSchema.ts');
|
||||||
|
helper.copy('schema/shields/BotTrafficOptionSchema.ts');
|
||||||
|
helper.copy('schema/shields/CountryBlacklistSchema.ts');
|
||||||
|
helper.copy('schema/shields/DomainWhitelistSchema.ts');
|
||||||
|
helper.copy('schema/shields/PageBlacklistSchema.ts');
|
||||||
|
|||||||
201
shared_global/data/PLANS.ts
Normal file
201
shared_global/data/PLANS.ts
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
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);
|
||||||
@@ -9,7 +9,8 @@ const templateMap = {
|
|||||||
limit_90: '/limit/90',
|
limit_90: '/limit/90',
|
||||||
limit_max: '/limit/max',
|
limit_max: '/limit/max',
|
||||||
invite_project: '/invite',
|
invite_project: '/invite',
|
||||||
invite_project_noaccount: '/invite/noaccount'
|
invite_project_noaccount: '/invite/noaccount',
|
||||||
|
brevolist_add: '/brevolist/add'
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type EmailTemplate = keyof typeof templateMap;
|
export type EmailTemplate = keyof typeof templateMap;
|
||||||
@@ -27,6 +28,7 @@ type EmailData =
|
|||||||
| { template: 'limit_max', 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', data: { target: string, projectName: string, link: string } }
|
||||||
| { template: 'invite_project_noaccount', data: { target: string, projectName: string, link: string } }
|
| { template: 'invite_project_noaccount', data: { target: string, projectName: string, link: string } }
|
||||||
|
| { template: 'brevolist_add', data: { email: string } }
|
||||||
|
|
||||||
export class EmailService {
|
export class EmailService {
|
||||||
static getEmailServerInfo<T extends EmailTemplate>(template: T, data: Extract<EmailData, { template: T }>['data']): EmailServerInfo {
|
static getEmailServerInfo<T extends EmailTemplate>(template: T, data: Extract<EmailData, { template: T }>['data']): EmailServerInfo {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export class RedisStreamService {
|
|||||||
|
|
||||||
|
|
||||||
private static METRICS_MAX_ENTRIES = 1000;
|
private static METRICS_MAX_ENTRIES = 1000;
|
||||||
|
private static METRICS_MAX_ENTRIES_PRODUCER = 1000;
|
||||||
|
|
||||||
static async METRICS_onProcess(id: string, time: number) {
|
static async METRICS_onProcess(id: string, time: number) {
|
||||||
const key = `___dev_metrics`;
|
const key = `___dev_metrics`;
|
||||||
@@ -39,6 +40,18 @@ export class RedisStreamService {
|
|||||||
return data.map(e => e.split(':')) as [string, string][];
|
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() {
|
static async connect() {
|
||||||
await this.client.connect();
|
await this.client.connect();
|
||||||
|
|||||||
Reference in New Issue
Block a user