From fb82c62decaf0414116d33f351aaa2a8fec1a5ea Mon Sep 17 00:00:00 2001 From: Darkdusk Date: Thu, 26 Feb 2026 09:15:55 +0100 Subject: [PATCH 01/16] Removed https part of the authurl variable. Instead put it when used in .env file --- app/src/modules/authClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/modules/authClient.ts b/app/src/modules/authClient.ts index ee1090d8..6689ea86 100644 --- a/app/src/modules/authClient.ts +++ b/app/src/modules/authClient.ts @@ -4,7 +4,7 @@ import type { JwtPayload, JwtUserData, UserRole } from 'schemas'; import type { User } from 'database/schema'; import decodeJwt from 'jwt-decode'; -const completeAuthUrl = `https://${import.meta.env.EXPOSED_SERVER_URL}${import.meta.env.EXPOSED_AUTH_PATH}`; +const completeAuthUrl = `${import.meta.env.EXPOSED_SERVER_URL}${import.meta.env.EXPOSED_AUTH_PATH}`; // console.log('authUrl: ', completeAuthUrl); const authEndpoint = axios.create({ baseURL: completeAuthUrl, withCredentials: true }); From c8e6a043d610b937214efedc37cfefe3b1ec38dc Mon Sep 17 00:00:00 2001 From: Darkdusk Date: Thu, 26 Feb 2026 14:19:33 +0100 Subject: [PATCH 02/16] Fixed a problem with auth server when running locally --- app/src/modules/authClient.ts | 8 +++++--- backend/auth/src/index.ts | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/src/modules/authClient.ts b/app/src/modules/authClient.ts index 6689ea86..6b3a3a3f 100644 --- a/app/src/modules/authClient.ts +++ b/app/src/modules/authClient.ts @@ -5,8 +5,10 @@ import type { User } from 'database/schema'; import decodeJwt from 'jwt-decode'; const completeAuthUrl = `${import.meta.env.EXPOSED_SERVER_URL}${import.meta.env.EXPOSED_AUTH_PATH}`; +const completeAuthUrlForGuest = `http://${import.meta.env.EXPOSED_SERVER_URL}:${import.meta.env.EXPOSED_AUTH_PORT}`; // console.log('authUrl: ', completeAuthUrl); const authEndpoint = axios.create({ baseURL: completeAuthUrl, withCredentials: true }); +const authEndpointForGuest = axios.create({ baseURL: completeAuthUrlForGuest, withCredentials: true }); export function createUser(username: string, password: string, role: UserRole) { return handleResponse(() => authEndpoint.post('/user/create', { @@ -186,12 +188,12 @@ export const loginWithAutoToken = async (username: string, password: string) => export const guestJwt = (params?: {requestedUsername?: string, previousToken?: string}) => { if(params?.previousToken){ - return handleResponse(() =>authEndpoint.get(`/guest-jwt?prevToken=${params.previousToken}`)); + return handleResponse(() => authEndpointForGuest.get(`/guest-jwt?prevToken=${params.previousToken}`)); } if(params?.requestedUsername){ - return handleResponse(() =>authEndpoint.get(`/guest-jwt?username=${params.requestedUsername}`)); + return handleResponse(() => authEndpointForGuest.get(`/guest-jwt?username=${params.requestedUsername}`)); } - return handleResponse(() =>authEndpoint.get('/guest-jwt')); + return handleResponse(() => authEndpointForGuest.get('/guest-jwt')); }; export const getJwt = () => handleResponse(() => authEndpoint.get('user/jwt')); export const getMe = () => handleResponse(() => authEndpoint.get('/user/me')); diff --git a/backend/auth/src/index.ts b/backend/auth/src/index.ts index fcc84896..e60a20a4 100644 --- a/backend/auth/src/index.ts +++ b/backend/auth/src/index.ts @@ -34,9 +34,9 @@ let cookieHttpOnly = false; let cookieSecure = true; if (devMode) { // NOTE: I couldnt come up with a way to allow all origins so we have hardcoded the devservers url here - const devServerUrl = 'http://localhost:5173'; - console.log('allowing cors for development: ', devServerUrl); - app.use(cors({ credentials: true, origin: [devServerUrl] })); + const devServerUrls = ['http://localhost:5173', 'http://127.0.0.1:5173']; + console.log('allowing cors for development: ', devServerUrls); + app.use(cors({ credentials: true, origin: devServerUrls })); console.log('allowing cookie despite not https'); cookieHttpOnly = false; From 6ff6043e58462933517bbd3dc78f1ab1d2db8e5a Mon Sep 17 00:00:00 2001 From: Darkdusk Date: Thu, 26 Feb 2026 15:00:37 +0100 Subject: [PATCH 03/16] Fixed problem with running websocket on localhost --- app/src/modules/trpcClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/modules/trpcClient.ts b/app/src/modules/trpcClient.ts index bf373f2e..1e9397dd 100644 --- a/app/src/modules/trpcClient.ts +++ b/app/src/modules/trpcClient.ts @@ -13,7 +13,7 @@ import { createReceiver } from 'ts-event-bridge/receiver' import { shallowRef, type ShallowRef, computed, type ComputedRef } from 'vue'; import type { Payload } from 'ts-event-bridge/sender'; -const wsBaseURL = `wss://${import.meta.env.EXPOSED_SERVER_URL}${import.meta.env.EXPOSED_MEDIASOUP_PATH}`; +const wsBaseURL = `ws://${import.meta.env.EXPOSED_SERVER_URL}:${import.meta.env.EXPOSED_MEDIASOUP_PORT}${import.meta.env.EXPOSED_MEDIASOUP_PATH}`; const { receiver, onMessageReceived } = createReceiver({ onUnhandledEvent(evt, msg) { From 77d84cdb2f7ff94e6f1b50be7c1944c4bdbd8189 Mon Sep 17 00:00:00 2001 From: Darkdusk Date: Fri, 27 Feb 2026 10:48:59 +0100 Subject: [PATCH 04/16] Fixed problem with login not working and not giving correct status code --- app/src/modules/authClient.ts | 12 ++++++------ backend/auth/src/userRoutes.ts | 6 +++++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/app/src/modules/authClient.ts b/app/src/modules/authClient.ts index 6b3a3a3f..9c759bf4 100644 --- a/app/src/modules/authClient.ts +++ b/app/src/modules/authClient.ts @@ -35,25 +35,25 @@ export function createSender(username: string, password: string, streamId: strin } export function updateUser(userData: {userId: string, username?: string, password?: string}) { - return handleResponse(() => authEndpoint.post('/user/update', userData)); + return handleResponse(() => authEndpointForGuest.post('/user/update', userData)); } export function deleteUser(userId: string) { - return handleResponse(() => authEndpoint.post('/user/delete', {userId})); + return handleResponse(() => authEndpointForGuest.post('/user/delete', { userId })); } type FetchedUsers = Omit[] export function getUsers() { - return handleResponse(() => authEndpoint.get('/user/get-users')); + return handleResponse(() => authEndpointForGuest.get('/user/get-users')); } export function getAdmins() { - return handleResponse(() => authEndpoint.get('/user/get-admins')); + return handleResponse(() => authEndpointForGuest.get('/user/get-admins')); } // type FetchedSenders = FetchedUsers[number] export function getSendersForStream(streamId: string) { - const response = handleResponse(() => authEndpoint.post('/user/get-sender', { + const response = handleResponse(() => authEndpointForGuest.post('/user/get-sender', { streamId, })); return response; @@ -72,7 +72,7 @@ const handleResponse = async (apiCall: () => Promise { try { - await authEndpoint.post('/user/login', { + await authEndpointForGuest.post('/user/login', { username, password, }); diff --git a/backend/auth/src/userRoutes.ts b/backend/auth/src/userRoutes.ts index 2e8453f7..415419ca 100644 --- a/backend/auth/src/userRoutes.ts +++ b/backend/auth/src/userRoutes.ts @@ -409,7 +409,11 @@ const loginUser: RequestHandler = async (req, res) => { } } catch (e) { console.error(e); - res.status(501).send('failed when trying to login'); + if (e instanceof Error && e.message === 'no user with that username found!') { + res.status(404).send('User with that username not found'); + } else { + res.status(501).send('failed when trying to login'); + } return; } res.status(403).send('You shall not pass!'); From 4cf33f118c549a0f6cc79322a12e0569547b3c2f Mon Sep 17 00:00:00 2001 From: Darkdusk Date: Mon, 2 Mar 2026 11:59:04 +0100 Subject: [PATCH 05/16] Uppdated error message to not be specific in what was wrong with login data --- backend/auth/src/userRoutes.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/backend/auth/src/userRoutes.ts b/backend/auth/src/userRoutes.ts index 415419ca..104d7ebc 100644 --- a/backend/auth/src/userRoutes.ts +++ b/backend/auth/src/userRoutes.ts @@ -105,6 +105,7 @@ interface CreateSenderRequest extends ExpressReq { streamId?: StreamId, } } + const createSenderForVenue: RequestHandler = async (req : CreateSenderRequest, res) => { const userData = req.session.user // console.log(userData); @@ -147,6 +148,7 @@ interface UpdateUserRequest extends ExpressReq { password?: string, } } + const updateUser: RequestHandler = async (req: UpdateUserRequest, res) => { const userData = req.session.user; const payload = req.body; @@ -206,6 +208,7 @@ interface DeleteUserRequest extends ExpressReq { userId: UserId } } + const deleteUser: RequestHandler = async (req: DeleteUserRequest, res) => { try { const userData = req.session.user; @@ -397,7 +400,7 @@ const loginUser: RequestHandler = async (req, res) => { where: eq(schema.users.username, username), }) if (!foundUser) { - throw new Error('no user with that username found!'); + throw new Error('no user with that username and password found!'); } const correct = await bcrypt.compare(password, foundUser.password); if (correct) { @@ -406,18 +409,18 @@ const loginUser: RequestHandler = async (req, res) => { req.session.user = pickedUserData; res.status(200).send(); return; + } else { + throw new Error('no user with that username and password found!'); } } catch (e) { console.error(e); - if (e instanceof Error && e.message === 'no user with that username found!') { - res.status(404).send('User with that username not found'); + if (e instanceof Error && e.message === 'no user with that username and password found!') { + res.status(401).send('User with that username or password not found'); } else { res.status(501).send('failed when trying to login'); } return; } - res.status(403).send('You shall not pass!'); - return; }; From 523639351437a50cfbf0608763691773cfeaff0c Mon Sep 17 00:00:00 2001 From: Darkdusk Date: Mon, 2 Mar 2026 11:59:44 +0100 Subject: [PATCH 06/16] Removed guest version of endpoint variable since it the original now works --- app/src/modules/authClient.ts | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/app/src/modules/authClient.ts b/app/src/modules/authClient.ts index 9c759bf4..77cba39b 100644 --- a/app/src/modules/authClient.ts +++ b/app/src/modules/authClient.ts @@ -4,11 +4,9 @@ import type { JwtPayload, JwtUserData, UserRole } from 'schemas'; import type { User } from 'database/schema'; import decodeJwt from 'jwt-decode'; -const completeAuthUrl = `${import.meta.env.EXPOSED_SERVER_URL}${import.meta.env.EXPOSED_AUTH_PATH}`; -const completeAuthUrlForGuest = `http://${import.meta.env.EXPOSED_SERVER_URL}:${import.meta.env.EXPOSED_AUTH_PORT}`; +const completeAuthUrl = `http://${import.meta.env.EXPOSED_SERVER_URL}:${import.meta.env.EXPOSED_AUTH_PORT}`; // console.log('authUrl: ', completeAuthUrl); const authEndpoint = axios.create({ baseURL: completeAuthUrl, withCredentials: true }); -const authEndpointForGuest = axios.create({ baseURL: completeAuthUrlForGuest, withCredentials: true }); export function createUser(username: string, password: string, role: UserRole) { return handleResponse(() => authEndpoint.post('/user/create', { @@ -35,25 +33,25 @@ export function createSender(username: string, password: string, streamId: strin } export function updateUser(userData: {userId: string, username?: string, password?: string}) { - return handleResponse(() => authEndpointForGuest.post('/user/update', userData)); + return handleResponse(() => authEndpoint.post('/user/update', userData)); } export function deleteUser(userId: string) { - return handleResponse(() => authEndpointForGuest.post('/user/delete', { userId })); + return handleResponse(() => authEndpoint.post('/user/delete', { userId })); } type FetchedUsers = Omit[] export function getUsers() { - return handleResponse(() => authEndpointForGuest.get('/user/get-users')); + return handleResponse(() => authEndpoint.get('/user/get-users')); } export function getAdmins() { - return handleResponse(() => authEndpointForGuest.get('/user/get-admins')); + return handleResponse(() => authEndpoint.get('/user/get-admins')); } // type FetchedSenders = FetchedUsers[number] export function getSendersForStream(streamId: string) { - const response = handleResponse(() => authEndpointForGuest.post('/user/get-sender', { + const response = handleResponse(() => authEndpoint.post('/user/get-sender', { streamId, })); return response; @@ -72,7 +70,7 @@ const handleResponse = async (apiCall: () => Promise { try { - await authEndpointForGuest.post('/user/login', { + await authEndpoint.post('/user/login', { username, password, }); @@ -188,15 +186,15 @@ export const loginWithAutoToken = async (username: string, password: string) => export const guestJwt = (params?: {requestedUsername?: string, previousToken?: string}) => { if(params?.previousToken){ - return handleResponse(() => authEndpointForGuest.get(`/guest-jwt?prevToken=${params.previousToken}`)); + return handleResponse(() => authEndpoint.get(`/guest-jwt?prevToken=${params.previousToken}`)); } if(params?.requestedUsername){ - return handleResponse(() => authEndpointForGuest.get(`/guest-jwt?username=${params.requestedUsername}`)); + return handleResponse(() => authEndpoint.get(`/guest-jwt?username=${params.requestedUsername}`)); } - return handleResponse(() => authEndpointForGuest.get('/guest-jwt')); + return handleResponse(() => authEndpoint.get('/guest-jwt')); }; export const getJwt = () => handleResponse(() => authEndpoint.get('user/jwt')); -export const getMe = () => handleResponse(() => authEndpoint.get('/user/me')); +export const getMe = () => handleResponse(() => authEndpoint.get('user/me')); export const logout = () => { clearTimeout(activeTimeout); return handleResponse(() => authEndpoint.get('user/logout')); From b7e82fc135b8f160c99babbeb9d45a12e1d08263 Mon Sep 17 00:00:00 2001 From: Darkdusk Date: Thu, 5 Mar 2026 14:20:40 +0100 Subject: [PATCH 07/16] Fixed problem with fileserver not working with localhost --- app/src/modules/utils.ts | 4 ++-- backend/fileserver/src/index.ts | 22 +++++++++++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/app/src/modules/utils.ts b/app/src/modules/utils.ts index 8564561f..15d6d79e 100644 --- a/app/src/modules/utils.ts +++ b/app/src/modules/utils.ts @@ -8,11 +8,11 @@ export function getAssetUrl(generatedName: T) { // return generatedName // } - return `https://${import.meta.env.EXPOSED_SERVER_URL}${import.meta.env.EXPOSED_FILESERVER_PATH}/file/${generatedName}`; + return `http://${import.meta.env.EXPOSED_SERVER_URL}:${import.meta.env.EXPOSED_FILESERVER_PORT}/file/${generatedName}`; // return `https://${process.env.EXPOSED_SERVER_URL}${process.env.EXPOSED_FILESERVER_PATH}/files/${generatedName}`; } -const fileserverUrl = `https://${import.meta.env.EXPOSED_SERVER_URL}${import.meta.env.EXPOSED_FILESERVER_PATH}` as const +const fileserverUrl = `http://${import.meta.env.EXPOSED_SERVER_URL}:${import.meta.env.EXPOSED_FILESERVER_PORT}` as const export const assetsUrl = `${fileserverUrl}/file/` as const; export async function uploadFileData({ data, authToken, onProgress, abortController }: { data: FormData, authToken: string, onProgress?: (progressEvent: AxiosProgressEvent) => void, abortController?: AbortController }) { // We can apparently receive upload progress after the upload is actually finished. diff --git a/backend/fileserver/src/index.ts b/backend/fileserver/src/index.ts index a4511688..18bc187d 100644 --- a/backend/fileserver/src/index.ts +++ b/backend/fileserver/src/index.ts @@ -3,6 +3,7 @@ import { HttpStatus } from 'http-status-ts'; import { serveStatic } from '@hono/node-server/serve-static' import { zValidator } from '@hono/zod-validator'; import { Hono } from 'hono' +import { cors } from 'hono/cors' // import { logger } from 'hono/logger'; // import { createMiddleware } from 'hono/factory' import { HTTPException } from 'hono/http-exception' @@ -13,6 +14,7 @@ import { randomUUID } from 'crypto' import { Stream, pipeline } from 'node:stream'; import type { ReadableStream } from 'node:stream/web'; import fs from 'fs' +import promise from 'fs/promises'; import { hc, InferRequestType, InferResponseType } from 'hono/client' @@ -30,6 +32,8 @@ import sharp from 'sharp'; const savePathAbsolute = path.resolve('.', 'uploads') const savePathRelative = './uploads/' +await promise.mkdir(savePathAbsolute, { recursive: true }); + // const authHandler = basicAuth({ username: 'gunnar', password: 'hemligt' }) // const jwtHandler = jwt({ secret: 'secret' }); // const jwtAuthHandler = createMiddleware<{ Variables: { jwtPayload: JwtPayload } }>(jwtHandler); @@ -83,6 +87,9 @@ publicRoutes.get('/file/:filename', root: savePathRelative, }), ) + .get('/test', (c) => { + return c.text('test', HttpStatus.OK); + }); const privateRoutes = new Hono<{ Variables: { jwtPayload: JwtPayload } }>() .use((c, next) => { @@ -202,9 +209,18 @@ const privateRoutes = new Hono<{ Variables: { jwtPayload: JwtPayload } }>() return c.json(dbResponse, HttpStatus.OK); }); -const app = new Hono<{ Variables: { jwtPayload: JwtPayload } }>() - .route('/', publicRoutes) - .route('/', privateRoutes); +const app = new Hono<{ Variables: { jwtPayload: JwtPayload } }>(); + +app.use( + '*', + cors({ + origin: ['http://localhost:5173', 'http://127.0.0.1:5173'], + credentials: true, + }) +); + +app.route('/', publicRoutes) +app.route('/', privateRoutes); const port = Number.parseInt(process.env.FILESERVER_PORT ?? '3000'); console.log(`Server is running on port ${port}`) From 1f8e93dd67ee0f63358e19f2a5d90b548ba60bdf Mon Sep 17 00:00:00 2001 From: Darkdusk Date: Mon, 9 Mar 2026 08:54:01 +0100 Subject: [PATCH 08/16] Updated code so it should work if program is run on either localhost or on a server. --- app/src/modules/authClient.ts | 10 +++++++++- app/src/modules/trpcClient.ts | 2 +- app/src/modules/utils.ts | 15 +++++++++++++-- backend/fileserver/src/index.ts | 25 ++++++++++++++++++------- example.env | 7 +++++++ 5 files changed, 48 insertions(+), 11 deletions(-) diff --git a/app/src/modules/authClient.ts b/app/src/modules/authClient.ts index 77cba39b..a1d066f0 100644 --- a/app/src/modules/authClient.ts +++ b/app/src/modules/authClient.ts @@ -4,7 +4,15 @@ import type { JwtPayload, JwtUserData, UserRole } from 'schemas'; import type { User } from 'database/schema'; import decodeJwt from 'jwt-decode'; -const completeAuthUrl = `http://${import.meta.env.EXPOSED_SERVER_URL}:${import.meta.env.EXPOSED_AUTH_PORT}`; +const devMode = import.meta.env.DEV; + +let completeAuthUrl: string; +if (devMode) { + completeAuthUrl = `${import.meta.env.EXPOSED_SERVER_URL}:${import.meta.env.EXPOSED_AUTH_PORT}`; +} else { + completeAuthUrl = `${import.meta.env.EXPOSED_SERVER_URL}${import.meta.env.EXPOSED_AUTH_PATH}`; +} + // console.log('authUrl: ', completeAuthUrl); const authEndpoint = axios.create({ baseURL: completeAuthUrl, withCredentials: true }); diff --git a/app/src/modules/trpcClient.ts b/app/src/modules/trpcClient.ts index 1e9397dd..042863a7 100644 --- a/app/src/modules/trpcClient.ts +++ b/app/src/modules/trpcClient.ts @@ -13,7 +13,7 @@ import { createReceiver } from 'ts-event-bridge/receiver' import { shallowRef, type ShallowRef, computed, type ComputedRef } from 'vue'; import type { Payload } from 'ts-event-bridge/sender'; -const wsBaseURL = `ws://${import.meta.env.EXPOSED_SERVER_URL}:${import.meta.env.EXPOSED_MEDIASOUP_PORT}${import.meta.env.EXPOSED_MEDIASOUP_PATH}`; +const wsBaseURL = `ws://${import.meta.env.EXPOSED_WEBSOCKET_SERVER_URL}:${import.meta.env.EXPOSED_MEDIASOUP_PORT}${import.meta.env.EXPOSED_MEDIASOUP_PATH}`; const { receiver, onMessageReceived } = createReceiver({ onUnhandledEvent(evt, msg) { diff --git a/app/src/modules/utils.ts b/app/src/modules/utils.ts index 15d6d79e..7dfc86cf 100644 --- a/app/src/modules/utils.ts +++ b/app/src/modules/utils.ts @@ -2,17 +2,28 @@ import axios, { CanceledError, type AxiosProgressEvent } from "axios"; import type { UploadResponse } from "fileserver"; import type { AssetId } from "schemas"; +const devMode = import.meta.env.DEV; + export function getAssetUrl(generatedName: T) { // console.log('getAssetUrl called', generatedName); // if (generatedName === undefined) { // return generatedName // } - return `http://${import.meta.env.EXPOSED_SERVER_URL}:${import.meta.env.EXPOSED_FILESERVER_PORT}/file/${generatedName}`; + if (devMode) { + return `${import.meta.env.EXPOSED_SERVER_URL}:${import.meta.env.EXPOSED_FILESERVER_PORT}/file/${generatedName}`; + } else { + return `${import.meta.env.EXPOSED_SERVER_URL}${import.meta.env.EXPOSED_FILESERVER_PATH}/files/${generatedName}`; + } // return `https://${process.env.EXPOSED_SERVER_URL}${process.env.EXPOSED_FILESERVER_PATH}/files/${generatedName}`; } -const fileserverUrl = `http://${import.meta.env.EXPOSED_SERVER_URL}:${import.meta.env.EXPOSED_FILESERVER_PORT}` as const +let fileserverUrl: string; +if (devMode) { + fileserverUrl = `${import.meta.env.EXPOSED_SERVER_URL}:${import.meta.env.EXPOSED_FILESERVER_PORT}`; +} else { + fileserverUrl = `${import.meta.env.EXPOSED_SERVER_URL}${import.meta.env.EXPOSED_FILESERVER_PATH}`; +} export const assetsUrl = `${fileserverUrl}/file/` as const; export async function uploadFileData({ data, authToken, onProgress, abortController }: { data: FormData, authToken: string, onProgress?: (progressEvent: AxiosProgressEvent) => void, abortController?: AbortController }) { // We can apparently receive upload progress after the upload is actually finished. diff --git a/backend/fileserver/src/index.ts b/backend/fileserver/src/index.ts index 18bc187d..b2c84c50 100644 --- a/backend/fileserver/src/index.ts +++ b/backend/fileserver/src/index.ts @@ -31,6 +31,7 @@ import sharp from 'sharp'; const savePathAbsolute = path.resolve('.', 'uploads') const savePathRelative = './uploads/' +const devMode = process.env.DEVELOPMENT === 'true'; await promise.mkdir(savePathAbsolute, { recursive: true }); @@ -211,13 +212,23 @@ const privateRoutes = new Hono<{ Variables: { jwtPayload: JwtPayload } }>() const app = new Hono<{ Variables: { jwtPayload: JwtPayload } }>(); -app.use( - '*', - cors({ - origin: ['http://localhost:5173', 'http://127.0.0.1:5173'], - credentials: true, - }) -); +if (devMode) { + app.use( + '*', + cors({ + origin: ['http://localhost:5173', 'http://127.0.0.1:5173'], + credentials: true, + }) + ); +} else { + app.use( + '*', + cors({ + origin: [`${process.env.EXPOSED_SERVER_URL}`], + credentials: true + }) + ) +} app.route('/', publicRoutes) app.route('/', privateRoutes); diff --git a/example.env b/example.env index b45c1d8b..b4de891f 100644 --- a/example.env +++ b/example.env @@ -11,7 +11,14 @@ EXPOSED_PROJECT_NAME="samvr" +# Set this to true to enable development mode. This will for example make the server use the EXPOSED_AUTH_PORT variable instead of the EXPOSED_AUTH_PATH variable to construct the complete auth url. So if you want to test the production urls locally, set this to false. +DEVELOPMENT=true + +# The protocol to use in the exposed server url. So either http or https. This is used to construct the complete exposed server url by concatenating this with the EXPOSED_SERVER_URL variable. +CONNECTION_PROTOCOL="http://" EXPOSED_SERVER_URL="example.com" +# What adress for the websocket to use. +EXPOSED_WEBSOCKET_SERVER_URL="example" # The public IP of the server I.E. The IP that faces the internetzz LISTEN_IP="123.123.123.123" From 67671b7c543443107e583207373014303895f3a2 Mon Sep 17 00:00:00 2001 From: Gunnar Oledal Date: Tue, 10 Mar 2026 12:39:19 +0000 Subject: [PATCH 09/16] adjustments of local vs https --- Caddyfile | 6 +++--- app/src/modules/authClient.ts | 7 ++++--- app/src/modules/trpcClient.ts | 6 +++++- app/src/modules/utils.ts | 14 +++++++------- backend/auth/src/index.ts | 18 +++++++++--------- backend/fileserver/src/index.ts | 5 +++-- 6 files changed, 31 insertions(+), 25 deletions(-) diff --git a/Caddyfile b/Caddyfile index b2fc8c5b..69138526 100644 --- a/Caddyfile +++ b/Caddyfile @@ -8,18 +8,18 @@ file_server # serve the files! } -(production_tls) { +(online_tls) { # log } -(development_tls) { +(local_tls) { tls ./certs/localhost+2.pem ./certs/localhost+2-key.pem } # @isDev expression `{$ENVIRONMENT} == development` {$EXPOSED_SERVER_URL} { - import `{$ENVIRONMENT:production}_tls` + import `{$LOCAL:online}_tls` # tls ./certs/localhost+2.pem ./certs/localhost+2-key.pem # Important to use the route directive so that the reverse_proxy is matched first # (default is try_files before reverse_proxy) diff --git a/app/src/modules/authClient.ts b/app/src/modules/authClient.ts index a1d066f0..b948064e 100644 --- a/app/src/modules/authClient.ts +++ b/app/src/modules/authClient.ts @@ -5,12 +5,13 @@ import type { User } from 'database/schema'; import decodeJwt from 'jwt-decode'; const devMode = import.meta.env.DEV; +const localMode = import.meta.env.LOCAL === 'true'; let completeAuthUrl: string; -if (devMode) { - completeAuthUrl = `${import.meta.env.EXPOSED_SERVER_URL}:${import.meta.env.EXPOSED_AUTH_PORT}`; +if (localMode) { + completeAuthUrl = `http://${import.meta.env.EXPOSED_SERVER_URL}:${import.meta.env.EXPOSED_AUTH_PORT}`; } else { - completeAuthUrl = `${import.meta.env.EXPOSED_SERVER_URL}${import.meta.env.EXPOSED_AUTH_PATH}`; + completeAuthUrl = `https://${import.meta.env.EXPOSED_SERVER_URL}${import.meta.env.EXPOSED_AUTH_PATH}`; } // console.log('authUrl: ', completeAuthUrl); diff --git a/app/src/modules/trpcClient.ts b/app/src/modules/trpcClient.ts index 042863a7..a7fefe09 100644 --- a/app/src/modules/trpcClient.ts +++ b/app/src/modules/trpcClient.ts @@ -13,8 +13,12 @@ import { createReceiver } from 'ts-event-bridge/receiver' import { shallowRef, type ShallowRef, computed, type ComputedRef } from 'vue'; import type { Payload } from 'ts-event-bridge/sender'; -const wsBaseURL = `ws://${import.meta.env.EXPOSED_WEBSOCKET_SERVER_URL}:${import.meta.env.EXPOSED_MEDIASOUP_PORT}${import.meta.env.EXPOSED_MEDIASOUP_PATH}`; +const localMode = import.meta.env.LOCAL === 'true'; +let wsBaseURL = `wss://${import.meta.env.EXPOSED_SERVER_URL}${import.meta.env.EXPOSED_MEDIASOUP_PATH}`; +if (localMode) { + wsBaseURL = `ws://${import.meta.env.EXPOSED_SERVER_URL}:${import.meta.env.EXPOSED_MEDIASOUP_PORT}`; +} const { receiver, onMessageReceived } = createReceiver({ onUnhandledEvent(evt, msg) { console.warn('unhandled event: ', evt, msg); diff --git a/app/src/modules/utils.ts b/app/src/modules/utils.ts index 7dfc86cf..c0a25f37 100644 --- a/app/src/modules/utils.ts +++ b/app/src/modules/utils.ts @@ -3,6 +3,7 @@ import type { UploadResponse } from "fileserver"; import type { AssetId } from "schemas"; const devMode = import.meta.env.DEV; +const localMode = import.meta.env.LOCAL === 'true'; export function getAssetUrl(generatedName: T) { // console.log('getAssetUrl called', generatedName); @@ -10,19 +11,18 @@ export function getAssetUrl(generatedName: T) { // return generatedName // } - if (devMode) { - return `${import.meta.env.EXPOSED_SERVER_URL}:${import.meta.env.EXPOSED_FILESERVER_PORT}/file/${generatedName}`; + if (localMode) { + return `http://${import.meta.env.EXPOSED_SERVER_URL}:${import.meta.env.EXPOSED_FILESERVER_PORT}/file/${generatedName}`; } else { - return `${import.meta.env.EXPOSED_SERVER_URL}${import.meta.env.EXPOSED_FILESERVER_PATH}/files/${generatedName}`; + return `https://${import.meta.env.EXPOSED_SERVER_URL}${import.meta.env.EXPOSED_FILESERVER_PATH}/file/${generatedName}`; } - // return `https://${process.env.EXPOSED_SERVER_URL}${process.env.EXPOSED_FILESERVER_PATH}/files/${generatedName}`; } let fileserverUrl: string; -if (devMode) { - fileserverUrl = `${import.meta.env.EXPOSED_SERVER_URL}:${import.meta.env.EXPOSED_FILESERVER_PORT}`; +if (localMode) { + fileserverUrl = `http://${import.meta.env.EXPOSED_SERVER_URL}:${import.meta.env.EXPOSED_FILESERVER_PORT}`; } else { - fileserverUrl = `${import.meta.env.EXPOSED_SERVER_URL}${import.meta.env.EXPOSED_FILESERVER_PATH}`; + fileserverUrl = `https://${import.meta.env.EXPOSED_SERVER_URL}${import.meta.env.EXPOSED_FILESERVER_PATH}`; } export const assetsUrl = `${fileserverUrl}/file/` as const; export async function uploadFileData({ data, authToken, onProgress, abortController }: { data: FormData, authToken: string, onProgress?: (progressEvent: AxiosProgressEvent) => void, abortController?: AbortController }) { diff --git a/backend/auth/src/index.ts b/backend/auth/src/index.ts index e60a20a4..bdb280ae 100644 --- a/backend/auth/src/index.ts +++ b/backend/auth/src/index.ts @@ -21,6 +21,7 @@ const haikunator = new Haikunator({ // console.log('environment: ', process.env); const devMode = process.env.DEVELOPMENT; +const localMode = process.env.LOCAL === 'true'; const app = express(); @@ -32,11 +33,11 @@ app.set('trust proxy', 1); // Find a way to make our auth flow work without this set to false let cookieHttpOnly = false; let cookieSecure = true; -if (devMode) { +let originUrls: string[] = []; +if (localMode) { // NOTE: I couldnt come up with a way to allow all origins so we have hardcoded the devservers url here - const devServerUrls = ['http://localhost:5173', 'http://127.0.0.1:5173']; - console.log('allowing cors for development: ', devServerUrls); - app.use(cors({ credentials: true, origin: devServerUrls })); + originUrls = ['http://localhost:5173', 'http://127.0.0.1:5173']; + console.log('allowing cors for local development: ', originUrls); console.log('allowing cookie despite not https'); cookieHttpOnly = false; @@ -46,13 +47,12 @@ if (devMode) { console.error('no EXPOSED_SERVER_URL provided from env'); process.exit(1); } - console.log('restricting CORS for production'); - app.use(cors({ - origin: [process.env.EXPOSED_SERVER_URL], - credentials: true - })); + originUrls = [`https://${process.env.EXPOSED_SERVER_URL}`]; + console.log('restricting CORS for https:', originUrls); } +app.use(cors({ credentials: true, origin: originUrls })); + app.use((req, res, next) => { return parseJsonBody()(req, res, (err) => { if (err) { diff --git a/backend/fileserver/src/index.ts b/backend/fileserver/src/index.ts index b2c84c50..c6dd385c 100644 --- a/backend/fileserver/src/index.ts +++ b/backend/fileserver/src/index.ts @@ -32,6 +32,7 @@ import sharp from 'sharp'; const savePathAbsolute = path.resolve('.', 'uploads') const savePathRelative = './uploads/' const devMode = process.env.DEVELOPMENT === 'true'; +const localMode = process.env.LOCAL === 'true'; await promise.mkdir(savePathAbsolute, { recursive: true }); @@ -212,7 +213,7 @@ const privateRoutes = new Hono<{ Variables: { jwtPayload: JwtPayload } }>() const app = new Hono<{ Variables: { jwtPayload: JwtPayload } }>(); -if (devMode) { +if (localMode) { app.use( '*', cors({ @@ -224,7 +225,7 @@ if (devMode) { app.use( '*', cors({ - origin: [`${process.env.EXPOSED_SERVER_URL}`], + origin: [`https://${process.env.EXPOSED_SERVER_URL}`], credentials: true }) ) From 43dc67fc8143a2a5c455da85b2ad3508d615f995 Mon Sep 17 00:00:00 2001 From: Gunnar Oledal Date: Thu, 12 Mar 2026 21:53:02 +0100 Subject: [PATCH 10/16] make local env var exposed to frontend --- Caddyfile | 6 ++++-- app/src/modules/authClient.ts | 5 +++-- app/src/modules/trpcClient.ts | 2 +- app/src/modules/utils.ts | 2 +- backend/auth/src/index.ts | 2 +- backend/fileserver/src/index.ts | 4 ++-- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Caddyfile b/Caddyfile index 69138526..84df8fb9 100644 --- a/Caddyfile +++ b/Caddyfile @@ -5,7 +5,7 @@ (production) { root * app/dist # will be used as the start directory for the ditrectives below try_files {path} /index.html # catch all (non existent files) redirect for SPA frontend - file_server # serve the files! + file_server # serve the app files! } (online_tls) { @@ -19,7 +19,9 @@ # @isDev expression `{$ENVIRONMENT} == development` {$EXPOSED_SERVER_URL} { - import `{$LOCAL:online}_tls` + # This does not work currently because the variable is a true or false rather than an actual value + # import `{$EXPOSED_LOCAL:online}_tls` + # tls ./certs/localhost+2.pem ./certs/localhost+2-key.pem # Important to use the route directive so that the reverse_proxy is matched first # (default is try_files before reverse_proxy) diff --git a/app/src/modules/authClient.ts b/app/src/modules/authClient.ts index b948064e..7f748a48 100644 --- a/app/src/modules/authClient.ts +++ b/app/src/modules/authClient.ts @@ -5,7 +5,8 @@ import type { User } from 'database/schema'; import decodeJwt from 'jwt-decode'; const devMode = import.meta.env.DEV; -const localMode = import.meta.env.LOCAL === 'true'; +const localMode = import.meta.env.EXPOSED_LOCAL === 'true'; +console.log('localMode:', localMode); let completeAuthUrl: string; if (localMode) { @@ -14,7 +15,7 @@ if (localMode) { completeAuthUrl = `https://${import.meta.env.EXPOSED_SERVER_URL}${import.meta.env.EXPOSED_AUTH_PATH}`; } -// console.log('authUrl: ', completeAuthUrl); +console.log('authUrl: ', completeAuthUrl); const authEndpoint = axios.create({ baseURL: completeAuthUrl, withCredentials: true }); export function createUser(username: string, password: string, role: UserRole) { diff --git a/app/src/modules/trpcClient.ts b/app/src/modules/trpcClient.ts index a7fefe09..bd69bae1 100644 --- a/app/src/modules/trpcClient.ts +++ b/app/src/modules/trpcClient.ts @@ -13,7 +13,7 @@ import { createReceiver } from 'ts-event-bridge/receiver' import { shallowRef, type ShallowRef, computed, type ComputedRef } from 'vue'; import type { Payload } from 'ts-event-bridge/sender'; -const localMode = import.meta.env.LOCAL === 'true'; +const localMode = import.meta.env.EXPOSED_LOCAL === 'true'; let wsBaseURL = `wss://${import.meta.env.EXPOSED_SERVER_URL}${import.meta.env.EXPOSED_MEDIASOUP_PATH}`; if (localMode) { diff --git a/app/src/modules/utils.ts b/app/src/modules/utils.ts index c0a25f37..3d6dd623 100644 --- a/app/src/modules/utils.ts +++ b/app/src/modules/utils.ts @@ -3,7 +3,7 @@ import type { UploadResponse } from "fileserver"; import type { AssetId } from "schemas"; const devMode = import.meta.env.DEV; -const localMode = import.meta.env.LOCAL === 'true'; +const localMode = import.meta.env.EXPOSED_LOCAL === 'true'; export function getAssetUrl(generatedName: T) { // console.log('getAssetUrl called', generatedName); diff --git a/backend/auth/src/index.ts b/backend/auth/src/index.ts index bdb280ae..0ec4e549 100644 --- a/backend/auth/src/index.ts +++ b/backend/auth/src/index.ts @@ -21,7 +21,7 @@ const haikunator = new Haikunator({ // console.log('environment: ', process.env); const devMode = process.env.DEVELOPMENT; -const localMode = process.env.LOCAL === 'true'; +const localMode = process.env.EXPOSED_LOCAL === 'true'; const app = express(); diff --git a/backend/fileserver/src/index.ts b/backend/fileserver/src/index.ts index c6dd385c..9dc36817 100644 --- a/backend/fileserver/src/index.ts +++ b/backend/fileserver/src/index.ts @@ -32,7 +32,7 @@ import sharp from 'sharp'; const savePathAbsolute = path.resolve('.', 'uploads') const savePathRelative = './uploads/' const devMode = process.env.DEVELOPMENT === 'true'; -const localMode = process.env.LOCAL === 'true'; +const localMode = process.env.EXPOSED_LOCAL === 'true'; await promise.mkdir(savePathAbsolute, { recursive: true }); @@ -234,7 +234,7 @@ if (localMode) { app.route('/', publicRoutes) app.route('/', privateRoutes); -const port = Number.parseInt(process.env.FILESERVER_PORT ?? '3000'); +const port = Number.parseInt(process.env.EXPOSED_FILESERVER_PORT ?? '3000'); console.log(`Server is running on port ${port}`) serve({ From b2ea9388a1e25aad5104487483a9b8047d332091 Mon Sep 17 00:00:00 2001 From: Gunnar Oledal Date: Fri, 13 Mar 2026 08:46:49 +0100 Subject: [PATCH 11/16] updated the env example --- example.env | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/example.env b/example.env index b4de891f..447e05fa 100644 --- a/example.env +++ b/example.env @@ -9,16 +9,18 @@ # ########################### -EXPOSED_PROJECT_NAME="samvr" +# possible levels from less to more logging: TRACE, DEBUG, INFO, WARN, ERROR, FATAL and OFF +# DEBUG_LEVEL="DEBUG" + +# If doing local development (localhost), set this to true. +# Local dev uses localhost and ports instead of paths to construct the urls. +# set to false in server environments with reverse proxy and https +EXPOSED_LOCAL="false" -# Set this to true to enable development mode. This will for example make the server use the EXPOSED_AUTH_PORT variable instead of the EXPOSED_AUTH_PATH variable to construct the complete auth url. So if you want to test the production urls locally, set this to false. -DEVELOPMENT=true -# The protocol to use in the exposed server url. So either http or https. This is used to construct the complete exposed server url by concatenating this with the EXPOSED_SERVER_URL variable. -CONNECTION_PROTOCOL="http://" +EXPOSED_PROJECT_NAME="samvr" + EXPOSED_SERVER_URL="example.com" -# What adress for the websocket to use. -EXPOSED_WEBSOCKET_SERVER_URL="example" # The public IP of the server I.E. The IP that faces the internetzz LISTEN_IP="123.123.123.123" From 82c80f0e5cefd346c5ec6cf5faa4cb5a06327fba Mon Sep 17 00:00:00 2001 From: Gunnar Oledal Date: Fri, 13 Mar 2026 08:48:39 +0100 Subject: [PATCH 12/16] Nicer default project name --- example.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example.env b/example.env index 447e05fa..bb44f7d9 100644 --- a/example.env +++ b/example.env @@ -18,7 +18,7 @@ EXPOSED_LOCAL="false" -EXPOSED_PROJECT_NAME="samvr" +EXPOSED_PROJECT_NAME="SamVR" EXPOSED_SERVER_URL="example.com" From 72cf56bbbf67410812a092072744b029ff63fd6d Mon Sep 17 00:00:00 2001 From: Gunnar Oledal Date: Fri, 13 Mar 2026 08:56:56 +0100 Subject: [PATCH 13/16] no tls shit in caddyfile. Let that stuff do it's own magic! --- Caddyfile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Caddyfile b/Caddyfile index 84df8fb9..4e1da523 100644 --- a/Caddyfile +++ b/Caddyfile @@ -8,13 +8,13 @@ file_server # serve the app files! } -(online_tls) { - # log -} +# (online_tls) { +# # log +# } -(local_tls) { - tls ./certs/localhost+2.pem ./certs/localhost+2-key.pem -} +# (local_tls) { +# tls ./certs/localhost+2.pem ./certs/localhost+2-key.pem +# } # @isDev expression `{$ENVIRONMENT} == development` From 980ba00c35ced8028ccd232a88d0fb6137d27ae9 Mon Sep 17 00:00:00 2001 From: Gunnar Oledal Date: Fri, 13 Mar 2026 09:01:58 +0100 Subject: [PATCH 14/16] missed several env updates in the codes!!! gah! --- Caddyfile | 6 +++--- backend/auth/src/index.ts | 2 +- backend/fileserver/src/index_old.ts | 2 +- backend/mediaserver/src/index.ts | 2 +- example.env | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Caddyfile b/Caddyfile index 4e1da523..95d620d1 100644 --- a/Caddyfile +++ b/Caddyfile @@ -27,13 +27,13 @@ # (default is try_files before reverse_proxy) route { handle_path /auth* { - reverse_proxy localhost:{$AUTH_PORT} + reverse_proxy localhost:{$EXPOSED_AUTH_PORT} } handle_path /socket* { - reverse_proxy localhost:{$MEDIASOUP_PORT} + reverse_proxy localhost:{$EXPOSED_MEDIASOUP_PORT} } handle_path /files* { - reverse_proxy localhost:{$FILESERVER_PORT} + reverse_proxy localhost:{$EXPOSED_FILESERVER_PORT} } import {$ENVIRONMENT:production} diff --git a/backend/auth/src/index.ts b/backend/auth/src/index.ts index 0ec4e549..e1bee183 100644 --- a/backend/auth/src/index.ts +++ b/backend/auth/src/index.ts @@ -167,7 +167,7 @@ app.get('/guest-jwt', (req, res) => { }); -const port = Number.parseInt(process.env.AUTH_PORT || '3333'); +const port = Number.parseInt(process.env.EXPOSED_AUTH_PORT || '3333'); app.listen(port, () => { console.log(`listening on ${port}`); if (process.env.DEVELOPMENT) diff --git a/backend/fileserver/src/index_old.ts b/backend/fileserver/src/index_old.ts index 3a7f93c1..fcbfac20 100644 --- a/backend/fileserver/src/index_old.ts +++ b/backend/fileserver/src/index_old.ts @@ -182,5 +182,5 @@ app.post('/remove', (req,res) => { }) // Run Express app -const port = Number.parseInt(process.env.FILESERVER_PORT || '9002') +const port = Number.parseInt(process.env.EXPOSED_FILESERVER_PORT || '9002') app.listen(port, () => console.log('Application listening on port ' + port)) diff --git a/backend/mediaserver/src/index.ts b/backend/mediaserver/src/index.ts index d68502c2..472902fc 100644 --- a/backend/mediaserver/src/index.ts +++ b/backend/mediaserver/src/index.ts @@ -247,7 +247,7 @@ app.ws('/*', { onSocketClose(ws, msgString); } -}).listen(Number.parseInt(process.env.MEDIASOUP_PORT || '9001'), (listenSocket) => { +}).listen(Number.parseInt(process.env.EXPOSED_MEDIASOUP_PORT || '9001'), (listenSocket) => { if (listenSocket) { console.log('listenSocket:' ,listenSocket); console.log('Listening to port 9001'); diff --git a/example.env b/example.env index bb44f7d9..3820ea6e 100644 --- a/example.env +++ b/example.env @@ -29,13 +29,13 @@ LISTEN_IP="123.123.123.123" # If this isnt provided the server will try to get it's own local IP using the node module called "ip" INTERNAL_IP="456.456.456.456" -AUTH_PORT="3333" +EXPOSED_AUTH_PORT="3333" EXPOSED_AUTH_PATH="/auth" -MEDIASOUP_PORT="9001" +EXPOSED_MEDIASOUP_PORT="9001" EXPOSED_MEDIASOUP_PATH="/socket" -FILESERVER_PORT="9002" +EXPOSED_FILESERVER_PORT="9002" EXPOSED_FILESERVER_PATH='/files' # Be aware! The database variables are used on INITIAL STARTUP of the database container only. So change these to appropriate values BEFORE creating the database container. From 9e774d737dee23a3644c86a12313a3e724112650 Mon Sep 17 00:00:00 2001 From: Darkdusk Date: Mon, 13 Apr 2026 11:57:17 +0200 Subject: [PATCH 15/16] Added unique error code and message if error is caused by username already existing --- backend/auth/src/userRoutes.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/auth/src/userRoutes.ts b/backend/auth/src/userRoutes.ts index 104d7ebc..801f3b21 100644 --- a/backend/auth/src/userRoutes.ts +++ b/backend/auth/src/userRoutes.ts @@ -90,6 +90,12 @@ const createUser: RequestHandler = async (req: CreateUserRequest, res) => { res.status(201).send(dbResponse); return; } catch (e) { + //Checking if error is from unique constraint violation on username + if (typeof e === 'object' && e !== null && 'code' in e && (e as { code?: string }).code === '23505') { + res.status(409).send('username already exists'); + return; + } + console.error('database error when creating user'); console.error(e); res.status(501).send(e); From d2498fbc64af8bd4660e510ecbd55da4c9c4e485 Mon Sep 17 00:00:00 2001 From: Darkdusk Date: Mon, 13 Apr 2026 15:02:10 +0200 Subject: [PATCH 16/16] Added message that shows if creating a user was successful or if something went wrong --- app/src/views/admin/AdminUserManagerView.vue | 50 +++++++++++++++++--- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/app/src/views/admin/AdminUserManagerView.vue b/app/src/views/admin/AdminUserManagerView.vue index 10fd4dbc..8cdd6e20 100644 --- a/app/src/views/admin/AdminUserManagerView.vue +++ b/app/src/views/admin/AdminUserManagerView.vue @@ -39,6 +39,9 @@ +
+ {{ responseMessage }} +

Befintliga användare

@@ -92,7 +95,7 @@ class="btn"> edit - @@ -143,6 +146,10 @@ function getClassForRole(role: UserRole) { } } +const responseMessage = ref(); +const responseType = ref<'alert-success' | 'alert-error'>('alert-success'); +const messageTimeoutId = ref(null); + const createdUsername = ref(''); const createdPassword = ref(''); const createdRole = ref('guest'); @@ -209,12 +216,41 @@ onBeforeMount(async () => { console.log(fetchedUsers.value); }); -async function makeCallThenResetList(fetchReq: (...p: any) => Promise) { - await fetchReq(); - editedUserId.value = undefined; - createdUsername.value = ''; - createdPassword.value = ''; - fetchedUsers.value = await getUsers(); +async function makeCallThenResetList( + fetchReq: (...p: any) => Promise, + showMessageBool: boolean = true) { + try { + await fetchReq(); + + if (showMessageBool) { + showMessage(`Användare "${createdUsername.value}" skapad!`, 'alert-success'); + } + createdUsername.value = ''; + createdPassword.value = ''; + createdRole.value = 'guest'; + } catch (e: any) { + if (e.message === 'username already exists') { + showMessage('Användarnamnet finns redan.', 'alert-error'); + } else { + showMessage(`Fel: ${e.message || 'Okänt fel vid skapande av användare'}`, 'alert-error'); + } + } finally { + fetchedUsers.value = await getUsers(); + editedUserId.value = undefined; + } +} + +function showMessage(msg: string, type: 'alert-success' | 'alert-error') { + responseMessage.value = msg; + responseType.value = type; + + if (messageTimeoutId.value) { + clearTimeout(messageTimeoutId.value); + } + + messageTimeoutId.value = setTimeout(() => { + responseMessage.value = ''; + }, 5000); }