Compare commits

..

24 Commits

Author SHA1 Message Date
cae791ae27 style: Added Logo to Login
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2024-03-24 19:06:42 +01:00
ee3d211af1 style: Added Easter Eggs 2024-03-24 19:01:47 +01:00
779bc7d91f style: fix Action Column
All checks were successful
continuous-integration/drone Build is passing
2024-03-24 14:47:49 +01:00
53628a319e fix: Git Hash #3
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-24 14:36:50 +01:00
ec92f4bd5d fix: Git Hash #2
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-24 14:33:12 +01:00
4c57faef52 fix: Git Hash #2
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-24 14:29:09 +01:00
f9c83d6459 fix: Git Hash
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-24 14:24:55 +01:00
91592b06d7 style: Mobile Styles
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-24 14:16:13 +01:00
5534f00a7d style: Empty String to number
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-24 01:38:56 +01:00
5c0dab3448 feat: Realtime for Votes + Deadlines 2024-03-24 01:36:16 +01:00
b1cb25c823 style: Change Star to Check
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-24 00:28:57 +01:00
dbdc7c1540 fix: Hash in Build #4
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-24 00:17:56 +01:00
4e65112476 fix: Hash in Build #4
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-23 23:51:48 +01:00
b0ea02928a fix: Hash in Build #3
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-23 23:45:56 +01:00
fb64c29015 fix: Hash in Build #2
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-23 23:38:03 +01:00
1887add00d fix: Hash in Build
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-23 23:30:06 +01:00
4bb02faf14 build: Adjust Build Path
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone Build is passing
2024-03-23 22:47:03 +01:00
18388246f8 build: Adjust Build Path
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-23 22:42:14 +01:00
041232bcae feat: Added Footer
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-23 22:26:05 +01:00
3e58347acb style: Changed Icon 2024-03-23 22:21:36 +01:00
73dab4dd6e feat: Changed Vote Icon to Check 2024-03-23 21:28:13 +01:00
c2a2ae0d07 feat: Added Jobs Admin Page 2024-03-23 21:27:57 +01:00
b6f9968a46 feat: Confetti on Deadline Confirm
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-23 19:01:47 +01:00
f497b3af55 chore: Updated .env.example
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-23 18:07:15 +01:00
29 changed files with 319 additions and 33 deletions

View File

@@ -12,6 +12,8 @@ steps:
commands:
- cp -af . /var/www/html/bookstack.octolabs.net
- cd /var/www/html/bookstack.octolabs.net
- git reset --hard
- git clean -fd
- composer install
- pnpm install
- chown -R flycro:www-data /var/www/html/bookstack.octolabs.net
@@ -21,9 +23,11 @@ steps:
- docker compose exec php php artisan migrate --force
- docker compose exec php php artisan optimize
- docker compose exec php php artisan storage:link
- git config --global --add safe.directory /var/www/html/bookstack.octolabs.net
- npx nuxi cleanup
- pnpm run build
- find /var/www/html/bookstack.octolabs.net -type f -exec chmod 664 {} \\;
- find /var/www/html/bookstack.octolabs.net -type d -exec chmod 775 {} \\;
- env HOME=/home/flycro pm2 stop ecosystem.config.cjs
- env HOME=/home/flycro pm2 start ecosystem.config.cjs
- export GIT_HASH=$(git rev-parse --short HEAD)
- env HOME=/home/flycro GIT_HASH=$GIT_HASH pm2 stop ecosystem.config.cjs
- env HOME=/home/flycro GIT_HASH=$GIT_HASH pm2 start ecosystem.config.cjs

View File

@@ -45,7 +45,7 @@ CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_CLIENT=predis
REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Events\Deadline;
use App\Models\Deadline;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class DeadlineCreated implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(public Deadline $deadline)
{
//
}
public function broadcastAs(): string
{
return 'DeadlineCreated';
}
/**
* Get the channels the event should broadcast on.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new PrivateChannel('Deadline'),
];
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Events\Vote;
use App\Models\Vote;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class VoteCreated implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(
public Vote $vote
)
{
//
}
public function broadcastAs(): string
{
return 'VoteCreated';
}
/**
* Get the channels the event should broadcast on.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new PrivateChannel('Vote'),
];
}
}

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
use App\Models\BookRecommendation;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Storage;
class BookRecommendationController extends Controller
@@ -136,4 +137,10 @@ class BookRecommendationController extends Controller
$bookRecommendation->delete();
return response()->json(null, 204);
}
public function fetchCover(Request $request)
{
Artisan::call('book:open-library-fetch-cover-art');
return response()->json(Artisan::output());
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Models;
use App\Events\Deadline\DeadlineCreated;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@@ -16,6 +17,13 @@ class Deadline extends Model
'target_chapter',
];
protected static function booted() :void
{
static::created(static function ($deadline) {
event(new DeadlineCreated($deadline));
});
}
public function bookRecommendation()
{
return $this->belongsTo(BookRecommendation::class);

View File

@@ -2,6 +2,7 @@
namespace App\Models;
use App\Events\Vote\VoteCreated;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@@ -14,6 +15,13 @@ class Vote extends Model
'book_recommendation_id',
];
protected static function booted() :void
{
static::created(static function ($vote) {
broadcast(new VoteCreated($vote))->toOthers();
});
}
public function user()
{
return $this->belongsTo(User::class);

View File

@@ -5,7 +5,10 @@ module.exports = {
port: '3377',
exec_mode: 'cluster',
instances: 'max',
script: './.output/server/index.mjs'
script: './.output/server/index.mjs',
env_production: {
"GIT_HASH": process.env.GIT_HASH,
}
}
]
}

View File

@@ -1,4 +1,6 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
import pkg from './package.json'
export default defineNuxtConfig({
srcDir: 'nuxt/',
@@ -76,6 +78,8 @@ export default defineNuxtConfig({
apiBase: process.env.API_URL,
apiPrefix: '/api/v1',
storageBase: `${process.env.API_URL}/storage/`,
packageVersion: pkg.version,
gitHash: process.env.GIT_HASH,
providers: {
google: {
name: 'Google',

View File

@@ -2,9 +2,12 @@
</script>
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
<UMain>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</UMain>
<Footer />
<UNotifications />
</template>

View File

@@ -0,0 +1,33 @@
<script setup lang="ts">
import party from 'party-js'
const config = useRuntimeConfig()
function confirmDeadline(event) {
party.confetti(event)
}
</script>
<template>
<div>
<UDivider />
<UFooter>
<template #left>
<div class="flex items-center gap-4">
<!-- <OctolabsLogo class="fill-primary rotate-45" width="36" /> -->
<div class="text-xs">
Copyright © {{ new Date().getFullYear() }}
</div>
</div>
</template>
<template #center>
Made with <span class="cursor-pointer transition duration-500 hover:scale-125 hover:animate-pulse" @click="confirmDeadline"></span> by <a class="text-primary hover:text-primary-600 ml-1" href="https://flycro.me" target="_blank">Flycro</a>
</template>
<template #right>
<div class="font-mono text-xs">
{{ config.public.packageVersion }} - {{ config.public.gitHash !== '' ? config.public.gitHash : '0000000' }}
</div>
</template>
</UFooter>
</div>
</template>

View File

@@ -36,7 +36,12 @@ if (authStore.user?.roles.includes('admin')) {
{
label: 'Votes',
to: '/admin/votes',
icon: 'i-heroicons-star',
icon: 'i-heroicons-check-circle',
},
{
label: 'Jobs',
to: '/admin/jobs',
icon: 'i-heroicons-briefcase',
},
],
})

View File

@@ -0,0 +1,31 @@
<script setup lang="ts">
import { twMerge } from 'tailwind-merge'
const props = defineProps<{
width?: string
height?: string
class?: string
}>()
const cssClasses = computed(() => {
return twMerge([
'fill-current',
props.class,
])
})
</script>
<template>
<svg xmlns="http://www.w3.org/2000/svg" :class="cssClasses" :width="props.width" :height="props.height" viewBox="0 0 101.27 145.9">
<g id="Ebene_2" data-name="Ebene 2">
<g id="Ebene_8" data-name="Ebene 8">
<path d="M50.63 0C22.67 0 0 21.46 0 47.93 0 61.85 6.69 76.49 19.35 85a69.67 69.67 0 0 0 21.59 10c6.49 1.62 13.4 1.7 19.38 1.7 28 0 40.95-22.34 40.95-48.81S78.6 0 50.63 0Zm15.18 65.91c-5.46 5.24-13.51 7.59-20.51 5.71a20.29 20.29 0 0 1-14.43-14.47c-1.49-5.43-.48-12.33 2.88-16.9 2.45-3.34 4.76-6 8.51-8a20.24 20.24 0 0 1 15-2 24.4 24.4 0 0 1 6.67 3.14 22.12 22.12 0 0 1 2.48 2.15 23.32 23.32 0 0 1 6.26 14.76c.07 8.44-5.17 13.98-6.86 15.61Z" class="cls-1" />
<path d="M57.8 49.89c.67-2.92-.89-5.08-3.49-6.15-3.13-1.3-7 .07-9.14 2.5A8.35 8.35 0 0 0 45 57.1c4.53 5.9 14 3.39 15.73-3.68 1.79-7.42-6.38-14.55-13.25-10.23-3.17 2-5.39 7.23-4.11 10.8s5.45 4.43 9.14 3.94c4.58-.62 7.51-5.9 5.11-10M19.62 74.51c-4.41 4-7.87 9.7-6.41 15.89 1.59 6.71 7.23 11.22 5.08 18.75-1.83 6.42-8.36 10-10.34 16.38-1.54 5-.45 9.84 3.78 13 3.1 2.31 6.1-2.9 3-5.18-5.27-3.92 2.69-11.66 5-14.57A21.51 21.51 0 0 0 24.72 107a21 21 0 0 0-2.8-12C20.58 92.51 19 90.3 19 87.43c0-3.62 2.32-6.35 4.87-8.68 2.86-2.61-1.39-6.84-4.24-4.24ZM55.6 88c-9.62 2.3-11.4 12.24-7 20 3 5.23 7.65 11.16 5.78 17.61-1.37 4.72-5.26 7.5-5.63 12.71-.27 3.85 5.73 3.83 6 0s3.7-6.47 5-9.88a18.49 18.49 0 0 0 0-11.76c-1.83-6.1-14.25-20.06-2.57-22.86 3.75-.89 2.16-6.68-1.6-5.78ZM78.89 84.18c10 4.89.34 21.37 1.44 29.69.56 4.26 2.72 7.27 5.31 10.55 4.11 5.2 5.53 10.92 2 17-1.93 3.35 3.26 6.38 5.18 3a20 20 0 0 0 1.93-16.37c-2.06-6.19-8.63-9.79-8.63-16.72 0-10.37 9-25.92-4.23-32.35-3.46-1.69-6.51 3.48-3 5.18Z" class="cls-1" />
</g>
</g>
</svg>
</template>
<style scoped>
</style>

View File

@@ -13,7 +13,7 @@ const { refresh: onClick, status } = useFetch<any>(`admin/add-total-votes-all`,
async onResponse({ response }) {
if (response.ok) {
useToast().add({
icon: 'i-heroicons-check-circle-20-solid',
icon: 'i-heroicons-check-circle-solid',
title: 'Es wurden allen Nutzern 2 Votes hinzugefügt.',
color: 'emerald',
})
@@ -25,12 +25,12 @@ const { refresh: onClick, status } = useFetch<any>(`admin/add-total-votes-all`,
</script>
<template>
<UButton icon="i-heroicons-star" solid label="Nutzern Votes hinzufügen" @click="isOpen = true" />
<UButton icon="i-heroicons-check-circle" solid label="Nutzern Votes hinzufügen" @click="isOpen = true" />
<UDashboardModal
v-model="isOpen"
title="Votes hinzufügen"
description="Bist du dir sicher das du jedem Benutzer 2 Votes geben möchtest?"
icon="i-heroicons-star"
icon="i-heroicons-check-circle"
:ui="{
icon: { base: 'text-primary dark:text-primary-400' } as any,
footer: { base: 'ml-16' } as any,

View File

@@ -71,6 +71,18 @@ function resolveStatus(status: string) {
return bookRecommendationStore.statusOptions.find(option => option.value === status)
}
const rows = computed(() => {
// return all bookRecommendationStore.recommendations but add actions
return bookRecommendationStore.recommendations.map((recommendation) => {
return {
...recommendation,
actions: {
class: '!max-w-96',
},
}
})
})
onMounted(() => {
$echo.private(`BookRecommendation`)
.listen('.BookRecommendationUpdated', (e) => {
@@ -83,24 +95,24 @@ onMounted(() => {
bookRecommendationStore.createRecommendationWS(e.bookRecommendation)
})
authStore.socketId = $echo.socketId()
$echo.private(`Vote`)
.listen('.VoteCreated', (e) => {
bookRecommendationStore.createVoteWS(e.vote)
})
})
</script>
<template>
<div>
<NewBookRecommendation />
<UTable :sort="sort" :loading="bookRecommendationStore.fetchRecommendationsStatus === 'pending'" :columns="columns" :rows="bookRecommendationStore.recommendations">
<UTable :ui="{ td: { base: 'max-w-[0] truncate' } }" :sort="sort" :loading="bookRecommendationStore.fetchRecommendationsStatus === 'pending'" :columns="columns" :rows="rows">
<template #created_at-data="{ row }">
<div>{{ dayjs(row.created_at).format('DD.MM.YYYY') }}</div>
</template>
<template #published_at-data="{ row }">
<div>{{ dayjs(row.published_at).format('DD.MM.YYYY') }}</div>
</template>
<template #description-data="{ row }">
<div v-if="row.description">
{{ `${row.description.substring(0, 50)}...` }}
</div>
</template>
<template #votes-data="{ row }">
{{ row.votes.length }}
</template>
@@ -110,7 +122,7 @@ onMounted(() => {
</UBadge>
</template>
<template #actions-data="{ row }">
<div class="flex space-x-2">
<div class="flex max-w-full space-x-2">
<CastVote :row="row" />
<EditBookRecommendation :row="row" />
<DeleteBookRecommendation :row="row" />

View File

@@ -35,7 +35,7 @@ const gray = computed({
<UPopover mode="hover" :popper="{ strategy: 'absolute' }" :ui="{ width: 'w-[156px]' }">
<template #default="{ open }">
<UButton color="gray" variant="ghost" square :class="[open && 'bg-gray-50 dark:bg-gray-800']" aria-label="Color picker">
<UIcon name="i-heroicons-swatch-20-solid" class="text-primary-500 dark:text-primary-400 size-5" />
<UIcon name="i-heroicons-paint-brush-20-solid" class="text-primary-500 dark:text-primary-400 size-5" />
</UButton>
</template>

View File

@@ -17,12 +17,12 @@ const dayjs = useDayjs()
<div class="hidden w-1/5 md:block">
<img :src="$storage(props.book.cover_image)" :alt="props.book.book_name" class="rounded-lg">
</div>
<div class="w-4/5 space-y-4">
<div class="w-full space-y-4 md:w-4/5">
<div class="space-y-2">
<h1 class="font-sans text-3xl font-bold">
{{ props.book.book_name }}
</h1>
<div class="flex justify-between">
<div class="flex flex-col flex-wrap gap-y-2 md:flex-row md:justify-between">
<div>
<UBadge>
{{ props.book.author }}

View File

@@ -4,6 +4,8 @@ import ConfirmUserDeadline from '~/components/modal/ConfirmUserDeadline.vue'
const props = defineProps<{
bookRecommendationId: number
}>()
const { $echo } = useNuxtApp()
const authStore = useAuthStore()
const { refresh: deadlineRefresh, status: deadlineStatus, data: deadlines } = useFetch(() => `book-recommendations/${props.bookRecommendationId}/deadlines`)
@@ -40,6 +42,13 @@ const rows = computed(() => {
}
})
})
onMounted(() => {
$echo.private(`Deadline`)
.listen('.DeadlineCreated', (e) => {
deadlineRefresh()
})
})
</script>
<template>

View File

@@ -32,7 +32,7 @@ const { refresh: onVote, status } = useFetch<any>(`vote`, {
async onResponse({ response }) {
if (response.ok) {
useToast().add({
icon: 'i-heroicons-check-circle-20-solid',
icon: 'i-heroicons-check-circle',
title: 'Abstimmung erfolgreich.',
color: 'emerald',
})
@@ -45,12 +45,12 @@ const { refresh: onVote, status } = useFetch<any>(`vote`, {
</script>
<template>
<UButton v-if="props.row.status === 'PENDING'" class="transition-150 transform-gpu hover:scale-110" icon="i-heroicons-star" size="sm" color="green" variant="solid" square :disabled="authStore.user.total_votes === 0" @click="isOpen = true" />
<UButton v-if="props.row.status === 'PENDING'" class="transition-150 transform-gpu hover:scale-110" icon="i-heroicons-check-circle" size="sm" color="green" variant="solid" square :disabled="authStore.user.total_votes === 0" @click="isOpen = true" />
<UDashboardModal
v-model="isOpen"
title="Für Buch abstimmen"
:description="`Bist du dir sicher das du für die Buchempfehlung &quot;${row.book_name}&quot; abstimmen möchtest?`"
icon="i-heroicons-star"
icon="i-heroicons-check-circle"
:ui="{
icon: { base: 'text-primary-500 dark:text-primary-400' } as any,
footer: { base: 'ml-16' } as any,

View File

@@ -1,4 +1,6 @@
<script setup lang="ts">
import party from 'party-js'
const props = defineProps<{
userDeadlineId: number
}>()
@@ -23,6 +25,11 @@ const { refresh: onConfirmDeadline, status } = useFetch<any>(() => `user-deadlin
}
},
})
function confirmDeadline(event) {
party.confetti(event)
onConfirmDeadline()
}
</script>
<template>
@@ -38,7 +45,7 @@ const { refresh: onConfirmDeadline, status } = useFetch<any>(() => `user-deadlin
}"
>
<template #footer>
<UButton color="primary" label="Abschließen" :loading="status === 'pending'" @click="onConfirmDeadline" />
<UButton color="primary" label="Abschließen" :loading="status === 'pending'" @click="confirmDeadline" />
<UButton color="white" label="Abbrechen" @click="isOpen = false" />
</template>
</UDashboardModal>

40
nuxt/pages/admin/jobs.vue Normal file
View File

@@ -0,0 +1,40 @@
<script setup lang="ts">
definePageMeta({ middleware: ['role-admin'] })
const serverLogs = ref<string>('')
async function runJobs(job: string) {
switch (job) {
case 'fetch_cover':
serverLogs.value = await $fetch('jobs/fetch-cover', { method: 'POST' })
break
}
}
</script>
<template>
<div class="flex flex-col gap-4 md:flex-row">
<UCard class="w-full md:w-1/2">
<template #header>
<h1 class="font-sans text-3xl font-bold">
Server Jobs
</h1>
<UButton class="mt-4" @click="runJobs('fetch_cover')">
Cover Bilder anfragen
</UButton>
</template>
</UCard>
<UCard class="w-full md:w-1/2">
<template #header>
<h1 class="font-sans text-3xl font-bold">
Server Logs
</h1>
<UTextarea v-model="serverLogs" autoresize disabled class="mt-4" />
</template>
</UCard>
</div>
</template>
<style scoped>
</style>

View File

@@ -6,7 +6,7 @@ definePageMeta({ middleware: ['role-admin'] })
<template>
<div>
<UCard class="w-1/2">
<UCard class="w-full md:w-1/2">
<template #header>
<h1 class="font-sans text-3xl font-bold">
Benutzer Aktionen

View File

@@ -65,9 +65,9 @@ async function handleMessage(event: { data: any }): Promise<void> {
<div class="mx-auto flex min-h-screen w-full items-center justify-center">
<UCard class="w-96">
<template #header>
<h1 class="text-center text-2xl font-bold">
Login
</h1>
<div class="text-center text-4xl font-bold">
<Logo />
</div>
</template>
<UForm ref="form" :state="state" class="space-y-4" @submit="onSubmit">

View File

@@ -43,7 +43,6 @@ export default defineNuxtPlugin({
// Conditionally add X-Socket-ID header if socket is connected
if (auth.isLoggedIn && auth.socketId) {
console.log('auth.socketId', auth.socketId)
const socketHeaders = {
'X-Socket-ID': auth.socketId,
}

View File

@@ -123,6 +123,17 @@ export const useBookRecommendationStore = defineStore('bookRecommendations', ()
})
}
const createVoteWS = async (data: Partial<Vote>) => {
const index = recommendations.value.findIndex(r => r.id === data.book_recommendation_id)
// check if vote already exists otherwise add an empty array
if (index !== -1) {
if (!recommendations.value[index]?.votes) {
recommendations.value[index].votes = []
}
recommendations.value[index].votes.push(data)
}
}
function resetRecommendations() {
recommendations.value = []
}
@@ -133,6 +144,7 @@ export const useBookRecommendationStore = defineStore('bookRecommendations', ()
updateRecommendationWS,
deleteRecommendationWS,
createRecommendationWS,
createVoteWS,
statusOptions,
fetchRecommendations,
fetchRecommendationsStatus,

View File

@@ -1,12 +1,13 @@
{
"name": "laravel-nuxt",
"version": "0.0.4",
"private": true,
"type": "module",
"scripts": {
"serve": "cross-env PORT=3000 HOST=127.0.0.1 node .output/server/index.mjs",
"build": "nuxi cleanup && nuxi build",
"build": "GIT_HASH=$(git rev-parse --short HEAD) nuxi build",
"cleanup": "nuxi cleanup",
"dev": "nuxt dev --port=3000 --host=127.0.0.1",
"dev": "GIT_HASH=$(git rev-parse --short HEAD) nuxt dev --port=3000 --host=127.0.0.1",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare",
@@ -35,6 +36,7 @@
},
"dependencies": {
"laravel-echo": "^1.16.0",
"party-js": "^2.2.0",
"pusher-js": "8.4.0-rc2"
}
}

7
pnpm-lock.yaml generated
View File

@@ -11,6 +11,9 @@ dependencies:
laravel-echo:
specifier: ^1.16.0
version: 1.16.0
party-js:
specifier: ^2.2.0
version: 2.2.0
pusher-js:
specifier: 8.4.0-rc2
version: 8.4.0-rc2
@@ -7176,6 +7179,10 @@ packages:
engines: {node: '>= 0.8'}
dev: true
/party-js@2.2.0:
resolution: {integrity: sha512-50hGuALCpvDTrQLPQ1fgUgxKIWAH28ShVkmeK/3zhO0YJyCqkhrZhQEkWPxDYLvbFJ7YAXyROmFEu35gKpZLtQ==}
dev: false
/path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}

View File

@@ -42,6 +42,8 @@ Route::prefix('api/v1')->group(function () {
Route::post('account/update', [AccountController::class, 'update'])->name('account.update');
Route::post('account/password', [AccountController::class, 'password'])->name('account.password');
Route::post('jobs/fetch-cover', [BookRecommendationController::class, 'fetchCover'])->name('jobs.fetch-cover');
Route::middleware(['throttle:uploads'])->group(function () {
Route::post('upload', [UploadController::class, 'image'])->name('upload.image');
});

View File

@@ -9,3 +9,11 @@ Broadcast::channel('App.Models.User.{ulid}', function ($user, $ulid) {
Broadcast::channel('BookRecommendation', function ($user) {
return $user;
});
Broadcast::channel('Vote', function ($user) {
return $user;
});
Broadcast::channel('Deadline', function ($user) {
return $user;
});