mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-10 07:48:37 +01:00
shields update
This commit is contained in:
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 [];
|
||||||
@@ -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>
|
||||||
|
|||||||
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 rejecting traffic from non-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/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 { DialogShieldsAddDomain, DialogShieldsDeleteDomain } from '#components';
|
||||||
|
|
||||||
|
definePageMeta({ layout: 'dashboard' });
|
||||||
|
|
||||||
|
const { project } = useProject();
|
||||||
|
|
||||||
|
const { data: blackAddresses, refresh: refreshAddresses, pending: pendingAddresses } = useFetch('/api/shields/ip/list', {
|
||||||
|
headers: useComputedHeaders({})
|
||||||
|
});
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
const modal = useModal();
|
||||||
|
|
||||||
|
function showAddDomainModal() {
|
||||||
|
modal.open(DialogShieldsAddDomain, {
|
||||||
|
onSuccess: () => {
|
||||||
|
refreshAddresses();
|
||||||
|
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: () => {
|
||||||
|
refreshAddresses();
|
||||||
|
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"> IP Block Llist </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="showAddDomainModal()"> Add Domain </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 class="col-span-3">Domain</div>
|
||||||
|
<div>Actions</div>
|
||||||
|
<LyxUiSeparator class="col-span-4 my-3"></LyxUiSeparator>
|
||||||
|
<template v-for="domain of blackAddresses">
|
||||||
|
<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>
|
||||||
101
dashboard/components/shields/Domains.vue
Normal file
101
dashboard/components/shields/Domains.vue
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { DialogShieldsAddDomain, DialogShieldsDeleteDomain } from '#components';
|
||||||
|
|
||||||
|
definePageMeta({ layout: 'dashboard' });
|
||||||
|
|
||||||
|
const { project } = useProject();
|
||||||
|
|
||||||
|
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>
|
||||||
@@ -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 },
|
||||||
|
|||||||
38
dashboard/pages/shields.vue
Normal file
38
dashboard/pages/shields.vue
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<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: '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>
|
||||||
|
<div class="flex items-center justify-center py-20 text-[1.2rem]"> Coming soon </div>
|
||||||
|
<!-- <ShieldsAddresses></ShieldsAddresses> -->
|
||||||
|
</template>
|
||||||
|
<template #countries>
|
||||||
|
<div class="flex items-center justify-center py-20 text-[1.2rem]"> Coming soon </div>
|
||||||
|
</template>
|
||||||
|
<template #pages>
|
||||||
|
<div class="flex items-center justify-center py-20 text-[1.2rem]"> Coming soon </div>
|
||||||
|
</template>
|
||||||
|
</CustomTab>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
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());
|
||||||
|
});
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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: {}
|
||||||
|
|||||||
13
producer/src/controller.ts
Normal file
13
producer/src/controller.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { DomainWhitelistModel } from "./shared/schema/shields/DomainWhitelistSchema";
|
||||||
|
|
||||||
|
export async function isAllowedToLog(project_id: string, website: string) {
|
||||||
|
const whitelist = await DomainWhitelistModel.findOne({ project_id }, { website: 1 });
|
||||||
|
if (!whitelist) return;
|
||||||
|
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);
|
||||||
|
if (!allowed) return res.status(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);
|
||||||
|
if (!allowed) return res.status(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);
|
||||||
|
if (!allowed) return res.status(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);
|
||||||
|
if (!allowed) return res.status(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);
|
||||||
|
if (!allowed) return res.status(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}`));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,8 @@ 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/DomainWhitelistSchema.ts');
|
||||||
|
|||||||
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/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