Compare commits
3 Commits
v0.3.0-rc.
...
channels
| Author | SHA1 | Date | |
|---|---|---|---|
| 92f7bae50d | |||
| 7b2cfb4b56 | |||
| 65583b1564 |
Binary file not shown.
7
client/app/components.d.ts
vendored
7
client/app/components.d.ts
vendored
@@ -13,20 +13,13 @@ declare module 'vue' {
|
|||||||
PrimeButton: typeof import('primevue/button')['default']
|
PrimeButton: typeof import('primevue/button')['default']
|
||||||
PrimeButtonGroup: typeof import('primevue/buttongroup')['default']
|
PrimeButtonGroup: typeof import('primevue/buttongroup')['default']
|
||||||
PrimeCard: typeof import('primevue/card')['default']
|
PrimeCard: typeof import('primevue/card')['default']
|
||||||
PrimeDivider: typeof import('primevue/divider')['default']
|
|
||||||
PrimeFloatLabel: typeof import('primevue/floatlabel')['default']
|
PrimeFloatLabel: typeof import('primevue/floatlabel')['default']
|
||||||
PrimeInputGroup: typeof import('primevue/inputgroup')['default']
|
|
||||||
PrimeInputText: typeof import('primevue/inputtext')['default']
|
PrimeInputText: typeof import('primevue/inputtext')['default']
|
||||||
PrimePassword: typeof import('primevue/password')['default']
|
PrimePassword: typeof import('primevue/password')['default']
|
||||||
PrimeProgressBar: typeof import('primevue/progressbar')['default']
|
|
||||||
PrimeScrollPanel: typeof import('primevue/scrollpanel')['default']
|
PrimeScrollPanel: typeof import('primevue/scrollpanel')['default']
|
||||||
PrimeSelect: typeof import('primevue/select')['default']
|
|
||||||
PrimeSelectButton: typeof import('primevue/selectbutton')['default']
|
PrimeSelectButton: typeof import('primevue/selectbutton')['default']
|
||||||
PrimeSlider: typeof import('primevue/slider')['default']
|
PrimeSlider: typeof import('primevue/slider')['default']
|
||||||
PrimeTag: typeof import('primevue/tag')['default']
|
|
||||||
PrimeTextarea: typeof import('primevue/textarea')['default']
|
|
||||||
PrimeToast: typeof import('primevue/toast')['default']
|
PrimeToast: typeof import('primevue/toast')['default']
|
||||||
PrimeToggleSwitch: typeof import('primevue/toggleswitch')['default']
|
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { getVersion } from '@tauri-apps/api/app'
|
import { getVersion } from '@tauri-apps/api/app'
|
||||||
import { openUrl as tauriOpenUrl } from '@tauri-apps/plugin-opener'
|
|
||||||
import { computedAsync, createGlobalState } from '@vueuse/core'
|
import { computedAsync, createGlobalState } from '@vueuse/core'
|
||||||
import { useClients } from '~/composables/use-clients'
|
import { useClients } from '~/composables/use-clients'
|
||||||
|
|
||||||
@@ -134,15 +134,6 @@ export const useApp = createGlobalState(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openUrl(href: string) {
|
|
||||||
if (isTauri.value) {
|
|
||||||
await tauriOpenUrl(href)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
window.open(href, '_blank', 'noopener noreferrer')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ready,
|
ready,
|
||||||
clients,
|
clients,
|
||||||
@@ -162,6 +153,5 @@ export const useApp = createGlobalState(() => {
|
|||||||
videoEnabled,
|
videoEnabled,
|
||||||
sharingEnabled,
|
sharingEnabled,
|
||||||
somebodyStreamingVideo,
|
somebodyStreamingVideo,
|
||||||
openUrl,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
import { createGlobalState } from '@vueuse/core'
|
|
||||||
|
|
||||||
export interface ChatClientMessage {
|
|
||||||
text: string
|
|
||||||
replyTo?: {
|
|
||||||
messageId: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChatMessage {
|
|
||||||
id: string
|
|
||||||
sender: string
|
|
||||||
text: string
|
|
||||||
createdAt: string
|
|
||||||
replyTo?: {
|
|
||||||
messageId: string
|
|
||||||
sender: string
|
|
||||||
text: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useChat = createGlobalState(() => {
|
|
||||||
const signaling = useSignaling()
|
|
||||||
const { emit } = useEventBus()
|
|
||||||
|
|
||||||
const messages = shallowRef<ChatMessage[]>([])
|
|
||||||
|
|
||||||
watch(signaling.socket, (socket) => {
|
|
||||||
if (!socket)
|
|
||||||
return
|
|
||||||
|
|
||||||
socket.on('chat:new-message', (message: ChatMessage) => {
|
|
||||||
messages.value.push(message)
|
|
||||||
triggerRef(messages)
|
|
||||||
|
|
||||||
emit('chat:new-message')
|
|
||||||
})
|
|
||||||
|
|
||||||
socket.on('disconnect', () => {
|
|
||||||
messages.value = []
|
|
||||||
})
|
|
||||||
}, { immediate: true })
|
|
||||||
|
|
||||||
function sendMessage(message: ChatClientMessage) {
|
|
||||||
if (!signaling.connected.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
message.text = message.text.trim()
|
|
||||||
|
|
||||||
if (!message.text.length)
|
|
||||||
return
|
|
||||||
|
|
||||||
signaling.socket.value!.emit('chat:message', message)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
messages,
|
|
||||||
sendMessage,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -29,8 +29,6 @@ export interface AppEvents extends Record<EventType, unknown> {
|
|||||||
'video:disabled': void
|
'video:disabled': void
|
||||||
'share:enabled': void
|
'share:enabled': void
|
||||||
'share:disabled': void
|
'share:disabled': void
|
||||||
|
|
||||||
'chat:new-message': void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const emitter = mitt<AppEvents>()
|
const emitter = mitt<AppEvents>()
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export interface SyncedPreferences {
|
|||||||
export const usePreferences = createGlobalState(() => {
|
export const usePreferences = createGlobalState(() => {
|
||||||
const { videoInputs, audioInputs, audioOutputs } = useDevices()
|
const { videoInputs, audioInputs, audioOutputs } = useDevices()
|
||||||
|
|
||||||
const synced = ref(false)
|
const fetched = ref(false)
|
||||||
|
|
||||||
const inputDeviceId = useLocalStorage<MediaDeviceInfo['deviceId']>('INPUT_DEVICE_ID', 'default')
|
const inputDeviceId = useLocalStorage<MediaDeviceInfo['deviceId']>('INPUT_DEVICE_ID', 'default')
|
||||||
const outputDeviceId = useLocalStorage<MediaDeviceInfo['deviceId']>('OUTPUT_DEVICE_ID', 'default')
|
const outputDeviceId = useLocalStorage<MediaDeviceInfo['deviceId']>('OUTPUT_DEVICE_ID', 'default')
|
||||||
@@ -58,7 +58,7 @@ export const usePreferences = createGlobalState(() => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
synced,
|
fetched,
|
||||||
inputDeviceId,
|
inputDeviceId,
|
||||||
outputDeviceId,
|
outputDeviceId,
|
||||||
videoDeviceId,
|
videoDeviceId,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ function hashStringToNumber(str: string, cap: number): number {
|
|||||||
|
|
||||||
const oneShots: Howl[] = []
|
const oneShots: Howl[] = []
|
||||||
|
|
||||||
type SfxEvent = 'mic-on' | 'mic-off' | 'stream-on' | 'stream-off' | 'connection' | 'message'
|
type SfxEvent = 'mic-on' | 'mic-off' | 'stream-on' | 'stream-off' | 'connection'
|
||||||
|
|
||||||
const EVENT_VOLUME: Record<SfxEvent, number> = {
|
const EVENT_VOLUME: Record<SfxEvent, number> = {
|
||||||
'mic-on': 0.2,
|
'mic-on': 0.2,
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export const useSignaling = createSharedComposable(() => {
|
|||||||
|
|
||||||
const uri = host ? `${protocol}//${host}` : ``
|
const uri = host ? `${protocol}//${host}` : ``
|
||||||
|
|
||||||
socket.value = io(uri, {
|
socket.value = io(`${uri}`, {
|
||||||
path: `${pathname}/ws`,
|
path: `${pathname}/ws`,
|
||||||
transports: ['websocket'],
|
transports: ['websocket'],
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<div v-auto-animate class="w-1/2 m-auto">
|
<div v-auto-animate class="w-1/2 m-auto">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<PrimeSelectButton v-model="tab" class="mb-6" :options="options" option-label="label" :allow-empty="false" />
|
<PrimeSelectButton :model-value="tab" class="mb-6" :options="options" option-label="label" :allow-empty="false" @update:model-value="onTabChanged" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<slot />
|
<slot />
|
||||||
@@ -15,28 +15,35 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { RouteLocationRaw } from 'vue-router'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
const { version } = useApp()
|
const { version } = useApp()
|
||||||
|
|
||||||
|
interface Tab {
|
||||||
|
label: string
|
||||||
|
route: RouteLocationRaw
|
||||||
|
}
|
||||||
|
|
||||||
const options = computed(() => {
|
const options = computed(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: 'Login',
|
label: 'Login',
|
||||||
routeName: 'Login',
|
route: { name: 'Login' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Register',
|
label: 'Register',
|
||||||
routeName: 'Register',
|
route: { name: 'Register' },
|
||||||
},
|
},
|
||||||
]
|
] as Tab[]
|
||||||
})
|
})
|
||||||
|
|
||||||
const tab = shallowRef(options.value.find(option => route.name === option.routeName))
|
const tab = computed(() => {
|
||||||
|
return options.value.find(option => route.name === router.resolve(option.route)?.name)
|
||||||
watch(tab, (tab) => {
|
|
||||||
if (!tab)
|
|
||||||
return
|
|
||||||
|
|
||||||
navigateTo({ name: tab.routeName })
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function onTabChanged(tab: Tab) {
|
||||||
|
navigateTo(tab.route)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -56,9 +56,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</PrimeScrollPanel>
|
</PrimeScrollPanel>
|
||||||
|
|
||||||
<div class="bg-surface-900 rounded-xl overflow-hidden p-3 flex flex-col min-h-full">
|
<PrimeScrollPanel class="bg-surface-900 rounded-xl overflow-hidden" style="min-height: 0">
|
||||||
<slot />
|
<div class="p-3">
|
||||||
</div>
|
<slot />
|
||||||
|
</div>
|
||||||
|
</PrimeScrollPanel>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FullscreenGallery />
|
<FullscreenGallery />
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ export default defineNuxtRouteMiddleware(async () => {
|
|||||||
if (!me.value)
|
if (!me.value)
|
||||||
return
|
return
|
||||||
|
|
||||||
const { synced, toggleInputHotkey, toggleOutputHotkey } = usePreferences()
|
const { fetched, toggleInputHotkey, toggleOutputHotkey } = usePreferences()
|
||||||
|
|
||||||
if (synced.value)
|
if (fetched.value)
|
||||||
return
|
return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -20,7 +20,7 @@ export default defineNuxtRouteMiddleware(async () => {
|
|||||||
|
|
||||||
toggleInputHotkey.value = preferences.toggleInputHotkey ?? toggleInputHotkey.value
|
toggleInputHotkey.value = preferences.toggleInputHotkey ?? toggleInputHotkey.value
|
||||||
toggleOutputHotkey.value = preferences.toggleOutputHotkey ?? toggleOutputHotkey.value
|
toggleOutputHotkey.value = preferences.toggleOutputHotkey ?? toggleOutputHotkey.value
|
||||||
synced.value = true
|
fetched.value = true
|
||||||
}
|
}
|
||||||
catch {}
|
catch {}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<PrimeScrollPanel class="grid grid-cols-[1fr_1fr] gap-2 min-h-0">
|
<div class="grid grid-cols-[1fr_1fr] gap-2">
|
||||||
<GalleryCard
|
<GalleryCard
|
||||||
v-for="producer in producers"
|
v-for="producer in producers"
|
||||||
:key="`producer-${producer.id}`"
|
:key="`producer-${producer.id}`"
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
:client="consumer.client"
|
:client="consumer.client"
|
||||||
:consumer="consumer.consumer"
|
:consumer="consumer.consumer"
|
||||||
/>
|
/>
|
||||||
</PrimeScrollPanel>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|||||||
@@ -1,161 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<p v-if="!messages.length" class="text-muted-color text-center m-auto">
|
<div>
|
||||||
Chat is empty
|
<div class="flex items-center justify-center">
|
||||||
</p>
|
<PrimeCard>
|
||||||
|
<template #content>
|
||||||
<PrimeScrollPanel v-else ref="scroll" class="flex-1 min-h-0 overflow-x-hidden">
|
The chat is under development.
|
||||||
<div class="flex flex-col gap-3">
|
</template>
|
||||||
<div
|
</PrimeCard>
|
||||||
v-for="message in messages"
|
|
||||||
:key="message.id"
|
|
||||||
class="w-fit max-w-[60%]"
|
|
||||||
:class="{
|
|
||||||
'ml-auto': message.sender === me?.username,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
v-if="message.sender !== me?.username"
|
|
||||||
class="text-sm text-muted-color mb-1"
|
|
||||||
>
|
|
||||||
{{ message.sender }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="px-3 py-2 rounded-lg"
|
|
||||||
:class="{
|
|
||||||
'bg-surface-800': message.sender !== me?.username,
|
|
||||||
'bg-surface-700': message.sender === me?.username,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<p class="[&>a]:break-all" @click="handleMessageClick" v-html="parseMessageText(message.text)" />
|
|
||||||
|
|
||||||
<p class="mt-1 text-right text-sm text-muted-color" :title="formatDate(message.createdAt, 'dd.MM.yyyy, HH:mm')">
|
|
||||||
{{ formatDate(message.createdAt) }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</PrimeScrollPanel>
|
|
||||||
|
|
||||||
<div class="mt-3 shrink-0">
|
|
||||||
<PrimeInputGroup>
|
|
||||||
<!-- <PrimeInputGroupAddon> -->
|
|
||||||
<!-- <PrimeButton severity="secondary" class="shrink-0" disabled> -->
|
|
||||||
<!-- <template #icon> -->
|
|
||||||
<!-- <Paperclip /> -->
|
|
||||||
<!-- </template> -->
|
|
||||||
<!-- </PrimeButton> -->
|
|
||||||
<!-- </PrimeInputGroupAddon> -->
|
|
||||||
|
|
||||||
<PrimeInputText
|
|
||||||
v-model="text"
|
|
||||||
placeholder="Write a message..."
|
|
||||||
fluid
|
|
||||||
autocomplete="off"
|
|
||||||
@keydown.enter.exact="sendMessage"
|
|
||||||
@vue:mounted="onInputMounted"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PrimeButton class="shrink-0" label="Send" severity="contrast" @click="sendMessage" />
|
|
||||||
</PrimeInputGroup>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useEventBus } from '#imports'
|
|
||||||
import { format } from 'date-fns'
|
|
||||||
import linkifyStr from 'linkify-string'
|
|
||||||
import { useChat } from '~/composables/use-chat'
|
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
name: 'Index',
|
name: 'Index',
|
||||||
})
|
})
|
||||||
|
|
||||||
const { openUrl } = useApp()
|
|
||||||
const { me } = useClients()
|
|
||||||
const chat = useChat()
|
|
||||||
const eventBus = useEventBus()
|
|
||||||
const { messages } = chat
|
|
||||||
|
|
||||||
const scrollRef = useTemplateRef('scroll')
|
|
||||||
const contentRef = computed(() => scrollRef.value?.$refs.content)
|
|
||||||
|
|
||||||
const text = ref('')
|
|
||||||
|
|
||||||
eventBus.on('chat:new-message', onNewMessage)
|
|
||||||
|
|
||||||
onScopeDispose(() => {
|
|
||||||
eventBus.off('chat:new-message', onNewMessage)
|
|
||||||
})
|
|
||||||
|
|
||||||
function parseMessageText(text: string) {
|
|
||||||
return linkifyStr(
|
|
||||||
text,
|
|
||||||
{
|
|
||||||
className: 'underline',
|
|
||||||
rel: 'noopener noreferrer',
|
|
||||||
target: '_blank',
|
|
||||||
},
|
|
||||||
).replaceAll('\n', '<br>')
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatDate(date: string, formatStr = 'HH:mm') {
|
|
||||||
return format(date, formatStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendMessage() {
|
|
||||||
if (!text.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
chat.sendMessage({
|
|
||||||
text: text.value,
|
|
||||||
})
|
|
||||||
|
|
||||||
text.value = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
function onInputMounted(ref: VNode) {
|
|
||||||
ref.el?.focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
const ARRIVED_STATE_THRESHOLD_PIXELS = 1
|
|
||||||
async function onNewMessage() {
|
|
||||||
if (!contentRef.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
const arrivedBottom = contentRef.value.scrollTop + contentRef.value.clientHeight
|
|
||||||
>= contentRef.value.scrollHeight - ARRIVED_STATE_THRESHOLD_PIXELS
|
|
||||||
|
|
||||||
const scrollable = contentRef.value.scrollHeight > contentRef.value.clientHeight
|
|
||||||
|
|
||||||
await nextTick()
|
|
||||||
|
|
||||||
if (scrollable && !arrivedBottom)
|
|
||||||
return
|
|
||||||
|
|
||||||
contentRef.value.scrollTo({
|
|
||||||
behavior: 'smooth',
|
|
||||||
top: contentRef.value.scrollHeight,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMessageClick({ target }: MouseEvent) {
|
|
||||||
if (!target)
|
|
||||||
return
|
|
||||||
|
|
||||||
if (target instanceof HTMLElement) {
|
|
||||||
if (target.tagName === 'A') {
|
|
||||||
target.addEventListener('click', onAnchorClick)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onAnchorClick(event: MouseEvent) {
|
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
const target = event.target as HTMLAnchorElement
|
|
||||||
|
|
||||||
console.log('yo')
|
|
||||||
openUrl(target.href)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<PrimeScrollPanel class="min-h-0">
|
<div>
|
||||||
<PrimeDivider align="left">
|
<PrimeDivider align="left">
|
||||||
Audio
|
Audio
|
||||||
</PrimeDivider>
|
</PrimeDivider>
|
||||||
@@ -81,6 +81,7 @@
|
|||||||
size="small"
|
size="small"
|
||||||
option-label="label"
|
option-label="label"
|
||||||
option-value="value"
|
option-value="value"
|
||||||
|
:allow-empty="false"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -132,7 +133,7 @@
|
|||||||
@click="checkForUpdates"
|
@click="checkForUpdates"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</PrimeScrollPanel>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ export default defineNuxtPlugin(() => {
|
|||||||
|
|
||||||
// Connection sounds
|
// Connection sounds
|
||||||
on('socket:authenticated', ({ socketId }) => {
|
on('socket:authenticated', ({ socketId }) => {
|
||||||
sfx.playEvent('stream-on')
|
sfx.playRandomConnectionSound(socketId)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Client events
|
// Client events
|
||||||
on('client:added', (client) => {
|
on('client:added', (client) => {
|
||||||
sfx.playEvent('stream-on')
|
sfx.playRandomConnectionSound(client.socketId)
|
||||||
})
|
})
|
||||||
|
|
||||||
on('client:removed', () => {
|
on('client:removed', () => {
|
||||||
@@ -54,8 +54,4 @@ export default defineNuxtPlugin(() => {
|
|||||||
sfx.playEvent('stream-off')
|
sfx.playEvent('stream-off')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
on('chat:new-message', () => {
|
|
||||||
sfx.playEvent('message')
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -14,16 +14,12 @@
|
|||||||
"@nuxt/fonts": "^0.11.4",
|
"@nuxt/fonts": "^0.11.4",
|
||||||
"@primeuix/themes": "^1.2.5",
|
"@primeuix/themes": "^1.2.5",
|
||||||
"@tailwindcss/vite": "^4.1.14",
|
"@tailwindcss/vite": "^4.1.14",
|
||||||
"@tauri-apps/plugin-global-shortcut": "^2.3.1",
|
"@tauri-apps/plugin-global-shortcut": "~2",
|
||||||
"@tauri-apps/plugin-opener": "~2",
|
"@tauri-apps/plugin-process": "~2",
|
||||||
"@tauri-apps/plugin-process": "^2.3.1",
|
"@tauri-apps/plugin-updater": "~2",
|
||||||
"@tauri-apps/plugin-updater": "^2.10.1",
|
|
||||||
"@vueuse/core": "^13.9.0",
|
"@vueuse/core": "^13.9.0",
|
||||||
"date-fns": "^4.1.0",
|
|
||||||
"hotkeys-js": "^4.0.0",
|
"hotkeys-js": "^4.0.0",
|
||||||
"howler": "^2.2.4",
|
"howler": "^2.2.4",
|
||||||
"linkify-string": "^4.3.2",
|
|
||||||
"linkifyjs": "^4.3.2",
|
|
||||||
"lucide-vue-next": "^0.562.0",
|
"lucide-vue-next": "^0.562.0",
|
||||||
"mediasoup-client": "^3.18.6",
|
"mediasoup-client": "^3.18.6",
|
||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
|
|||||||
2359
client/src-tauri/Cargo.lock
generated
2359
client/src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -24,8 +24,6 @@ log = "0.4"
|
|||||||
tauri = { version = "2.8.5", features = [] }
|
tauri = { version = "2.8.5", features = [] }
|
||||||
tauri-plugin-log = "2"
|
tauri-plugin-log = "2"
|
||||||
tauri-plugin-process = "2"
|
tauri-plugin-process = "2"
|
||||||
windows = { version = "0.52", features = ["Win32_UI_Shell"] }
|
|
||||||
tauri-plugin-opener = "2"
|
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
tauri-plugin-global-shortcut = "2"
|
tauri-plugin-global-shortcut = "2"
|
||||||
|
|||||||
@@ -13,8 +13,6 @@
|
|||||||
"global-shortcut:allow-is-registered",
|
"global-shortcut:allow-is-registered",
|
||||||
"global-shortcut:allow-register",
|
"global-shortcut:allow-register",
|
||||||
"global-shortcut:allow-unregister",
|
"global-shortcut:allow-unregister",
|
||||||
"global-shortcut:allow-unregister-all",
|
"global-shortcut:allow-unregister-all"
|
||||||
"opener:allow-default-urls",
|
|
||||||
"opener:allow-open-url"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_opener::init())
|
|
||||||
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
|
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
|
||||||
.plugin(tauri_plugin_process::init())
|
.plugin(tauri_plugin_process::init())
|
||||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||||
// .plugin(tauri_plugin_single_instance::init(|_, _, _| {}))
|
.plugin(tauri_plugin_single_instance::init(|_, _, _| {}))
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
app.handle().plugin(
|
app.handle().plugin(
|
||||||
|
|||||||
@@ -1,21 +1,6 @@
|
|||||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
fn set_app_user_model_id() {
|
|
||||||
use windows::core::HSTRING;
|
|
||||||
use windows::Win32::UI::Shell::SetCurrentProcessExplicitAppUserModelID;
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
SetCurrentProcessExplicitAppUserModelID(&HSTRING::from("xyz.koptilnya.chad"))
|
|
||||||
.ok()
|
|
||||||
.expect("Failed to set AppUserModelID");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
set_app_user_model_id();
|
|
||||||
|
|
||||||
app_lib::run();
|
app_lib::run();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"productName": "Chad",
|
"productName": "Chad",
|
||||||
"version": "0.3.0-rc.2",
|
"version": "0.2.33",
|
||||||
"identifier": "xyz.koptilnya.chad",
|
"identifier": "xyz.koptilnya.chad",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../.output/public",
|
"frontendDist": "../.output/public",
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
"fullscreen": false,
|
"fullscreen": false,
|
||||||
"center": true,
|
"center": true,
|
||||||
"theme": "Dark",
|
"theme": "Dark",
|
||||||
"additionalBrowserArgs": "--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection --autoplay-policy=no-user-gesture-required --lang=en",
|
"additionalBrowserArgs": "--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection --autoplay-policy=no-user-gesture-required",
|
||||||
"incognito": false
|
"incognito": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -2824,10 +2824,10 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@tauri-apps/api@npm:^2.10.1":
|
"@tauri-apps/api@npm:^2.6.0":
|
||||||
version: 2.10.1
|
version: 2.8.0
|
||||||
resolution: "@tauri-apps/api@npm:2.10.1"
|
resolution: "@tauri-apps/api@npm:2.8.0"
|
||||||
checksum: 10c0/f3c0b2ba67a0b887440a7faa1e0589e847760ee30ec29b964f22573a46b817cb3af2199d6f5f7dfdda54d65b465ebaaa280454c610a5c53d808a0911fa15e45d
|
checksum: 10c0/fb111e4d7572372997b440ebe6879543fa8c4765151878e3fddfbfe809b18da29eed142ce83061d14a9ca6d896b3266dc8a4927c642d71cdc0b4277dc7e3aabf
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -2959,7 +2959,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@tauri-apps/plugin-global-shortcut@npm:^2.3.1":
|
"@tauri-apps/plugin-global-shortcut@npm:~2":
|
||||||
version: 2.3.1
|
version: 2.3.1
|
||||||
resolution: "@tauri-apps/plugin-global-shortcut@npm:2.3.1"
|
resolution: "@tauri-apps/plugin-global-shortcut@npm:2.3.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -2968,30 +2968,21 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@tauri-apps/plugin-opener@npm:~2":
|
"@tauri-apps/plugin-process@npm:~2":
|
||||||
version: 2.5.3
|
version: 2.3.0
|
||||||
resolution: "@tauri-apps/plugin-opener@npm:2.5.3"
|
resolution: "@tauri-apps/plugin-process@npm:2.3.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@tauri-apps/api": "npm:^2.8.0"
|
"@tauri-apps/api": "npm:^2.6.0"
|
||||||
checksum: 10c0/9ef2fae01e03f3bb16d8e55bfd921cf7c1d284e6459bd5b45777806304eb70ab0b50cbf03be76fc05e64ef70a37493e0cd90b0acc16eaee4a4fc2cfff7e43b71
|
checksum: 10c0/ef50344a7436d92278c2ef4526f72daaf3171c4d65743bbc1f7a00fa581644a8583bb8680f637a34af5c7e6a0e8722c22189290e903584fef70ed83b64b6e9c0
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@tauri-apps/plugin-process@npm:^2.3.1":
|
"@tauri-apps/plugin-updater@npm:~2":
|
||||||
version: 2.3.1
|
version: 2.9.0
|
||||||
resolution: "@tauri-apps/plugin-process@npm:2.3.1"
|
resolution: "@tauri-apps/plugin-updater@npm:2.9.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@tauri-apps/api": "npm:^2.8.0"
|
"@tauri-apps/api": "npm:^2.6.0"
|
||||||
checksum: 10c0/2e5086898f1c9f25f6426a752404c788727237142bbb7c8f418b97c76c5360874d06203150d136e51114df9e720022e4fa3681fd1d4cb6f777dc83c3553f8670
|
checksum: 10c0/72ce83d1c241308a13b9929f0900e4d33453875877009166e3998e3e75a1003ac48c3641086b4d3230f0f18c64f475ad6c3556d1603fc641ca50dc9c18d61866
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@tauri-apps/plugin-updater@npm:^2.10.1":
|
|
||||||
version: 2.10.1
|
|
||||||
resolution: "@tauri-apps/plugin-updater@npm:2.10.1"
|
|
||||||
dependencies:
|
|
||||||
"@tauri-apps/api": "npm:^2.10.1"
|
|
||||||
checksum: 10c0/5d3813851ccbbf90253ad4647dbd97501c2bb75db864175693322fa6eb062b6e1bae03890810ad0bbe7f6da4e020af35e8c787e6999f8c7c645121164587dc29
|
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -4068,19 +4059,15 @@ __metadata:
|
|||||||
"@primevue/nuxt-module": "npm:^4.4.0"
|
"@primevue/nuxt-module": "npm:^4.4.0"
|
||||||
"@tailwindcss/vite": "npm:^4.1.14"
|
"@tailwindcss/vite": "npm:^4.1.14"
|
||||||
"@tauri-apps/cli": "npm:^2.8.4"
|
"@tauri-apps/cli": "npm:^2.8.4"
|
||||||
"@tauri-apps/plugin-global-shortcut": "npm:^2.3.1"
|
"@tauri-apps/plugin-global-shortcut": "npm:~2"
|
||||||
"@tauri-apps/plugin-opener": "npm:~2"
|
"@tauri-apps/plugin-process": "npm:~2"
|
||||||
"@tauri-apps/plugin-process": "npm:^2.3.1"
|
"@tauri-apps/plugin-updater": "npm:~2"
|
||||||
"@tauri-apps/plugin-updater": "npm:^2.10.1"
|
|
||||||
"@types/howler": "npm:^2"
|
"@types/howler": "npm:^2"
|
||||||
"@vueuse/core": "npm:^13.9.0"
|
"@vueuse/core": "npm:^13.9.0"
|
||||||
date-fns: "npm:^4.1.0"
|
|
||||||
eslint: "npm:^9.36.0"
|
eslint: "npm:^9.36.0"
|
||||||
eslint-plugin-format: "npm:^1.0.2"
|
eslint-plugin-format: "npm:^1.0.2"
|
||||||
hotkeys-js: "npm:^4.0.0"
|
hotkeys-js: "npm:^4.0.0"
|
||||||
howler: "npm:^2.2.4"
|
howler: "npm:^2.2.4"
|
||||||
linkify-string: "npm:^4.3.2"
|
|
||||||
linkifyjs: "npm:^4.3.2"
|
|
||||||
lucide-vue-next: "npm:^0.562.0"
|
lucide-vue-next: "npm:^0.562.0"
|
||||||
mediasoup-client: "npm:^3.18.6"
|
mediasoup-client: "npm:^3.18.6"
|
||||||
mitt: "npm:^3.0.1"
|
mitt: "npm:^3.0.1"
|
||||||
@@ -4579,13 +4566,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"date-fns@npm:^4.1.0":
|
|
||||||
version: 4.1.0
|
|
||||||
resolution: "date-fns@npm:4.1.0"
|
|
||||||
checksum: 10c0/b79ff32830e6b7faa009590af6ae0fb8c3fd9ffad46d930548fbb5acf473773b4712ae887e156ba91a7b3dc30591ce0f517d69fd83bd9c38650fdc03b4e0bac8
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"db0@npm:^0.3.4":
|
"db0@npm:^0.3.4":
|
||||||
version: 0.3.4
|
version: 0.3.4
|
||||||
resolution: "db0@npm:0.3.4"
|
resolution: "db0@npm:0.3.4"
|
||||||
@@ -6973,22 +6953,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"linkify-string@npm:^4.3.2":
|
|
||||||
version: 4.3.2
|
|
||||||
resolution: "linkify-string@npm:4.3.2"
|
|
||||||
peerDependencies:
|
|
||||||
linkifyjs: ^4.0.0
|
|
||||||
checksum: 10c0/674e908b46aa6da3ee7e5c0749464d8de55f4d44933d7e9dea4d2f9bb5af0137d45142494288cd120335e21d8298c76ec4524ab6e67edede4613adb5f17f7dc6
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"linkifyjs@npm:^4.3.2":
|
|
||||||
version: 4.3.2
|
|
||||||
resolution: "linkifyjs@npm:4.3.2"
|
|
||||||
checksum: 10c0/1a85e6b368304a4417567fe5e38651681e3e82465590836942d1b4f3c834cc35532898eb1e2479f6337d9144b297d418eb708b6be8ed0b3dc3954a3588e07971
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"listhen@npm:^1.9.0":
|
"listhen@npm:^1.9.0":
|
||||||
version: 1.9.0
|
version: 1.9.0
|
||||||
resolution: "listhen@npm:1.9.0"
|
resolution: "listhen@npm:1.9.0"
|
||||||
|
|||||||
73
server/CLAUDE.md
Normal file
73
server/CLAUDE.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
Chad is a real-time voice/video chat server (think Discord-like) built with Fastify, Socket.IO, and mediasoup for WebRTC media handling. It uses SQLite via Prisma ORM and Lucia for session-based authentication. The client is a Tauri desktop app (separate repo).
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
- **Start server:** `yarn start` (runs `ts-node --transpile-only server.ts`)
|
||||||
|
- **Deploy DB (migrate + seed + generate):** `yarn db:deploy`
|
||||||
|
- **Generate Prisma client after schema changes:** `npx prisma generate`
|
||||||
|
- **Create a migration:** `npx prisma migrate dev --name <name>`
|
||||||
|
- **Lint:** `npx eslint .`
|
||||||
|
- **Package manager:** Yarn 4 (corepack). Do not use npm.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Entry Point & Plugin System
|
||||||
|
|
||||||
|
`server.ts` creates a Fastify instance and uses `@fastify/autoload` to auto-register everything in `plugins/` and `routes/` (prefixed under `/chad`). Plugins use `fastify-plugin` (`fp`) with named dependencies to control load order.
|
||||||
|
|
||||||
|
### Plugin Load Order (dependency chain)
|
||||||
|
|
||||||
|
1. `plugins/auth.ts` — Adds `req.user` / `req.session` via Lucia cookie validation on every request (preHandler hook)
|
||||||
|
2. `plugins/mediasoup-worker.ts` — Creates a mediasoup Worker, decorates `fastify.mediasoupWorker`
|
||||||
|
3. `plugins/mediasoup-router.ts` — Creates a mediasoup Router (depends on worker), decorates `fastify.mediasoupRouter`. Configures supported audio/video codecs (Opus, VP8, VP9, H.264, AV1)
|
||||||
|
4. `plugins/socket.ts` — Creates Socket.IO server at `/chad/ws` (depends on worker + router), decorates `fastify.io`. Registers socket handlers on `fastify.ready()`
|
||||||
|
|
||||||
|
### Socket Handlers (`socket/`)
|
||||||
|
|
||||||
|
- `socket/webrtc.ts` — Main WebRTC signaling: join/leave, transport creation, producer/consumer lifecycle, audio level observation, active speaker detection
|
||||||
|
- `socket/channel.ts` — Channel-based socket logic (in development on `channels` branch)
|
||||||
|
|
||||||
|
Both handlers authenticate by looking up `socket.handshake.auth.userId` against the DB.
|
||||||
|
|
||||||
|
### REST Routes (`routes/`)
|
||||||
|
|
||||||
|
All routes are prefixed with `/chad` via autoload config.
|
||||||
|
|
||||||
|
- `routes/auth.ts` — `/register`, `/login`, `/logout`, `/me`
|
||||||
|
- `routes/user.ts` — `/preferences` (GET/PATCH), `/profile` (PATCH). Profile changes broadcast to connected socket peers via `clientChanged` event.
|
||||||
|
|
||||||
|
### Type System (`types/socket.ts`)
|
||||||
|
|
||||||
|
Fully typed Socket.IO events: `ClientToServerEvents`, `ServerToClientEvents`, `SocketData`. The `SomeSocket` union type covers both live `Socket` and `RemoteSocket` (from `fetchSockets()`).
|
||||||
|
|
||||||
|
### Database
|
||||||
|
|
||||||
|
- SQLite with Prisma 7 + `@prisma/adapter-better-sqlite3`
|
||||||
|
- Schema at `prisma/schema.prisma`, generated client at `prisma/generated/client/`
|
||||||
|
- Models: `User`, `Session` (Lucia), `UserPreferences`, `Channel`
|
||||||
|
- Seed creates a persistent "Default" channel
|
||||||
|
- Config in `prisma.config.ts` using `dotenv` for `DATABASE_URL`
|
||||||
|
|
||||||
|
### Key Patterns
|
||||||
|
|
||||||
|
- Request validation uses Zod schemas defined inline in route handlers
|
||||||
|
- DTOs in `dto/` define Prisma select objects with `satisfies Prisma.*Select` for type-safe projections
|
||||||
|
- `utils/socket-to-client.ts` maps socket data to the `ChadClient` interface sent to clients
|
||||||
|
- ESLint uses `@antfu/eslint-config` (flat config) with `no-console` and `n/prefer-global/process` disabled
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
- `PORT` — Server port (default: 4000, Docker: 80)
|
||||||
|
- `DATABASE_URL` — SQLite path (e.g., `file:../data/database.db`)
|
||||||
|
- `ANNOUNCED_ADDRESS` — Public IP for WebRTC ICE candidates (default: `127.0.0.1`)
|
||||||
|
- `CORS_ORIGIN` — Socket.IO CORS origin (default: `*`)
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
`Dockerfile` requires python3 and build-essential for mediasoup native compilation. Runs `yarn db:deploy && yarn start`.
|
||||||
14
server/dto/channel.dto.ts
Normal file
14
server/dto/channel.dto.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// dto/channel.dto.ts
|
||||||
|
import type { Prisma } from '../prisma/generated/client'
|
||||||
|
|
||||||
|
export const channelPublicSelect = {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
owner_id: true,
|
||||||
|
persistent: true,
|
||||||
|
maxClients: true,
|
||||||
|
} as Prisma.ChannelSelect
|
||||||
|
|
||||||
|
export type ChannelPublicDTO = Prisma.ChannelGetPayload<{
|
||||||
|
select: typeof channelPublicSelect
|
||||||
|
}>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "server",
|
"name": "server",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "ts-node --transpile-only server.ts",
|
"start": "ts-node --transpile-only server.ts",
|
||||||
"db:deploy": "npx prisma migrate deploy && npx prisma generate"
|
"db:deploy": "npx prisma migrate deploy && npx prisma db seed && npx prisma generate"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "yarn@4.10.3",
|
"packageManager": "yarn@4.10.3",
|
||||||
@@ -10,9 +10,9 @@
|
|||||||
"@fastify/autoload": "^6.3.1",
|
"@fastify/autoload": "^6.3.1",
|
||||||
"@fastify/cookie": "^11.0.2",
|
"@fastify/cookie": "^11.0.2",
|
||||||
"@fastify/cors": "^11.1.0",
|
"@fastify/cors": "^11.1.0",
|
||||||
"@fastify/multipart": "^10.0.0",
|
|
||||||
"@lucia-auth/adapter-prisma": "^4.0.1",
|
"@lucia-auth/adapter-prisma": "^4.0.1",
|
||||||
"@prisma/client": "^6.17.0",
|
"@prisma/adapter-better-sqlite3": "^7.2.0",
|
||||||
|
"@prisma/client": "7",
|
||||||
"bcrypt": "^6.0.0",
|
"bcrypt": "^6.0.0",
|
||||||
"consola": "^3.4.2",
|
"consola": "^3.4.2",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
@@ -20,9 +20,8 @@
|
|||||||
"fastify-plugin": "^5.1.0",
|
"fastify-plugin": "^5.1.0",
|
||||||
"lucia": "^3.2.2",
|
"lucia": "^3.2.2",
|
||||||
"mediasoup": "^3.19.3",
|
"mediasoup": "^3.19.3",
|
||||||
"prisma": "^6.17.0",
|
"prisma": "7",
|
||||||
"socket.io": "^4.8.1",
|
"socket.io": "^4.8.1",
|
||||||
"uuid": "^13.0.0",
|
|
||||||
"ws": "^8.18.3",
|
"ws": "^8.18.3",
|
||||||
"zod": "^4.1.12"
|
"zod": "^4.1.12"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import type { FastifyInstance } from 'fastify'
|
import type { FastifyInstance } from 'fastify'
|
||||||
import type { ServerOptions } from 'socket.io'
|
import type { ServerOptions } from 'socket.io'
|
||||||
|
import type { ChadClient } from '../types/socket.ts'
|
||||||
|
import { consola } from 'consola'
|
||||||
import fp from 'fastify-plugin'
|
import fp from 'fastify-plugin'
|
||||||
import { Server } from 'socket.io'
|
import { Server } from 'socket.io'
|
||||||
import registerChatSocket from '../socket/chat.ts'
|
import prisma from '../prisma/client.ts'
|
||||||
import registerWebrtcSocket from '../socket/webrtc.ts'
|
import registerChannelHandlers from '../socket/channel.ts'
|
||||||
|
import registerWebrtcHandlers from '../socket/webrtc.ts'
|
||||||
|
|
||||||
declare module 'fastify' {
|
declare module 'fastify' {
|
||||||
interface FastifyInstance {
|
interface FastifyInstance {
|
||||||
@@ -23,9 +26,80 @@ export default fp<Partial<ServerOptions>>(
|
|||||||
await fastify.io.close()
|
await fastify.io.close()
|
||||||
})
|
})
|
||||||
|
|
||||||
fastify.ready(async () => {
|
fastify.ready(() => {
|
||||||
await registerWebrtcSocket(fastify.io, fastify.mediasoupRouter)
|
const audioLevelObserver = await fastify.mediasoupRouter.createAudioLevelObserver({
|
||||||
await registerChatSocket(fastify.io)
|
maxEntries: 10,
|
||||||
|
threshold: -80,
|
||||||
|
interval: 800,
|
||||||
|
})
|
||||||
|
|
||||||
|
const activeSpeakerObserver = await fastify.mediasoupRouter.createActiveSpeakerObserver()
|
||||||
|
|
||||||
|
audioLevelObserver.on('volumes', async (volumes: types.AudioLevelObserverVolume[]) => {
|
||||||
|
fastify.io.emit('webrtc:speaking-peers', volumes.map(({ producer, volume }) => {
|
||||||
|
const { socketId } = producer.appData as { socketId: ChadClient['socketId'] }
|
||||||
|
|
||||||
|
return {
|
||||||
|
clientId: socketId,
|
||||||
|
volume,
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
audioLevelObserver.on('silence', () => {
|
||||||
|
fastify.io.emit('webrtc:speaking-peers', [])
|
||||||
|
fastify.io.emit('webrtc:active-speaker', undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
activeSpeakerObserver.on('dominantspeaker', ({ producer }) => {
|
||||||
|
const { socketId } = producer.appData as { socketId: ChadClient['socketId'] }
|
||||||
|
|
||||||
|
fastify.io.emit('webrtc:active-speaker', socketId)
|
||||||
|
})
|
||||||
|
|
||||||
|
fastify.io.on('connection', async (socket) => {
|
||||||
|
consola.info('New connection', socket.id)
|
||||||
|
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: {
|
||||||
|
id: socket.handshake.auth.userId,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
username: true,
|
||||||
|
displayName: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
socket.disconnect()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id, username, displayName } = user
|
||||||
|
|
||||||
|
socket.data.userId = id
|
||||||
|
socket.data.username = username
|
||||||
|
socket.data.displayName = displayName
|
||||||
|
|
||||||
|
consola.info('User authorized', ...Object.values(user))
|
||||||
|
|
||||||
|
const channel = await registerChannelHandlers(fastify.io, socket)
|
||||||
|
const webrtc = await registerWebrtcHandlers(
|
||||||
|
fastify.io,
|
||||||
|
socket,
|
||||||
|
fastify.mediasoupRouter,
|
||||||
|
audioLevelObserver,
|
||||||
|
activeSpeakerObserver,
|
||||||
|
)
|
||||||
|
|
||||||
|
socket.emit('webrtc:authenticated', {
|
||||||
|
...channel,
|
||||||
|
...webrtc,
|
||||||
|
rtpCapabilities: fastify.mediasoupRouter.rtpCapabilities,
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{ name: 'socket-io', dependencies: ['mediasoup-worker', 'mediasoup-router'] },
|
{ name: 'socket-io', dependencies: ['mediasoup-worker', 'mediasoup-router'] },
|
||||||
|
|||||||
13
server/prisma.config.ts
Normal file
13
server/prisma.config.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { defineConfig, env } from 'prisma/config'
|
||||||
|
import 'dotenv/config'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
schema: './prisma/schema.prisma',
|
||||||
|
migrations: {
|
||||||
|
path: './prisma/migrations',
|
||||||
|
seed: 'ts-node ./prisma/seed.ts',
|
||||||
|
},
|
||||||
|
datasource: {
|
||||||
|
url: env('DATABASE_URL'),
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -1,6 +1,14 @@
|
|||||||
import { PrismaClient } from '@prisma/client'
|
import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3'
|
||||||
|
import { env } from 'prisma/config'
|
||||||
|
import { PrismaClient } from './generated/client/index.js'
|
||||||
|
import 'dotenv/config'
|
||||||
|
|
||||||
const client = new PrismaClient({
|
const client = new PrismaClient({
|
||||||
|
adapter: new PrismaBetterSqlite3({
|
||||||
|
url: env('DATABASE_URL'),
|
||||||
|
}, {
|
||||||
|
timestampFormat: 'unixepoch-ms',
|
||||||
|
}),
|
||||||
log: ['query', 'error', 'warn'],
|
log: ['query', 'error', 'warn'],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
39
server/prisma/generated/client/browser.ts
Normal file
39
server/prisma/generated/client/browser.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* This file should be your main import to use Prisma-related types and utilities in a browser.
|
||||||
|
* Use it to get access to models, enums, and input types.
|
||||||
|
*
|
||||||
|
* This file does not contain a `PrismaClient` class, nor several other helpers that are intended as server-side only.
|
||||||
|
* See `client.ts` for the standard, server-side entry point.
|
||||||
|
*
|
||||||
|
* 🟢 You can import this file directly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as Prisma from './internal/prismaNamespaceBrowser.ts'
|
||||||
|
export { Prisma }
|
||||||
|
export * as $Enums from './enums.ts'
|
||||||
|
export * from './enums.ts';
|
||||||
|
/**
|
||||||
|
* Model User
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type User = Prisma.UserModel
|
||||||
|
/**
|
||||||
|
* Model Session
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Session = Prisma.SessionModel
|
||||||
|
/**
|
||||||
|
* Model UserPreferences
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type UserPreferences = Prisma.UserPreferencesModel
|
||||||
|
/**
|
||||||
|
* Model Channel
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Channel = Prisma.ChannelModel
|
||||||
1
server/prisma/generated/client/client.d.ts
vendored
Normal file
1
server/prisma/generated/client/client.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./index"
|
||||||
5
server/prisma/generated/client/client.js
Normal file
5
server/prisma/generated/client/client.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
module.exports = { ...require('.') }
|
||||||
61
server/prisma/generated/client/client.ts
Normal file
61
server/prisma/generated/client/client.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* This file should be your main import to use Prisma. Through it you get access to all the models, enums, and input types.
|
||||||
|
* If you're looking for something you can import in the client-side of your application, please refer to the `browser.ts` file instead.
|
||||||
|
*
|
||||||
|
* 🟢 You can import this file directly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as process from 'node:process'
|
||||||
|
import * as path from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
globalThis['__dirname'] = path.dirname(fileURLToPath(import.meta.url))
|
||||||
|
|
||||||
|
import * as runtime from "@prisma/client/runtime/client"
|
||||||
|
import * as $Enums from "./enums.ts"
|
||||||
|
import * as $Class from "./internal/class.ts"
|
||||||
|
import * as Prisma from "./internal/prismaNamespace.ts"
|
||||||
|
|
||||||
|
export * as $Enums from './enums.ts'
|
||||||
|
export * from "./enums.ts"
|
||||||
|
/**
|
||||||
|
* ## Prisma Client
|
||||||
|
*
|
||||||
|
* Type-safe database client for TypeScript
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const prisma = new PrismaClient()
|
||||||
|
* // Fetch zero or more Users
|
||||||
|
* const users = await prisma.user.findMany()
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more in our [docs](https://pris.ly/d/client).
|
||||||
|
*/
|
||||||
|
export const PrismaClient = $Class.getPrismaClientClass()
|
||||||
|
export type PrismaClient<LogOpts extends Prisma.LogLevel = never, OmitOpts extends Prisma.PrismaClientOptions["omit"] = Prisma.PrismaClientOptions["omit"], ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = $Class.PrismaClient<LogOpts, OmitOpts, ExtArgs>
|
||||||
|
export { Prisma }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model User
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type User = Prisma.UserModel
|
||||||
|
/**
|
||||||
|
* Model Session
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Session = Prisma.SessionModel
|
||||||
|
/**
|
||||||
|
* Model UserPreferences
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type UserPreferences = Prisma.UserPreferencesModel
|
||||||
|
/**
|
||||||
|
* Model Channel
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Channel = Prisma.ChannelModel
|
||||||
298
server/prisma/generated/client/commonInputTypes.ts
Normal file
298
server/prisma/generated/client/commonInputTypes.ts
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* This file exports various common sort, input & filter types that are not directly linked to a particular model.
|
||||||
|
*
|
||||||
|
* 🟢 You can import this file directly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type * as runtime from "@prisma/client/runtime/client"
|
||||||
|
import * as $Enums from "./enums.ts"
|
||||||
|
import type * as Prisma from "./internal/prismaNamespace.ts"
|
||||||
|
|
||||||
|
|
||||||
|
export type StringFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
in?: string[]
|
||||||
|
notIn?: string[]
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringFilter<$PrismaModel> | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DateTimeFilter<$PrismaModel = never> = {
|
||||||
|
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
in?: Date[] | string[]
|
||||||
|
notIn?: Date[] | string[]
|
||||||
|
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StringWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
in?: string[]
|
||||||
|
notIn?: string[]
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedStringFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedStringFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DateTimeWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
in?: Date[] | string[]
|
||||||
|
notIn?: Date[] | string[]
|
||||||
|
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StringNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: string[] | null
|
||||||
|
notIn?: string[] | null
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SortOrderInput = {
|
||||||
|
sort: Prisma.SortOrder
|
||||||
|
nulls?: Prisma.NullsOrder
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StringNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: string[] | null
|
||||||
|
notIn?: string[] | null
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null
|
||||||
|
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IntNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: number[] | null
|
||||||
|
notIn?: number[] | null
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BoolFilter<$PrismaModel = never> = {
|
||||||
|
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IntNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: number[] | null
|
||||||
|
notIn?: number[] | null
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel> | number | null
|
||||||
|
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>
|
||||||
|
_sum?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BoolWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedBoolFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedBoolFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedStringFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
in?: string[]
|
||||||
|
notIn?: string[]
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringFilter<$PrismaModel> | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedDateTimeFilter<$PrismaModel = never> = {
|
||||||
|
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
in?: Date[] | string[]
|
||||||
|
notIn?: Date[] | string[]
|
||||||
|
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedStringWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
in?: string[]
|
||||||
|
notIn?: string[]
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedStringFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedStringFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedIntFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
in?: number[]
|
||||||
|
notIn?: number[]
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntFilter<$PrismaModel> | number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
in?: Date[] | string[]
|
||||||
|
notIn?: Date[] | string[]
|
||||||
|
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedStringNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: string[] | null
|
||||||
|
notIn?: string[] | null
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedStringNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: string[] | null
|
||||||
|
notIn?: string[] | null
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null
|
||||||
|
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedIntNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: number[] | null
|
||||||
|
notIn?: number[] | null
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedBoolFilter<$PrismaModel = never> = {
|
||||||
|
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedIntNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: number[] | null
|
||||||
|
notIn?: number[] | null
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel> | number | null
|
||||||
|
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>
|
||||||
|
_sum?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedFloatNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: number[] | null
|
||||||
|
notIn?: number[] | null
|
||||||
|
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedFloatNullableFilter<$PrismaModel> | number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedBoolWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedBoolFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedBoolFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
1
server/prisma/generated/client/default.d.ts
vendored
Normal file
1
server/prisma/generated/client/default.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./index"
|
||||||
5
server/prisma/generated/client/default.js
Normal file
5
server/prisma/generated/client/default.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
module.exports = { ...require('#main-entry-point') }
|
||||||
1
server/prisma/generated/client/edge.d.ts
vendored
Normal file
1
server/prisma/generated/client/edge.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./default"
|
||||||
165
server/prisma/generated/client/edge.js
Normal file
165
server/prisma/generated/client/edge.js
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
|
||||||
|
const {
|
||||||
|
PrismaClientKnownRequestError,
|
||||||
|
PrismaClientUnknownRequestError,
|
||||||
|
PrismaClientRustPanicError,
|
||||||
|
PrismaClientInitializationError,
|
||||||
|
PrismaClientValidationError,
|
||||||
|
getPrismaClient,
|
||||||
|
sqltag,
|
||||||
|
empty,
|
||||||
|
join,
|
||||||
|
raw,
|
||||||
|
skip,
|
||||||
|
Decimal,
|
||||||
|
Debug,
|
||||||
|
DbNull,
|
||||||
|
JsonNull,
|
||||||
|
AnyNull,
|
||||||
|
NullTypes,
|
||||||
|
makeStrictEnum,
|
||||||
|
Extensions,
|
||||||
|
warnOnce,
|
||||||
|
defineDmmfProperty,
|
||||||
|
Public,
|
||||||
|
getRuntime,
|
||||||
|
createParam,
|
||||||
|
} = require('./runtime/wasm-compiler-edge.js')
|
||||||
|
|
||||||
|
|
||||||
|
const Prisma = {}
|
||||||
|
|
||||||
|
exports.Prisma = Prisma
|
||||||
|
exports.$Enums = {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prisma Client JS version: 7.2.0
|
||||||
|
* Query Engine version: 0c8ef2ce45c83248ab3df073180d5eda9e8be7a3
|
||||||
|
*/
|
||||||
|
Prisma.prismaVersion = {
|
||||||
|
client: "7.2.0",
|
||||||
|
engine: "0c8ef2ce45c83248ab3df073180d5eda9e8be7a3"
|
||||||
|
}
|
||||||
|
|
||||||
|
Prisma.PrismaClientKnownRequestError = PrismaClientKnownRequestError;
|
||||||
|
Prisma.PrismaClientUnknownRequestError = PrismaClientUnknownRequestError
|
||||||
|
Prisma.PrismaClientRustPanicError = PrismaClientRustPanicError
|
||||||
|
Prisma.PrismaClientInitializationError = PrismaClientInitializationError
|
||||||
|
Prisma.PrismaClientValidationError = PrismaClientValidationError
|
||||||
|
Prisma.Decimal = Decimal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-export of sql-template-tag
|
||||||
|
*/
|
||||||
|
Prisma.sql = sqltag
|
||||||
|
Prisma.empty = empty
|
||||||
|
Prisma.join = join
|
||||||
|
Prisma.raw = raw
|
||||||
|
Prisma.validator = Public.validator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extensions
|
||||||
|
*/
|
||||||
|
Prisma.getExtensionContext = Extensions.getExtensionContext
|
||||||
|
Prisma.defineExtension = Extensions.defineExtension
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shorthand utilities for JSON filtering
|
||||||
|
*/
|
||||||
|
Prisma.DbNull = DbNull
|
||||||
|
Prisma.JsonNull = JsonNull
|
||||||
|
Prisma.AnyNull = AnyNull
|
||||||
|
|
||||||
|
Prisma.NullTypes = NullTypes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enums
|
||||||
|
*/
|
||||||
|
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
|
||||||
|
Serializable: 'Serializable'
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.Prisma.UserScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
username: 'username',
|
||||||
|
password: 'password',
|
||||||
|
displayName: 'displayName',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.SessionScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
userId: 'userId',
|
||||||
|
expiresAt: 'expiresAt'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.UserPreferencesScalarFieldEnum = {
|
||||||
|
userId: 'userId',
|
||||||
|
toggleInputHotkey: 'toggleInputHotkey',
|
||||||
|
toggleOutputHotkey: 'toggleOutputHotkey'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.ChannelScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
maxClients: 'maxClients',
|
||||||
|
persistent: 'persistent',
|
||||||
|
owner_id: 'owner_id'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.SortOrder = {
|
||||||
|
asc: 'asc',
|
||||||
|
desc: 'desc'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.NullsOrder = {
|
||||||
|
first: 'first',
|
||||||
|
last: 'last'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
exports.Prisma.ModelName = {
|
||||||
|
User: 'User',
|
||||||
|
Session: 'Session',
|
||||||
|
UserPreferences: 'UserPreferences',
|
||||||
|
Channel: 'Channel'
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Create the Client
|
||||||
|
*/
|
||||||
|
const config = {
|
||||||
|
"previewFeatures": [],
|
||||||
|
"clientVersion": "7.2.0",
|
||||||
|
"engineVersion": "0c8ef2ce45c83248ab3df073180d5eda9e8be7a3",
|
||||||
|
"activeProvider": "sqlite",
|
||||||
|
"inlineSchema": "datasource db {\n provider = \"sqlite\"\n}\n\ngenerator client {\n provider = \"prisma-client-js\"\n output = \"./generated/client\"\n}\n\nmodel User {\n id String @id @default(cuid())\n username String @unique\n password String\n displayName String\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n Session Session[]\n UserPreferences UserPreferences?\n channels Channel[]\n}\n\nmodel Session {\n id String @id\n userId String\n expiresAt DateTime\n user User @relation(references: [id], fields: [userId], onDelete: Cascade)\n\n @@index([userId])\n}\n\nmodel UserPreferences {\n userId String @id\n toggleInputHotkey String? @default(\"\")\n toggleOutputHotkey String? @default(\"\")\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n}\n\nmodel Channel {\n id String @id\n name String\n maxClients Int?\n persistent Boolean @default(false)\n owner_id String?\n owner User? @relation(fields: [owner_id], references: [id], onDelete: Cascade)\n}\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"username\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"displayName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"Session\",\"kind\":\"object\",\"type\":\"Session\",\"relationName\":\"SessionToUser\"},{\"name\":\"UserPreferences\",\"kind\":\"object\",\"type\":\"UserPreferences\",\"relationName\":\"UserToUserPreferences\"},{\"name\":\"channels\",\"kind\":\"object\",\"type\":\"Channel\",\"relationName\":\"ChannelToUser\"}],\"dbName\":null},\"Session\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"SessionToUser\"}],\"dbName\":null},\"UserPreferences\":{\"fields\":[{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"toggleInputHotkey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"toggleOutputHotkey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"UserToUserPreferences\"}],\"dbName\":null},\"Channel\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"maxClients\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"persistent\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"owner_id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"owner\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ChannelToUser\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}")
|
||||||
|
defineDmmfProperty(exports.Prisma, config.runtimeDataModel)
|
||||||
|
config.compilerWasm = {
|
||||||
|
getRuntime: async () => require('./query_compiler_bg.js'),
|
||||||
|
getQueryCompilerWasmModule: async () => {
|
||||||
|
const loader = (await import('#wasm-compiler-loader')).default
|
||||||
|
const compiler = (await loader).default
|
||||||
|
return compiler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeof globalThis !== 'undefined' && globalThis['DEBUG'] || (typeof process !== 'undefined' && process.env && process.env.DEBUG) || undefined) {
|
||||||
|
Debug.enable(typeof globalThis !== 'undefined' && globalThis['DEBUG'] || (typeof process !== 'undefined' && process.env && process.env.DEBUG) || undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
const PrismaClient = getPrismaClient(config)
|
||||||
|
exports.PrismaClient = PrismaClient
|
||||||
|
Object.assign(exports, Prisma)
|
||||||
15
server/prisma/generated/client/enums.ts
Normal file
15
server/prisma/generated/client/enums.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* This file exports all enum related types from the schema.
|
||||||
|
*
|
||||||
|
* 🟢 You can import this file directly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// This file is empty because there are no enums in the schema.
|
||||||
|
export {}
|
||||||
196
server/prisma/generated/client/index-browser.js
Normal file
196
server/prisma/generated/client/index-browser.js
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
|
||||||
|
const {
|
||||||
|
Decimal,
|
||||||
|
DbNull,
|
||||||
|
JsonNull,
|
||||||
|
AnyNull,
|
||||||
|
NullTypes,
|
||||||
|
makeStrictEnum,
|
||||||
|
Public,
|
||||||
|
getRuntime,
|
||||||
|
skip
|
||||||
|
} = require('./runtime/index-browser.js')
|
||||||
|
|
||||||
|
|
||||||
|
const Prisma = {}
|
||||||
|
|
||||||
|
exports.Prisma = Prisma
|
||||||
|
exports.$Enums = {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prisma Client JS version: 7.2.0
|
||||||
|
* Query Engine version: 0c8ef2ce45c83248ab3df073180d5eda9e8be7a3
|
||||||
|
*/
|
||||||
|
Prisma.prismaVersion = {
|
||||||
|
client: "7.2.0",
|
||||||
|
engine: "0c8ef2ce45c83248ab3df073180d5eda9e8be7a3"
|
||||||
|
}
|
||||||
|
|
||||||
|
Prisma.PrismaClientKnownRequestError = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`PrismaClientKnownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)};
|
||||||
|
Prisma.PrismaClientUnknownRequestError = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`PrismaClientUnknownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.PrismaClientRustPanicError = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`PrismaClientRustPanicError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.PrismaClientInitializationError = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`PrismaClientInitializationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.PrismaClientValidationError = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`PrismaClientValidationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.Decimal = Decimal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-export of sql-template-tag
|
||||||
|
*/
|
||||||
|
Prisma.sql = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`sqltag is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.empty = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`empty is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.join = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`join is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.raw = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`raw is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.validator = Public.validator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extensions
|
||||||
|
*/
|
||||||
|
Prisma.getExtensionContext = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`Extensions.getExtensionContext is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.defineExtension = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`Extensions.defineExtension is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shorthand utilities for JSON filtering
|
||||||
|
*/
|
||||||
|
Prisma.DbNull = DbNull
|
||||||
|
Prisma.JsonNull = JsonNull
|
||||||
|
Prisma.AnyNull = AnyNull
|
||||||
|
|
||||||
|
Prisma.NullTypes = NullTypes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enums
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
|
||||||
|
Serializable: 'Serializable'
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.Prisma.UserScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
username: 'username',
|
||||||
|
password: 'password',
|
||||||
|
displayName: 'displayName',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.SessionScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
userId: 'userId',
|
||||||
|
expiresAt: 'expiresAt'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.UserPreferencesScalarFieldEnum = {
|
||||||
|
userId: 'userId',
|
||||||
|
toggleInputHotkey: 'toggleInputHotkey',
|
||||||
|
toggleOutputHotkey: 'toggleOutputHotkey'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.ChannelScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
maxClients: 'maxClients',
|
||||||
|
persistent: 'persistent',
|
||||||
|
owner_id: 'owner_id'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.SortOrder = {
|
||||||
|
asc: 'asc',
|
||||||
|
desc: 'desc'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.NullsOrder = {
|
||||||
|
first: 'first',
|
||||||
|
last: 'last'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
exports.Prisma.ModelName = {
|
||||||
|
User: 'User',
|
||||||
|
Session: 'Session',
|
||||||
|
UserPreferences: 'UserPreferences',
|
||||||
|
Channel: 'Channel'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a stub Prisma Client that will error at runtime if called.
|
||||||
|
*/
|
||||||
|
class PrismaClient {
|
||||||
|
constructor() {
|
||||||
|
return new Proxy(this, {
|
||||||
|
get(target, prop) {
|
||||||
|
let message
|
||||||
|
const runtime = getRuntime()
|
||||||
|
if (runtime.isEdge) {
|
||||||
|
message = `PrismaClient is not configured to run in ${runtime.prettyName}. In order to run Prisma Client on edge runtime, either:
|
||||||
|
- Use Prisma Accelerate: https://pris.ly/d/accelerate
|
||||||
|
- Use Driver Adapters: https://pris.ly/d/driver-adapters
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
message = 'PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `' + runtime.prettyName + '`).'
|
||||||
|
}
|
||||||
|
|
||||||
|
message += `
|
||||||
|
If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report`
|
||||||
|
|
||||||
|
throw new Error(message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.PrismaClient = PrismaClient
|
||||||
|
|
||||||
|
Object.assign(exports, Prisma)
|
||||||
6981
server/prisma/generated/client/index.d.ts
vendored
Normal file
6981
server/prisma/generated/client/index.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
165
server/prisma/generated/client/index.js
Normal file
165
server/prisma/generated/client/index.js
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
|
||||||
|
const {
|
||||||
|
PrismaClientKnownRequestError,
|
||||||
|
PrismaClientUnknownRequestError,
|
||||||
|
PrismaClientRustPanicError,
|
||||||
|
PrismaClientInitializationError,
|
||||||
|
PrismaClientValidationError,
|
||||||
|
getPrismaClient,
|
||||||
|
sqltag,
|
||||||
|
empty,
|
||||||
|
join,
|
||||||
|
raw,
|
||||||
|
skip,
|
||||||
|
Decimal,
|
||||||
|
Debug,
|
||||||
|
DbNull,
|
||||||
|
JsonNull,
|
||||||
|
AnyNull,
|
||||||
|
NullTypes,
|
||||||
|
makeStrictEnum,
|
||||||
|
Extensions,
|
||||||
|
warnOnce,
|
||||||
|
defineDmmfProperty,
|
||||||
|
Public,
|
||||||
|
getRuntime,
|
||||||
|
createParam,
|
||||||
|
} = require('./runtime/client.js')
|
||||||
|
|
||||||
|
|
||||||
|
const Prisma = {}
|
||||||
|
|
||||||
|
exports.Prisma = Prisma
|
||||||
|
exports.$Enums = {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prisma Client JS version: 7.2.0
|
||||||
|
* Query Engine version: 0c8ef2ce45c83248ab3df073180d5eda9e8be7a3
|
||||||
|
*/
|
||||||
|
Prisma.prismaVersion = {
|
||||||
|
client: "7.2.0",
|
||||||
|
engine: "0c8ef2ce45c83248ab3df073180d5eda9e8be7a3"
|
||||||
|
}
|
||||||
|
|
||||||
|
Prisma.PrismaClientKnownRequestError = PrismaClientKnownRequestError;
|
||||||
|
Prisma.PrismaClientUnknownRequestError = PrismaClientUnknownRequestError
|
||||||
|
Prisma.PrismaClientRustPanicError = PrismaClientRustPanicError
|
||||||
|
Prisma.PrismaClientInitializationError = PrismaClientInitializationError
|
||||||
|
Prisma.PrismaClientValidationError = PrismaClientValidationError
|
||||||
|
Prisma.Decimal = Decimal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-export of sql-template-tag
|
||||||
|
*/
|
||||||
|
Prisma.sql = sqltag
|
||||||
|
Prisma.empty = empty
|
||||||
|
Prisma.join = join
|
||||||
|
Prisma.raw = raw
|
||||||
|
Prisma.validator = Public.validator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extensions
|
||||||
|
*/
|
||||||
|
Prisma.getExtensionContext = Extensions.getExtensionContext
|
||||||
|
Prisma.defineExtension = Extensions.defineExtension
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shorthand utilities for JSON filtering
|
||||||
|
*/
|
||||||
|
Prisma.DbNull = DbNull
|
||||||
|
Prisma.JsonNull = JsonNull
|
||||||
|
Prisma.AnyNull = AnyNull
|
||||||
|
|
||||||
|
Prisma.NullTypes = NullTypes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enums
|
||||||
|
*/
|
||||||
|
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
|
||||||
|
Serializable: 'Serializable'
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.Prisma.UserScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
username: 'username',
|
||||||
|
password: 'password',
|
||||||
|
displayName: 'displayName',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.SessionScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
userId: 'userId',
|
||||||
|
expiresAt: 'expiresAt'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.UserPreferencesScalarFieldEnum = {
|
||||||
|
userId: 'userId',
|
||||||
|
toggleInputHotkey: 'toggleInputHotkey',
|
||||||
|
toggleOutputHotkey: 'toggleOutputHotkey'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.ChannelScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
maxClients: 'maxClients',
|
||||||
|
persistent: 'persistent',
|
||||||
|
owner_id: 'owner_id'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.SortOrder = {
|
||||||
|
asc: 'asc',
|
||||||
|
desc: 'desc'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.NullsOrder = {
|
||||||
|
first: 'first',
|
||||||
|
last: 'last'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
exports.Prisma.ModelName = {
|
||||||
|
User: 'User',
|
||||||
|
Session: 'Session',
|
||||||
|
UserPreferences: 'UserPreferences',
|
||||||
|
Channel: 'Channel'
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Create the Client
|
||||||
|
*/
|
||||||
|
const config = {
|
||||||
|
"previewFeatures": [],
|
||||||
|
"clientVersion": "7.2.0",
|
||||||
|
"engineVersion": "0c8ef2ce45c83248ab3df073180d5eda9e8be7a3",
|
||||||
|
"activeProvider": "sqlite",
|
||||||
|
"inlineSchema": "datasource db {\n provider = \"sqlite\"\n}\n\ngenerator client {\n provider = \"prisma-client-js\"\n output = \"./generated/client\"\n}\n\nmodel User {\n id String @id @default(cuid())\n username String @unique\n password String\n displayName String\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n Session Session[]\n UserPreferences UserPreferences?\n channels Channel[]\n}\n\nmodel Session {\n id String @id\n userId String\n expiresAt DateTime\n user User @relation(references: [id], fields: [userId], onDelete: Cascade)\n\n @@index([userId])\n}\n\nmodel UserPreferences {\n userId String @id\n toggleInputHotkey String? @default(\"\")\n toggleOutputHotkey String? @default(\"\")\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n}\n\nmodel Channel {\n id String @id\n name String\n maxClients Int?\n persistent Boolean @default(false)\n owner_id String?\n owner User? @relation(fields: [owner_id], references: [id], onDelete: Cascade)\n}\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"username\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"displayName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"Session\",\"kind\":\"object\",\"type\":\"Session\",\"relationName\":\"SessionToUser\"},{\"name\":\"UserPreferences\",\"kind\":\"object\",\"type\":\"UserPreferences\",\"relationName\":\"UserToUserPreferences\"},{\"name\":\"channels\",\"kind\":\"object\",\"type\":\"Channel\",\"relationName\":\"ChannelToUser\"}],\"dbName\":null},\"Session\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"SessionToUser\"}],\"dbName\":null},\"UserPreferences\":{\"fields\":[{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"toggleInputHotkey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"toggleOutputHotkey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"UserToUserPreferences\"}],\"dbName\":null},\"Channel\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"maxClients\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"persistent\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"owner_id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"owner\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ChannelToUser\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}")
|
||||||
|
defineDmmfProperty(exports.Prisma, config.runtimeDataModel)
|
||||||
|
config.compilerWasm = {
|
||||||
|
getRuntime: async () => require('./query_compiler_bg.js'),
|
||||||
|
getQueryCompilerWasmModule: async () => {
|
||||||
|
const { Buffer } = require('node:buffer')
|
||||||
|
const { wasm } = require('./query_compiler_bg.wasm-base64.js')
|
||||||
|
const queryCompilerWasmFileBytes = Buffer.from(wasm, 'base64')
|
||||||
|
|
||||||
|
return new WebAssembly.Module(queryCompilerWasmFileBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PrismaClient = getPrismaClient(config)
|
||||||
|
exports.PrismaClient = PrismaClient
|
||||||
|
Object.assign(exports, Prisma)
|
||||||
220
server/prisma/generated/client/internal/class.ts
Normal file
220
server/prisma/generated/client/internal/class.ts
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* WARNING: This is an internal file that is subject to change!
|
||||||
|
*
|
||||||
|
* 🛑 Under no circumstances should you import this file directly! 🛑
|
||||||
|
*
|
||||||
|
* Please import the `PrismaClient` class from the `client.ts` file instead.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as runtime from "@prisma/client/runtime/client"
|
||||||
|
import type * as Prisma from "./prismaNamespace.ts"
|
||||||
|
|
||||||
|
|
||||||
|
const config: runtime.GetPrismaClientConfig = {
|
||||||
|
"previewFeatures": [],
|
||||||
|
"clientVersion": "7.2.0",
|
||||||
|
"engineVersion": "0c8ef2ce45c83248ab3df073180d5eda9e8be7a3",
|
||||||
|
"activeProvider": "sqlite",
|
||||||
|
"inlineSchema": "datasource db {\n provider = \"sqlite\"\n // url = env(\"DATABASE_URL\")\n}\n\ngenerator client {\n provider = \"prisma-client\"\n output = \"./generated/client\"\n}\n\nmodel User {\n id String @id @default(cuid())\n username String @unique\n password String\n displayName String\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n Session Session[]\n UserPreferences UserPreferences?\n channels Channel[]\n}\n\nmodel Session {\n id String @id\n userId String\n expiresAt DateTime\n user User @relation(references: [id], fields: [userId], onDelete: Cascade)\n\n @@index([userId])\n}\n\nmodel UserPreferences {\n userId String @id\n toggleInputHotkey String? @default(\"\")\n toggleOutputHotkey String? @default(\"\")\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n}\n\nmodel Channel {\n id String @id @default(cuid())\n name String\n maxClients Int?\n persistent Boolean @default(false)\n owner_id String?\n owner User? @relation(fields: [owner_id], references: [id], onDelete: Cascade)\n}\n",
|
||||||
|
"runtimeDataModel": {
|
||||||
|
"models": {},
|
||||||
|
"enums": {},
|
||||||
|
"types": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"username\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"displayName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"Session\",\"kind\":\"object\",\"type\":\"Session\",\"relationName\":\"SessionToUser\"},{\"name\":\"UserPreferences\",\"kind\":\"object\",\"type\":\"UserPreferences\",\"relationName\":\"UserToUserPreferences\"},{\"name\":\"channels\",\"kind\":\"object\",\"type\":\"Channel\",\"relationName\":\"ChannelToUser\"}],\"dbName\":null},\"Session\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"SessionToUser\"}],\"dbName\":null},\"UserPreferences\":{\"fields\":[{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"toggleInputHotkey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"toggleOutputHotkey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"UserToUserPreferences\"}],\"dbName\":null},\"Channel\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"maxClients\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"persistent\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"owner_id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"owner\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ChannelToUser\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}")
|
||||||
|
|
||||||
|
async function decodeBase64AsWasm(wasmBase64: string): Promise<WebAssembly.Module> {
|
||||||
|
const { Buffer } = await import('node:buffer')
|
||||||
|
const wasmArray = Buffer.from(wasmBase64, 'base64')
|
||||||
|
return new WebAssembly.Module(wasmArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
config.compilerWasm = {
|
||||||
|
getRuntime: async () => await import("@prisma/client/runtime/query_compiler_bg.sqlite.mjs"),
|
||||||
|
|
||||||
|
getQueryCompilerWasmModule: async () => {
|
||||||
|
const { wasm } = await import("@prisma/client/runtime/query_compiler_bg.sqlite.wasm-base64.mjs")
|
||||||
|
return await decodeBase64AsWasm(wasm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export type LogOptions<ClientOptions extends Prisma.PrismaClientOptions> =
|
||||||
|
'log' extends keyof ClientOptions ? ClientOptions['log'] extends Array<Prisma.LogLevel | Prisma.LogDefinition> ? Prisma.GetEvents<ClientOptions['log']> : never : never
|
||||||
|
|
||||||
|
export interface PrismaClientConstructor {
|
||||||
|
/**
|
||||||
|
* ## Prisma Client
|
||||||
|
*
|
||||||
|
* Type-safe database client for TypeScript
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const prisma = new PrismaClient()
|
||||||
|
* // Fetch zero or more Users
|
||||||
|
* const users = await prisma.user.findMany()
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more in our [docs](https://pris.ly/d/client).
|
||||||
|
*/
|
||||||
|
|
||||||
|
new <
|
||||||
|
Options extends Prisma.PrismaClientOptions = Prisma.PrismaClientOptions,
|
||||||
|
LogOpts extends LogOptions<Options> = LogOptions<Options>,
|
||||||
|
OmitOpts extends Prisma.PrismaClientOptions['omit'] = Options extends { omit: infer U } ? U : Prisma.PrismaClientOptions['omit'],
|
||||||
|
ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs
|
||||||
|
>(options: Prisma.Subset<Options, Prisma.PrismaClientOptions> ): PrismaClient<LogOpts, OmitOpts, ExtArgs>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ## Prisma Client
|
||||||
|
*
|
||||||
|
* Type-safe database client for TypeScript
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const prisma = new PrismaClient()
|
||||||
|
* // Fetch zero or more Users
|
||||||
|
* const users = await prisma.user.findMany()
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more in our [docs](https://pris.ly/d/client).
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface PrismaClient<
|
||||||
|
in LogOpts extends Prisma.LogLevel = never,
|
||||||
|
in out OmitOpts extends Prisma.PrismaClientOptions['omit'] = undefined,
|
||||||
|
in out ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs
|
||||||
|
> {
|
||||||
|
[K: symbol]: { types: Prisma.TypeMap<ExtArgs>['other'] }
|
||||||
|
|
||||||
|
$on<V extends LogOpts>(eventType: V, callback: (event: V extends 'query' ? Prisma.QueryEvent : Prisma.LogEvent) => void): PrismaClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect with the database
|
||||||
|
*/
|
||||||
|
$connect(): runtime.Types.Utils.JsPromise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect from the database
|
||||||
|
*/
|
||||||
|
$disconnect(): runtime.Types.Utils.JsPromise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a prepared raw query and returns the number of affected rows.
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const result = await prisma.$executeRaw`UPDATE User SET cool = ${true} WHERE email = ${'user@email.com'};`
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more in our [docs](https://pris.ly/d/raw-queries).
|
||||||
|
*/
|
||||||
|
$executeRaw<T = unknown>(query: TemplateStringsArray | Prisma.Sql, ...values: any[]): Prisma.PrismaPromise<number>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a raw query and returns the number of affected rows.
|
||||||
|
* Susceptible to SQL injections, see documentation.
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const result = await prisma.$executeRawUnsafe('UPDATE User SET cool = $1 WHERE email = $2 ;', true, 'user@email.com')
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more in our [docs](https://pris.ly/d/raw-queries).
|
||||||
|
*/
|
||||||
|
$executeRawUnsafe<T = unknown>(query: string, ...values: any[]): Prisma.PrismaPromise<number>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a prepared raw query and returns the `SELECT` data.
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const result = await prisma.$queryRaw`SELECT * FROM User WHERE id = ${1} OR email = ${'user@email.com'};`
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more in our [docs](https://pris.ly/d/raw-queries).
|
||||||
|
*/
|
||||||
|
$queryRaw<T = unknown>(query: TemplateStringsArray | Prisma.Sql, ...values: any[]): Prisma.PrismaPromise<T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a raw query and returns the `SELECT` data.
|
||||||
|
* Susceptible to SQL injections, see documentation.
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const result = await prisma.$queryRawUnsafe('SELECT * FROM User WHERE id = $1 OR email = $2;', 1, 'user@email.com')
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more in our [docs](https://pris.ly/d/raw-queries).
|
||||||
|
*/
|
||||||
|
$queryRawUnsafe<T = unknown>(query: string, ...values: any[]): Prisma.PrismaPromise<T>;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the running of a sequence of read/write operations that are guaranteed to either succeed or fail as a whole.
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const [george, bob, alice] = await prisma.$transaction([
|
||||||
|
* prisma.user.create({ data: { name: 'George' } }),
|
||||||
|
* prisma.user.create({ data: { name: 'Bob' } }),
|
||||||
|
* prisma.user.create({ data: { name: 'Alice' } }),
|
||||||
|
* ])
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more in our [docs](https://www.prisma.io/docs/concepts/components/prisma-client/transactions).
|
||||||
|
*/
|
||||||
|
$transaction<P extends Prisma.PrismaPromise<any>[]>(arg: [...P], options?: { isolationLevel?: Prisma.TransactionIsolationLevel }): runtime.Types.Utils.JsPromise<runtime.Types.Utils.UnwrapTuple<P>>
|
||||||
|
|
||||||
|
$transaction<R>(fn: (prisma: Omit<PrismaClient, runtime.ITXClientDenyList>) => runtime.Types.Utils.JsPromise<R>, options?: { maxWait?: number, timeout?: number, isolationLevel?: Prisma.TransactionIsolationLevel }): runtime.Types.Utils.JsPromise<R>
|
||||||
|
|
||||||
|
$extends: runtime.Types.Extensions.ExtendsHook<"extends", Prisma.TypeMapCb<OmitOpts>, ExtArgs, runtime.Types.Utils.Call<Prisma.TypeMapCb<OmitOpts>, {
|
||||||
|
extArgs: ExtArgs
|
||||||
|
}>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `prisma.user`: Exposes CRUD operations for the **User** model.
|
||||||
|
* Example usage:
|
||||||
|
* ```ts
|
||||||
|
* // Fetch zero or more Users
|
||||||
|
* const users = await prisma.user.findMany()
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
get user(): Prisma.UserDelegate<ExtArgs, { omit: OmitOpts }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `prisma.session`: Exposes CRUD operations for the **Session** model.
|
||||||
|
* Example usage:
|
||||||
|
* ```ts
|
||||||
|
* // Fetch zero or more Sessions
|
||||||
|
* const sessions = await prisma.session.findMany()
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
get session(): Prisma.SessionDelegate<ExtArgs, { omit: OmitOpts }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `prisma.userPreferences`: Exposes CRUD operations for the **UserPreferences** model.
|
||||||
|
* Example usage:
|
||||||
|
* ```ts
|
||||||
|
* // Fetch zero or more UserPreferences
|
||||||
|
* const userPreferences = await prisma.userPreferences.findMany()
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
get userPreferences(): Prisma.UserPreferencesDelegate<ExtArgs, { omit: OmitOpts }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `prisma.channel`: Exposes CRUD operations for the **Channel** model.
|
||||||
|
* Example usage:
|
||||||
|
* ```ts
|
||||||
|
* // Fetch zero or more Channels
|
||||||
|
* const channels = await prisma.channel.findMany()
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
get channel(): Prisma.ChannelDelegate<ExtArgs, { omit: OmitOpts }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPrismaClientClass(): PrismaClientConstructor {
|
||||||
|
return runtime.getPrismaClient(config) as unknown as PrismaClientConstructor
|
||||||
|
}
|
||||||
1000
server/prisma/generated/client/internal/prismaNamespace.ts
Normal file
1000
server/prisma/generated/client/internal/prismaNamespace.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,128 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* WARNING: This is an internal file that is subject to change!
|
||||||
|
*
|
||||||
|
* 🛑 Under no circumstances should you import this file directly! 🛑
|
||||||
|
*
|
||||||
|
* All exports from this file are wrapped under a `Prisma` namespace object in the browser.ts file.
|
||||||
|
* While this enables partial backward compatibility, it is not part of the stable public API.
|
||||||
|
*
|
||||||
|
* If you are looking for your Models, Enums, and Input Types, please import them from the respective
|
||||||
|
* model files in the `model` directory!
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as runtime from "@prisma/client/runtime/index-browser"
|
||||||
|
|
||||||
|
export type * from '../models.ts'
|
||||||
|
export type * from './prismaNamespace.ts'
|
||||||
|
|
||||||
|
export const Decimal = runtime.Decimal
|
||||||
|
|
||||||
|
|
||||||
|
export const NullTypes = {
|
||||||
|
DbNull: runtime.NullTypes.DbNull as (new (secret: never) => typeof runtime.DbNull),
|
||||||
|
JsonNull: runtime.NullTypes.JsonNull as (new (secret: never) => typeof runtime.JsonNull),
|
||||||
|
AnyNull: runtime.NullTypes.AnyNull as (new (secret: never) => typeof runtime.AnyNull),
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Helper for filtering JSON entries that have `null` on the database (empty on the db)
|
||||||
|
*
|
||||||
|
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
|
||||||
|
*/
|
||||||
|
export const DbNull = runtime.DbNull
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for filtering JSON entries that have JSON `null` values (not empty on the db)
|
||||||
|
*
|
||||||
|
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
|
||||||
|
*/
|
||||||
|
export const JsonNull = runtime.JsonNull
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for filtering JSON entries that are `Prisma.DbNull` or `Prisma.JsonNull`
|
||||||
|
*
|
||||||
|
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
|
||||||
|
*/
|
||||||
|
export const AnyNull = runtime.AnyNull
|
||||||
|
|
||||||
|
|
||||||
|
export const ModelName = {
|
||||||
|
User: 'User',
|
||||||
|
Session: 'Session',
|
||||||
|
UserPreferences: 'UserPreferences',
|
||||||
|
Channel: 'Channel'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Enums
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const TransactionIsolationLevel = {
|
||||||
|
Serializable: 'Serializable'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type TransactionIsolationLevel = (typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel]
|
||||||
|
|
||||||
|
|
||||||
|
export const UserScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
username: 'username',
|
||||||
|
password: 'password',
|
||||||
|
displayName: 'displayName',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type UserScalarFieldEnum = (typeof UserScalarFieldEnum)[keyof typeof UserScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const SessionScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
userId: 'userId',
|
||||||
|
expiresAt: 'expiresAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type SessionScalarFieldEnum = (typeof SessionScalarFieldEnum)[keyof typeof SessionScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const UserPreferencesScalarFieldEnum = {
|
||||||
|
userId: 'userId',
|
||||||
|
toggleInputHotkey: 'toggleInputHotkey',
|
||||||
|
toggleOutputHotkey: 'toggleOutputHotkey'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type UserPreferencesScalarFieldEnum = (typeof UserPreferencesScalarFieldEnum)[keyof typeof UserPreferencesScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const ChannelScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
maxClients: 'maxClients',
|
||||||
|
persistent: 'persistent',
|
||||||
|
owner_id: 'owner_id'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type ChannelScalarFieldEnum = (typeof ChannelScalarFieldEnum)[keyof typeof ChannelScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const SortOrder = {
|
||||||
|
asc: 'asc',
|
||||||
|
desc: 'desc'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder]
|
||||||
|
|
||||||
|
|
||||||
|
export const NullsOrder = {
|
||||||
|
first: 'first',
|
||||||
|
last: 'last'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder]
|
||||||
|
|
||||||
15
server/prisma/generated/client/models.ts
Normal file
15
server/prisma/generated/client/models.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* This is a barrel export file for all models and their related types.
|
||||||
|
*
|
||||||
|
* 🟢 You can import this file directly.
|
||||||
|
*/
|
||||||
|
export type * from './models/User.ts'
|
||||||
|
export type * from './models/Session.ts'
|
||||||
|
export type * from './models/UserPreferences.ts'
|
||||||
|
export type * from './models/Channel.ts'
|
||||||
|
export type * from './commonInputTypes.ts'
|
||||||
1412
server/prisma/generated/client/models/Channel.ts
Normal file
1412
server/prisma/generated/client/models/Channel.ts
Normal file
File diff suppressed because it is too large
Load Diff
1267
server/prisma/generated/client/models/Session.ts
Normal file
1267
server/prisma/generated/client/models/Session.ts
Normal file
File diff suppressed because it is too large
Load Diff
1597
server/prisma/generated/client/models/User.ts
Normal file
1597
server/prisma/generated/client/models/User.ts
Normal file
File diff suppressed because it is too large
Load Diff
1228
server/prisma/generated/client/models/UserPreferences.ts
Normal file
1228
server/prisma/generated/client/models/UserPreferences.ts
Normal file
File diff suppressed because it is too large
Load Diff
144
server/prisma/generated/client/package.json
Normal file
144
server/prisma/generated/client/package.json
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
{
|
||||||
|
"name": "prisma-client-bc59e606b3f744a8b15c18a26f89a0eb5328cd0b7e5ee2c8265028eef68b0317",
|
||||||
|
"main": "index.js",
|
||||||
|
"types": "index.d.ts",
|
||||||
|
"browser": "default.js",
|
||||||
|
"exports": {
|
||||||
|
"./client": {
|
||||||
|
"require": {
|
||||||
|
"node": "./index.js",
|
||||||
|
"edge-light": "./edge.js",
|
||||||
|
"workerd": "./edge.js",
|
||||||
|
"worker": "./edge.js",
|
||||||
|
"browser": "./index-browser.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
"node": "./index.js",
|
||||||
|
"edge-light": "./edge.js",
|
||||||
|
"workerd": "./edge.js",
|
||||||
|
"worker": "./edge.js",
|
||||||
|
"browser": "./index-browser.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"./package.json": "./package.json",
|
||||||
|
".": {
|
||||||
|
"require": {
|
||||||
|
"node": "./index.js",
|
||||||
|
"edge-light": "./edge.js",
|
||||||
|
"workerd": "./edge.js",
|
||||||
|
"worker": "./edge.js",
|
||||||
|
"browser": "./index-browser.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
"node": "./index.js",
|
||||||
|
"edge-light": "./edge.js",
|
||||||
|
"workerd": "./edge.js",
|
||||||
|
"worker": "./edge.js",
|
||||||
|
"browser": "./index-browser.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"./extension": {
|
||||||
|
"types": "./extension.d.ts",
|
||||||
|
"require": "./extension.js",
|
||||||
|
"import": "./extension.js",
|
||||||
|
"default": "./extension.js"
|
||||||
|
},
|
||||||
|
"./index-browser": {
|
||||||
|
"types": "./index.d.ts",
|
||||||
|
"require": "./index-browser.js",
|
||||||
|
"import": "./index-browser.js",
|
||||||
|
"default": "./index-browser.js"
|
||||||
|
},
|
||||||
|
"./index": {
|
||||||
|
"types": "./index.d.ts",
|
||||||
|
"require": "./index.js",
|
||||||
|
"import": "./index.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"./edge": {
|
||||||
|
"types": "./edge.d.ts",
|
||||||
|
"require": "./edge.js",
|
||||||
|
"import": "./edge.js",
|
||||||
|
"default": "./edge.js"
|
||||||
|
},
|
||||||
|
"./runtime/client": {
|
||||||
|
"types": "./runtime/client.d.ts",
|
||||||
|
"node": {
|
||||||
|
"require": "./runtime/client.js",
|
||||||
|
"default": "./runtime/client.js"
|
||||||
|
},
|
||||||
|
"require": "./runtime/client.js",
|
||||||
|
"import": "./runtime/client.mjs",
|
||||||
|
"default": "./runtime/client.mjs"
|
||||||
|
},
|
||||||
|
"./runtime/wasm-compiler-edge": {
|
||||||
|
"types": "./runtime/wasm-compiler-edge.d.ts",
|
||||||
|
"require": "./runtime/wasm-compiler-edge.js",
|
||||||
|
"import": "./runtime/wasm-compiler-edge.mjs",
|
||||||
|
"default": "./runtime/wasm-compiler-edge.mjs"
|
||||||
|
},
|
||||||
|
"./runtime/index-browser": {
|
||||||
|
"types": "./runtime/index-browser.d.ts",
|
||||||
|
"require": "./runtime/index-browser.js",
|
||||||
|
"import": "./runtime/index-browser.mjs",
|
||||||
|
"default": "./runtime/index-browser.mjs"
|
||||||
|
},
|
||||||
|
"./generator-build": {
|
||||||
|
"require": "./generator-build/index.js",
|
||||||
|
"import": "./generator-build/index.js",
|
||||||
|
"default": "./generator-build/index.js"
|
||||||
|
},
|
||||||
|
"./sql": {
|
||||||
|
"require": {
|
||||||
|
"types": "./sql.d.ts",
|
||||||
|
"node": "./sql.js",
|
||||||
|
"default": "./sql.js"
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
"types": "./sql.d.ts",
|
||||||
|
"node": "./sql.mjs",
|
||||||
|
"default": "./sql.mjs"
|
||||||
|
},
|
||||||
|
"default": "./sql.js"
|
||||||
|
},
|
||||||
|
"./*": "./*"
|
||||||
|
},
|
||||||
|
"version": "7.2.0",
|
||||||
|
"sideEffects": false,
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/client-runtime-utils": "7.2.0"
|
||||||
|
},
|
||||||
|
"imports": {
|
||||||
|
"#wasm-compiler-loader": {
|
||||||
|
"edge-light": "./wasm-edge-light-loader.mjs",
|
||||||
|
"workerd": "./wasm-worker-loader.mjs",
|
||||||
|
"worker": "./wasm-worker-loader.mjs",
|
||||||
|
"default": "./wasm-worker-loader.mjs"
|
||||||
|
},
|
||||||
|
"#main-entry-point": {
|
||||||
|
"require": {
|
||||||
|
"node": "./index.js",
|
||||||
|
"edge-light": "./edge.js",
|
||||||
|
"workerd": "./edge.js",
|
||||||
|
"worker": "./edge.js",
|
||||||
|
"browser": "./index-browser.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
"node": "./index.js",
|
||||||
|
"edge-light": "./edge.js",
|
||||||
|
"workerd": "./edge.js",
|
||||||
|
"worker": "./edge.js",
|
||||||
|
"browser": "./index-browser.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"default": "./index.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
server/prisma/generated/client/query_compiler_bg.js
Normal file
2
server/prisma/generated/client/query_compiler_bg.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
"use strict";var h=Object.defineProperty;var T=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var j=Object.prototype.hasOwnProperty;var D=(e,t)=>{for(var n in t)h(e,n,{get:t[n],enumerable:!0})},O=(e,t,n,_)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of M(t))!j.call(e,r)&&r!==n&&h(e,r,{get:()=>t[r],enumerable:!(_=T(t,r))||_.enumerable});return e};var B=e=>O(h({},"__esModule",{value:!0}),e);var xe={};D(xe,{QueryCompiler:()=>F,__wbg_Error_e83987f665cf5504:()=>q,__wbg_Number_bb48ca12f395cd08:()=>C,__wbg_String_8f0eb39a4a4c2f66:()=>k,__wbg___wbindgen_boolean_get_6d5a1ee65bab5f68:()=>W,__wbg___wbindgen_debug_string_df47ffb5e35e6763:()=>V,__wbg___wbindgen_in_bb933bd9e1b3bc0f:()=>z,__wbg___wbindgen_is_object_c818261d21f283a4:()=>L,__wbg___wbindgen_is_string_fbb76cb2940daafd:()=>P,__wbg___wbindgen_is_undefined_2d472862bd29a478:()=>Q,__wbg___wbindgen_jsval_loose_eq_b664b38a2f582147:()=>Y,__wbg___wbindgen_number_get_a20bf9b85341449d:()=>G,__wbg___wbindgen_string_get_e4f06c90489ad01b:()=>J,__wbg___wbindgen_throw_b855445ff6a94295:()=>X,__wbg_entries_e171b586f8f6bdbf:()=>H,__wbg_getTime_14776bfb48a1bff9:()=>K,__wbg_get_7bed016f185add81:()=>Z,__wbg_get_with_ref_key_1dc361bd10053bfe:()=>v,__wbg_instanceof_ArrayBuffer_70beb1189ca63b38:()=>ee,__wbg_instanceof_Uint8Array_20c8e73002f7af98:()=>te,__wbg_isSafeInteger_d216eda7911dde36:()=>ne,__wbg_length_69bca3cb64fc8748:()=>re,__wbg_length_cdd215e10d9dd507:()=>_e,__wbg_new_0_f9740686d739025c:()=>oe,__wbg_new_1acc0b6eea89d040:()=>ce,__wbg_new_5a79be3ab53b8aa5:()=>ie,__wbg_new_68651c719dcda04e:()=>se,__wbg_new_e17d9f43105b08be:()=>ue,__wbg_prototypesetcall_2a6620b6922694b2:()=>fe,__wbg_set_3f1d0b984ed272ed:()=>be,__wbg_set_907fb406c34a251d:()=>de,__wbg_set_c213c871859d6500:()=>ae,__wbg_set_message_82ae475bb413aa5c:()=>ge,__wbg_set_wasm:()=>N,__wbindgen_cast_2241b6af4c4b2941:()=>le,__wbindgen_cast_4625c577ab2ec9ee:()=>we,__wbindgen_cast_9ae0607507abb057:()=>pe,__wbindgen_cast_d6cd19b81560fd6e:()=>ye,__wbindgen_init_externref_table:()=>me});module.exports=B(xe);var A=()=>{};A.prototype=A;let o;function N(e){o=e}let p=null;function a(){return(p===null||p.byteLength===0)&&(p=new Uint8Array(o.memory.buffer)),p}let y=new TextDecoder("utf-8",{ignoreBOM:!0,fatal:!0});y.decode();const U=2146435072;let S=0;function R(e,t){return S+=t,S>=U&&(y=new TextDecoder("utf-8",{ignoreBOM:!0,fatal:!0}),y.decode(),S=t),y.decode(a().subarray(e,e+t))}function m(e,t){return e=e>>>0,R(e,t)}let f=0;const g=new TextEncoder;"encodeInto"in g||(g.encodeInto=function(e,t){const n=g.encode(e);return t.set(n),{read:e.length,written:n.length}});function l(e,t,n){if(n===void 0){const i=g.encode(e),d=t(i.length,1)>>>0;return a().subarray(d,d+i.length).set(i),f=i.length,d}let _=e.length,r=t(_,1)>>>0;const s=a();let c=0;for(;c<_;c++){const i=e.charCodeAt(c);if(i>127)break;s[r+c]=i}if(c!==_){c!==0&&(e=e.slice(c)),r=n(r,_,_=c+e.length*3,1)>>>0;const i=a().subarray(r+c,r+_),d=g.encodeInto(e,i);c+=d.written,r=n(r,_,c,1)>>>0}return f=c,r}let b=null;function u(){return(b===null||b.buffer.detached===!0||b.buffer.detached===void 0&&b.buffer!==o.memory.buffer)&&(b=new DataView(o.memory.buffer)),b}function x(e){return e==null}function I(e){const t=typeof e;if(t=="number"||t=="boolean"||e==null)return`${e}`;if(t=="string")return`"${e}"`;if(t=="symbol"){const r=e.description;return r==null?"Symbol":`Symbol(${r})`}if(t=="function"){const r=e.name;return typeof r=="string"&&r.length>0?`Function(${r})`:"Function"}if(Array.isArray(e)){const r=e.length;let s="[";r>0&&(s+=I(e[0]));for(let c=1;c<r;c++)s+=", "+I(e[c]);return s+="]",s}const n=/\[object ([^\]]+)\]/.exec(toString.call(e));let _;if(n&&n.length>1)_=n[1];else return toString.call(e);if(_=="Object")try{return"Object("+JSON.stringify(e)+")"}catch{return"Object"}return e instanceof Error?`${e.name}: ${e.message}
|
||||||
|
${e.stack}`:_}function $(e,t){return e=e>>>0,a().subarray(e/1,e/1+t)}function w(e){const t=o.__wbindgen_externrefs.get(e);return o.__externref_table_dealloc(e),t}const E=typeof FinalizationRegistry>"u"?{register:()=>{},unregister:()=>{}}:new FinalizationRegistry(e=>o.__wbg_querycompiler_free(e>>>0,1));class F{__destroy_into_raw(){const t=this.__wbg_ptr;return this.__wbg_ptr=0,E.unregister(this),t}free(){const t=this.__destroy_into_raw();o.__wbg_querycompiler_free(t,0)}compileBatch(t){const n=l(t,o.__wbindgen_malloc,o.__wbindgen_realloc),_=f,r=o.querycompiler_compileBatch(this.__wbg_ptr,n,_);if(r[2])throw w(r[1]);return w(r[0])}constructor(t){const n=o.querycompiler_new(t);if(n[2])throw w(n[1]);return this.__wbg_ptr=n[0]>>>0,E.register(this,this.__wbg_ptr,this),this}compile(t){const n=l(t,o.__wbindgen_malloc,o.__wbindgen_realloc),_=f,r=o.querycompiler_compile(this.__wbg_ptr,n,_);if(r[2])throw w(r[1]);return w(r[0])}}Symbol.dispose&&(F.prototype[Symbol.dispose]=F.prototype.free);function q(e,t){return Error(m(e,t))}function C(e){return Number(e)}function k(e,t){const n=String(t),_=l(n,o.__wbindgen_malloc,o.__wbindgen_realloc),r=f;u().setInt32(e+4*1,r,!0),u().setInt32(e+4*0,_,!0)}function W(e){const t=e,n=typeof t=="boolean"?t:void 0;return x(n)?16777215:n?1:0}function V(e,t){const n=I(t),_=l(n,o.__wbindgen_malloc,o.__wbindgen_realloc),r=f;u().setInt32(e+4*1,r,!0),u().setInt32(e+4*0,_,!0)}function z(e,t){return e in t}function L(e){const t=e;return typeof t=="object"&&t!==null}function P(e){return typeof e=="string"}function Q(e){return e===void 0}function Y(e,t){return e==t}function G(e,t){const n=t,_=typeof n=="number"?n:void 0;u().setFloat64(e+8*1,x(_)?0:_,!0),u().setInt32(e+4*0,!x(_),!0)}function J(e,t){const n=t,_=typeof n=="string"?n:void 0;var r=x(_)?0:l(_,o.__wbindgen_malloc,o.__wbindgen_realloc),s=f;u().setInt32(e+4*1,s,!0),u().setInt32(e+4*0,r,!0)}function X(e,t){throw new Error(m(e,t))}function H(e){return Object.entries(e)}function K(e){return e.getTime()}function Z(e,t){return e[t>>>0]}function v(e,t){return e[t]}function ee(e){let t;try{t=e instanceof ArrayBuffer}catch{t=!1}return t}function te(e){let t;try{t=e instanceof Uint8Array}catch{t=!1}return t}function ne(e){return Number.isSafeInteger(e)}function re(e){return e.length}function _e(e){return e.length}function oe(){return new Date}function ce(){return new Object}function ie(e){return new Uint8Array(e)}function se(){return new Map}function ue(){return new Array}function fe(e,t,n){Uint8Array.prototype.set.call($(e,t),n)}function be(e,t,n){e[t]=n}function de(e,t,n){return e.set(t,n)}function ae(e,t,n){e[t>>>0]=n}function ge(e,t){global.PRISMA_WASM_PANIC_REGISTRY.set_message(m(e,t))}function le(e,t){return m(e,t)}function we(e){return BigInt.asUintN(64,e)}function pe(e){return e}function ye(e){return e}function me(){const e=o.__wbindgen_externrefs,t=e.grow(4);e.set(0,void 0),e.set(t+0,void 0),e.set(t+1,null),e.set(t+2,!0),e.set(t+3,!1)}0&&(module.exports={QueryCompiler,__wbg_Error_e83987f665cf5504,__wbg_Number_bb48ca12f395cd08,__wbg_String_8f0eb39a4a4c2f66,__wbg___wbindgen_boolean_get_6d5a1ee65bab5f68,__wbg___wbindgen_debug_string_df47ffb5e35e6763,__wbg___wbindgen_in_bb933bd9e1b3bc0f,__wbg___wbindgen_is_object_c818261d21f283a4,__wbg___wbindgen_is_string_fbb76cb2940daafd,__wbg___wbindgen_is_undefined_2d472862bd29a478,__wbg___wbindgen_jsval_loose_eq_b664b38a2f582147,__wbg___wbindgen_number_get_a20bf9b85341449d,__wbg___wbindgen_string_get_e4f06c90489ad01b,__wbg___wbindgen_throw_b855445ff6a94295,__wbg_entries_e171b586f8f6bdbf,__wbg_getTime_14776bfb48a1bff9,__wbg_get_7bed016f185add81,__wbg_get_with_ref_key_1dc361bd10053bfe,__wbg_instanceof_ArrayBuffer_70beb1189ca63b38,__wbg_instanceof_Uint8Array_20c8e73002f7af98,__wbg_isSafeInteger_d216eda7911dde36,__wbg_length_69bca3cb64fc8748,__wbg_length_cdd215e10d9dd507,__wbg_new_0_f9740686d739025c,__wbg_new_1acc0b6eea89d040,__wbg_new_5a79be3ab53b8aa5,__wbg_new_68651c719dcda04e,__wbg_new_e17d9f43105b08be,__wbg_prototypesetcall_2a6620b6922694b2,__wbg_set_3f1d0b984ed272ed,__wbg_set_907fb406c34a251d,__wbg_set_c213c871859d6500,__wbg_set_message_82ae475bb413aa5c,__wbg_set_wasm,__wbindgen_cast_2241b6af4c4b2941,__wbindgen_cast_4625c577ab2ec9ee,__wbindgen_cast_9ae0607507abb057,__wbindgen_cast_d6cd19b81560fd6e,__wbindgen_init_externref_table});
|
||||||
BIN
server/prisma/generated/client/query_compiler_bg.wasm
Normal file
BIN
server/prisma/generated/client/query_compiler_bg.wasm
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
3317
server/prisma/generated/client/runtime/client.d.ts
vendored
Normal file
3317
server/prisma/generated/client/runtime/client.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
86
server/prisma/generated/client/runtime/client.js
Normal file
86
server/prisma/generated/client/runtime/client.js
Normal file
File diff suppressed because one or more lines are too long
87
server/prisma/generated/client/runtime/index-browser.d.ts
vendored
Normal file
87
server/prisma/generated/client/runtime/index-browser.d.ts
vendored
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import { AnyNull } from '@prisma/client-runtime-utils';
|
||||||
|
import { DbNull } from '@prisma/client-runtime-utils';
|
||||||
|
import { Decimal } from '@prisma/client-runtime-utils';
|
||||||
|
import { isAnyNull } from '@prisma/client-runtime-utils';
|
||||||
|
import { isDbNull } from '@prisma/client-runtime-utils';
|
||||||
|
import { isJsonNull } from '@prisma/client-runtime-utils';
|
||||||
|
import { JsonNull } from '@prisma/client-runtime-utils';
|
||||||
|
import { NullTypes } from '@prisma/client-runtime-utils';
|
||||||
|
|
||||||
|
export { AnyNull }
|
||||||
|
|
||||||
|
declare type Args<T, F extends Operation> = T extends {
|
||||||
|
[K: symbol]: {
|
||||||
|
types: {
|
||||||
|
operations: {
|
||||||
|
[K in F]: {
|
||||||
|
args: any;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} ? T[symbol]['types']['operations'][F]['args'] : any;
|
||||||
|
|
||||||
|
export { DbNull }
|
||||||
|
|
||||||
|
export { Decimal }
|
||||||
|
|
||||||
|
declare type Exact<A, W> = (A extends unknown ? (W extends A ? {
|
||||||
|
[K in keyof A]: Exact<A[K], W[K]>;
|
||||||
|
} : W) : never) | (A extends Narrowable ? A : never);
|
||||||
|
|
||||||
|
export declare function getRuntime(): GetRuntimeOutput;
|
||||||
|
|
||||||
|
declare type GetRuntimeOutput = {
|
||||||
|
id: RuntimeName;
|
||||||
|
prettyName: string;
|
||||||
|
isEdge: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { isAnyNull }
|
||||||
|
|
||||||
|
export { isDbNull }
|
||||||
|
|
||||||
|
export { isJsonNull }
|
||||||
|
|
||||||
|
export { JsonNull }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates more strict variant of an enum which, unlike regular enum,
|
||||||
|
* throws on non-existing property access. This can be useful in following situations:
|
||||||
|
* - we have an API, that accepts both `undefined` and `SomeEnumType` as an input
|
||||||
|
* - enum values are generated dynamically from DMMF.
|
||||||
|
*
|
||||||
|
* In that case, if using normal enums and no compile-time typechecking, using non-existing property
|
||||||
|
* will result in `undefined` value being used, which will be accepted. Using strict enum
|
||||||
|
* in this case will help to have a runtime exception, telling you that you are probably doing something wrong.
|
||||||
|
*
|
||||||
|
* Note: if you need to check for existence of a value in the enum you can still use either
|
||||||
|
* `in` operator or `hasOwnProperty` function.
|
||||||
|
*
|
||||||
|
* @param definition
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export declare function makeStrictEnum<T extends Record<PropertyKey, string | number>>(definition: T): T;
|
||||||
|
|
||||||
|
declare type Narrowable = string | number | bigint | boolean | [];
|
||||||
|
|
||||||
|
export { NullTypes }
|
||||||
|
|
||||||
|
declare type Operation = 'findFirst' | 'findFirstOrThrow' | 'findUnique' | 'findUniqueOrThrow' | 'findMany' | 'create' | 'createMany' | 'createManyAndReturn' | 'update' | 'updateMany' | 'updateManyAndReturn' | 'upsert' | 'delete' | 'deleteMany' | 'aggregate' | 'count' | 'groupBy' | '$queryRaw' | '$executeRaw' | '$queryRawUnsafe' | '$executeRawUnsafe' | 'findRaw' | 'aggregateRaw' | '$runCommandRaw';
|
||||||
|
|
||||||
|
declare namespace Public {
|
||||||
|
export {
|
||||||
|
validator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export { Public }
|
||||||
|
|
||||||
|
declare type RuntimeName = 'workerd' | 'deno' | 'netlify' | 'node' | 'bun' | 'edge-light' | '';
|
||||||
|
|
||||||
|
declare function validator<V>(): <S>(select: Exact<S, V>) => S;
|
||||||
|
|
||||||
|
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation>(client: C, model: M, operation: O): <S>(select: Exact<S, Args<C[M], O>>) => S;
|
||||||
|
|
||||||
|
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation, P extends keyof Args<C[M], O>>(client: C, model: M, operation: O, prop: P): <S>(select: Exact<S, Args<C[M], O>[P]>) => S;
|
||||||
|
|
||||||
|
export { }
|
||||||
6
server/prisma/generated/client/runtime/index-browser.js
Normal file
6
server/prisma/generated/client/runtime/index-browser.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
"use strict";var s=Object.defineProperty;var g=Object.getOwnPropertyDescriptor;var p=Object.getOwnPropertyNames;var f=Object.prototype.hasOwnProperty;var a=(e,t)=>{for(var n in t)s(e,n,{get:t[n],enumerable:!0})},y=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of p(t))!f.call(e,i)&&i!==n&&s(e,i,{get:()=>t[i],enumerable:!(r=g(t,i))||r.enumerable});return e};var x=e=>y(s({},"__esModule",{value:!0}),e);var O={};a(O,{AnyNull:()=>o.AnyNull,DbNull:()=>o.DbNull,Decimal:()=>m.Decimal,JsonNull:()=>o.JsonNull,NullTypes:()=>o.NullTypes,Public:()=>l,getRuntime:()=>c,isAnyNull:()=>o.isAnyNull,isDbNull:()=>o.isDbNull,isJsonNull:()=>o.isJsonNull,makeStrictEnum:()=>u});module.exports=x(O);var l={};a(l,{validator:()=>d});function d(...e){return t=>t}var b=new Set(["toJSON","$$typeof","asymmetricMatch",Symbol.iterator,Symbol.toStringTag,Symbol.isConcatSpreadable,Symbol.toPrimitive]);function u(e){return new Proxy(e,{get(t,n){if(n in t)return t[n];if(!b.has(n))throw new TypeError("Invalid enum value: ".concat(String(n)))}})}var N=()=>{var e,t;return((t=(e=globalThis.process)==null?void 0:e.release)==null?void 0:t.name)==="node"},S=()=>{var e,t;return!!globalThis.Bun||!!((t=(e=globalThis.process)==null?void 0:e.versions)!=null&&t.bun)},E=()=>!!globalThis.Deno,R=()=>typeof globalThis.Netlify=="object",h=()=>typeof globalThis.EdgeRuntime=="object",C=()=>{var e;return((e=globalThis.navigator)==null?void 0:e.userAgent)==="Cloudflare-Workers"};function k(){var n;return(n=[[R,"netlify"],[h,"edge-light"],[C,"workerd"],[E,"deno"],[S,"bun"],[N,"node"]].flatMap(r=>r[0]()?[r[1]]:[]).at(0))!=null?n:""}var M={node:"Node.js",workerd:"Cloudflare Workers",deno:"Deno and Deno Deploy",netlify:"Netlify Edge Functions","edge-light":"Edge Runtime (Vercel Edge Functions, Vercel Edge Middleware, Next.js (Pages Router) Edge API Routes, Next.js (App Router) Edge Route Handlers or Next.js Middleware)"};function c(){let e=k();return{id:e,prettyName:M[e]||e,isEdge:["workerd","deno","netlify","edge-light"].includes(e)}}var o=require("@prisma/client-runtime-utils"),m=require("@prisma/client-runtime-utils");0&&(module.exports={AnyNull,DbNull,Decimal,JsonNull,NullTypes,Public,getRuntime,isAnyNull,isDbNull,isJsonNull,makeStrictEnum});
|
||||||
|
//# sourceMappingURL=index-browser.js.map
|
||||||
76
server/prisma/generated/client/runtime/wasm-compiler-edge.js
Normal file
76
server/prisma/generated/client/runtime/wasm-compiler-edge.js
Normal file
File diff suppressed because one or more lines are too long
45
server/prisma/generated/client/schema.prisma
Normal file
45
server/prisma/generated/client/schema.prisma
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
datasource db {
|
||||||
|
provider = "sqlite"
|
||||||
|
}
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
output = "./generated/client"
|
||||||
|
}
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
username String @unique
|
||||||
|
password String
|
||||||
|
displayName String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
Session Session[]
|
||||||
|
UserPreferences UserPreferences?
|
||||||
|
channels Channel[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model Session {
|
||||||
|
id String @id
|
||||||
|
userId String
|
||||||
|
expiresAt DateTime
|
||||||
|
user User @relation(references: [id], fields: [userId], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@index([userId])
|
||||||
|
}
|
||||||
|
|
||||||
|
model UserPreferences {
|
||||||
|
userId String @id
|
||||||
|
toggleInputHotkey String? @default("")
|
||||||
|
toggleOutputHotkey String? @default("")
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
}
|
||||||
|
|
||||||
|
model Channel {
|
||||||
|
id String @id
|
||||||
|
name String
|
||||||
|
maxClients Int?
|
||||||
|
persistent Boolean @default(false)
|
||||||
|
owner_id String?
|
||||||
|
owner User? @relation(fields: [owner_id], references: [id], onDelete: Cascade)
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
export default import('./query_compiler_bg.wasm?module')
|
||||||
5
server/prisma/generated/client/wasm-worker-loader.mjs
Normal file
5
server/prisma/generated/client/wasm-worker-loader.mjs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
export default import('./query_compiler_bg.wasm')
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `volumes` on the `UserPreferences` table. All the data in the column will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Channel" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"maxClients" INTEGER,
|
||||||
|
"persistent" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"owner_id" TEXT,
|
||||||
|
CONSTRAINT "Channel_owner_id_fkey" FOREIGN KEY ("owner_id") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- RedefineTables
|
||||||
|
PRAGMA defer_foreign_keys=ON;
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_UserPreferences" (
|
||||||
|
"userId" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"toggleInputHotkey" TEXT DEFAULT '',
|
||||||
|
"toggleOutputHotkey" TEXT DEFAULT '',
|
||||||
|
CONSTRAINT "UserPreferences_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_UserPreferences" ("toggleInputHotkey", "toggleOutputHotkey", "userId") SELECT "toggleInputHotkey", "toggleOutputHotkey", "userId" FROM "UserPreferences";
|
||||||
|
DROP TABLE "UserPreferences";
|
||||||
|
ALTER TABLE "new_UserPreferences" RENAME TO "UserPreferences";
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
|
PRAGMA defer_foreign_keys=OFF;
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
datasource db {
|
datasource db {
|
||||||
provider = "sqlite"
|
provider = "sqlite"
|
||||||
url = env("DATABASE_URL")
|
// url = env("DATABASE_URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
generator client {
|
generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client"
|
||||||
// output = "./generated/client"
|
output = "./generated/client"
|
||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
@@ -15,16 +15,15 @@ model User {
|
|||||||
displayName String
|
displayName String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
Session Session[]
|
Session Session[]
|
||||||
UserPreferences UserPreferences?
|
UserPreferences UserPreferences?
|
||||||
|
channels Channel[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model Session {
|
model Session {
|
||||||
id String @id
|
id String @id
|
||||||
userId String
|
userId String
|
||||||
expiresAt DateTime
|
expiresAt DateTime
|
||||||
|
|
||||||
user User @relation(references: [id], fields: [userId], onDelete: Cascade)
|
user User @relation(references: [id], fields: [userId], onDelete: Cascade)
|
||||||
|
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@ -34,7 +33,14 @@ model UserPreferences {
|
|||||||
userId String @id
|
userId String @id
|
||||||
toggleInputHotkey String? @default("")
|
toggleInputHotkey String? @default("")
|
||||||
toggleOutputHotkey String? @default("")
|
toggleOutputHotkey String? @default("")
|
||||||
volumes Json? @default("{}")
|
|
||||||
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model Channel {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
maxClients Int?
|
||||||
|
persistent Boolean @default(false)
|
||||||
|
owner_id String?
|
||||||
|
owner User? @relation(fields: [owner_id], references: [id], onDelete: Cascade)
|
||||||
|
}
|
||||||
23
server/prisma/seed.ts
Normal file
23
server/prisma/seed.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import prisma from '../prisma/client.ts'
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
await prisma.channel.upsert({
|
||||||
|
where: { id: 'default' },
|
||||||
|
update: {},
|
||||||
|
create: {
|
||||||
|
id: 'default',
|
||||||
|
name: 'Default',
|
||||||
|
persistent: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.then(async () => {
|
||||||
|
await prisma.$disconnect()
|
||||||
|
})
|
||||||
|
.catch(async (e) => {
|
||||||
|
console.error(e)
|
||||||
|
await prisma.$disconnect()
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import type { FastifyInstance } from 'fastify'
|
|
||||||
import bcrypt from 'bcrypt'
|
|
||||||
import { z } from 'zod'
|
|
||||||
|
|
||||||
export default function (fastify: FastifyInstance) {
|
|
||||||
fastify.post('/attachments/upload', async (req, reply) => {
|
|
||||||
try {
|
|
||||||
const schema = z.object({
|
|
||||||
file: z.file(),
|
|
||||||
})
|
|
||||||
const input = schema.parse(req.body)
|
|
||||||
|
|
||||||
// const file = req.file({ limits: { } })
|
|
||||||
|
|
||||||
const id = await bcrypt.hash(input.file, 10)
|
|
||||||
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
fastify.log.error(err)
|
|
||||||
reply.code(400)
|
|
||||||
|
|
||||||
if (err instanceof z.ZodError) {
|
|
||||||
reply.send({ error: z.prettifyError(err) })
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
reply.send({ error: err.message })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
255
server/routes/channels.ts
Normal file
255
server/routes/channels.ts
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
import type { FastifyInstance } from 'fastify'
|
||||||
|
import { z } from 'zod'
|
||||||
|
import prisma from '../prisma/client.ts'
|
||||||
|
import { channelPublicSelect } from '../dto/channel.dto.ts'
|
||||||
|
|
||||||
|
export default function (fastify: FastifyInstance) {
|
||||||
|
// GET /chad/channels - List all channels with client counts
|
||||||
|
fastify.get('/channels', async (req, reply) => {
|
||||||
|
if (!req.user) {
|
||||||
|
return reply.code(401).send({ error: 'Unauthorized' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const channels = await prisma.channel.findMany({
|
||||||
|
select: channelPublicSelect,
|
||||||
|
orderBy: { name: 'asc' },
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add client count to each channel using Socket.IO rooms
|
||||||
|
const channelsWithCounts = await Promise.all(
|
||||||
|
channels.map(async channel => {
|
||||||
|
const socketsInChannel = await fastify.io.in(channel.id).fetchSockets()
|
||||||
|
return {
|
||||||
|
...channel,
|
||||||
|
clientCount: socketsInChannel.length,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
return channelsWithCounts
|
||||||
|
})
|
||||||
|
|
||||||
|
// GET /chad/channels/:id - Get specific channel with client list
|
||||||
|
fastify.get('/channels/:id', async (req, reply) => {
|
||||||
|
if (!req.user) {
|
||||||
|
return reply.code(401).send({ error: 'Unauthorized' })
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const paramsSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
})
|
||||||
|
const params = paramsSchema.parse(req.params)
|
||||||
|
|
||||||
|
const channel = await prisma.channel.findUnique({
|
||||||
|
where: { id: params.id },
|
||||||
|
select: channelPublicSelect,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!channel) {
|
||||||
|
return reply.code(404).send({ error: 'Channel not found' })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get clients in this channel using Socket.IO rooms
|
||||||
|
const sockets = await fastify.io.in(params.id).fetchSockets()
|
||||||
|
const clients = sockets
|
||||||
|
.filter(s => s.data.joined)
|
||||||
|
.map(s => {
|
||||||
|
const channelId = Array.from(s.rooms).find(room => room !== s.id) || 'default'
|
||||||
|
return {
|
||||||
|
socketId: s.id,
|
||||||
|
userId: s.data.userId,
|
||||||
|
username: s.data.username,
|
||||||
|
displayName: s.data.displayName,
|
||||||
|
inputMuted: s.data.inputMuted,
|
||||||
|
outputMuted: s.data.outputMuted,
|
||||||
|
currentChannelId: channelId,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
...channel,
|
||||||
|
clients,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
fastify.log.error(err)
|
||||||
|
reply.code(400)
|
||||||
|
if (err instanceof z.ZodError) {
|
||||||
|
reply.send({ error: z.prettifyError(err) })
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
reply.send({ error: err.message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// POST /chad/channels - Create channel
|
||||||
|
fastify.post('/channels', async (req, reply) => {
|
||||||
|
if (!req.user) {
|
||||||
|
return reply.code(401).send({ error: 'Unauthorized' })
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const schema = z.object({
|
||||||
|
name: z.string().min(1).max(50),
|
||||||
|
maxClients: z.number().int().positive().optional().nullable(),
|
||||||
|
persistent: z.boolean().default(false),
|
||||||
|
})
|
||||||
|
const input = schema.parse(req.body)
|
||||||
|
|
||||||
|
const channel = await prisma.channel.create({
|
||||||
|
data: {
|
||||||
|
name: input.name,
|
||||||
|
maxClients: input.maxClients,
|
||||||
|
persistent: input.persistent,
|
||||||
|
owner_id: req.user.id,
|
||||||
|
},
|
||||||
|
select: channelPublicSelect,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Notify all connected clients about new channel
|
||||||
|
fastify.io.emit('channelCreated', channel)
|
||||||
|
|
||||||
|
return channel
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
fastify.log.error(err)
|
||||||
|
reply.code(400)
|
||||||
|
if (err instanceof z.ZodError) {
|
||||||
|
reply.send({ error: z.prettifyError(err) })
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
reply.send({ error: err.message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// PATCH /chad/channels/:id - Update channel
|
||||||
|
fastify.patch('/channels/:id', async (req, reply) => {
|
||||||
|
if (!req.user) {
|
||||||
|
return reply.code(401).send({ error: 'Unauthorized' })
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const paramsSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
})
|
||||||
|
const params = paramsSchema.parse(req.params)
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
name: z.string().min(1).max(50).optional(),
|
||||||
|
maxClients: z.number().int().positive().optional().nullable(),
|
||||||
|
})
|
||||||
|
const input = schema.parse(req.body)
|
||||||
|
|
||||||
|
// Cannot update default channel
|
||||||
|
if (params.id === 'default') {
|
||||||
|
return reply.code(403).send({ error: 'Cannot modify default channel' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = await prisma.channel.findUnique({
|
||||||
|
where: { id: params.id },
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!existing) {
|
||||||
|
return reply.code(404).send({ error: 'Channel not found' })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existing.owner_id !== req.user.id) {
|
||||||
|
return reply.code(403).send({ error: 'Not channel owner' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const channel = await prisma.channel.update({
|
||||||
|
where: { id: params.id },
|
||||||
|
data: input,
|
||||||
|
select: channelPublicSelect,
|
||||||
|
})
|
||||||
|
|
||||||
|
fastify.io.emit('channelUpdated', channel)
|
||||||
|
|
||||||
|
return channel
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
fastify.log.error(err)
|
||||||
|
reply.code(400)
|
||||||
|
if (err instanceof z.ZodError) {
|
||||||
|
reply.send({ error: z.prettifyError(err) })
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
reply.send({ error: err.message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// DELETE /chad/channels/:id - Delete channel
|
||||||
|
fastify.delete('/channels/:id', async (req, reply) => {
|
||||||
|
if (!req.user) {
|
||||||
|
return reply.code(401).send({ error: 'Unauthorized' })
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const paramsSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
})
|
||||||
|
const params = paramsSchema.parse(req.params)
|
||||||
|
|
||||||
|
if (params.id === 'default') {
|
||||||
|
return reply.code(403).send({ error: 'Cannot delete default channel' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = await prisma.channel.findUnique({
|
||||||
|
where: { id: params.id },
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!existing) {
|
||||||
|
return reply.code(404).send({ error: 'Channel not found' })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existing.owner_id !== req.user.id) {
|
||||||
|
return reply.code(403).send({ error: 'Not channel owner' })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move all users in this channel back to default using Socket.IO rooms
|
||||||
|
const sockets = await fastify.io.in(params.id).fetchSockets()
|
||||||
|
|
||||||
|
for (const socket of sockets) {
|
||||||
|
// Close all their producers
|
||||||
|
for (const producer of socket.data.producers.values()) {
|
||||||
|
producer.close()
|
||||||
|
}
|
||||||
|
socket.data.producers.clear()
|
||||||
|
|
||||||
|
// Close all their consumers
|
||||||
|
for (const consumer of socket.data.consumers.values()) {
|
||||||
|
consumer.close()
|
||||||
|
}
|
||||||
|
socket.data.consumers.clear()
|
||||||
|
|
||||||
|
// Move to default room
|
||||||
|
await socket.leave(params.id)
|
||||||
|
await socket.join('default')
|
||||||
|
|
||||||
|
socket.emit('forcedChannelSwitch', { channelId: 'default' })
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.channel.delete({
|
||||||
|
where: { id: params.id },
|
||||||
|
})
|
||||||
|
|
||||||
|
fastify.io.emit('channelDeleted', { channelId: params.id })
|
||||||
|
|
||||||
|
return { success: true }
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
fastify.log.error(err)
|
||||||
|
reply.code(400)
|
||||||
|
if (err instanceof z.ZodError) {
|
||||||
|
reply.send({ error: z.prettifyError(err) })
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
reply.send({ error: err.message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { FastifyInstance } from 'fastify'
|
import type { FastifyInstance } from 'fastify'
|
||||||
import type { Namespace } from '../types/webrtc.ts'
|
import type { Namespace } from '../types/socket.ts'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import prisma from '../prisma/client.ts'
|
import prisma from '../prisma/client.ts'
|
||||||
import { socketToClient } from '../utils/socket-to-client.ts'
|
import { socketToClient } from '../utils/socket-to-client.ts'
|
||||||
@@ -77,7 +77,7 @@ export default function (fastify: FastifyInstance) {
|
|||||||
|
|
||||||
if (found) {
|
if (found) {
|
||||||
found.data.displayName = input.displayName
|
found.data.displayName = input.displayName
|
||||||
namespace.emit('clientChanged', found.id, socketToClient(found))
|
namespace.emit('webrtc:client-changed', found.id, socketToClient(found))
|
||||||
}
|
}
|
||||||
|
|
||||||
return updatedUser
|
return updatedUser
|
||||||
|
|||||||
@@ -3,12 +3,9 @@ import { fileURLToPath } from 'node:url'
|
|||||||
import FastifyAutoLoad from '@fastify/autoload'
|
import FastifyAutoLoad from '@fastify/autoload'
|
||||||
import FastifyCookie from '@fastify/cookie'
|
import FastifyCookie from '@fastify/cookie'
|
||||||
import FastifyCors from '@fastify/cors'
|
import FastifyCors from '@fastify/cors'
|
||||||
import FastifyMultipart from '@fastify/multipart'
|
|
||||||
import Fastify from 'fastify'
|
import Fastify from 'fastify'
|
||||||
import prisma from './prisma/client.ts'
|
import prisma from './prisma/client.ts'
|
||||||
|
|
||||||
console.log(process.env.DATABASE_URL)
|
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url)
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
const __dirname = dirname(__filename)
|
const __dirname = dirname(__filename)
|
||||||
|
|
||||||
@@ -28,7 +25,6 @@ fastify.register(FastifyCors, {
|
|||||||
})
|
})
|
||||||
|
|
||||||
fastify.register(FastifyCookie)
|
fastify.register(FastifyCookie)
|
||||||
fastify.register(FastifyMultipart)
|
|
||||||
|
|
||||||
fastify.register(FastifyAutoLoad, {
|
fastify.register(FastifyAutoLoad, {
|
||||||
dir: join(__dirname, 'plugins'),
|
dir: join(__dirname, 'plugins'),
|
||||||
|
|||||||
150
server/socket/channel.ts
Normal file
150
server/socket/channel.ts
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
import type {
|
||||||
|
ChadSocket,
|
||||||
|
ClientToServerEvents,
|
||||||
|
ExtractCallbackPayload,
|
||||||
|
SocketServer,
|
||||||
|
} from '../types/socket.ts'
|
||||||
|
import { consola } from 'consola'
|
||||||
|
import { channelPublicSelect } from '../dto/channel.dto.ts'
|
||||||
|
import prisma from '../prisma/client.ts'
|
||||||
|
import { socketToClient } from '../utils/socket-to-client.ts'
|
||||||
|
|
||||||
|
export default async function (io: SocketServer, socket: ChadSocket) {
|
||||||
|
// io.on('channel:join', async (cb) => {
|
||||||
|
// if (socket.data.joined) {
|
||||||
|
// consola.error('[WebRtc]', 'Already joined')
|
||||||
|
// cb({ error: 'Already joined' })
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// socket.data.joined = true
|
||||||
|
// socket.data.rtpCapabilities = rtpCapabilities
|
||||||
|
//
|
||||||
|
// // Get current channel from Socket.IO rooms
|
||||||
|
// const currentChannelId = Array.from(socket.rooms).find(room => room !== socket.id) || 'default'
|
||||||
|
// const joinedSockets = await getJoinedSockets(socket.id, currentChannelId)
|
||||||
|
//
|
||||||
|
// cb(joinedSockets.map(socketToClient))
|
||||||
|
//
|
||||||
|
// for (const joinedSocket of joinedSockets) {
|
||||||
|
// for (const producer of joinedSocket.data.producers.values()) {
|
||||||
|
// createConsumer(
|
||||||
|
// socket,
|
||||||
|
// joinedSocket,
|
||||||
|
// producer,
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Broadcast only to same channel using Socket.IO room
|
||||||
|
// socket.to(currentChannelId).emit('newPeer', socketToClient(socket))
|
||||||
|
// })
|
||||||
|
|
||||||
|
socket.on('channel:join', async ({ channelId }, cb) => {
|
||||||
|
try {
|
||||||
|
cb(await handleJoin(channelId))
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
cb(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await handleJoin('default')
|
||||||
|
|
||||||
|
const channels = await prisma.channel.findMany({
|
||||||
|
select: channelPublicSelect,
|
||||||
|
orderBy: { name: 'asc' },
|
||||||
|
})
|
||||||
|
|
||||||
|
type ChannelJoinCallback = ExtractCallbackPayload<ClientToServerEvents['channel:join']>
|
||||||
|
async function handleJoin(channelId: string): Promise<ChannelJoinCallback> {
|
||||||
|
try {
|
||||||
|
const channel = await prisma.channel.findUnique({
|
||||||
|
where: { id: channelId },
|
||||||
|
select: channelPublicSelect,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!channel) {
|
||||||
|
return { error: 'Channel not found' }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channel.maxClients) {
|
||||||
|
const socketsInChannel = await io.in(channelId).fetchSockets()
|
||||||
|
if (socketsInChannel.length >= channel.maxClients) {
|
||||||
|
return { error: 'Channel is full' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldChannelId = Array.from(socket.rooms).find(room => room !== socket.id)
|
||||||
|
|
||||||
|
// for (const producer of socket.data.producers.values()) {
|
||||||
|
// producer.close()
|
||||||
|
// }
|
||||||
|
// socket.data.producers.clear()
|
||||||
|
//
|
||||||
|
// for (const consumer of socket.data.consumers.values()) {
|
||||||
|
// consumer.close()
|
||||||
|
// }
|
||||||
|
// socket.data.consumers.clear()
|
||||||
|
|
||||||
|
if (oldChannelId) {
|
||||||
|
await socket.leave(oldChannelId)
|
||||||
|
io.emit('channel:user-left', { channelId: oldChannelId, clientId: socket.id })
|
||||||
|
|
||||||
|
// // Auto-delete non-persistent empty channels
|
||||||
|
// if (isLeavingNonPersistentChannel) {
|
||||||
|
// const oldChannelSockets = await io.in(oldChannelId).fetchSockets()
|
||||||
|
//
|
||||||
|
// if (oldChannelSockets.length === 0) {
|
||||||
|
// const oldChannel = await prisma.channel.findUnique({
|
||||||
|
// where: { id: oldChannelId },
|
||||||
|
// select: { persistent: true, id: true },
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// if (oldChannel && !oldChannel.persistent) {
|
||||||
|
// await prisma.channel.delete({ where: { id: oldChannelId } })
|
||||||
|
// io.emit('channelDeleted', { channelId: oldChannelId })
|
||||||
|
// consola.info('[Channel]', `Auto-deleted empty non-persistent channel: ${oldChannelId}`)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
await socket.join(channelId)
|
||||||
|
|
||||||
|
// Get new channel members
|
||||||
|
// const newChannelSockets = await getJoinedSockets(socket.id, channelId)
|
||||||
|
//
|
||||||
|
// // Create consumers for existing producers in new channel
|
||||||
|
// for (const peer of newChannelSockets) {
|
||||||
|
// for (const producer of peer.data.producers.values()) {
|
||||||
|
// createConsumer(socket, peer, producer)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
io.emit('channel:user-joined', {
|
||||||
|
channelId,
|
||||||
|
client: socketToClient(socket),
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
channel,
|
||||||
|
clients: newChannelSockets.map(socketToClient),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
consola.error('[channel:join]', error)
|
||||||
|
|
||||||
|
if (error instanceof Error) {
|
||||||
|
return { error: error.message }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return { error: 'Something went wrong' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
channels,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import type { Server as SocketServer } from 'socket.io'
|
|
||||||
import type { ChatClientMessage, ChatMessage } from '../types/chat.ts'
|
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
|
||||||
|
|
||||||
export default async function (io: SocketServer) {
|
|
||||||
const messages: ChatMessage[] = []
|
|
||||||
|
|
||||||
io.on('connection', async (socket) => {
|
|
||||||
socket.on('chat:message', async (clientMessage: ChatClientMessage, cb) => {
|
|
||||||
const message: ChatMessage = {
|
|
||||||
id: uuidv4(),
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
sender: socket.data.username,
|
|
||||||
text: clientMessage.text,
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(message)
|
|
||||||
|
|
||||||
messages.push(message)
|
|
||||||
|
|
||||||
if (messages.length > 5000) {
|
|
||||||
messages.shift()
|
|
||||||
}
|
|
||||||
|
|
||||||
io.emit('chat:new-message', message)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,49 +1,21 @@
|
|||||||
import type { types } from 'mediasoup'
|
import type { types } from 'mediasoup'
|
||||||
|
import type { ActiveSpeakerObserver, AudioLevelObserver } from 'mediasoup/types'
|
||||||
import type { Server as SocketServer } from 'socket.io'
|
import type { Server as SocketServer } from 'socket.io'
|
||||||
import type {
|
import type {
|
||||||
ChadClient,
|
ChadSocket,
|
||||||
SomeSocket,
|
SomeSocket,
|
||||||
} from '../types/webrtc.ts'
|
} from '../types/socket.ts'
|
||||||
import { consola } from 'consola'
|
import { consola } from 'consola'
|
||||||
import prisma from '../prisma/client.ts'
|
|
||||||
import { socketToClient } from '../utils/socket-to-client.ts'
|
import { socketToClient } from '../utils/socket-to-client.ts'
|
||||||
|
|
||||||
export default async function (io: SocketServer, router: types.Router) {
|
export default async function (
|
||||||
const audioLevelObserver = await router.createAudioLevelObserver({
|
io: SocketServer,
|
||||||
maxEntries: 10,
|
socket: ChadSocket,
|
||||||
threshold: -80,
|
router: types.Router,
|
||||||
interval: 800,
|
audioLevelObserver: AudioLevelObserver,
|
||||||
})
|
activeSpeakerObserver: ActiveSpeakerObserver,
|
||||||
|
) {
|
||||||
const activeSpeakerObserver = await router.createActiveSpeakerObserver()
|
|
||||||
|
|
||||||
audioLevelObserver.on('volumes', async (volumes: types.AudioLevelObserverVolume[]) => {
|
|
||||||
io.emit('speakingPeers', volumes.map(({ producer, volume }) => {
|
|
||||||
const { socketId } = producer.appData as { socketId: ChadClient['socketId'] }
|
|
||||||
|
|
||||||
return {
|
|
||||||
clientId: socketId,
|
|
||||||
volume,
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
audioLevelObserver.on('silence', () => {
|
|
||||||
io.emit('speakingPeers', [])
|
|
||||||
io.emit('activeSpeaker', undefined)
|
|
||||||
})
|
|
||||||
|
|
||||||
activeSpeakerObserver.on('dominantspeaker', ({ producer }) => {
|
|
||||||
const { socketId } = producer.appData as { socketId: ChadClient['socketId'] }
|
|
||||||
|
|
||||||
io.emit('activeSpeaker', socketId)
|
|
||||||
})
|
|
||||||
|
|
||||||
io.on('connection', async (socket) => {
|
io.on('connection', async (socket) => {
|
||||||
consola.info('[WebRtc]', 'Client connected', socket.id)
|
|
||||||
|
|
||||||
socket.data.joined = false
|
|
||||||
|
|
||||||
socket.data.inputMuted = false
|
socket.data.inputMuted = false
|
||||||
socket.data.outputMuted = false
|
socket.data.outputMuted = false
|
||||||
|
|
||||||
@@ -51,54 +23,11 @@ export default async function (io: SocketServer, router: types.Router) {
|
|||||||
socket.data.producers = new Map()
|
socket.data.producers = new Map()
|
||||||
socket.data.consumers = new Map()
|
socket.data.consumers = new Map()
|
||||||
|
|
||||||
const { id, username, displayName } = await prisma.user.findUnique({
|
socket.on('webrtc:get-rtp-capabilities', (cb) => {
|
||||||
where: {
|
|
||||||
id: socket.handshake.auth.userId,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
username: true,
|
|
||||||
displayName: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
socket.data.userId = id
|
|
||||||
socket.data.username = username
|
|
||||||
socket.data.displayName = displayName
|
|
||||||
|
|
||||||
socket.emit('authenticated')
|
|
||||||
|
|
||||||
socket.on('join', async ({ rtpCapabilities }, cb) => {
|
|
||||||
if (socket.data.joined) {
|
|
||||||
consola.error('[WebRtc]', 'Already joined')
|
|
||||||
cb({ error: 'Already joined' })
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.data.joined = true
|
|
||||||
socket.data.rtpCapabilities = rtpCapabilities
|
|
||||||
|
|
||||||
const joinedSockets = await getJoinedSockets()
|
|
||||||
|
|
||||||
cb(joinedSockets.map(socketToClient))
|
|
||||||
|
|
||||||
for (const joinedSocket of joinedSockets.filter(joinedSocket => joinedSocket.id !== socket.id)) {
|
|
||||||
for (const producer of joinedSocket.data.producers.values()) {
|
|
||||||
createConsumer(
|
|
||||||
socket,
|
|
||||||
joinedSocket,
|
|
||||||
producer,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.broadcast.emit('newPeer', socketToClient(socket))
|
|
||||||
})
|
|
||||||
|
|
||||||
socket.on('getRtpCapabilities', (cb) => {
|
|
||||||
cb(router.rtpCapabilities)
|
cb(router.rtpCapabilities)
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.on('createTransport', async ({ producing, consuming }, cb) => {
|
socket.on('webrtc:create-transport', async ({ producing, consuming }, cb) => {
|
||||||
try {
|
try {
|
||||||
const transport = await router.createWebRtcTransport({
|
const transport = await router.createWebRtcTransport({
|
||||||
listenInfos: [
|
listenInfos: [
|
||||||
@@ -153,7 +82,7 @@ export default async function (io: SocketServer, router: types.Router) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.on('connectTransport', async ({ transportId, dtlsParameters }, cb) => {
|
socket.on('webrtc:connect-transport', async ({ transportId, dtlsParameters }, cb) => {
|
||||||
const transport = socket.data.transports.get(transportId)
|
const transport = socket.data.transports.get(transportId)
|
||||||
|
|
||||||
if (!transport) {
|
if (!transport) {
|
||||||
@@ -176,7 +105,7 @@ export default async function (io: SocketServer, router: types.Router) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.on('produce', async ({ transportId, kind, rtpParameters, appData }, cb) => {
|
socket.on('webrtc:produce', async ({ transportId, kind, rtpParameters, appData }, cb) => {
|
||||||
if (!socket.data.joined) {
|
if (!socket.data.joined) {
|
||||||
consola.error('Peer not joined yet')
|
consola.error('Peer not joined yet')
|
||||||
cb({ error: 'Peer not joined yet' })
|
cb({ error: 'Peer not joined yet' })
|
||||||
@@ -184,6 +113,14 @@ export default async function (io: SocketServer, router: types.Router) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Block production in default channel
|
||||||
|
const currentChannelId = Array.from(socket.rooms).find(room => room !== socket.id) || 'default'
|
||||||
|
if (currentChannelId === 'default') {
|
||||||
|
consola.error('Cannot produce in default channel')
|
||||||
|
cb({ error: 'Cannot produce media in default channel' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const transport = socket.data.transports.get(transportId)
|
const transport = socket.data.transports.get(transportId)
|
||||||
|
|
||||||
if (!transport) {
|
if (!transport) {
|
||||||
@@ -200,7 +137,8 @@ export default async function (io: SocketServer, router: types.Router) {
|
|||||||
|
|
||||||
cb({ id: producer.id })
|
cb({ id: producer.id })
|
||||||
|
|
||||||
const otherSockets = await getJoinedSockets(socket.id)
|
// Filter by channel when creating consumers
|
||||||
|
const otherSockets = await getJoinedSockets(socket.id, currentChannelId)
|
||||||
|
|
||||||
for (const otherSocket of otherSockets) {
|
for (const otherSocket of otherSockets) {
|
||||||
createConsumer(
|
createConsumer(
|
||||||
@@ -221,7 +159,7 @@ export default async function (io: SocketServer, router: types.Router) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.on('closeProducer', async ({ producerId }, cb) => {
|
socket.on('webrtc:close-producer', async ({ producerId }, cb) => {
|
||||||
if (!socket.data.joined) {
|
if (!socket.data.joined) {
|
||||||
consola.error('Peer not joined yet')
|
consola.error('Peer not joined yet')
|
||||||
cb({ error: 'Peer not joined yet' })
|
cb({ error: 'Peer not joined yet' })
|
||||||
@@ -245,7 +183,7 @@ export default async function (io: SocketServer, router: types.Router) {
|
|||||||
cb({ ok: true })
|
cb({ ok: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.on('pauseProducer', async ({ producerId }, cb) => {
|
socket.on('webrtc:pause-producer', async ({ producerId }, cb) => {
|
||||||
if (!socket.data.joined) {
|
if (!socket.data.joined) {
|
||||||
consola.error('Peer not joined yet')
|
consola.error('Peer not joined yet')
|
||||||
cb({ error: 'Peer not joined yet' })
|
cb({ error: 'Peer not joined yet' })
|
||||||
@@ -270,7 +208,7 @@ export default async function (io: SocketServer, router: types.Router) {
|
|||||||
cb({ ok: true })
|
cb({ ok: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.on('resumeProducer', async ({ producerId }, cb) => {
|
socket.on('webrtc:resume-producer', async ({ producerId }, cb) => {
|
||||||
if (!socket.data.joined) {
|
if (!socket.data.joined) {
|
||||||
consola.error('Peer not joined yet')
|
consola.error('Peer not joined yet')
|
||||||
cb({ error: 'Peer not joined yet' })
|
cb({ error: 'Peer not joined yet' })
|
||||||
@@ -292,7 +230,7 @@ export default async function (io: SocketServer, router: types.Router) {
|
|||||||
cb({ ok: true })
|
cb({ ok: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.on('pauseConsumer', async ({ consumerId }, cb) => {
|
socket.on('webrtc:pause-consumer', async ({ consumerId }, cb) => {
|
||||||
if (!socket.data.joined) {
|
if (!socket.data.joined) {
|
||||||
consola.error('Peer not joined yet')
|
consola.error('Peer not joined yet')
|
||||||
cb({ error: 'Peer not joined yet' })
|
cb({ error: 'Peer not joined yet' })
|
||||||
@@ -314,7 +252,7 @@ export default async function (io: SocketServer, router: types.Router) {
|
|||||||
cb({ ok: true })
|
cb({ ok: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.on('resumeConsumer', async ({ consumerId }, cb) => {
|
socket.on('webrtc:resume-consumer', async ({ consumerId }, cb) => {
|
||||||
if (!socket.data.joined) {
|
if (!socket.data.joined) {
|
||||||
consola.error('Peer not joined yet')
|
consola.error('Peer not joined yet')
|
||||||
cb({ error: 'Peer not joined yet' })
|
cb({ error: 'Peer not joined yet' })
|
||||||
@@ -336,7 +274,7 @@ export default async function (io: SocketServer, router: types.Router) {
|
|||||||
cb({ ok: true })
|
cb({ ok: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.on('updateClient', async (updatedClient, cb) => {
|
socket.on('webrtc:update-client', async (updatedClient, cb) => {
|
||||||
if (typeof updatedClient.inputMuted === 'boolean') {
|
if (typeof updatedClient.inputMuted === 'boolean') {
|
||||||
socket.data.inputMuted = updatedClient.inputMuted
|
socket.data.inputMuted = updatedClient.inputMuted
|
||||||
}
|
}
|
||||||
@@ -347,14 +285,21 @@ export default async function (io: SocketServer, router: types.Router) {
|
|||||||
|
|
||||||
cb(socketToClient(socket))
|
cb(socketToClient(socket))
|
||||||
|
|
||||||
io.emit('clientChanged', socket.id, socketToClient(socket))
|
io.emit('webrtc:client-changed', socket.id, socketToClient(socket))
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.on('disconnect', () => {
|
socket.on('disconnect', () => {
|
||||||
consola.info('Client disconnected:', socket.id)
|
consola.info('Client disconnected:', socket.id)
|
||||||
|
|
||||||
|
// Get current channel from Socket.IO rooms
|
||||||
|
const channelId = Array.from(socket.rooms).find(room => room !== socket.id)
|
||||||
|
|
||||||
if (socket.data.joined) {
|
if (socket.data.joined) {
|
||||||
socket.broadcast.emit('peerClosed', socket.id)
|
// Notify only same channel using Socket.IO room
|
||||||
|
if (channelId) {
|
||||||
|
socket.to(channelId).emit('webrtc:peer-closed', socket.id)
|
||||||
|
io.emit('channelUserLeft', { channelId, clientId: socket.id })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const transport of socket.data.transports.values()) {
|
for (const transport of socket.data.transports.values()) {
|
||||||
@@ -363,10 +308,21 @@ export default async function (io: SocketServer, router: types.Router) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
async function getJoinedSockets(excludeId?: string) {
|
async function getJoinedSockets(excludeId?: string, channelId?: string) {
|
||||||
const sockets = await io.fetchSockets()
|
let sockets = await io.fetchSockets()
|
||||||
|
|
||||||
return sockets.filter(socket => socket.data.joined && (excludeId ? excludeId !== socket.id : true))
|
// Filter by channel using Socket.IO rooms
|
||||||
|
if (channelId) {
|
||||||
|
sockets = await io.in(channelId).fetchSockets()
|
||||||
|
}
|
||||||
|
|
||||||
|
return sockets.filter((socket) => {
|
||||||
|
if (!socket.data.joined)
|
||||||
|
return false
|
||||||
|
if (excludeId && socket.id === excludeId)
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createConsumer(
|
async function createConsumer(
|
||||||
@@ -424,24 +380,24 @@ export default async function (io: SocketServer, router: types.Router) {
|
|||||||
consumer.on('producerclose', () => {
|
consumer.on('producerclose', () => {
|
||||||
consumerSocket.data.consumers.delete(consumer.id)
|
consumerSocket.data.consumers.delete(consumer.id)
|
||||||
|
|
||||||
consumerSocket.emit('consumerClosed', { consumerId: consumer.id })
|
consumerSocket.emit('webrtc:consumer-closed', { consumerId: consumer.id })
|
||||||
})
|
})
|
||||||
|
|
||||||
consumer.on('producerpause', () => {
|
consumer.on('producerpause', () => {
|
||||||
consumerSocket.emit('consumerPaused', { consumerId: consumer.id })
|
consumerSocket.emit('webrtc:consumer-paused', { consumerId: consumer.id })
|
||||||
})
|
})
|
||||||
|
|
||||||
consumer.on('producerresume', () => {
|
consumer.on('producerresume', () => {
|
||||||
consumerSocket.emit('consumerResumed', { consumerId: consumer.id })
|
consumerSocket.emit('webrtc:consumer-resumed', { consumerId: consumer.id })
|
||||||
})
|
})
|
||||||
|
|
||||||
consumer.on('score', (score: types.ConsumerScore) => {
|
consumer.on('score', (score: types.ConsumerScore) => {
|
||||||
consumerSocket.emit('consumerScore', { consumerId: consumer.id, score })
|
consumerSocket.emit('webrtc:consumer-score', { consumerId: consumer.id, score })
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await consumerSocket.emitWithAck(
|
await consumerSocket.emitWithAck(
|
||||||
'newConsumer',
|
'webrtc:new-consumer',
|
||||||
{
|
{
|
||||||
socketId: producerSocket.id,
|
socketId: producerSocket.id,
|
||||||
producerId: producer.id,
|
producerId: producer.id,
|
||||||
@@ -457,7 +413,7 @@ export default async function (io: SocketServer, router: types.Router) {
|
|||||||
await consumer.resume()
|
await consumer.resume()
|
||||||
|
|
||||||
consumerSocket.emit(
|
consumerSocket.emit(
|
||||||
'consumerScore',
|
'webrtc:consumer-score',
|
||||||
{
|
{
|
||||||
consumerId: consumer.id,
|
consumerId: consumer.id,
|
||||||
score: consumer.score,
|
score: consumer.score,
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2016",
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "nodenext",
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"strict": true,
|
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"allowImportingTsExtensions": true
|
"allowImportingTsExtensions": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"target": "ES2023",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
export interface ChatClientMessage {
|
|
||||||
text: string
|
|
||||||
replyTo?: {
|
|
||||||
messageId: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChatMessage {
|
|
||||||
id: string
|
|
||||||
sender: string
|
|
||||||
text: string
|
|
||||||
createdAt: string
|
|
||||||
replyTo?: {
|
|
||||||
messageId: string
|
|
||||||
sender: string
|
|
||||||
text: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
173
server/types/socket.ts
Normal file
173
server/types/socket.ts
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
import type { types } from 'mediasoup'
|
||||||
|
import type { RemoteSocket, Server, Socket } from 'socket.io'
|
||||||
|
import type { ChannelPublicDTO } from '../dto/channel.dto.ts'
|
||||||
|
import type { ChannelModel, UserModel } from '../prisma/generated/client/models.ts'
|
||||||
|
|
||||||
|
export interface ServerInfo {
|
||||||
|
owner_id: UserModel['id']
|
||||||
|
channels: ChannelPublicDTO[]
|
||||||
|
rtpCapabilities: types.RtpCapabilities
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChadClient {
|
||||||
|
socketId: string
|
||||||
|
userId: UserModel['id']
|
||||||
|
username: UserModel['username']
|
||||||
|
displayName: UserModel['displayName']
|
||||||
|
inputMuted: boolean
|
||||||
|
outputMuted: boolean
|
||||||
|
currentChannelId: ChannelModel['id']
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProducerShort {
|
||||||
|
producerId: types.Producer['id']
|
||||||
|
kind: types.MediaKind
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ErrorCallbackResult {
|
||||||
|
error: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SuccessCallbackResult {
|
||||||
|
ok: true
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventCallback<T = SuccessCallbackResult> = (result: T | ErrorCallbackResult) => void
|
||||||
|
export type EventCallbackResult<T = SuccessCallbackResult> = Parameters<EventCallback<T>>[0]
|
||||||
|
|
||||||
|
type LastArg<T extends any[]> = T extends [...any[], infer Last] ? Last : T extends [infer Only] ? Only : never
|
||||||
|
|
||||||
|
export type ExtractCallbackPayload<
|
||||||
|
T,
|
||||||
|
Fallback = EventCallbackResult,
|
||||||
|
>
|
||||||
|
= T extends (...args: infer Args) => any
|
||||||
|
? LastArg<Args> extends (...inner: infer CallbackArgs) => any
|
||||||
|
? CallbackArgs extends [infer First, ...any[]]
|
||||||
|
? First
|
||||||
|
: Fallback
|
||||||
|
: Fallback
|
||||||
|
: Fallback
|
||||||
|
|
||||||
|
export interface ClientToServerEvents {
|
||||||
|
'webrtc:get-rtp-capabilities': (
|
||||||
|
cb: EventCallback<types.RtpCapabilities>
|
||||||
|
) => void
|
||||||
|
'webrtc:create-transport': (
|
||||||
|
options: {
|
||||||
|
producing: boolean
|
||||||
|
consuming: boolean
|
||||||
|
},
|
||||||
|
cb: EventCallback<Pick<types.WebRtcTransport, 'id' | 'iceParameters' | 'iceCandidates' | 'dtlsParameters'>>
|
||||||
|
) => void
|
||||||
|
'webrtc:connect-transport': (
|
||||||
|
options: {
|
||||||
|
transportId: types.WebRtcTransport['id']
|
||||||
|
dtlsParameters: types.WebRtcTransport['dtlsParameters']
|
||||||
|
},
|
||||||
|
cb: EventCallback
|
||||||
|
) => void
|
||||||
|
'webrtc:produce': (
|
||||||
|
options: {
|
||||||
|
transportId: types.WebRtcTransport['id']
|
||||||
|
kind: types.MediaKind
|
||||||
|
rtpParameters: types.RtpParameters
|
||||||
|
appData: { source: 'share' | string }
|
||||||
|
},
|
||||||
|
cb: EventCallback<{ id: types.Producer['id'] }>
|
||||||
|
) => void
|
||||||
|
'webrtc:close-producer': (
|
||||||
|
options: {
|
||||||
|
producerId: types.Producer['id']
|
||||||
|
},
|
||||||
|
cb: EventCallback
|
||||||
|
) => void
|
||||||
|
'webrtc:pause-producer': (
|
||||||
|
options: {
|
||||||
|
producerId: types.Producer['id']
|
||||||
|
},
|
||||||
|
cb: EventCallback
|
||||||
|
) => void
|
||||||
|
'webrtc:resume-producer': (
|
||||||
|
options: {
|
||||||
|
producerId: types.Producer['id']
|
||||||
|
},
|
||||||
|
cb: EventCallback
|
||||||
|
) => void
|
||||||
|
'webrtc:pause-consumer': (
|
||||||
|
options: {
|
||||||
|
consumerId: types.Consumer['id']
|
||||||
|
},
|
||||||
|
cb: EventCallback
|
||||||
|
) => void
|
||||||
|
'webrtc:resume-consumer': (
|
||||||
|
options: {
|
||||||
|
consumerId: types.Consumer['id']
|
||||||
|
},
|
||||||
|
cb: EventCallback
|
||||||
|
) => void
|
||||||
|
'webrtc:update-client': (
|
||||||
|
options: Partial<Pick<ChadClient, 'inputMuted' | 'outputMuted'>>,
|
||||||
|
cb: EventCallback<ChadClient>
|
||||||
|
) => void
|
||||||
|
|
||||||
|
'channel:join': (
|
||||||
|
options: { channelId: ChannelModel['id'] },
|
||||||
|
cb: EventCallback<{ channel: ChannelPublicDTO, clients: ChadClient[] }>
|
||||||
|
) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServerToClientEvents {
|
||||||
|
'webrtc:authenticated': (arg: ServerInfo) => void
|
||||||
|
'webrtc:producers': (arg: ProducerShort[]) => void
|
||||||
|
'webrtc:new-consumer': (
|
||||||
|
arg: {
|
||||||
|
socketId: string
|
||||||
|
producerId: types.Producer['id']
|
||||||
|
id: types.Consumer['id']
|
||||||
|
kind: types.MediaKind
|
||||||
|
rtpParameters: types.RtpParameters
|
||||||
|
type: types.ConsumerType
|
||||||
|
appData: types.Producer['appData']
|
||||||
|
producerPaused: types.Consumer['producerPaused']
|
||||||
|
},
|
||||||
|
cb: EventCallback
|
||||||
|
) => void
|
||||||
|
'webrtc:peer-closed': (arg: string) => void
|
||||||
|
'webrtc:consumer-closed': (arg: { consumerId: string }) => void
|
||||||
|
'webrtc:consumer-paused': (arg: { consumerId: string }) => void
|
||||||
|
'webrtc:consumer-resumed': (arg: { consumerId: string }) => void
|
||||||
|
'webrtc:consumer-score': (arg: { consumerId: string, score: types.ConsumerScore }) => void
|
||||||
|
'webrtc:client-changed': (clientId: ChadClient['socketId'], client: ChadClient) => void
|
||||||
|
'webrtc:speaking-peers': (arg: { clientId: ChadClient['socketId'], volume: types.AudioLevelObserverVolume['volume'] }[]) => void
|
||||||
|
'webrtc:active-speaker': (clientId?: ChadClient['socketId']) => void
|
||||||
|
|
||||||
|
'channel:user-joined': (arg: { channelId: string, client: ChadClient }) => void
|
||||||
|
'channel:user-left': (arg: { channelId: string, clientId: string }) => void
|
||||||
|
'channel:deleted': (arg: { channelId: string }) => void
|
||||||
|
'channel:created': (arg: ChannelPublicDTO) => void
|
||||||
|
'channel:updated': (arg: ChannelPublicDTO) => void
|
||||||
|
'channel:force-switch': (arg: { channelId: string }) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InterServerEvent {}
|
||||||
|
|
||||||
|
export interface SocketData {
|
||||||
|
joined: boolean
|
||||||
|
userId: UserModel['id']
|
||||||
|
username: UserModel['username']
|
||||||
|
displayName: UserModel['displayName']
|
||||||
|
inputMuted: boolean
|
||||||
|
outputMuted: boolean
|
||||||
|
rtpCapabilities: types.RtpCapabilities
|
||||||
|
transports: Map<types.WebRtcTransport['id'], types.WebRtcTransport>
|
||||||
|
producers: Map<types.Producer['id'], types.Producer>
|
||||||
|
consumers: Map<types.Consumer['id'], types.Consumer>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ChadSocket = Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvent, SocketData>
|
||||||
|
export type ChadRemoteSocket = RemoteSocket<ServerToClientEvents, SocketData>
|
||||||
|
|
||||||
|
export type SomeSocket = ChadSocket | ChadRemoteSocket
|
||||||
|
|
||||||
|
export type SocketServer = Server<ClientToServerEvents, ServerToClientEvents, InterServerEvent, SocketData>
|
||||||
4
server/types/utils.ts
Normal file
4
server/types/utils.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export type LastArgument<T>
|
||||||
|
= T extends (...args: [...any[], infer Last]) => any
|
||||||
|
? Last
|
||||||
|
: never
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
import type { types } from 'mediasoup'
|
|
||||||
import type { RemoteSocket, Socket, Namespace as SocketNamespace } from 'socket.io'
|
|
||||||
import type { User } from '../prisma/client'
|
|
||||||
|
|
||||||
export interface ChadClient {
|
|
||||||
socketId: string
|
|
||||||
userId: User['id']
|
|
||||||
username: User['username']
|
|
||||||
displayName: User['displayName']
|
|
||||||
inputMuted: boolean
|
|
||||||
outputMuted: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ProducerShort {
|
|
||||||
producerId: types.Producer['id']
|
|
||||||
kind: types.MediaKind
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ErrorCallbackResult {
|
|
||||||
error: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SuccessCallbackResult {
|
|
||||||
ok: true
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventCallback<T = SuccessCallbackResult> = (result: T | ErrorCallbackResult) => void
|
|
||||||
|
|
||||||
export interface ClientToServerEvents {
|
|
||||||
join: (
|
|
||||||
options: {
|
|
||||||
rtpCapabilities: types.RtpCapabilities
|
|
||||||
},
|
|
||||||
cb: EventCallback<ChadClient[]>
|
|
||||||
) => void
|
|
||||||
getRtpCapabilities: (
|
|
||||||
cb: EventCallback<types.RtpCapabilities>
|
|
||||||
) => void
|
|
||||||
createTransport: (
|
|
||||||
options: {
|
|
||||||
producing: boolean
|
|
||||||
consuming: boolean
|
|
||||||
},
|
|
||||||
cb: EventCallback<Pick<types.WebRtcTransport, 'id' | 'iceParameters' | 'iceCandidates' | 'dtlsParameters'>>
|
|
||||||
) => void
|
|
||||||
connectTransport: (
|
|
||||||
options: {
|
|
||||||
transportId: types.WebRtcTransport['id']
|
|
||||||
dtlsParameters: types.WebRtcTransport['dtlsParameters']
|
|
||||||
},
|
|
||||||
cb: EventCallback
|
|
||||||
) => void
|
|
||||||
produce: (
|
|
||||||
options: {
|
|
||||||
transportId: types.WebRtcTransport['id']
|
|
||||||
kind: types.MediaKind
|
|
||||||
rtpParameters: types.RtpParameters
|
|
||||||
appData: { source: 'share' | string }
|
|
||||||
},
|
|
||||||
cb: EventCallback<{ id: types.Producer['id'] }>
|
|
||||||
) => void
|
|
||||||
closeProducer: (
|
|
||||||
options: {
|
|
||||||
producerId: types.Producer['id']
|
|
||||||
},
|
|
||||||
cb: EventCallback
|
|
||||||
) => void
|
|
||||||
pauseProducer: (
|
|
||||||
options: {
|
|
||||||
producerId: types.Producer['id']
|
|
||||||
},
|
|
||||||
cb: EventCallback
|
|
||||||
) => void
|
|
||||||
resumeProducer: (
|
|
||||||
options: {
|
|
||||||
producerId: types.Producer['id']
|
|
||||||
},
|
|
||||||
cb: EventCallback
|
|
||||||
) => void
|
|
||||||
pauseConsumer: (
|
|
||||||
options: {
|
|
||||||
consumerId: types.Consumer['id']
|
|
||||||
},
|
|
||||||
cb: EventCallback
|
|
||||||
) => void
|
|
||||||
resumeConsumer: (
|
|
||||||
options: {
|
|
||||||
consumerId: types.Consumer['id']
|
|
||||||
},
|
|
||||||
cb: EventCallback
|
|
||||||
) => void
|
|
||||||
updateClient: (
|
|
||||||
options: Partial<Pick<ChadClient, 'inputMuted' | 'outputMuted'>>,
|
|
||||||
cb: EventCallback<ChadClient>
|
|
||||||
) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ServerToClientEvents {
|
|
||||||
authenticated: () => void
|
|
||||||
newPeer: (arg: ChadClient) => void
|
|
||||||
producers: (arg: ProducerShort[]) => void
|
|
||||||
newConsumer: (
|
|
||||||
arg: {
|
|
||||||
socketId: string
|
|
||||||
producerId: types.Producer['id']
|
|
||||||
id: types.Consumer['id']
|
|
||||||
kind: types.MediaKind
|
|
||||||
rtpParameters: types.RtpParameters
|
|
||||||
type: types.ConsumerType
|
|
||||||
appData: types.Producer['appData']
|
|
||||||
producerPaused: types.Consumer['producerPaused']
|
|
||||||
},
|
|
||||||
cb: EventCallback
|
|
||||||
) => void
|
|
||||||
peerClosed: (arg: string) => void
|
|
||||||
consumerClosed: (arg: { consumerId: string }) => void
|
|
||||||
consumerPaused: (arg: { consumerId: string }) => void
|
|
||||||
consumerResumed: (arg: { consumerId: string }) => void
|
|
||||||
consumerScore: (arg: { consumerId: string, score: types.ConsumerScore }) => void
|
|
||||||
clientChanged: (clientId: ChadClient['socketId'], client: ChadClient) => void
|
|
||||||
speakingPeers: (arg: { clientId: ChadClient['socketId'], volume: types.AudioLevelObserverVolume['volume'] }[]) => void
|
|
||||||
activeSpeaker: (clientId?: ChadClient['socketId']) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface InterServerEvent {}
|
|
||||||
|
|
||||||
export interface SocketData {
|
|
||||||
joined: boolean
|
|
||||||
userId: User['id']
|
|
||||||
username: User['username']
|
|
||||||
displayName: User['displayName']
|
|
||||||
inputMuted: boolean
|
|
||||||
outputMuted: boolean
|
|
||||||
rtpCapabilities: types.RtpCapabilities
|
|
||||||
transports: Map<types.WebRtcTransport['id'], types.WebRtcTransport>
|
|
||||||
producers: Map<types.Producer['id'], types.Producer>
|
|
||||||
consumers: Map<types.Consumer['id'], types.Consumer>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SomeSocket = Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvent, SocketData> | RemoteSocket<ServerToClientEvents, SocketData>
|
|
||||||
|
|
||||||
export type Namespace = SocketNamespace<ClientToServerEvents, ServerToClientEvents, InterServerEvent, SocketData>
|
|
||||||
16
server/utils/fetch-sockets.ts
Normal file
16
server/utils/fetch-sockets.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import type { SocketServer } from '../types/socket.ts'
|
||||||
|
|
||||||
|
export async function fetchSockets(io: SocketServer, excludeId?: string, channelId?: string) {
|
||||||
|
let sockets: Awaited<ReturnType<typeof io.fetchSockets>>
|
||||||
|
|
||||||
|
if (channelId) {
|
||||||
|
sockets = await io.in(channelId).fetchSockets()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sockets = await io.fetchSockets()
|
||||||
|
}
|
||||||
|
|
||||||
|
return sockets.filter((socket) => {
|
||||||
|
return !(excludeId && socket.id === excludeId)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
import type { ChadClient, SomeSocket } from '../types/webrtc.ts'
|
import type { ChadClient, SomeSocket } from '../types/socket.ts'
|
||||||
|
|
||||||
export function socketToClient(socket: SomeSocket): ChadClient {
|
export function socketToClient(socket: SomeSocket): ChadClient {
|
||||||
|
// Socket.IO rooms: Extract channel room (filter out the socket's own room)
|
||||||
|
const channelId = Array.from(socket.rooms).find(room => room !== socket.id) || 'default'
|
||||||
|
|
||||||
return {
|
return {
|
||||||
socketId: socket.id,
|
socketId: socket.id,
|
||||||
userId: socket.data.userId,
|
userId: socket.data.userId,
|
||||||
@@ -8,5 +11,6 @@ export function socketToClient(socket: SomeSocket): ChadClient {
|
|||||||
displayName: socket.data.displayName,
|
displayName: socket.data.displayName,
|
||||||
inputMuted: socket.data.inputMuted,
|
inputMuted: socket.data.inputMuted,
|
||||||
outputMuted: socket.data.outputMuted,
|
outputMuted: socket.data.outputMuted,
|
||||||
|
currentChannelId: channelId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
863
server/yarn.lock
863
server/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user