Compare commits

...

3 Commits

Author SHA1 Message Date
Kevin ae0cf61124 fix: add migration step in CI deploy
Build & Deploy / build (push) Successful in 27s
Build & Deploy / docker (push) Successful in 19s
2026-07-07 20:58:15 +02:00
Kevin c4cad4f875 fix: TS errors, serialize calls, unused imports, pre-commit hook
Build & Deploy / build (push) Successful in 32s
Build & Deploy / docker (push) Successful in 1m42s
2026-07-07 20:49:52 +02:00
Kevin 15d3a13d0a fix: cookies HTTP, logout route prefix, header logout method
Build & Deploy / build (push) Failing after 28s
Build & Deploy / docker (push) Has been skipped
2026-07-07 20:35:41 +02:00
22 changed files with 38 additions and 108 deletions
+2 -2
View File
@@ -38,8 +38,8 @@ const routes = {
}, },
'session.destroy': { 'session.destroy': {
methods: ["POST"], methods: ["POST"],
pattern: '/logout', pattern: '/auth/logout',
tokens: [{"old":"/logout","type":0,"val":"logout","end":""}], tokens: [{"old":"/auth/logout","type":0,"val":"auth","end":""},{"old":"/auth/logout","type":0,"val":"logout","end":""}],
types: placeholder as Registry['session.destroy']['types'], types: placeholder as Registry['session.destroy']['types'],
}, },
'computers.index': { 'computers.index': {
+1 -1
View File
@@ -69,7 +69,7 @@ export interface Registry {
} }
'session.destroy': { 'session.destroy': {
methods: ["POST"] methods: ["POST"]
pattern: '/logout' pattern: '/auth/logout'
types: { types: {
body: {} body: {}
paramsTuple: [] paramsTuple: []
+1 -1
View File
@@ -32,10 +32,10 @@ declare module '@adonisjs/inertia/types' {
'widgets/ComputerCard': ExtractProps<(typeof import('../../inertia/pages/widgets/ComputerCard.vue'))['default']> 'widgets/ComputerCard': ExtractProps<(typeof import('../../inertia/pages/widgets/ComputerCard.vue'))['default']>
'widgets/ComputerComponentsTable': ExtractProps<(typeof import('../../inertia/pages/widgets/ComputerComponentsTable.vue'))['default']> 'widgets/ComputerComponentsTable': ExtractProps<(typeof import('../../inertia/pages/widgets/ComputerComponentsTable.vue'))['default']>
'widgets/ComputersViewer': ExtractProps<(typeof import('../../inertia/pages/widgets/ComputersViewer.vue'))['default']> 'widgets/ComputersViewer': ExtractProps<(typeof import('../../inertia/pages/widgets/ComputersViewer.vue'))['default']>
'widgets/CustomSelect': ExtractProps<(typeof import('../../inertia/pages/widgets/CustomSelect.vue'))['default']>
'widgets/DeleteConfirmationModal': ExtractProps<(typeof import('../../inertia/pages/widgets/DeleteConfirmationModal.vue'))['default']> 'widgets/DeleteConfirmationModal': ExtractProps<(typeof import('../../inertia/pages/widgets/DeleteConfirmationModal.vue'))['default']>
'widgets/SimpleGallery': ExtractProps<(typeof import('../../inertia/pages/widgets/SimpleGallery.vue'))['default']> 'widgets/SimpleGallery': ExtractProps<(typeof import('../../inertia/pages/widgets/SimpleGallery.vue'))['default']>
'widgets/TableComponent': ExtractProps<(typeof import('../../inertia/pages/widgets/TableComponent.vue'))['default']> 'widgets/TableComponent': ExtractProps<(typeof import('../../inertia/pages/widgets/TableComponent.vue'))['default']>
'widgets/ValidationError': ExtractProps<(typeof import('../../inertia/pages/widgets/ValidationError.vue'))['default']> 'widgets/ValidationError': ExtractProps<(typeof import('../../inertia/pages/widgets/ValidationError.vue'))['default']>
'widgets/CustomSelect': ExtractProps<(typeof import('../../inertia/pages/widgets/CustomSelect.vue'))['default']>
} }
} }
+2 -1
View File
@@ -51,5 +51,6 @@ jobs:
cd /volume1/docker/pc_builder && cd /volume1/docker/pc_builder &&
echo '${{ secrets.NASPASS }}' | sudo -S /usr/bin/bash setup-db.sh && echo '${{ secrets.NASPASS }}' | sudo -S /usr/bin/bash setup-db.sh &&
echo '${{ secrets.NASPASS }}' | sudo -S /usr/bin/docker compose -f docker-compose.prod.yml pull && echo '${{ secrets.NASPASS }}' | sudo -S /usr/bin/docker compose -f docker-compose.prod.yml pull &&
echo '${{ secrets.NASPASS }}' | sudo -S /usr/bin/docker compose -f docker-compose.prod.yml up -d --remove-orphans echo '${{ secrets.NASPASS }}' | sudo -S /usr/bin/docker compose -f docker-compose.prod.yml up -d --remove-orphans &&
echo '${{ secrets.NASPASS }}' | sudo -S /usr/bin/docker exec pc-builder node ace migration:run --force
" "
+3 -3
View File
@@ -9,7 +9,7 @@ export default class ComponentsController {
async index({ inertia, auth }: HttpContext) { async index({ inertia, auth }: HttpContext) {
const user = await auth.authenticate() const user = await auth.authenticate()
const components = await Component.query().preload('type').where('userId', user.id) const components = await Component.query().preload('type').where('userId', user.id)
return inertia.render('components/index', { components: components.map((c) => c.serialize({ relations: true })) }) return inertia.render('components/index', { components: components.map((c) => c.serialize()) })
} }
async create({ inertia }: HttpContext) { async create({ inertia }: HttpContext) {
@@ -40,7 +40,7 @@ export default class ComponentsController {
.where('userId', user.id) .where('userId', user.id)
.andWhere('id', componentId) .andWhere('id', componentId)
.firstOrFail() .firstOrFail()
return inertia.render('components/show', { component: component.serialize({ relations: true }) }) return inertia.render('components/show', { component: component.serialize() })
} }
async edit({ params, auth, inertia }: HttpContext) { async edit({ params, auth, inertia }: HttpContext) {
@@ -53,7 +53,7 @@ export default class ComponentsController {
.andWhere('id', componentId) .andWhere('id', componentId)
.firstOrFail() .firstOrFail()
return inertia.render('components/edit', { return inertia.render('components/edit', {
component: component.serialize({ relations: true }), component: component.serialize(),
types: types.map((t) => t.serialize()), types: types.map((t) => t.serialize()),
}) })
} }
+4 -4
View File
@@ -19,7 +19,7 @@ export default class ComputersController {
picturesQuery.where('order', 1) picturesQuery.where('order', 1)
}) })
return inertia.render('computers/index', { computers: computers.map((c) => c.serialize({ relations: true })) }) return inertia.render('computers/index', { computers: computers.map((c) => c.serialize()) })
} }
async create({ auth, inertia }: HttpContext) { async create({ auth, inertia }: HttpContext) {
@@ -29,7 +29,7 @@ export default class ComputersController {
.where('userId', user.id) .where('userId', user.id)
.whereNull('computerId') .whereNull('computerId')
.preload('type') .preload('type')
return inertia.render('computers/create', { components: components.map((c) => c.serialize({ relations: true })), states: states.map((s) => s.serialize()) }) return inertia.render('computers/create', { components: components.map((c) => c.serialize()), states: states.map((s) => s.serialize()) })
} }
async store({ auth, request, response }: HttpContext) { async store({ auth, request, response }: HttpContext) {
@@ -60,7 +60,7 @@ export default class ComputersController {
.where('user_id', user.id) .where('user_id', user.id)
.andWhere('id', computerId) .andWhere('id', computerId)
.firstOrFail() .firstOrFail()
return inertia.render('computers/show', { computer: computer.serialize({ relations: true }) }) return inertia.render('computers/show', { computer: computer.serialize() })
} }
async edit({ params, inertia, auth }: HttpContext) { async edit({ params, inertia, auth }: HttpContext) {
@@ -86,7 +86,7 @@ export default class ComputersController {
'id', 'id',
computer.components.map((c) => c.id) computer.components.map((c) => c.id)
) )
return inertia.render('computers/edit', { computer: computer.serialize({ relations: true }), availableComponents: availableComponents.map((c) => c.serialize({ relations: true })), states: states.map((s) => s.serialize()) }) return inertia.render('computers/edit', { computer: computer.serialize(), availableComponents: availableComponents.map((c) => c.serialize()), states: states.map((s) => s.serialize()) })
} }
async update({ params, request, response }: HttpContext) { async update({ params, request, response }: HttpContext) {
+1 -2
View File
@@ -1,4 +1,3 @@
import app from '@adonisjs/core/services/app'
import { defineConfig } from '@adonisjs/core/http' import { defineConfig } from '@adonisjs/core/http'
/** /**
@@ -66,7 +65,7 @@ export const http = defineConfig({
/** /**
* Send cookies only over HTTPS in production. * Send cookies only over HTTPS in production.
*/ */
secure: app.inProduction, secure: false,
/** /**
* Cross-site policy for cookie sending. * Cross-site policy for cookie sending.
+1 -2
View File
@@ -1,5 +1,4 @@
import env from '#start/env' import env from '#start/env'
import app from '@adonisjs/core/services/app'
import { defineConfig, stores } from '@adonisjs/session' import { defineConfig, stores } from '@adonisjs/session'
const sessionConfig = defineConfig({ const sessionConfig = defineConfig({
@@ -43,7 +42,7 @@ const sessionConfig = defineConfig({
/** /**
* Send cookies only over HTTPS in production. * Send cookies only over HTTPS in production.
*/ */
secure: app.inProduction, secure: false,
/** /**
* Cross-site policy for cookie sending. * Cross-site policy for cookie sending.
+2 -2
View File
@@ -415,9 +415,9 @@ export class StreamSchema extends BaseModel {
declare udpForwarding: number declare udpForwarding: number
} }
export class UserSchema extends BaseModel { export class NpmUserSchema extends BaseModel {
static $columns = ['avatar', 'createdOn', 'email', 'id', 'isDeleted', 'isDisabled', 'modifiedOn', 'name', 'nickname', 'roles'] as const static $columns = ['avatar', 'createdOn', 'email', 'id', 'isDeleted', 'isDisabled', 'modifiedOn', 'name', 'nickname', 'roles'] as const
$columns = UserSchema.$columns $columns = NpmUserSchema.$columns
@column() @column()
declare avatar: string declare avatar: string
@column.dateTime() @column.dateTime()
-21
View File
@@ -1,35 +1,14 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Link } from '@adonisjs/inertia/vue' import { Link } from '@adonisjs/inertia/vue'
import { useForm } from '@inertiajs/vue3' import { useForm } from '@inertiajs/vue3'
import { computed } from 'vue'
import EmptyLayout from '~/pages/layout/emptyLayout.vue' import EmptyLayout from '~/pages/layout/emptyLayout.vue'
defineOptions({ layout: EmptyLayout }) defineOptions({ layout: EmptyLayout })
const props = defineProps<{ errors?: Record<string, string> }>()
const vineErrors = computed(() => {
if (props.errors && !Array.isArray(props.errors)) {
return props.errors
}
return {}
})
const authError = computed(() => {
if (props.errors && !Array.isArray(props.errors)) {
return props.errors
}
return null
})
const form = useForm({ const form = useForm({
email: '', email: '',
password: '', password: '',
}) })
function getErrorMessage(field: string): string | null {
return vineErrors.value[field] || null
}
</script> </script>
<template> <template>
+1 -1
View File
@@ -21,7 +21,7 @@ const form = useForm({
<p class="text-sm text-gray-600 mt-2">Join PC Creator today</p> <p class="text-sm text-gray-600 mt-2">Join PC Creator today</p>
</div> </div>
<Form route="new_account.store" #default="{ processing, errors }"> <Form route="new_account.store" #default="{ processing }">
<div class="space-y-4 sm:space-y-6"> <div class="space-y-4 sm:space-y-6">
<div> <div>
<label class="block"> <label class="block">
+2 -15
View File
@@ -1,11 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, reactive } from 'vue' import { reactive } from 'vue'
import { Link } from '@adonisjs/inertia/vue' import { Link } from '@adonisjs/inertia/vue'
import { useForm } from '@inertiajs/vue3' import { useForm } from '@inertiajs/vue3'
import CustomSelect from '~/pages/widgets/CustomSelect.vue' import CustomSelect from '~/pages/widgets/CustomSelect.vue'
const props = defineProps<{ types: any[]; errors?: Record<string, string> }>() defineProps<{ types: any[]; errors?: Record<string, string> }>()
const errors = computed(() => props.errors || {})
const form = reactive({ const form = reactive({
name: '', name: '',
price: 0, price: 0,
@@ -14,18 +13,6 @@ const form = reactive({
quantity: 1, quantity: 1,
}) })
function getErrorMessage(field: string): string | null {
return errors.value[field] || null
}
function formatDateForMySQL(date: string) {
const d = new Date(date)
const year = d.getFullYear()
const month = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
form.purchase_date = `${year}-${month}-${day}`
}
function submitForm() { function submitForm() {
const inertiaForm = useForm(form) const inertiaForm = useForm(form)
inertiaForm.post('/components') inertiaForm.post('/components')
+1 -14
View File
@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, reactive } from 'vue' import { reactive } from 'vue'
import { Link } from '@adonisjs/inertia/vue' import { Link } from '@adonisjs/inertia/vue'
import { usePage } from '@inertiajs/vue3' import { usePage } from '@inertiajs/vue3'
import CustomSelect from '~/pages/widgets/CustomSelect.vue' import CustomSelect from '~/pages/widgets/CustomSelect.vue'
@@ -11,7 +11,6 @@ const { props } = usePage<{
errors?: Record<string, string> errors?: Record<string, string>
}>() }>()
const { component, types, origin } = props const { component, types, origin } = props
const errors = computed(() => props.errors || {})
const form = reactive({ const form = reactive({
id: component.id, id: component.id,
@@ -21,18 +20,6 @@ const form = reactive({
purchaseDate: component.purchaseDate, purchaseDate: component.purchaseDate,
origin: origin, origin: origin,
}) })
function getErrorMessage(field: string): string | null {
return errors.value[field] || null
}
function formatDateForMySQL(date: string) {
const d = new Date(date)
const year = d.getFullYear()
const month = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
form.purchase_date = `${year}-${month}-${day}`
}
</script> </script>
<template> <template>
-5
View File
@@ -9,7 +9,6 @@ const { props } = usePage<{
errors: Record<string, string> errors: Record<string, string>
}>() }>()
const { components, states } = props const { components, states } = props
const errors = computed(() => props.errors || {})
const componentTypeOrder = [ const componentTypeOrder = [
'Processor', 'Processor',
@@ -48,10 +47,6 @@ const form = reactive({
globalScore: 0, globalScore: 0,
componentsId: [] as number[], componentsId: [] as number[],
}) })
function getErrorMessage(field: string): string | null {
return errors.value[field] || null
}
</script> </script>
<template> <template>
+2 -11
View File
@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, reactive } from 'vue' import { reactive } from 'vue'
import { Link } from '@adonisjs/inertia/vue' import { Link } from '@adonisjs/inertia/vue'
import { router, usePage } from '@inertiajs/vue3' import { router, usePage } from '@inertiajs/vue3'
@@ -9,8 +9,7 @@ const { props } = usePage<{
states: any[] states: any[]
errors?: Record<string, string> errors?: Record<string, string>
}>() }>()
const { computer, availableComponents, states } = props const { computer, states } = props
const errors = computed(() => props.errors || {})
const form = reactive({ const form = reactive({
id: computer.id, id: computer.id,
@@ -23,17 +22,9 @@ const form = reactive({
componentsId: computer.components.map((c: any) => c.id), componentsId: computer.components.map((c: any) => c.id),
}) })
function updateComponents(componentsId: number[]) {
form.componentsId = componentsId
}
function deleteComputer(id: number) { function deleteComputer(id: number) {
router.delete(`/computers/${id}`) router.delete(`/computers/${id}`)
} }
function getErrorMessage(field: string): string | null {
return errors.value[field] || null
}
</script> </script>
<template> <template>
+1 -1
View File
@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { Link } from '@adonisjs/inertia/vue' import { Link } from '@adonisjs/inertia/vue'
const { props } = defineProps<{ computer: any }>() const { computer } = defineProps<{ computer: any }>()
</script> </script>
<template> <template>
+1 -2
View File
@@ -1,12 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { useSidebar } from '~/composables/useSidebar'
import { Link } from '@adonisjs/inertia/vue' import { Link } from '@adonisjs/inertia/vue'
import { usePage } from '@inertiajs/vue3' import { usePage } from '@inertiajs/vue3'
import { userProfileIcon } from '~/assets/icons' import { userProfileIcon } from '~/assets/icons'
const dropdownOpen = ref(false) const dropdownOpen = ref(false)
const { isOpen } = useSidebar()
const page = usePage() const page = usePage()
@@ -80,6 +78,7 @@ const user = computed(() => {
<Link <Link
href="/auth/logout" href="/auth/logout"
method="post"
class="flex items-center gap-3 px-4 py-2 text-body-sm font-body-sm text-on-surface hover:bg-surface-container-low transition-colors" class="flex items-center gap-3 px-4 py-2 text-body-sm font-body-sm text-on-surface hover:bg-surface-container-low transition-colors"
> >
<span class="material-symbols-outlined text-[20px]">logout</span> <span class="material-symbols-outlined text-[20px]">logout</span>
+2 -16
View File
@@ -1,6 +1,3 @@
import { PageProps } from '@adonisjs/inertia/types'
import { InertiaAppProps } from '@inertiajs/vue3/types/app'
interface ComponentType { interface ComponentType {
id: number id: number
name: string name: string
@@ -39,13 +36,13 @@ export type Component = {
type: ComponentType type: ComponentType
} }
interface ServerImage { export interface ServerImage {
id: number id: number
link: string link: string
order: number order: number
} }
interface NewImage { export interface NewImage {
file: File file: File
order: number order: number
} }
@@ -63,15 +60,4 @@ export type UIAuthError = {
message: string message: string
} }
interface SharedProps extends PageProps {
auth: {
user: {
id: number
name: string
email: string
}
}
}
export type PageWithSharedProps = InertiaAppProps<SharedProps>
export type Image = ServerImage | NewImage export type Image = ServerImage | NewImage
+6
View File
@@ -1,4 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
type UIAuthError = {
status: number
code: string
message: string
}
defineProps<{ defineProps<{
authError?: UIAuthError authError?: UIAuthError
}>() }>()
+3 -4
View File
@@ -84,8 +84,7 @@
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import { crossIcon } from '~/assets/icons' import { crossIcon } from '~/assets/icons'
import { Navigation, Pagination } from 'vue3-carousel' import { Navigation, Pagination } from 'vue3-carousel'
import type { NewImage, ServerImage } from '../../../app/types/UItypes' import type { Image, NewImage, ServerImage } from '~/pages/types/UITypes'
import type { Image } from '~/pages/types/UITypes'
const props = defineProps<{ const props = defineProps<{
galleryId: string galleryId: string
@@ -134,12 +133,12 @@ function addImage() {
function removeImage(image: Image) { function removeImage(image: Image) {
if ('id' in image) { if ('id' in image) {
const index = serverImages.value.findIndex((img) => img.id === image.id) const index = serverImages.value.findIndex((img: ServerImage) => img.id === image.id)
if (index !== -1) { if (index !== -1) {
serverImages.value.splice(index, 1) serverImages.value.splice(index, 1)
} }
} else { } else {
const index = newImages.value.findIndex((img) => img.file === image.file) const index = newImages.value.findIndex((img: NewImage) => img.file === image.file)
if (index !== -1) { if (index !== -1) {
newImages.value.splice(index, 1) newImages.value.splice(index, 1)
} }
+1
View File
@@ -20,6 +20,7 @@ export default await Env.create(new URL('../', import.meta.url), {
// App // App
APP_KEY: Env.schema.secret(), APP_KEY: Env.schema.secret(),
APP_URL: Env.schema.string(),
// Session // Session
SESSION_DRIVER: Env.schema.enum(['cookie', 'memory', 'database'] as const), SESSION_DRIVER: Env.schema.enum(['cookie', 'memory', 'database'] as const),
+1
View File
@@ -30,6 +30,7 @@ router
.group(() => { .group(() => {
router.post('logout', [controllers.Session, 'destroy']) router.post('logout', [controllers.Session, 'destroy'])
}) })
.prefix('/auth')
.use(middleware.auth()) .use(middleware.auth())
router router