Skip to content

Commit

Permalink
Rework on pusher services (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
adrianrudnik authored Nov 29, 2023
1 parent d2cef72 commit b5b81d2
Show file tree
Hide file tree
Showing 83 changed files with 1,629 additions and 677 deletions.
4 changes: 2 additions & 2 deletions frontend/src/components/auth/MyAvatar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<OverlayPanel ref="userPanel" class="w-full md:w-24rem">
<div class="mb-3">
<p class="font-semibold">
{{ username }} [{{ isAdmin ? t('role.admin') : t('role.guest') }}]
{{ displayName }} [{{ isAdmin ? t('role.admin') : t('role.guest') }}]
</p>
<i18n-t keypath="user-avatar.from-ip" tag="p">
<template v-slot:ip>
Expand All @@ -33,7 +33,7 @@ import { useSessionStore } from '@/stores/session'
import { ref } from 'vue'
const { t } = useI18n()
const { username, ip, isGuest, isAdmin } = storeToRefs(useSessionStore())
const { displayName, ip, isGuest, isAdmin } = storeToRefs(useSessionStore())
const userPanel = ref()
Expand Down
32 changes: 0 additions & 32 deletions frontend/src/components/auth/UserAvatar.vue

This file was deleted.

12 changes: 8 additions & 4 deletions frontend/src/components/parts/ActiveUsers.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
<template>
<AvatarGroup v-if="entries.length > 1">
<UserAvatar :user="user" v-for="user in entries.slice(0, visibleMemberCount)" :key="user.id" />
<ClientAvatar
:client="client"
v-for="client in entries.slice(0, visibleMemberCount)"
:key="client.id"
/>
<Avatar
v-if="entries.length > visibleMemberCount"
:label="'+' + (entries.length - visibleMemberCount)"
Expand All @@ -11,14 +15,14 @@
</template>

<script setup lang="ts">
import { useUserStore } from '@/stores/users'
import UserAvatar from '@/components/auth/UserAvatar.vue'
import { useUserClientStore } from '@/stores/users'
import ClientAvatar from '@/components/parts/ClientAvatar.vue'
import Avatar from 'primevue/avatar'
import AvatarGroup from 'primevue/avatargroup'
import { computed } from 'vue'
import { breakpointsPrimeFlex, useBreakpoints } from '@vueuse/core'
const { entries } = useUserStore()
const { entries } = useUserClientStore()
const { sm, md, lg } = useBreakpoints(breakpointsPrimeFlex)
Expand Down
40 changes: 40 additions & 0 deletions frontend/src/components/parts/ClientAvatar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<template>
<Avatar
icon="pi pi-user"
class="mr-2"
:style="{ 'background-color': colorizeUserClient(props.client).color, color: 'white' }"
shape="circle"
v-tooltip.left="tooltip"
/>
</template>

<script setup lang="ts">
import Avatar from 'primevue/avatar'
import type { UserClient } from '@/stores/users'
import { computed } from 'vue'
import { colorizeUserClient } from '@/stores/users'
import { useSessionStore } from '@/stores/session'
import { useI18n } from 'vue-i18n'
import { storeToRefs } from 'pinia'
const props = defineProps<{ client: UserClient }>()
const { t } = useI18n()
const { clientId, userId } = storeToRefs(useSessionStore())
const tooltip = computed(() => {
// Same user, different client
if (props.client.user_id === userId.value && props.client.id !== clientId.value) {
return t('user-avatar.you.different-client')
}
// Same user, same client
if (props.client.id === clientId.value) {
return t('user-avatar.you.same-client')
}
// Someone else's client
const v = props.client.user_display_name
return props.client.ip ? `${v} connecting from ${props.client.ip}` : v
})
</script>
19 changes: 15 additions & 4 deletions frontend/src/components/search/SearchResultFocusDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,42 @@
<Button
outlined
icon="pi pi pi-folder-open"
v-if="dialogRef.data.result.pathFolder"
v-if="isAdmin && dialogRef.data.result.pathFolder"
@click="openLocalPath(dialogRef.data.result.pathFolder)"
:label="t('common.button.open-folder')"
/>

<Button
outlined
icon="pi pi-file-o"
v-if="dialogRef.data.result.pathAbsolute"
v-if="isAdmin && dialogRef.data.result.pathAbsolute"
@click="openLocalPath(dialogRef.data.result.pathAbsolute)"
:label="t('common.button.open-file')"
/>

<Button
outlined
icon="pi pi-heart"
v-if="isGuest && dialogRef.data.result.id"
@click="suggestSearchResult(dialogRef.data.result.id)"
:label="t('search-result-focus-dialog.actions.suggest')"
/>
</div>
</template>

<script setup lang="ts">
import { inject } from 'vue'
import type { Component } from 'vue'
import { inject } from 'vue'
import type { HitFieldset } from '@/plugins/search/result'
import Button from 'primevue/button'
import { openLocalPath } from '@/plugins/api'
import { openLocalPath, suggestSearchResult } from '@/plugins/api'
import { useI18n } from 'vue-i18n'
import { useSessionStore } from '@/stores/session'
const { t } = useI18n()
const { isAdmin, isGuest } = useSessionStore()
const dialogRef = inject('dialogRef') as {
data: {
component: Component
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/locales/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,8 @@ search-query-toolbar:

search-result-focus-dialog:
header: Result focus view
actions:
suggest: Suggest to admin

result-item-component:
button:
Expand Down Expand Up @@ -705,7 +707,9 @@ user-avatar:
from-ip: Connecting from IP {ip}.
login: Login
logout: Logout
you: This is you
you:
same-client: You
different-client: You (different tab)
form:
master-password:
title: Master password
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/plugins/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,10 @@ export async function openLocalPath(path: string) {
body: JSON.stringify({ path })
})
}

export async function suggestSearchResult(id: string) {
return await fetchApi('/api/suggestions', {
method: 'POST',
body: JSON.stringify({ id })
})
}
50 changes: 29 additions & 21 deletions frontend/src/stores/session.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,52 @@
import { defineStore } from 'pinia'
import { computed, ref, nextTick } from 'vue'
import { computed, ref } from 'vue'
import { fetchApi } from '@/plugins/api'
import { useUserStore } from '@/stores/users'
import { GuestRole, useUserClientStore } from '@/stores/users'
import { useFilesStore } from '@/stores/files'
import { websocket } from '@/websocket'
import type { UserRoles } from '@/stores/users'

const DefaultUsername = 'Unknown user'
const DefaultIp = 'Unknown IP'

export const useSessionStore = defineStore('session', () => {
const id = ref<string | undefined>(undefined)
const username = ref(DefaultUsername)
const clientId = ref<string | undefined>(undefined)

const userId = ref<string | undefined>(undefined)
const displayName = ref(DefaultUsername)
const role = ref<'admin' | 'guest'>('guest')
const ip = ref(DefaultIp)
const isAdmin = ref(false)
const isGuest = computed(() => !isAdmin.value)

const { clear: clearUsers } = useUserStore()
const isAdmin = computed(() => role.value === 'admin')
const isGuest = computed(() => role.value === 'guest')

const { clear: clearUsers } = useUserClientStore()
const { clear: clearFiles } = useFilesStore()

const hello = async () => {
try {
const r = await fetchApi<{
username: string
ip: string
role: 'admin' | 'guest'
}>('/api/auth/hello')
username.value = r.username
ip.value = r.ip
isAdmin.value = r.role === 'admin'
id: string
display_name: string
role: UserRoles
}>('/api/auth', {
method: 'POST'
})
userId.value = r.id
displayName.value = r.display_name
role.value = r.role
} catch (e) {
username.value = DefaultUsername
ip.value = DefaultIp
isAdmin.value = false
displayName.value = DefaultUsername
role.value = GuestRole

console.error('Server hello failed', e)
}
}

const goodbye = async () => {
try {
await fetchApi('/api/auth/goodbye', {
method: 'POST'
await fetchApi('/api/auth', {
method: 'DELETE'
})
} catch (e) {
console.error('Server goodbye failed', e)
Expand All @@ -65,8 +71,10 @@ export const useSessionStore = defineStore('session', () => {
hello,
goodbye,
reconsider,
id,
username,
clientId,
userId,
displayName,
role,
ip,
isAdmin,
isGuest
Expand Down
21 changes: 14 additions & 7 deletions frontend/src/stores/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,26 @@ import { defineStore } from 'pinia'
import { setupStore } from '@/stores/base'
import uniqolor from 'uniqolor'

export type UserRoles = 'admin' | 'user'
export const AdminRole = 'admin'
export type AdminRole = typeof AdminRole
export const GuestRole = 'guest'
export type GuestRole = typeof GuestRole

export interface User {
export type UserRoles = AdminRole | GuestRole

export interface UserClient {
id: string
display_name: string
role: UserRoles
ip?: string

user_id: string
user_display_name: string
user_role: UserRoles
}

export const useUserStore = defineStore('users', setupStore<User>())
export const useUserClientStore = defineStore('clients', setupStore<UserClient>())

export const colorizeUser = (
user: User
export const colorizeUserClient = (
user: UserClient
): {
color: string
isLight: boolean
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/views/OtpAuthView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ import Message from 'primevue/message'
import { onMounted, ref } from 'vue'
import { fetchApi } from '@/plugins/api'
import { useRoute, useRouter } from 'vue-router'
import { useSessionStore } from '@/stores/session'
const { t } = useI18n()
const errorMessage = ref<string | null>(null)
const route = useRoute()
const router = useRouter()
const { hello } = useSessionStore()
onMounted(async () => {
try {
Expand All @@ -40,6 +42,8 @@ onMounted(async () => {
})
})
await hello()
await router.replace({ name: 'app' })
} catch (e: any) {
errorMessage.value = e.message
Expand Down
17 changes: 9 additions & 8 deletions frontend/src/websocket/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { PushMessage } from '@/websocket/messages/global'
import { PushMessageType } from '@/websocket/messages/global'
import router from '@/router'
import { createTagFromString, useTagStore } from '@/stores/tags'
import { useUserStore } from '@/stores/users'
import { useUserClientStore } from '@/stores/users'
import { useSessionStore } from '@/stores/session'

export function getWebsocketUrl() {
Expand All @@ -23,6 +23,7 @@ export function getWebsocketUrl() {
export const websocket = useWebSocket(getWebsocketUrl(), {
immediate: false,
autoReconnect: true,

async onMessage(ws, event) {
const payload = JSON.parse(event.data) as PushMessage

Expand Down Expand Up @@ -52,17 +53,17 @@ export const websocket = useWebSocket(getWebsocketUrl(), {
await router.push({ name: payload.target })
break

case PushMessageType.UserCurrent:
case PushMessageType.UserWelcome:
useUserStore().update(payload)
case PushMessageType.UserClient:
case PushMessageType.ClientWelcome:
useUserClientStore().update(payload)
break

case PushMessageType.UserGoodbye:
useUserStore().removeById(payload.id)
case PushMessageType.ClientGoodbye:
useUserClientStore().removeById(payload.id)
break

case PushMessageType.AboutYou:
useSessionStore().id = payload.id
case PushMessageType.ClientId:
useSessionStore().clientId = payload.id
break
}
}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/websocket/messages/about_you.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { PushMessageType } from '@/websocket/messages/global'

export interface AboutYouPushMessage {
type: PushMessageType.AboutYou
export interface ClientIdPushMessage {
type: PushMessageType.ClientId
id: string
}
Loading

0 comments on commit b5b81d2

Please sign in to comment.