Fix Docker builds, TS errors, and deploy config
- API: PHP 8.4, composer install --no-scripts + dump-autoload after COPY - Admin: fix TS (Event.upload_password, unused router, api XSRF, window.open) - Upload: Uppy v5 (hideProgressDetails, headers, destroy), unused watch, api XSRF - Build script: loop over api/admin/upload, push :latest as well as VERSION - Deploy: MySQL from docker.io, platform linux/amd64; README troubleshooting Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,8 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const notification = ref<{ type: 'success' | 'error'; message: string } | null>(null)
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useRoute } from 'vue-router'
|
||||
import AdminLoading from '../components/AdminLoading.vue'
|
||||
import { useEvents } from '../composables/useEvents'
|
||||
import { api } from '../services/api'
|
||||
@@ -8,7 +8,6 @@ import type { Event } from '../types/event'
|
||||
import type { Upload } from '../types/upload'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const eventId = computed(() => Number(route.params.id))
|
||||
const { fetchEvent } = useEvents()
|
||||
|
||||
@@ -37,6 +36,10 @@ function copyUploadUrl() {
|
||||
alert('Upload URL copied to clipboard')
|
||||
}
|
||||
|
||||
function openInNewTab(url: string) {
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
|
||||
async function getDownloadUrl(upload: Upload) {
|
||||
const { data } = await api.get<{ url: string }>(`/admin/uploads/${upload.id}/download-url`)
|
||||
if (data.url) window.open(data.url, '_blank')
|
||||
@@ -108,7 +111,7 @@ function statusBadge(status: string) {
|
||||
<button
|
||||
v-if="u.google_drive_web_link"
|
||||
class="btn btn-outline-primary"
|
||||
@click="window.open(u.google_drive_web_link!, '_blank')"
|
||||
@click="openInNewTab(u.google_drive_web_link!)"
|
||||
>
|
||||
Open in Drive
|
||||
</button>
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import AdminLoading from '../components/AdminLoading.vue'
|
||||
import { useEvents } from '../composables/useEvents'
|
||||
|
||||
const router = useRouter()
|
||||
const { events, loading, pagination, fetchEvents, deleteEvent } = useEvents()
|
||||
|
||||
onMounted(() => fetchEvents())
|
||||
|
||||
@@ -17,8 +17,9 @@ api.interceptors.request.use((config) => {
|
||||
const token = document.cookie
|
||||
.split('; ')
|
||||
.find((row) => row.startsWith('XSRF-TOKEN='))
|
||||
if (token) {
|
||||
config.headers['X-XSRF-TOKEN'] = decodeURIComponent(token.split('=')[1])
|
||||
const value = token?.split('=')[1]
|
||||
if (value) {
|
||||
config.headers['X-XSRF-TOKEN'] = decodeURIComponent(value)
|
||||
}
|
||||
return config
|
||||
})
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface Event {
|
||||
allowed_extensions: string[]
|
||||
require_password: boolean
|
||||
has_password: boolean
|
||||
upload_password?: string | null
|
||||
created_at: string
|
||||
updated_at: string
|
||||
uploads_count?: number
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# event-uploader API – Laravel. Same image used for api and queue (different command).
|
||||
FROM php:8.3-cli-alpine
|
||||
FROM php:8.4-cli-alpine
|
||||
|
||||
# PHP extensions for Laravel + MySQL + Google etc.
|
||||
RUN apk add --no-cache \
|
||||
@@ -25,13 +25,16 @@ ENV COMPOSER_ALLOW_SUPERUSER=1
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Dependencies first (better layer cache)
|
||||
# Dependencies first (better layer cache). --no-scripts so Laravel's post-autoload-dump (artisan) is not run yet.
|
||||
COPY composer.json composer.lock ./
|
||||
RUN composer install --no-dev --optimize-autoloader --no-interaction
|
||||
RUN composer install --no-dev --optimize-autoloader --no-interaction --no-scripts
|
||||
|
||||
# Application
|
||||
COPY . .
|
||||
|
||||
# Regenerate autoloader and run Laravel's post-install scripts (e.g. package:discover) now that artisan exists
|
||||
RUN composer dump-autoload --optimize --no-dev
|
||||
|
||||
# .env and APP_KEY are provided at runtime via compose
|
||||
|
||||
# Writable dirs (runtime will mount or use defaults)
|
||||
|
||||
@@ -43,3 +43,15 @@ After pushing, deploy on the server: set `TAG=1.0.0` in the stack `.env`, then i
|
||||
|
||||
- In Dockge: open the stack → **Pull** (to fetch new images from Gitea) → **Redeploy** (or Stop + Start).
|
||||
- Or on the server: set `TAG=1.0.0` in `.env`, then `docker compose pull && docker compose up -d`.
|
||||
|
||||
## Troubleshooting: "manifest unknown" (e.g. for admin)
|
||||
|
||||
This means the registry has no image for the tag Dockge is using.
|
||||
|
||||
1. **Match the tag** – In the stack `.env`, set `TAG` to the exact tag you pushed (e.g. the git hash from the build output, or `latest`). After running `./scripts/docker-build-push.sh`, the script prints the tag and now also pushes `:latest`, so `TAG=latest` works.
|
||||
2. **Re-push all images** – From project root run `./scripts/docker-build-push.sh` again so api, admin, and upload are all pushed with the same tag.
|
||||
3. **Registry login on the server** – Where Dockge runs, ensure `docker login 10.0.10.205:3000` has been done and that Docker has `10.0.10.205:3000` in `insecure-registries` (if using HTTP).
|
||||
|
||||
### "no matching manifest for linux/amd64" (MySQL)
|
||||
|
||||
The stack pins MySQL to `docker.io/library/mysql:8.0` with `platform: linux/amd64` so the server pulls the correct image from Docker Hub. If your Dockge server is ARM (e.g. Raspberry Pi), change the MySQL service in `docker-compose.yml` to `platform: linux/arm64`.
|
||||
|
||||
@@ -66,7 +66,8 @@ services:
|
||||
- event-uploader
|
||||
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
image: docker.io/library/mysql:8.0
|
||||
platform: linux/amd64
|
||||
ports:
|
||||
- "3004:3306"
|
||||
environment:
|
||||
|
||||
@@ -22,18 +22,21 @@ echo "Building and pushing images with tag: $VERSION"
|
||||
echo "Registry: $REGISTRY, Owner: $OWNER"
|
||||
echo ""
|
||||
|
||||
docker build -t "$REGISTRY/$OWNER/event-uploader-api:$VERSION" "$ROOT/api"
|
||||
docker push "$REGISTRY/$OWNER/event-uploader-api:$VERSION"
|
||||
|
||||
docker build -t "$REGISTRY/$OWNER/event-uploader-admin:$VERSION" "$ROOT/admin"
|
||||
docker push "$REGISTRY/$OWNER/event-uploader-admin:$VERSION"
|
||||
|
||||
docker build -t "$REGISTRY/$OWNER/event-uploader-upload:$VERSION" "$ROOT/upload"
|
||||
docker push "$REGISTRY/$OWNER/event-uploader-upload:$VERSION"
|
||||
for name in api admin upload; do
|
||||
docker build -t "$REGISTRY/$OWNER/event-uploader-$name:$VERSION" "$ROOT/$name"
|
||||
docker push "$REGISTRY/$OWNER/event-uploader-$name:$VERSION"
|
||||
if [ "$VERSION" != "latest" ]; then
|
||||
docker tag "$REGISTRY/$OWNER/event-uploader-$name:$VERSION" "$REGISTRY/$OWNER/event-uploader-$name:latest"
|
||||
docker push "$REGISTRY/$OWNER/event-uploader-$name:latest"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Done. Images pushed:"
|
||||
echo " $REGISTRY/$OWNER/event-uploader-api:$VERSION"
|
||||
echo " $REGISTRY/$OWNER/event-uploader-admin:$VERSION"
|
||||
echo " $REGISTRY/$OWNER/event-uploader-upload:$VERSION"
|
||||
echo "On Dockge server: set TAG=$VERSION in stack .env, then Pull and Redeploy."
|
||||
if [ "$VERSION" != "latest" ]; then
|
||||
echo " (also tagged and pushed as :latest)"
|
||||
fi
|
||||
echo "On Dockge: set TAG=$VERSION (or TAG=latest) in stack .env, then Pull and Redeploy."
|
||||
|
||||
@@ -35,7 +35,7 @@ onMounted(() => {
|
||||
target: uppyContainer.value,
|
||||
inline: true,
|
||||
proudlyDisplayPoweredByUppy: false,
|
||||
showProgressDetails: true,
|
||||
hideProgressDetails: false,
|
||||
height: 360,
|
||||
theme: 'light',
|
||||
})
|
||||
@@ -49,7 +49,7 @@ onMounted(() => {
|
||||
getResponseData() {
|
||||
return {}
|
||||
},
|
||||
getRequestHeaders() {
|
||||
headers: () => {
|
||||
const headers: Record<string, string> = {}
|
||||
const password = getPassword()
|
||||
if (password) {
|
||||
@@ -58,8 +58,9 @@ onMounted(() => {
|
||||
const token = document.cookie
|
||||
.split('; ')
|
||||
.find((row) => row.startsWith('XSRF-TOKEN='))
|
||||
if (token) {
|
||||
headers['X-XSRF-TOKEN'] = decodeURIComponent(token.split('=')[1])
|
||||
const value = token?.split('=')[1]
|
||||
if (value) {
|
||||
headers['X-XSRF-TOKEN'] = decodeURIComponent(value)
|
||||
}
|
||||
return headers
|
||||
},
|
||||
@@ -77,14 +78,14 @@ onMounted(() => {
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (successTimeout) clearTimeout(successTimeout)
|
||||
uppy?.close()
|
||||
uppy?.destroy()
|
||||
uppy = null
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.slug,
|
||||
() => {
|
||||
uppy?.close()
|
||||
uppy?.destroy()
|
||||
uppy = null
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ref, watch, type Ref } from 'vue'
|
||||
import { ref, type Ref } from 'vue'
|
||||
import { api } from '../services/api'
|
||||
import type { PublicEvent } from '../types/event'
|
||||
|
||||
|
||||
@@ -14,8 +14,9 @@ api.interceptors.request.use((config) => {
|
||||
const token = document.cookie
|
||||
.split('; ')
|
||||
.find((row) => row.startsWith('XSRF-TOKEN='))
|
||||
if (token) {
|
||||
config.headers['X-XSRF-TOKEN'] = decodeURIComponent(token.split('=')[1])
|
||||
const value = token?.split('=')[1]
|
||||
if (value) {
|
||||
config.headers['X-XSRF-TOKEN'] = decodeURIComponent(value)
|
||||
}
|
||||
return config
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user