brutalism design
This commit is contained in:
7
new-client/.claude/settings.local.json
Normal file
7
new-client/.claude/settings.local.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(python -c \"import sys,json; p=json.load\\(sys.stdin\\); deps={**p.get\\('dependencies',{}\\), **p.get\\('devDependencies',{}\\)}; [print\\(k,v\\) for k in sorted\\(deps\\) if any\\(x in k for x in ['mediasoup','socket','vueuse','vue']\\)]\")"
|
||||
]
|
||||
}
|
||||
}
|
||||
137
new-client/src/app/bootstrap/mediasoup.ts
Normal file
137
new-client/src/app/bootstrap/mediasoup.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { useMediasoup } from '@shared/composables/use-mediasoup'
|
||||
import { useSignaling } from '@shared/composables/use-signaling'
|
||||
import { Device } from 'mediasoup-client'
|
||||
import { markRaw, watch } from 'vue'
|
||||
|
||||
const ICE_SERVERS: RTCIceServer[] = [
|
||||
{ urls: 'stun:stun.l.google.com:19302' },
|
||||
{ urls: 'stun:stun.l.google.com:5349' },
|
||||
{ urls: 'stun:stun1.l.google.com:3478' },
|
||||
{ urls: 'stun:stun1.l.google.com:5349' },
|
||||
{ urls: 'stun:stun2.l.google.com:19302' },
|
||||
{ urls: 'stun:stun2.l.google.com:5349' },
|
||||
{ urls: 'stun:stun3.l.google.com:3478' },
|
||||
{ urls: 'stun:stun3.l.google.com:5349' },
|
||||
{ urls: 'stun:stun4.l.google.com:19302' },
|
||||
{ urls: 'stun:stun4.l.google.com:5349' },
|
||||
]
|
||||
|
||||
export default function () {
|
||||
const { socket } = useSignaling()
|
||||
const ms = useMediasoup()
|
||||
|
||||
watch(socket, (socket) => {
|
||||
if (!socket)
|
||||
return
|
||||
|
||||
socket.on('initialized', async (initData) => {
|
||||
ms.device.value = new Device()
|
||||
await ms.device.value.load({ routerRtpCapabilities: initData.rtpCapabilities })
|
||||
|
||||
const sendInfo = await socket.emitWithAck('create-transport', { producing: true, consuming: false })
|
||||
const sendTransport = ms.device.value.createSendTransport({
|
||||
...sendInfo,
|
||||
iceServers: [...ICE_SERVERS, ...(sendInfo.iceServers ?? [])],
|
||||
})
|
||||
ms.sendTransport.value = sendTransport
|
||||
await ms.transports.add({
|
||||
id: sendTransport.id,
|
||||
ref: markRaw(sendTransport),
|
||||
})
|
||||
|
||||
sendTransport.on('connect', async ({ dtlsParameters }, callback, errback) => {
|
||||
try {
|
||||
await socket.emitWithAck('connect-transport', { transportId: sendTransport.id, dtlsParameters })
|
||||
callback()
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error)
|
||||
errback(error)
|
||||
}
|
||||
})
|
||||
|
||||
sendTransport.on('produce', async ({ kind, rtpParameters, appData }, callback, errback) => {
|
||||
try {
|
||||
const { id } = await socket.emitWithAck('produce', {
|
||||
transportId: sendTransport.id,
|
||||
kind,
|
||||
rtpParameters,
|
||||
appData,
|
||||
})
|
||||
callback({ id })
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error)
|
||||
errback(error)
|
||||
}
|
||||
})
|
||||
|
||||
const recvInfo = await socket.emitWithAck('create-transport', { producing: false, consuming: true })
|
||||
const recvTransport = ms.device.value.createRecvTransport({
|
||||
...recvInfo,
|
||||
iceServers: [...ICE_SERVERS, ...(recvInfo.iceServers ?? [])],
|
||||
})
|
||||
ms.recvTransport.value = recvTransport
|
||||
await ms.transports.add({
|
||||
id: recvTransport.id,
|
||||
ref: markRaw(recvTransport),
|
||||
})
|
||||
|
||||
recvTransport.on('connect', async ({ dtlsParameters }, callback, errback) => {
|
||||
try {
|
||||
await socket.emitWithAck('connect-transport', { transportId: recvTransport.id, dtlsParameters })
|
||||
callback()
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error)
|
||||
errback(error)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
socket.on('new-consumer', async ({ id, producerId, kind, rtpParameters, socketId, appData, producerPaused }, cb) => {
|
||||
const rt = ms.recvTransport.value
|
||||
if (!rt)
|
||||
return
|
||||
|
||||
const consumer = await rt.consume({
|
||||
id,
|
||||
producerId,
|
||||
kind,
|
||||
rtpParameters,
|
||||
streamId: `${socketId}-${appData.source ?? 'stream'}`,
|
||||
appData: { ...appData, socketId },
|
||||
})
|
||||
|
||||
if (producerPaused)
|
||||
consumer.pause()
|
||||
|
||||
await ms.consumers.add({
|
||||
id: consumer.id,
|
||||
paused: consumer.paused,
|
||||
kind: consumer.kind,
|
||||
appData: consumer.appData as Record<string, unknown>,
|
||||
track: consumer.track,
|
||||
ref: markRaw(consumer),
|
||||
})
|
||||
|
||||
cb()
|
||||
})
|
||||
|
||||
socket.on('consumer-closed', ({ consumerId }) => {
|
||||
ms.consumers.get(consumerId)?.ref.close()
|
||||
})
|
||||
|
||||
socket.on('consumer-paused', ({ consumerId }) => {
|
||||
ms.consumers.get(consumerId)?.ref.pause()
|
||||
})
|
||||
|
||||
socket.on('consumer-resumed', ({ consumerId }) => {
|
||||
ms.consumers.get(consumerId)?.ref.resume()
|
||||
})
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
ms.clearAll()
|
||||
})
|
||||
}, { immediate: true })
|
||||
}
|
||||
50
new-client/src/app/bootstrap/signaling.ts
Normal file
50
new-client/src/app/bootstrap/signaling.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { ChatMessage } from '@shared/api/generated-chad-api.ts'
|
||||
import { useAuth } from '@shared/composables/use-auth.ts'
|
||||
import { useClients } from '@shared/composables/use-clients'
|
||||
import { useEventBus } from '@shared/composables/use-event-bus.ts'
|
||||
import { useSignaling } from '@shared/composables/use-signaling'
|
||||
import { watch, watchEffect } from 'vue'
|
||||
|
||||
export default function () {
|
||||
const { authorized } = useAuth()
|
||||
const signaling = useSignaling()
|
||||
const { addClient, updateClient, removeClient, clear: clearClients } = useClients()
|
||||
const eventBus = useEventBus()
|
||||
|
||||
watch(signaling.connected, (connected) => {
|
||||
if (!connected)
|
||||
return
|
||||
|
||||
const socket = signaling.socket.value!
|
||||
|
||||
socket.on('initialized', async ({ clients }) => {
|
||||
addClient(...clients)
|
||||
})
|
||||
|
||||
socket.on('new-client', (client) => {
|
||||
addClient(client)
|
||||
})
|
||||
|
||||
socket.on('client-switched-channel', (client) => {
|
||||
updateClient(client.socketId, client)
|
||||
})
|
||||
|
||||
socket.on('client-disconnected', (socketId) => {
|
||||
removeClient(socketId)
|
||||
})
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
clearClients()
|
||||
})
|
||||
|
||||
socket.on('chat:new-message', (message: ChatMessage) => {
|
||||
eventBus.emit('chat:new-message', message)
|
||||
})
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
if (authorized.value) {
|
||||
signaling.connect()
|
||||
}
|
||||
})
|
||||
}
|
||||
74
new-client/src/shared/composables/use-media-controls.ts
Normal file
74
new-client/src/shared/composables/use-media-controls.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { createGlobalState } from '@vueuse/core'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useMediasoup } from './use-mediasoup'
|
||||
import { useProducers } from './use-producers'
|
||||
|
||||
export const useMediaControls = createGlobalState(() => {
|
||||
const { producers } = useMediasoup()
|
||||
const { enableMic, enableVideo, disableVideo, enableShare, disableShare, pauseProducer, resumeProducer } = useProducers()
|
||||
|
||||
const micProducer = computed(() => producers.values.value.find(p => p.appData.source === 'mic'))
|
||||
const cameraProducer = computed(() => producers.values.value.find(p => p.appData.source === 'camera'))
|
||||
const shareProducer = computed(() => producers.values.value.find(p => p.appData.source === 'share'))
|
||||
|
||||
const isSoundEnabled = ref(true)
|
||||
|
||||
const isMicEnabled = computed(() => isSoundEnabled.value && !!micProducer.value && !micProducer.value.paused)
|
||||
const isCameraEnabled = computed(() => !!cameraProducer.value && !cameraProducer.value.paused)
|
||||
const isShareEnabled = computed(() => !!shareProducer.value)
|
||||
|
||||
watch(isSoundEnabled, async (enabled) => {
|
||||
if (!enabled) {
|
||||
if (micProducer.value && !micProducer.value.paused)
|
||||
await pauseProducer(micProducer.value.id)
|
||||
}
|
||||
else {
|
||||
if (micProducer.value?.paused)
|
||||
await resumeProducer(micProducer.value.id)
|
||||
}
|
||||
})
|
||||
|
||||
async function toggleMic() {
|
||||
if (!isSoundEnabled.value)
|
||||
return
|
||||
|
||||
if (isMicEnabled.value) {
|
||||
await pauseProducer(micProducer.value!.id)
|
||||
}
|
||||
else if (micProducer.value?.paused) {
|
||||
await resumeProducer(micProducer.value.id)
|
||||
}
|
||||
else {
|
||||
await enableMic()
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSound() {
|
||||
isSoundEnabled.value = !isSoundEnabled.value
|
||||
}
|
||||
|
||||
async function toggleCamera() {
|
||||
if (isCameraEnabled.value)
|
||||
await disableVideo()
|
||||
else
|
||||
await enableVideo()
|
||||
}
|
||||
|
||||
async function toggleShare() {
|
||||
if (isShareEnabled.value)
|
||||
await disableShare()
|
||||
else
|
||||
await enableShare()
|
||||
}
|
||||
|
||||
return {
|
||||
isMicEnabled,
|
||||
isSoundEnabled,
|
||||
isCameraEnabled,
|
||||
isShareEnabled,
|
||||
toggleMic,
|
||||
toggleSound,
|
||||
toggleCamera,
|
||||
toggleShare,
|
||||
}
|
||||
})
|
||||
119
new-client/src/shared/composables/use-mediasoup.ts
Normal file
119
new-client/src/shared/composables/use-mediasoup.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import type { Consumer, Device, Producer, Transport } from 'mediasoup-client/types'
|
||||
import type { Ref } from 'vue'
|
||||
import { createEventHook, createGlobalState } from '@vueuse/core'
|
||||
import { computed, ref, shallowRef } from 'vue'
|
||||
|
||||
interface Snapshot<T> {
|
||||
id: string
|
||||
ref: T
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface ProducerSnapshot extends Snapshot<Producer> {
|
||||
paused: boolean
|
||||
kind: string
|
||||
appData: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface ConsumerSnapshot extends Snapshot<Consumer> {
|
||||
paused: boolean
|
||||
kind: string
|
||||
appData: Record<string, unknown>
|
||||
track: MediaStreamTrack
|
||||
}
|
||||
|
||||
export interface TransportSnapshot extends Snapshot<Transport> {
|
||||
}
|
||||
|
||||
function createEntityStore<T extends { id: string }>() {
|
||||
const store = ref(new Map<string, T>()) as unknown as Ref<Map<string, T>>
|
||||
|
||||
const onAddEvent = createEventHook<T>()
|
||||
const onRemoveEvent = createEventHook<string>()
|
||||
|
||||
const values = computed(() => Array.from(store.value.values()))
|
||||
|
||||
async function add(entity: T) {
|
||||
store.value.set(entity.id, entity)
|
||||
// eslint-disable-next-line ts/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
await onAddEvent.trigger(entity)
|
||||
}
|
||||
|
||||
async function remove(id: string) {
|
||||
store.value.delete(id)
|
||||
await onRemoveEvent.trigger(id)
|
||||
}
|
||||
|
||||
function get(id: string) {
|
||||
return store.value.get(id)
|
||||
}
|
||||
|
||||
function clear() {
|
||||
store.value.clear()
|
||||
}
|
||||
|
||||
return {
|
||||
store,
|
||||
values,
|
||||
onAdd: onAddEvent.on,
|
||||
onRemove: onRemoveEvent.on,
|
||||
add,
|
||||
remove,
|
||||
get,
|
||||
clear,
|
||||
}
|
||||
}
|
||||
|
||||
export const useMediasoup = createGlobalState(() => {
|
||||
const device = shallowRef<Device>()
|
||||
const sendTransport = shallowRef<Transport>()
|
||||
const recvTransport = shallowRef<Transport>()
|
||||
|
||||
const producers = createEntityStore<ProducerSnapshot>()
|
||||
producers.onAdd((producer) => {
|
||||
producer.ref.observer.on('pause', () => {
|
||||
producers.store.value.set(producer.id, { ...producer, paused: true })
|
||||
})
|
||||
producer.ref.observer.on('resume', () => {
|
||||
producers.store.value.set(producer.id, { ...producer, paused: false })
|
||||
})
|
||||
producer.ref.observer.on('close', () => producers.remove(producer.id))
|
||||
})
|
||||
|
||||
const consumers = createEntityStore<ConsumerSnapshot>()
|
||||
consumers.onAdd((consumer) => {
|
||||
consumer.ref.on('trackended', () => consumer.ref.close())
|
||||
consumer.ref.observer.on('pause', () => {
|
||||
consumers.store.value.set(consumer.id, { ...consumer, paused: true })
|
||||
})
|
||||
consumer.ref.observer.on('resume', () => {
|
||||
consumers.store.value.set(consumer.id, { ...consumer, paused: false })
|
||||
})
|
||||
consumer.ref.observer.on('close', () => consumers.remove(consumer.id))
|
||||
})
|
||||
|
||||
const transports = createEntityStore<TransportSnapshot>()
|
||||
transports.onAdd((transport) => {
|
||||
transport.ref.observer.on('close', () => transports.remove(transport.id))
|
||||
})
|
||||
|
||||
function clearAll() {
|
||||
device.value = undefined
|
||||
sendTransport.value = undefined
|
||||
recvTransport.value = undefined
|
||||
producers.clear()
|
||||
consumers.clear()
|
||||
transports.clear()
|
||||
}
|
||||
|
||||
return {
|
||||
device,
|
||||
sendTransport,
|
||||
recvTransport,
|
||||
producers,
|
||||
consumers,
|
||||
transports,
|
||||
clearAll,
|
||||
}
|
||||
})
|
||||
164
new-client/src/shared/composables/use-producers.ts
Normal file
164
new-client/src/shared/composables/use-producers.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
import type { MediaKind, ProducerOptions } from 'mediasoup-client/types'
|
||||
import { createGlobalState } from '@vueuse/core'
|
||||
import { markRaw } from 'vue'
|
||||
import { useMediasoup } from './use-mediasoup'
|
||||
import { useSignaling } from './use-signaling'
|
||||
|
||||
export const useProducers = createGlobalState(() => {
|
||||
const { socket } = useSignaling()
|
||||
const { device, sendTransport, producers } = useMediasoup()
|
||||
|
||||
async function createProducer(options: ProducerOptions) {
|
||||
if (!sendTransport.value || !device.value || !options.track)
|
||||
return
|
||||
|
||||
if (!device.value.canProduce(options.track.kind as MediaKind))
|
||||
return
|
||||
|
||||
const producer = await sendTransport.value.produce({ disableTrackOnPause: true, ...options })
|
||||
|
||||
producer.observer.on('trackended', () => disableProducer(producer.id))
|
||||
|
||||
await producers.add({
|
||||
id: producer.id,
|
||||
paused: producer.paused,
|
||||
kind: producer.kind,
|
||||
appData: producer.appData as Record<string, unknown>,
|
||||
ref: markRaw(producer),
|
||||
})
|
||||
}
|
||||
|
||||
async function disableProducer(producerId: string) {
|
||||
const snap = producers.get(producerId)
|
||||
if (!snap)
|
||||
return
|
||||
|
||||
try {
|
||||
snap.ref.close()
|
||||
await socket.value?.emitWithAck('close-producer', { producerId })
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
|
||||
async function pauseProducer(producerId: string) {
|
||||
const snap = producers.get(producerId)
|
||||
if (!snap || snap.paused)
|
||||
return
|
||||
|
||||
try {
|
||||
snap.ref.pause()
|
||||
await socket.value?.emitWithAck('pause-producer', { producerId })
|
||||
}
|
||||
catch {
|
||||
snap.ref.resume()
|
||||
}
|
||||
}
|
||||
|
||||
async function resumeProducer(producerId: string) {
|
||||
const snap = producers.get(producerId)
|
||||
if (!snap || !snap.paused)
|
||||
return
|
||||
|
||||
try {
|
||||
snap.ref.resume()
|
||||
await socket.value?.emitWithAck('resume-producer', { producerId })
|
||||
}
|
||||
catch {
|
||||
snap.ref.pause()
|
||||
}
|
||||
}
|
||||
|
||||
async function enableMic() {
|
||||
for (const snap of producers.store.value.values()) {
|
||||
if (snap.kind === 'audio')
|
||||
return
|
||||
}
|
||||
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
const track = stream.getAudioTracks()[0]
|
||||
if (!track)
|
||||
return
|
||||
|
||||
await createProducer({
|
||||
track,
|
||||
streamId: 'mic-video',
|
||||
codecOptions: { opusStereo: true, opusDtx: true },
|
||||
appData: { source: 'mic' },
|
||||
})
|
||||
}
|
||||
|
||||
async function disableMic() {
|
||||
for (const snap of producers.store.value.values()) {
|
||||
if (snap.kind === 'audio')
|
||||
await disableProducer(snap.id)
|
||||
}
|
||||
}
|
||||
|
||||
async function enableVideo() {
|
||||
for (const snap of producers.store.value.values()) {
|
||||
if (snap.kind === 'video' && snap.appData.source !== 'share')
|
||||
return
|
||||
}
|
||||
|
||||
const stream = await navigator.mediaDevices.getUserMedia({
|
||||
video: { width: { ideal: 1920 }, height: { ideal: 1080 }, frameRate: { ideal: 60 } },
|
||||
})
|
||||
const track = stream.getVideoTracks()[0]
|
||||
if (!track)
|
||||
return
|
||||
|
||||
await createProducer({
|
||||
track,
|
||||
streamId: 'mic-video',
|
||||
appData: { source: 'camera' },
|
||||
})
|
||||
}
|
||||
|
||||
async function disableVideo() {
|
||||
for (const snap of producers.store.value.values()) {
|
||||
if (snap.kind === 'video' && snap.appData.source !== 'share')
|
||||
await disableProducer(snap.id)
|
||||
}
|
||||
}
|
||||
|
||||
async function enableShare() {
|
||||
for (const snap of producers.store.value.values()) {
|
||||
if (snap.appData.source === 'share')
|
||||
return
|
||||
}
|
||||
|
||||
const stream = await navigator.mediaDevices.getDisplayMedia({
|
||||
audio: false,
|
||||
video: { displaySurface: 'monitor' },
|
||||
})
|
||||
const track = stream.getVideoTracks()[0]
|
||||
if (!track)
|
||||
return
|
||||
|
||||
await createProducer({
|
||||
track,
|
||||
streamId: 'share',
|
||||
zeroRtpOnPause: true,
|
||||
appData: { source: 'share' },
|
||||
})
|
||||
}
|
||||
|
||||
async function disableShare() {
|
||||
for (const snap of producers.store.value.values()) {
|
||||
if (snap.appData.source === 'share')
|
||||
await disableProducer(snap.id)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
enableMic,
|
||||
disableMic,
|
||||
enableVideo,
|
||||
disableVideo,
|
||||
enableShare,
|
||||
disableShare,
|
||||
pauseProducer,
|
||||
resumeProducer,
|
||||
disableProducer,
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,101 @@
|
||||
PRAGMA defer_foreign_keys=ON;
|
||||
PRAGMA foreign_keys=OFF;
|
||||
|
||||
-- =====================
|
||||
-- Channel
|
||||
-- =====================
|
||||
CREATE TABLE "new_Channel" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"ownerId" TEXT,
|
||||
"ownerUsername" TEXT,
|
||||
"name" TEXT NOT NULL,
|
||||
"persistent" BOOLEAN NOT NULL,
|
||||
CONSTRAINT "Channel_ownerUsername_fkey" FOREIGN KEY ("ownerUsername") REFERENCES "User" ("username") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_Channel" ("id", "name", "ownerId", "ownerUsername", "persistent")
|
||||
SELECT c."id", c."name", c."ownerId",
|
||||
(SELECT u."username" FROM "User" u WHERE u."id" = c."ownerId"),
|
||||
c."persistent"
|
||||
FROM "Channel" c;
|
||||
DROP TABLE "Channel";
|
||||
ALTER TABLE "new_Channel" RENAME TO "Channel";
|
||||
|
||||
-- =====================
|
||||
-- Message
|
||||
-- =====================
|
||||
CREATE TABLE "new_Message" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"text" TEXT NOT NULL,
|
||||
"senderId" TEXT,
|
||||
"senderUsername" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Message_senderUsername_fkey" FOREIGN KEY ("senderUsername") REFERENCES "User" ("username") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_Message" ("id", "text", "senderId", "senderUsername", "createdAt", "updatedAt")
|
||||
SELECT m."id", m."text", m."senderId",
|
||||
(SELECT u."username" FROM "User" u WHERE u."id" = m."senderId"),
|
||||
m."createdAt", m."updatedAt"
|
||||
FROM "Message" m;
|
||||
DROP TABLE "Message";
|
||||
ALTER TABLE "new_Message" RENAME TO "Message";
|
||||
|
||||
-- =====================
|
||||
-- Session
|
||||
-- =====================
|
||||
CREATE TABLE "new_Session" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"userId" TEXT NOT NULL,
|
||||
"username" TEXT NOT NULL,
|
||||
"expiresAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Session_username_fkey" FOREIGN KEY ("username") REFERENCES "User" ("username") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_Session" ("id", "userId", "username", "expiresAt")
|
||||
SELECT s."id", s."userId",
|
||||
(SELECT u."username" FROM "User" u WHERE u."id" = s."userId"),
|
||||
s."expiresAt"
|
||||
FROM "Session" s;
|
||||
DROP TABLE "Session";
|
||||
ALTER TABLE "new_Session" RENAME TO "Session";
|
||||
CREATE INDEX "Session_userId_idx" ON "Session"("userId");
|
||||
|
||||
-- =====================
|
||||
-- User
|
||||
-- =====================
|
||||
CREATE TABLE "new_User" (
|
||||
"id" TEXT NOT NULL,
|
||||
"username" TEXT NOT NULL PRIMARY KEY,
|
||||
"password" TEXT NOT NULL,
|
||||
"displayName" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
INSERT INTO "new_User" ("createdAt", "displayName", "id", "password", "updatedAt", "username")
|
||||
SELECT "createdAt", "displayName", "id", "password", "updatedAt", "username"
|
||||
FROM "User";
|
||||
DROP TABLE "User";
|
||||
ALTER TABLE "new_User" RENAME TO "User";
|
||||
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
|
||||
|
||||
-- =====================
|
||||
-- UserPreferences
|
||||
-- =====================
|
||||
CREATE TABLE "new_UserPreferences" (
|
||||
"userId" TEXT NOT NULL,
|
||||
"username" TEXT NOT NULL PRIMARY KEY,
|
||||
"toggleInputHotkey" TEXT DEFAULT '',
|
||||
"toggleOutputHotkey" TEXT DEFAULT '',
|
||||
CONSTRAINT "UserPreferences_username_fkey" FOREIGN KEY ("username") REFERENCES "User" ("username") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_UserPreferences" ("userId", "username", "toggleInputHotkey", "toggleOutputHotkey")
|
||||
SELECT up."userId",
|
||||
(SELECT u."username" FROM "User" u WHERE u."id" = up."userId"),
|
||||
up."toggleInputHotkey", up."toggleOutputHotkey"
|
||||
FROM "UserPreferences" up;
|
||||
DROP TABLE "UserPreferences";
|
||||
ALTER TABLE "new_UserPreferences" RENAME TO "UserPreferences";
|
||||
CREATE UNIQUE INDEX "UserPreferences_userId_key" ON "UserPreferences"("userId");
|
||||
CREATE UNIQUE INDEX "UserPreferences_username_key" ON "UserPreferences"("username");
|
||||
|
||||
PRAGMA foreign_keys=ON;
|
||||
PRAGMA defer_foreign_keys=OFF;
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `ownerId` on the `Channel` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `senderId` on the `Message` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `userId` on the `Session` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `id` on the `User` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `userId` on the `UserPreferences` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- RedefineTables
|
||||
PRAGMA defer_foreign_keys=ON;
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Channel" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"ownerUsername" TEXT,
|
||||
"name" TEXT NOT NULL,
|
||||
"persistent" BOOLEAN NOT NULL,
|
||||
CONSTRAINT "Channel_ownerUsername_fkey" FOREIGN KEY ("ownerUsername") REFERENCES "User" ("username") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_Channel" ("id", "name", "ownerUsername", "persistent") SELECT "id", "name", "ownerUsername", "persistent" FROM "Channel";
|
||||
DROP TABLE "Channel";
|
||||
ALTER TABLE "new_Channel" RENAME TO "Channel";
|
||||
CREATE TABLE "new_Message" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"text" TEXT NOT NULL,
|
||||
"senderUsername" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Message_senderUsername_fkey" FOREIGN KEY ("senderUsername") REFERENCES "User" ("username") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_Message" ("createdAt", "id", "senderUsername", "text", "updatedAt") SELECT "createdAt", "id", "senderUsername", "text", "updatedAt" FROM "Message";
|
||||
DROP TABLE "Message";
|
||||
ALTER TABLE "new_Message" RENAME TO "Message";
|
||||
CREATE TABLE "new_Session" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"username" TEXT NOT NULL,
|
||||
"expiresAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Session_username_fkey" FOREIGN KEY ("username") REFERENCES "User" ("username") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_Session" ("expiresAt", "id", "username") SELECT "expiresAt", "id", "username" FROM "Session";
|
||||
DROP TABLE "Session";
|
||||
ALTER TABLE "new_Session" RENAME TO "Session";
|
||||
CREATE INDEX "Session_username_idx" ON "Session"("username");
|
||||
CREATE TABLE "new_User" (
|
||||
"username" TEXT NOT NULL PRIMARY KEY,
|
||||
"password" TEXT NOT NULL,
|
||||
"displayName" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
INSERT INTO "new_User" ("createdAt", "displayName", "password", "updatedAt", "username") SELECT "createdAt", "displayName", "password", "updatedAt", "username" FROM "User";
|
||||
DROP TABLE "User";
|
||||
ALTER TABLE "new_User" RENAME TO "User";
|
||||
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
|
||||
CREATE TABLE "new_UserPreferences" (
|
||||
"username" TEXT NOT NULL PRIMARY KEY,
|
||||
"toggleInputHotkey" TEXT DEFAULT '',
|
||||
"toggleOutputHotkey" TEXT DEFAULT '',
|
||||
CONSTRAINT "UserPreferences_username_fkey" FOREIGN KEY ("username") REFERENCES "User" ("username") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_UserPreferences" ("toggleInputHotkey", "toggleOutputHotkey", "username") SELECT "toggleInputHotkey", "toggleOutputHotkey", "username" FROM "UserPreferences";
|
||||
DROP TABLE "UserPreferences";
|
||||
ALTER TABLE "new_UserPreferences" RENAME TO "UserPreferences";
|
||||
CREATE UNIQUE INDEX "UserPreferences_username_key" ON "UserPreferences"("username");
|
||||
PRAGMA foreign_keys=ON;
|
||||
PRAGMA defer_foreign_keys=OFF;
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `username` on the `Session` table. All the data in the column will be lost.
|
||||
- Added the required column `userId` to the `Session` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- RedefineTables
|
||||
PRAGMA defer_foreign_keys=ON;
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Session" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"userId" TEXT NOT NULL,
|
||||
"expiresAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("username") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_Session" ("expiresAt", "id") SELECT "expiresAt", "id" FROM "Session";
|
||||
DROP TABLE "Session";
|
||||
ALTER TABLE "new_Session" RENAME TO "Session";
|
||||
PRAGMA foreign_keys=ON;
|
||||
PRAGMA defer_foreign_keys=OFF;
|
||||
Reference in New Issue
Block a user