feat(web)!: SPA (#5069)

* feat(web): SPA

* chore: remove unnecessary prune

* feat(web): merge with immich-server

* Correct method name

* fix: bugs, docs, workflows, etc.

* chore: keep dockerignore for dev

* chore: remove license

* fix: expose 2283

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
Jason Rasmussen
2023-11-17 23:13:36 -05:00
committed by GitHub
parent 5118d261ab
commit adae5dd758
115 changed files with 730 additions and 1446 deletions
+3 -3
View File
@@ -1,4 +1,4 @@
node_modules/
upload/
dist/
coverage/
.svelte-kit
build/
+2 -36
View File
@@ -1,44 +1,10 @@
# Our Node base image
FROM node:20.8-alpine3.18 as base
FROM node:20.8-alpine3.18
WORKDIR /usr/src/app
EXPOSE 3000
RUN apk add --no-cache setpriv tini
FROM base as builder
RUN chown node:node /usr/src/app
COPY --chown=node:node package*.json ./
RUN npm ci
COPY --chown=node:node . .
EXPOSE 3000
FROM builder AS dev
ENV CHOKIDAR_USEPOLLING=true
EXPOSE 24678
EXPOSE 3000
CMD ["npm", "run", "dev"]
FROM builder AS prod
RUN npm run build
RUN npm prune --omit=dev
FROM base
ENV NODE_ENV=production
WORKDIR /usr/src/app
COPY --from=prod /usr/src/app/node_modules ./node_modules
COPY --from=prod /usr/src/app/build ./build
COPY package.json package-lock.json ./
COPY entrypoint.sh ./
ENTRYPOINT ["tini", "--", "/bin/sh"]
CMD ["entrypoint.sh"]
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2022 Hau Tran
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-11
View File
@@ -1,11 +0,0 @@
#! /bin/sh
# Rebind env vars to PUBLIC_ for svelte
export PUBLIC_IMMICH_SERVER_URL=$IMMICH_SERVER_URL
export PUBLIC_IMMICH_API_URL_EXTERNAL=$IMMICH_API_URL_EXTERNAL
if [ "$(id -u)" -eq 0 ] && [ -n "$PUID" ] && [ -n "$PGID" ]; then
exec setpriv --reuid "$PUID" --regid "$PGID" --clear-groups node /usr/src/app/build/index.js
else
exec node /usr/src/app/build/index.js
fi
+6 -204
View File
@@ -31,7 +31,7 @@
"@babel/preset-typescript": "^7.22.5",
"@faker-js/faker": "^8.0.0",
"@floating-ui/dom": "^1.5.1",
"@sveltejs/adapter-node": "^1.2.0",
"@sveltejs/adapter-static": "^2.0.3",
"@sveltejs/kit": "^1.20.4",
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/svelte": "^4.0.3",
@@ -2989,98 +2989,6 @@
"integrity": "sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg==",
"dev": true
},
"node_modules/@rollup/plugin-commonjs": {
"version": "25.0.4",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.4.tgz",
"integrity": "sha512-L92Vz9WUZXDnlQQl3EwbypJR4+DM2EbsO+/KOcEkP4Mc6Ct453EeDB2uH9lgRwj4w5yflgNpq9pHOiY8aoUXBQ==",
"dev": true,
"dependencies": {
"@rollup/pluginutils": "^5.0.1",
"commondir": "^1.0.1",
"estree-walker": "^2.0.2",
"glob": "^8.0.3",
"is-reference": "1.2.1",
"magic-string": "^0.27.0"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^2.68.0||^3.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/@rollup/plugin-json": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.0.0.tgz",
"integrity": "sha512-i/4C5Jrdr1XUarRhVu27EEwjt4GObltD7c+MkCIpO2QIbojw8MUs+CCTqOphQi3Qtg1FLmYt+l+6YeoIf51J7w==",
"dev": true,
"dependencies": {
"@rollup/pluginutils": "^5.0.1"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^1.20.0||^2.0.0||^3.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/@rollup/plugin-node-resolve": {
"version": "15.2.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.1.tgz",
"integrity": "sha512-nsbUg588+GDSu8/NS8T4UAshO6xeaOfINNuXeVHcKV02LJtoRaM1SiOacClw4kws1SFiNhdLGxlbMY9ga/zs/w==",
"dev": true,
"dependencies": {
"@rollup/pluginutils": "^5.0.1",
"@types/resolve": "1.20.2",
"deepmerge": "^4.2.2",
"is-builtin-module": "^3.2.1",
"is-module": "^1.0.0",
"resolve": "^1.22.1"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^2.78.0||^3.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/@rollup/pluginutils": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.4.tgz",
"integrity": "sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g==",
"dev": true,
"dependencies": {
"@types/estree": "^1.0.0",
"estree-walker": "^2.0.2",
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^1.20.0||^2.0.0||^3.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/@sinclair/typebox": {
"version": "0.27.8",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
@@ -3110,19 +3018,13 @@
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
},
"node_modules/@sveltejs/adapter-node": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-1.3.1.tgz",
"integrity": "sha512-A0VgRQDCDPzdLNoiAbcOxGw4zT1Mc+n1LwT1OmO350R7WxrEqdMUChPPOd1iMfIDWlP4ie6E2d/WQf5es2d4Zw==",
"node_modules/@sveltejs/adapter-static": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-2.0.3.tgz",
"integrity": "sha512-VUqTfXsxYGugCpMqQv1U0LIdbR3S5nBkMMDmpjGVJyM6Q2jHVMFtdWJCkeHMySc6mZxJ+0eZK3T7IgmUCDrcUQ==",
"dev": true,
"dependencies": {
"@rollup/plugin-commonjs": "^25.0.0",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^15.0.1",
"rollup": "^3.7.0"
},
"peerDependencies": {
"@sveltejs/kit": "^1.0.0"
"@sveltejs/kit": "^1.5.0"
}
},
"node_modules/@sveltejs/kit": {
@@ -3678,12 +3580,6 @@
"integrity": "sha512-I469DU0UXNC1aHepwirWhu9YKg5fkxohZD95Ey/5A7lovC+Siu+MCLffva87lnfThaOrw9Vb1DUN5t55oULAAw==",
"dev": true
},
"node_modules/@types/resolve": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
"dev": true
},
"node_modules/@types/semver": {
"version": "7.5.3",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz",
@@ -4639,18 +4535,6 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true
},
"node_modules/builtin-modules": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
"integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
"dev": true,
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/bytewise": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/bytewise/-/bytewise-1.1.0.tgz",
@@ -4910,12 +4794,6 @@
"node": ">= 6"
}
},
"node_modules/commondir": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
"dev": true
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -6013,12 +5891,6 @@
"node": ">=4.0"
}
},
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true
},
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -6386,25 +6258,6 @@
"resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
"integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA=="
},
"node_modules/glob": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
"integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^5.0.1",
"once": "^1.3.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -6417,27 +6270,6 @@
"node": ">=10.13.0"
}
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/glob/node_modules/minimatch": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/global-prefix": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz",
@@ -6921,21 +6753,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-builtin-module": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
"integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==",
"dev": true,
"dependencies": {
"builtin-modules": "^3.3.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@@ -7031,12 +6848,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-module": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
"dev": true
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@@ -7087,15 +6898,6 @@
"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
"dev": true
},
"node_modules/is-reference": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
"integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
"dev": true,
"dependencies": {
"@types/estree": "*"
}
},
"node_modules/is-regex": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
+1 -1
View File
@@ -24,7 +24,7 @@
"@babel/preset-typescript": "^7.22.5",
"@faker-js/faker": "^8.0.0",
"@floating-ui/dom": "^1.5.1",
"@sveltejs/adapter-node": "^1.2.0",
"@sveltejs/adapter-static": "^2.0.3",
"@sveltejs/kit": "^1.20.4",
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/svelte": "^4.0.3",
+1 -1
View File
@@ -26,7 +26,7 @@ import { BASE_PATH } from './open-api/base';
import { DUMMY_BASE_URL, toPathString } from './open-api/common';
import type { ApiParams } from './types';
export class ImmichApi {
class ImmichApi {
public activityApi: ActivityApi;
public albumApi: AlbumApi;
public libraryApi: LibraryApi;
-5
View File
@@ -3,11 +3,6 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare namespace App {
interface Locals {
user?: import('@api').UserResponseDto;
api: import('@api').ImmichApi;
}
interface PageData {
meta: {
title: string;
+1
View File
@@ -13,6 +13,7 @@
document.documentElement.classList.remove('dark');
}
</script>
<link rel="stylesheet" href="/custom.css" />
</head>
<body class="bg-immich-bg dark:bg-immich-dark-bg">
+40
View File
@@ -0,0 +1,40 @@
import type { HandleClientError } from '@sveltejs/kit';
import type { AxiosError, AxiosResponse } from 'axios';
const LOG_PREFIX = '[hooks.client.ts]';
const DEFAULT_MESSAGE = 'Hmm, not sure about that. Check the logs or open a ticket?';
const parseError = (error: unknown) => {
const httpError = error as AxiosError;
const request = httpError?.request as Request & { path: string };
const response = httpError?.response as AxiosResponse<{
message: string;
statusCode: number;
error: string;
}>;
let code = response?.data?.statusCode || response?.status || httpError.code || '500';
if (response) {
code += ` - ${response.data?.error || response.statusText}`;
}
if (request && response) {
console.log({
status: response.status,
url: `${request.method} ${request.path}`,
response: response.data || 'No data',
});
}
return {
message: response?.data?.message || httpError?.message || DEFAULT_MESSAGE,
code,
stack: httpError?.stack,
};
};
export const handleError: HandleClientError = ({ error }) => {
const result = parseError(error);
console.error(`${LOG_PREFIX}:handleError ${result.message}`);
return result;
};
-77
View File
@@ -1,77 +0,0 @@
import { env } from '$env/dynamic/public';
import type { Handle, HandleServerError } from '@sveltejs/kit';
import type { AxiosError, AxiosResponse } from 'axios';
import { ImmichApi } from './api/api';
const LOG_PREFIX = '[hooks.server.ts]';
export const handle = (async ({ event, resolve }) => {
const basePath = env.PUBLIC_IMMICH_SERVER_URL || 'http://immich-server:3001';
const accessToken = event.cookies.get('immich_access_token');
const api = new ImmichApi({ basePath, accessToken });
// API instance that should be used for all server-side requests.
event.locals.api = api;
if (accessToken) {
try {
const { data: user } = await api.userApi.getMyUserInfo();
event.locals.user = user;
} catch (err) {
console.log(`${LOG_PREFIX} Unable to get my user`, parseError(err));
const apiError = err as AxiosError;
// Ignore 401 unauthorized errors and log all others.
if (apiError.response?.status && apiError.response?.status !== 401) {
console.error(`${LOG_PREFIX}:handle`, err);
} else if (!apiError.response?.status) {
console.error(`${LOG_PREFIX}:handle`, apiError?.message);
}
}
}
const res = await resolve(event);
// The link header can grow quite big and has caused issues with our nginx
// proxy returning a 502 Bad Gateway error. Therefore the header gets deleted.
res.headers.delete('Link');
return res;
}) satisfies Handle;
const DEFAULT_MESSAGE = 'Hmm, not sure about that. Check the logs or open a ticket?';
const parseError = (error: unknown) => {
const httpError = error as AxiosError;
const request = httpError?.request as Request & { path: string };
const response = httpError?.response as AxiosResponse<{
message: string;
statusCode: number;
error: string;
}>;
let code = response?.data?.statusCode || response?.status || httpError.code || '500';
if (response) {
code += ` - ${response.data?.error || response.statusText}`;
}
if (request && response) {
console.log({
status: response.status,
url: `${request.method} ${request.path}`,
response: response.data || 'No data',
});
}
return {
message: response?.data?.message || httpError?.message || DEFAULT_MESSAGE,
code,
stack: httpError?.stack,
};
};
export const handleError: HandleServerError = ({ error }) => {
const result = parseError(error);
console.error(`${LOG_PREFIX}:handleError ${result.message}`);
return result;
};
@@ -26,9 +26,6 @@
const logOut = async () => {
const { data } = await api.authenticationApi.logout();
await fetch('/auth/logout', { method: 'POST' });
goto(data.redirectUri || '/auth/login?autoLaunch=0');
};
</script>
+34
View File
@@ -0,0 +1,34 @@
import { api } from '@api';
import { redirect } from '@sveltejs/kit';
import { AppRoute } from '../constants';
export interface AuthOptions {
admin?: true;
}
export const getAuthUser = async () => {
try {
const { data: user } = await api.userApi.getMyUserInfo();
return user;
} catch {
return null;
}
};
// TODO: re-use already loaded user (once) instead of fetching on each page navigation
export const authenticate = async (options?: AuthOptions) => {
options = options || {};
const user = await getAuthUser();
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
}
if (options.admin && !user.isAdmin) {
throw redirect(302, AppRoute.PHOTOS);
}
return user;
};
export const isLoggedIn = async () => getAuthUser().then((user) => !!user);
@@ -1,23 +0,0 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load = (async ({ locals: { api, user } }) => {
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
}
try {
const { data: albums } = await api.albumApi.getAllAlbums();
return {
user: user,
albums: albums,
meta: {
title: 'Albums',
},
};
} catch (e) {
throw redirect(302, AppRoute.AUTH_LOGIN);
}
}) satisfies PageServerLoad;
+16
View File
@@ -0,0 +1,16 @@
import { authenticate } from '$lib/utils/auth';
import { api } from '@api';
import type { PageLoad } from './$types';
export const load = (async () => {
const user = await authenticate();
const { data: albums } = await api.albumApi.getAllAlbums();
return {
user,
albums,
meta: {
title: 'Albums',
},
};
}) satisfies PageLoad;
@@ -1,23 +0,0 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load = (async ({ params, locals: { api, user } }) => {
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
}
try {
const { data: album } = await api.albumApi.getAlbumInfo({ id: params.albumId, withoutAssets: true });
return {
album,
user,
meta: {
title: album.albumName,
},
};
} catch (e) {
throw redirect(302, AppRoute.ALBUMS);
}
}) satisfies PageServerLoad;
@@ -0,0 +1,16 @@
import { authenticate } from '$lib/utils/auth';
import { api } from '@api';
import type { PageLoad } from './$types';
export const load = (async ({ params }) => {
const user = await authenticate();
const { data: album } = await api.albumApi.getAlbumInfo({ id: params.albumId, withoutAssets: true });
return {
album,
user,
meta: {
title: album.albumName,
},
};
}) satisfies PageLoad;
@@ -1,15 +1,9 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types';
export const prerender = false;
export const load: PageLoad = async ({ params, parent }) => {
const { user } = await parent();
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
}
const albumId = params['albumId'];
export const load: PageLoad = async ({ params }) => {
const albumId = params.albumId;
if (albumId) {
throw redirect(302, `${AppRoute.ALBUMS}/${albumId}`);
@@ -1,16 +0,0 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load = (async ({ locals: { user } }) => {
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
}
return {
user,
meta: {
title: 'Archive',
},
};
}) satisfies PageServerLoad;
+13
View File
@@ -0,0 +1,13 @@
import { authenticate } from '$lib/utils/auth';
import type { PageLoad } from './$types';
export const load = (async () => {
const user = await authenticate();
return {
user,
meta: {
title: 'Archive',
},
};
}) satisfies PageLoad;
@@ -1,13 +1,7 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types';
export const prerender = false;
export const load: PageLoad = async ({ parent }) => {
const { user } = await parent();
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
}
export const load: PageLoad = async () => {
throw redirect(302, AppRoute.ARCHIVE);
};
@@ -1,21 +0,0 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load = (async ({ locals, parent }) => {
const { user } = await parent();
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
}
const { data: items } = await locals.api.searchApi.getExploreData();
const { data: response } = await locals.api.personApi.getAllPeople({ withHidden: false });
return {
user,
items,
response,
meta: {
title: 'Explore',
},
};
}) satisfies PageServerLoad;
+17
View File
@@ -0,0 +1,17 @@
import { authenticate } from '$lib/utils/auth';
import { api } from '@api';
import type { PageLoad } from './$types';
export const load = (async () => {
const user = await authenticate();
const { data: items } = await api.searchApi.getExploreData();
const { data: response } = await api.personApi.getAllPeople({ withHidden: false });
return {
user,
items,
response,
meta: {
title: 'Explore',
},
};
}) satisfies PageLoad;
@@ -1,16 +0,0 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load = (async ({ locals: { user } }) => {
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
}
return {
user,
meta: {
title: 'Favorites',
},
};
}) satisfies PageServerLoad;
+12
View File
@@ -0,0 +1,12 @@
import { authenticate } from '$lib/utils/auth';
import type { PageLoad } from './$types';
export const load = (async () => {
const user = await authenticate();
return {
user,
meta: {
title: 'Favorites',
},
};
}) satisfies PageLoad;
@@ -1,15 +0,0 @@
import { redirect } from '@sveltejs/kit';
export const prerender = false;
import { AppRoute } from '$lib/constants';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ parent }) => {
const { user } = await parent();
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
} else {
throw redirect(302, AppRoute.FAVORITES);
}
};
@@ -0,0 +1,7 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types';
export const load: PageLoad = async () => {
throw redirect(302, AppRoute.FAVORITES);
};
-16
View File
@@ -1,16 +0,0 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load = (async ({ locals: { user } }) => {
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
}
return {
user,
meta: {
title: 'Map',
},
};
}) satisfies PageServerLoad;
+12
View File
@@ -0,0 +1,12 @@
import { authenticate } from '$lib/utils/auth';
import type { PageLoad } from './$types';
export const load = (async () => {
const user = await authenticate();
return {
user,
meta: {
title: 'Map',
},
};
}) satisfies PageLoad;
@@ -1,16 +0,0 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load = (async ({ locals: { user } }) => {
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
}
return {
user,
meta: {
title: 'Memory',
},
};
}) satisfies PageServerLoad;
+12
View File
@@ -0,0 +1,12 @@
import { authenticate } from '$lib/utils/auth';
import type { PageLoad } from './$types';
export const load = (async () => {
const user = await authenticate();
return {
user,
meta: {
title: 'Memory',
},
};
}) satisfies PageLoad;
@@ -1,15 +0,0 @@
import { redirect } from '@sveltejs/kit';
export const prerender = false;
import { AppRoute } from '$lib/constants';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ parent }) => {
const { user } = await parent();
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
} else {
throw redirect(302, AppRoute.MEMORY);
}
};
@@ -0,0 +1,9 @@
import { AppRoute } from '$lib/constants';
import { authenticate } from '$lib/utils/auth';
import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types';
export const load = (async () => {
await authenticate();
throw redirect(302, AppRoute.MEMORY);
}) satisfies PageLoad;
@@ -1,15 +0,0 @@
import { redirect } from '@sveltejs/kit';
export const prerender = false;
import { AppRoute } from '$lib/constants';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ parent }) => {
const { user } = await parent();
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
} else {
throw redirect(302, AppRoute.MEMORY);
}
};
@@ -0,0 +1,7 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types';
export const load = (async () => {
throw redirect(302, AppRoute.PHOTOS);
}) satisfies PageLoad;
@@ -1,21 +0,0 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params, parent, locals: { api } }) => {
const { user } = await parent();
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
}
const { data: partner } = await api.userApi.getUserById({ id: params['userId'] });
return {
user,
partner,
meta: {
title: 'Partner',
},
};
};
@@ -0,0 +1,17 @@
import { authenticate } from '$lib/utils/auth';
import { api } from '@api';
import type { PageLoad } from './$types';
export const load = (async ({ params }) => {
const user = await authenticate();
const { data: partner } = await api.userApi.getUserById({ id: params.userId });
return {
user,
partner,
meta: {
title: 'Partner',
},
};
}) satisfies PageLoad;
@@ -1,19 +0,0 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load = (async ({ locals, parent }) => {
const { user } = await parent();
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
}
const { data: people } = await locals.api.personApi.getAllPeople({ withHidden: true });
return {
user,
people,
meta: {
title: 'People',
},
};
}) satisfies PageServerLoad;
+16
View File
@@ -0,0 +1,16 @@
import { authenticate } from '$lib/utils/auth';
import { api } from '@api';
import type { PageLoad } from './$types';
export const load = (async () => {
const user = await authenticate();
const { data: people } = await api.personApi.getAllPeople({ withHidden: true });
return {
user,
people,
meta: {
title: 'People',
},
};
}) satisfies PageLoad;
@@ -1,22 +0,0 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load = (async ({ locals, parent, params }) => {
const { user } = await parent();
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
}
const { data: person } = await locals.api.personApi.getPerson({ id: params.personId });
const { data: statistics } = await locals.api.personApi.getPersonStatistics({ id: params.personId });
return {
user,
person,
statistics,
meta: {
title: person.name || 'Person',
},
};
}) satisfies PageServerLoad;
@@ -0,0 +1,19 @@
import { authenticate } from '$lib/utils/auth';
import { api } from '@api';
import type { PageLoad } from './$types';
export const load = (async ({ params }) => {
const user = await authenticate();
const { data: person } = await api.personApi.getPerson({ id: params.personId });
const { data: statistics } = await api.personApi.getPersonStatistics({ id: params.personId });
return {
user,
person,
statistics,
meta: {
title: person.name || 'Person',
},
};
}) satisfies PageLoad;
@@ -1,14 +1,7 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types';
export const prerender = false;
export const load: PageLoad = async ({ params, parent }) => {
const { user } = await parent();
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
}
const personId = params['personId'];
throw redirect(302, `${AppRoute.PEOPLE}/${personId}`);
};
export const load = (async ({ params }) => {
throw redirect(302, `${AppRoute.PEOPLE}/${params.personId}`);
}) satisfies PageLoad;
@@ -1,16 +0,0 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load = (async ({ locals: { user } }) => {
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
}
return {
user,
meta: {
title: 'Photos',
},
};
}) satisfies PageServerLoad;
+12
View File
@@ -0,0 +1,12 @@
import { authenticate } from '$lib/utils/auth';
import type { PageLoad } from './$types';
export const load = (async () => {
const user = await authenticate();
return {
user,
meta: {
title: 'Photos',
},
};
}) satisfies PageLoad;
@@ -1,15 +0,0 @@
import { redirect } from '@sveltejs/kit';
export const prerender = false;
import { AppRoute } from '$lib/constants';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ parent }) => {
const { user } = await parent();
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
} else {
throw redirect(302, AppRoute.PHOTOS);
}
};
@@ -0,0 +1,7 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types';
export const load = (async () => {
throw redirect(302, AppRoute.PHOTOS);
}) satisfies PageLoad;
@@ -1,23 +0,0 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load = (async ({ locals, parent, url }) => {
const { user } = await parent();
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
}
const term = url.searchParams.get('q') || url.searchParams.get('query') || undefined;
const { data: results } = await locals.api.searchApi.search({}, { params: url.searchParams });
return {
user,
term,
results,
meta: {
title: 'Search',
},
};
}) satisfies PageServerLoad;
+20
View File
@@ -0,0 +1,20 @@
import { authenticate } from '$lib/utils/auth';
import { api } from '@api';
import type { PageLoad } from './$types';
export const load = (async () => {
const user = await authenticate();
const url = new URL(location.href);
const term = url.searchParams.get('q') || url.searchParams.get('query') || undefined;
const { data: results } = await api.searchApi.search({}, { params: url.searchParams });
return {
user,
term,
results,
meta: {
title: 'Search',
},
};
}) satisfies PageLoad;
@@ -1,13 +1,7 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types';
export const prerender = false;
export const load: PageLoad = async ({ parent }) => {
const { user } = await parent();
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
}
export const load = (async () => {
throw redirect(302, AppRoute.SEARCH);
};
}) satisfies PageLoad;
@@ -1,31 +1,32 @@
import featurePanelUrl from '$lib/assets/feature-panel.png';
import { api as clientApi, ThumbnailFormat } from '@api';
import { getAuthUser } from '$lib/utils/auth';
import { api, ThumbnailFormat } from '@api';
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import type { AxiosError } from 'axios';
import type { PageLoad } from './$types';
export const load = (async ({ params, locals: { api }, cookies }) => {
export const load = (async ({ params }) => {
const { key } = params;
const token = cookies.get('immich_shared_link_token');
const user = await getAuthUser();
try {
const { data: sharedLink } = await api.sharedLinkApi.getMySharedLink({ key, token });
const { data: sharedLink } = await api.sharedLinkApi.getMySharedLink({ key });
const assetCount = sharedLink.assets.length;
const assetId = sharedLink.album?.albumThumbnailAssetId || sharedLink.assets[0]?.id;
return {
user,
sharedLink,
meta: {
title: sharedLink.album ? sharedLink.album.albumName : 'Public Share',
description: sharedLink.description || `${assetCount} shared photos & videos.`,
imageUrl: assetId
? clientApi.getAssetThumbnailUrl(assetId, ThumbnailFormat.Webp, sharedLink.key)
: featurePanelUrl,
imageUrl: assetId ? api.getAssetThumbnailUrl(assetId, ThumbnailFormat.Webp, sharedLink.key) : featurePanelUrl,
},
};
} catch (e) {
// handle unauthorized error
// TODO this doesn't allow for 404 shared links anymore
if ((e as AxiosError).response?.status === 401) {
return {
passwordRequired: true,
@@ -40,4 +41,4 @@ export const load = (async ({ params, locals: { api }, cookies }) => {
message: 'Invalid shared link',
});
}
}) satisfies PageServerLoad;
}) satisfies PageLoad;
@@ -1,19 +0,0 @@
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load = (async ({ params, locals: { api } }) => {
const { key, assetId } = params;
const { data: asset } = await api.assetApi.getAssetById({ id: assetId, key });
if (!asset) {
throw error(404, 'Asset not found');
}
return {
asset,
key,
meta: {
title: 'Public Share',
},
};
}) satisfies PageServerLoad;
@@ -0,0 +1,15 @@
import { api } from '@api';
import type { PageLoad } from './$types';
export const load = (async ({ params }) => {
const { key, assetId } = params;
const { data: asset } = await api.assetApi.getAssetById({ id: assetId, key });
return {
asset,
key,
meta: {
title: 'Public Share',
},
};
}) satisfies PageLoad;
@@ -1,26 +0,0 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load = (async ({ locals: { api, user } }) => {
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
}
try {
const { data: sharedAlbums } = await api.albumApi.getAllAlbums({ shared: true });
const { data: partners } = await api.partnerApi.getPartners({ direction: 'shared-with' });
return {
user,
sharedAlbums,
partners,
meta: {
title: 'Sharing',
},
};
} catch (e) {
console.log(e);
throw redirect(302, AppRoute.AUTH_LOGIN);
}
}) satisfies PageServerLoad;
+18
View File
@@ -0,0 +1,18 @@
import { authenticate } from '$lib/utils/auth';
import { api } from '@api';
import type { PageLoad } from './$types';
export const load = (async () => {
const user = await authenticate();
const { data: sharedAlbums } = await api.albumApi.getAllAlbums({ shared: true });
const { data: partners } = await api.partnerApi.getPartners({ direction: 'shared-with' });
return {
user,
sharedAlbums,
partners,
meta: {
title: 'Sharing',
},
};
}) satisfies PageLoad;
@@ -1,16 +0,0 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load = (async ({ locals: { user } }) => {
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
}
return {
user,
meta: {
title: 'Shared Links',
},
};
}) satisfies PageServerLoad;
@@ -0,0 +1,12 @@
import { authenticate } from '$lib/utils/auth';
import type { PageLoad } from './$types';
export const load = (async () => {
const user = await authenticate();
return {
user,
meta: {
title: 'Shared Links',
},
};
}) satisfies PageLoad;
@@ -1,16 +0,0 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load = (async ({ locals: { user } }) => {
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
}
return {
user,
meta: {
title: 'Trash',
},
};
}) satisfies PageServerLoad;
+12
View File
@@ -0,0 +1,12 @@
import { authenticate } from '$lib/utils/auth';
import type { PageLoad } from './$types';
export const load = (async () => {
const user = await authenticate();
return {
user,
meta: {
title: 'Trash',
},
};
}) satisfies PageLoad;
@@ -1,13 +1,7 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types';
export const prerender = false;
export const load: PageLoad = async ({ parent }) => {
const { user } = await parent();
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
}
export const load = (async () => {
throw redirect(302, AppRoute.TRASH);
};
}) satisfies PageLoad;
@@ -1,22 +0,0 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load = (async ({ parent, locals }) => {
const { user } = await parent();
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
}
const { data: keys } = await locals.api.keyApi.getApiKeys();
const { data: devices } = await locals.api.authenticationApi.getAuthDevices();
return {
user,
keys,
devices,
meta: {
title: 'Settings',
},
};
}) satisfies PageServerLoad;
@@ -0,0 +1,19 @@
import { authenticate } from '$lib/utils/auth';
import { api } from '@api';
import type { PageLoad } from './$types';
export const load = (async () => {
const user = await authenticate();
const { data: keys } = await api.keyApi.getApiKeys();
const { data: devices } = await api.authenticationApi.getAuthDevices();
return {
user,
keys,
devices,
meta: {
title: 'Settings',
},
};
}) satisfies PageLoad;
-5
View File
@@ -1,5 +0,0 @@
import type { LayoutServerLoad } from './$types';
export const load = (async ({ locals: { user } }) => {
return { user };
}) satisfies LayoutServerLoad;
+6 -4
View File
@@ -24,7 +24,10 @@
export let data: LayoutData;
let albumId: string | undefined;
if ($page.route.id?.startsWith('/(user)/share/[key]')) {
const isSharedLinkRoute = (route: string | null) => route?.startsWith('/(user)/share/[key]');
const isAuthRoute = (route?: string) => route?.startsWith('/auth');
if (isSharedLinkRoute($page.route?.id)) {
api.setKey($page.params.key);
}
@@ -32,11 +35,11 @@
const fromRoute = from?.route?.id || '';
const toRoute = to?.route?.id || '';
if (fromRoute.startsWith('/auth') && !toRoute.startsWith('/auth')) {
if (isAuthRoute(fromRoute) && !isAuthRoute(toRoute)) {
openWebsocketConnection();
}
if (!fromRoute.startsWith('/auth') && toRoute.startsWith('/auth')) {
if (!isAuthRoute(fromRoute) && isAuthRoute(toRoute)) {
closeWebsocketConnection();
}
@@ -80,7 +83,6 @@
<svelte:head>
<title>{$page.data.meta?.title || 'Web'} - Immich</title>
<link rel="manifest" href="/manifest.json" />
<link rel="stylesheet" href="/custom.css" />
<meta name="theme-color" content="currentColor" />
<FaviconHeader />
<AppleHeader />
+25
View File
@@ -0,0 +1,25 @@
import { api } from '../api';
import type { LayoutLoad } from './$types';
const getUser = async () => {
try {
const { data: user } = await api.userApi.getMyUserInfo();
return user;
} catch {
return null;
}
};
export const ssr = false;
export const csr = true;
export const load = (async () => {
const user = await getUser();
return {
user,
meta: {
title: 'Immich',
},
};
}) satisfies LayoutLoad;
@@ -1,17 +1,19 @@
export const prerender = false;
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { api } from '../api';
import { isLoggedIn } from '../lib/utils/auth';
import type { PageLoad } from './$types';
export const load = (async ({ parent, locals: { api } }) => {
const { user } = await parent();
if (user) {
export const ssr = false;
export const csr = true;
export const load = (async () => {
const authenticated = await isLoggedIn();
if (authenticated) {
throw redirect(302, AppRoute.PHOTOS);
}
const { data } = await api.serverInfoApi.getServerConfig();
if (data.isInitialized) {
// Redirect to login page if there exists an admin account (i.e. server is initialized)
throw redirect(302, AppRoute.AUTH_LOGIN);
@@ -23,4 +25,4 @@ export const load = (async ({ parent, locals: { api } }) => {
description: 'Immich Web Interface',
},
};
}) satisfies PageServerLoad;
}) satisfies PageLoad;
@@ -1,11 +0,0 @@
import { json } from '@sveltejs/kit';
const endpoint = process.env.IMMICH_API_URL_EXTERNAL || '/api';
export const GET = async () => {
return json({
api: {
endpoint,
},
});
};
-15
View File
@@ -1,15 +0,0 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ parent }) => {
const { user } = await parent();
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
} else if (!user.isAdmin) {
throw redirect(302, AppRoute.PHOTOS);
}
throw redirect(302, AppRoute.ADMIN_USER_MANAGEMENT);
};
+7
View File
@@ -0,0 +1,7 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types';
export const load = (async () => {
throw redirect(302, AppRoute.ADMIN_USER_MANAGEMENT);
}) satisfies PageLoad;
@@ -1,26 +0,0 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load = (async ({ locals: { user, api } }) => {
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
} else if (!user.isAdmin) {
throw redirect(302, AppRoute.PHOTOS);
}
try {
const { data: jobs } = await api.jobApi.getAllJobsStatus();
return {
user,
jobs,
meta: {
title: 'Job Status',
},
};
} catch (err) {
console.error('[jobs] > getAllJobsStatus', err);
throw err;
}
}) satisfies PageServerLoad;
+17
View File
@@ -0,0 +1,17 @@
import { authenticate } from '$lib/utils/auth';
import { api } from '@api';
import type { PageLoad } from './$types';
export const load = (async () => {
const user = await authenticate({ admin: true });
const { data: jobs } = await api.jobApi.getAllJobsStatus();
return {
user,
jobs,
meta: {
title: 'Job Status',
},
};
}) satisfies PageLoad;
@@ -1,26 +0,0 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load = (async ({ parent, locals: { api } }) => {
const { user } = await parent();
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
} else if (!user.isAdmin) {
throw redirect(302, AppRoute.PHOTOS);
}
const {
data: { orphans, extras },
} = await api.auditApi.getAuditFiles();
return {
user,
orphans,
extras,
meta: {
title: 'Repair',
},
};
}) satisfies PageServerLoad;
+19
View File
@@ -0,0 +1,19 @@
import { authenticate } from '$lib/utils/auth';
import { api } from '@api';
import type { PageLoad } from './$types';
export const load = (async () => {
const user = await authenticate({ admin: true });
const {
data: { orphans, extras },
} = await api.auditApi.getAuditFiles();
return {
user,
orphans,
extras,
meta: {
title: 'Repair',
},
};
}) satisfies PageLoad;
@@ -1,23 +0,0 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load = (async ({ parent, locals: { api } }) => {
const { user } = await parent();
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
} else if (!user.isAdmin) {
throw redirect(302, AppRoute.PHOTOS);
}
const { data: stats } = await api.serverInfoApi.getServerStatistics();
return {
user,
stats,
meta: {
title: 'Server Stats',
},
};
}) satisfies PageServerLoad;
@@ -0,0 +1,16 @@
import { authenticate } from '$lib/utils/auth';
import { api } from '@api';
import type { PageLoad } from './$types';
export const load = (async () => {
const user = await authenticate({ admin: true });
const { data: stats } = await api.serverInfoApi.getServerStatistics();
return {
user,
stats,
meta: {
title: 'Server Stats',
},
};
}) satisfies PageLoad;
@@ -1,23 +0,0 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ parent, locals: { api } }) => {
const { user } = await parent();
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
} else if (!user.isAdmin) {
throw redirect(302, AppRoute.PHOTOS);
}
const { data: configs } = await api.systemConfigApi.getConfig();
return {
user,
configs,
meta: {
title: 'System Settings',
},
};
};
@@ -0,0 +1,16 @@
import { authenticate } from '$lib/utils/auth';
import { api } from '@api';
import type { PageLoad } from './$types';
export const load = (async () => {
const user = await authenticate({ admin: true });
const { data: configs } = await api.systemConfigApi.getConfig();
return {
user,
configs,
meta: {
title: 'System Settings',
},
};
}) satisfies PageLoad;
@@ -1,23 +0,0 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load = (async ({ parent, locals: { api } }) => {
const { user } = await parent();
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
} else if (!user.isAdmin) {
throw redirect(302, AppRoute.PHOTOS);
}
const { data: allUsers } = await api.userApi.getAllUsers({ isAll: false });
return {
user,
allUsers,
meta: {
title: 'User Management',
},
};
}) satisfies PageServerLoad;
@@ -0,0 +1,16 @@
import { authenticate } from '$lib/utils/auth';
import { api } from '@api';
import type { PageLoad } from './$types';
export const load = (async () => {
const user = await authenticate({ admin: true });
const { data: allUsers } = await api.userApi.getAllUsers({ isAll: false });
return {
user,
allUsers,
meta: {
title: 'User Management',
},
};
}) satisfies PageLoad;
@@ -1,18 +0,0 @@
import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load = (async ({ locals: { user } }) => {
if (!user) {
throw redirect(302, AppRoute.AUTH_LOGIN);
} else if (!user.shouldChangePassword) {
throw redirect(302, AppRoute.PHOTOS);
}
return {
user,
meta: {
title: 'Change Password',
},
};
}) satisfies PageServerLoad;
@@ -0,0 +1,18 @@
import { AppRoute } from '$lib/constants';
import { authenticate } from '$lib/utils/auth';
import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types';
export const load = (async () => {
const user = await authenticate();
if (!user.shouldChangePassword) {
throw redirect(302, AppRoute.PHOTOS);
}
return {
user,
meta: {
title: 'Change Password',
},
};
}) satisfies PageLoad;
@@ -1,8 +1,9 @@
import { AppRoute } from '$lib/constants';
import { api } from '@api';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import type { PageLoad } from './$types';
export const load = (async ({ locals: { api } }) => {
export const load = (async () => {
const { data } = await api.serverInfoApi.getServerConfig();
if (!data.isInitialized) {
// Admin not registered
@@ -14,4 +15,4 @@ export const load = (async ({ locals: { api } }) => {
title: 'Login',
},
};
}) satisfies PageServerLoad;
}) satisfies PageLoad;
-12
View File
@@ -1,12 +0,0 @@
import { api } from '@api';
import type { RequestHandler } from '@sveltejs/kit';
import { json } from '@sveltejs/kit';
export const POST = (async ({ cookies }) => {
api.removeAccessToken();
cookies.delete('immich_auth_type', { path: '/' });
cookies.delete('immich_access_token', { path: '/' });
return json({ ok: true });
}) satisfies RequestHandler;
@@ -1,8 +1,9 @@
import { AppRoute } from '$lib/constants';
import { api } from '@api';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import type { PageLoad } from './$types';
export const load = (async ({ locals: { api } }) => {
export const load = (async () => {
const { data } = await api.serverInfoApi.getServerConfig();
if (data.isInitialized) {
// Admin has been registered, redirect to login
@@ -14,4 +15,4 @@ export const load = (async ({ locals: { api } }) => {
title: 'Admin Registration',
},
};
}) satisfies PageServerLoad;
}) satisfies PageLoad;
-11
View File
@@ -1,11 +0,0 @@
import { RequestHandler, text } from '@sveltejs/kit';
export const GET = (async ({ locals: { api } }) => {
const {
data: { customCss },
} = await api.serverInfoApi.getTheme();
return text(customCss, {
headers: {
'Content-Type': 'text/css',
},
});
}) satisfies RequestHandler;
+10 -2
View File
@@ -1,4 +1,4 @@
import adapter from '@sveltejs/adapter-node';
import adapter from '@sveltejs/adapter-static';
import preprocess from 'svelte-preprocess';
/** @type {import('@sveltejs/kit').Config} */
@@ -11,7 +11,15 @@ const config = {
handler(warning);
},
kit: {
adapter: adapter({ out: 'build' }),
adapter: adapter({
// default options are shown. On some platforms
// these options are set automatically — see below
pages: 'build',
assets: 'build',
fallback: 'index.html',
precompress: false,
strict: true,
}),
},
};
+11 -8
View File
@@ -1,6 +1,14 @@
import { sveltekit } from '@sveltejs/kit/vite';
import path from 'path';
const upstream = {
target: process.env.IMMICH_SERVER_URL || 'http://immich-server:3001/',
secure: true,
changeOrigin: true,
logLevel: 'debug',
ws: true,
};
/** @type {import('vite').UserConfig} */
const config = {
resolve: {
@@ -12,14 +20,9 @@ const config = {
server: {
// connect to a remote backend during web-only development
proxy: {
'/api': {
target: process.env.PUBLIC_IMMICH_SERVER_URL,
secure: true,
changeOrigin: true,
logLevel: 'debug',
rewrite: (path) => path.replace(/^\/api/, ''),
ws: true,
},
'/api': upstream,
'/.well-known/immich': upstream,
'/custom.css': upstream,
},
},
plugins: [sveltekit()],