Skip to content

Commit 26fd73c

Browse files
feat: api (#170)
* feat: api * fix: missing package --------- Co-authored-by: Daniel Dietzler <[email protected]>
1 parent 65ae8ad commit 26fd73c

File tree

81 files changed

+2126
-976
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+2126
-976
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
runs-on: ubuntu-latest
2323
strategy:
2424
matrix:
25-
app_name: ['my', 'buy', 'get', 'next', 'datasets']
25+
app_name: ['api', 'next', 'my', 'buy', 'get', 'datasets']
2626

2727
steps:
2828
- name: Checkout code
@@ -96,7 +96,7 @@ jobs:
9696
strategy:
9797
fail-fast: false
9898
matrix:
99-
app_name: ['datasets', 'next', 'my', 'buy', 'get']
99+
app_name: ['api', 'next', 'my', 'buy', 'get', 'datasets']
100100
env:
101101
TF_VAR_app_name: ${{ matrix.app_name }}
102102
TF_VAR_stage: ${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.number) || '' }}

.github/workflows/destroy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
fail-fast: false
1515
matrix:
1616
environment: ['dev', 'prod']
17-
name: ['my', 'buy', 'get', 'next', 'datasets']
17+
name: ['api', 'next', 'my', 'buy', 'get', 'datasets']
1818
steps:
1919
- name: Checkout code
2020
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
runs-on: ubuntu-latest
1616
strategy:
1717
matrix:
18-
app_name: [my, buy, get, next, datasets]
18+
app_name: ['api', 'next', 'my', 'buy', 'get', 'datasets']
1919

2020
steps:
2121
- name: Checkout code
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<script lang="ts">
2+
import { beforeNavigate } from '$app/navigation';
3+
import { page } from '$app/state';
4+
import { getOpenApi } from '$lib/api/services/open-api';
5+
import Header from '$lib/components/Header.svelte';
6+
import PageContent from '$lib/components/PageContent.svelte';
7+
import { ApiPage } from '$lib/utils/api';
8+
import { getIcon } from '$lib/utils/icons';
9+
import { AppShell, AppShellHeader, AppShellSidebar, NavbarGroup, NavbarItem } from '@immich/ui';
10+
import {
11+
mdiApi,
12+
mdiCompass,
13+
mdiCompassOutline,
14+
mdiCube,
15+
mdiCubeOutline,
16+
mdiLock,
17+
mdiLockOutline,
18+
mdiNote,
19+
mdiNoteOutline,
20+
mdiSecurity,
21+
mdiTagMultiple,
22+
mdiTagMultipleOutline,
23+
} from '@mdi/js';
24+
import type { Snippet } from 'svelte';
25+
import { MediaQuery } from 'svelte/reactivity';
26+
27+
type Props = {
28+
children?: Snippet;
29+
};
30+
31+
const { children }: Props = $props();
32+
33+
const { tags } = getOpenApi();
34+
35+
const sidebar = new MediaQuery(`min-width: 850px`);
36+
let open = $derived(sidebar.current);
37+
38+
beforeNavigate(() => {
39+
if (!sidebar.current) {
40+
open = false;
41+
}
42+
});
43+
</script>
44+
45+
<AppShell>
46+
<AppShellHeader>
47+
<Header onToggleSidebar={() => (open = !open)} />
48+
</AppShellHeader>
49+
50+
<AppShellSidebar bind:open class="border-e">
51+
<div class="mt-8 pe-6 flex flex-col gap-4">
52+
<div>
53+
<NavbarItem title="Introduction" href={ApiPage.Introduction} icon={mdiNoteOutline} activeIcon={mdiNote} />
54+
<NavbarItem
55+
title="Getting Started"
56+
href={ApiPage.GettingStarted}
57+
icon={mdiCompassOutline}
58+
activeIcon={mdiCompass}
59+
/>
60+
<NavbarItem title="Authentication" href={ApiPage.Authentication} icon={mdiSecurity} />
61+
<NavbarItem title="Permissions" href={ApiPage.Permissions} icon={mdiLockOutline} activeIcon={mdiLock} />
62+
<NavbarItem title="SDK" href={ApiPage.Sdk} icon={mdiCubeOutline} activeIcon={mdiCube} />
63+
<NavbarItem
64+
title="Endpoints"
65+
href={ApiPage.Endpoints}
66+
icon={mdiApi}
67+
active={page.url.pathname === ApiPage.Endpoints}
68+
/>
69+
<NavbarItem
70+
title="Models"
71+
href={ApiPage.Models}
72+
icon={mdiTagMultipleOutline}
73+
activeIcon={mdiTagMultiple}
74+
active={page.url.pathname === ApiPage.Models}
75+
/>
76+
</div>
77+
<div>
78+
<NavbarGroup title="Endpoints" />
79+
{#each tags as tag (tag.href)}
80+
<NavbarItem title={tag.name} href={tag.href} icon={getIcon(tag.name)} variant="compact" />
81+
{/each}
82+
</div>
83+
</div>
84+
</AppShellSidebar>
85+
86+
<PageContent class="mx-auto w-full max-w-(--breakpoint-lg)">
87+
{@render children?.()}
88+
</PageContent>
89+
</AppShell>

apps/next.immich.app/routes/api/+layout.ts renamed to apps/api.immich.app/routes/(docs)/+layout.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { goto } from '$app/navigation';
2-
import { loadOpenApi } from '$lib/services/open-api';
2+
import { loadOpenApi } from '$lib/api/services/open-api';
33
import type { LayoutLoad } from './$types';
44

55
export const load = (async ({ fetch }) => {
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
<script lang="ts">
2+
import ApiPageContent from '$lib/api/components/ApiPageContent.svelte';
3+
import ApiPermission from '$lib/api/components/ApiPermission.svelte';
4+
import { ApiPage } from '$lib/utils/api';
5+
import { Alert, Card, CardBody, CardHeader, Code, Heading, Link, Text } from '@immich/ui';
6+
import { mdiLightbulbOnOutline } from '@mdi/js';
7+
</script>
8+
9+
<ApiPageContent
10+
title="Authentication"
11+
description="Learn how to authorize your requests to the Immich API"
12+
nextSteps={[
13+
{ href: ApiPage.Permissions, title: 'Learn about permissions' },
14+
{ href: ApiPage.Endpoints, title: 'View API endpoints' },
15+
]}
16+
>
17+
<section class="flex flex-col gap-2">
18+
<Heading tag="h2">Overview</Heading>
19+
<Text>
20+
While some endpoints do not require authentication, the majority do. In order to authenticate a request, the
21+
Immich API supports a few different schemes: session, shared key, and API key. 3rd party applications are
22+
encouraged to use API keys, which support fine-grained permissions. Lastly, shared links and their associated keys
23+
or slugs can be used to access a subset of API endpoints, and are scoped to a specific album or set of assets.
24+
</Text>
25+
</section>
26+
27+
<section class="flex flex-col gap-2">
28+
<Heading tag="h2">Authentication</Heading>
29+
<Text
30+
>Users or applications can authenticate with the Immich API using one of the values listed below. API keys can
31+
optionally be created with scoped permissions, while session tokens are tied to a specific user session, and have
32+
an implied <ApiPermission value="all" /> permission.
33+
</Text>
34+
35+
<Card class="mt-2">
36+
<CardHeader class="py-1 bg-subtle">
37+
<div class="grid grid-cols-3">
38+
<div class="font-bold">Type</div>
39+
<div class="font-bold">Value</div>
40+
<div class="font-bold">Description</div>
41+
</div>
42+
</CardHeader>
43+
<CardBody>
44+
<div class="grid grid-cols-3 gap-y-1">
45+
<div>Request Header</div>
46+
<div><Code>x-api-key</Code></div>
47+
<div>API key sent as a http header</div>
48+
49+
<div>Query Param</div>
50+
<div><Code>apiKey</Code></div>
51+
<div>API key sent as a query parameter</div>
52+
53+
<hr class="col-span-3 my-2" />
54+
55+
<div>Request Header</div>
56+
<div><Code>x-immich-user-token</Code></div>
57+
<div>Session token sent as a http header</div>
58+
59+
<div>Request Header</div>
60+
<div><Code>x-immich-session-token</Code></div>
61+
<div>Session token sent as a http header</div>
62+
63+
<div>Query Param</div>
64+
<div><Code>sessionKey</Code></div>
65+
<div>Session token sent as a query parameter</div>
66+
67+
<hr class="col-span-3 my-2" />
68+
69+
<div>Request Header</div>
70+
<div><Code>x-immich-share-key</Code></div>
71+
<div>Shared link key sent as a http header</div>
72+
73+
<div>Query Param</div>
74+
<div><Code>key</Code></div>
75+
<div>Shared link key sent as a query parameter</div>
76+
77+
<hr class="col-span-3 my-2" />
78+
79+
<div>Request Header</div>
80+
<div><Code>x-immich-share-slug</Code></div>
81+
<div>Shared link slug sent as a http header</div>
82+
83+
<div>Query Param</div>
84+
<div><Code>slug</Code></div>
85+
<div>Shared link slug sent as a query parameter</div>
86+
</div>
87+
</CardBody>
88+
</Card>
89+
</section>
90+
91+
<section class="flex flex-col gap-2">
92+
<Heading tag="h2">API Keys</Heading>
93+
<Text>
94+
In order to authenticate a request with an API key, you need to include the <Code color="info"
95+
>x-immich-api-key</Code
96+
> header in your request.
97+
</Text>
98+
99+
<Alert color="success" icon={mdiLightbulbOnOutline} class="mt-4">
100+
<Text
101+
>User-scoped api keys can be created in <Link href="https://my.immich.app/user-settings?isOpen=api-keys"
102+
>user settings</Link
103+
>
104+
</Text>
105+
</Alert>
106+
107+
<Card color="secondary" class="mt-2">
108+
<CardBody>
109+
<Code>x-immich-api-key: &lt;apiKey&gt;</Code>
110+
</CardBody>
111+
</Card>
112+
<Text class="pt-4">
113+
Alternatively, you can also include the API key by using the <Code color="info">apiKey</Code> query parameter in your
114+
request.
115+
</Text>
116+
<Card color="secondary" class="mt-2">
117+
<CardBody>
118+
<Code>https://demo.immich.app/api/assets?apiKey=&lt;apiKey&gt;</Code>
119+
</CardBody>
120+
</Card>
121+
</section>
122+
</ApiPageContent>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script lang="ts">
2+
import ApiPageContent from '$lib/api/components/ApiPageContent.svelte';
3+
import ApiTagSummary from '$lib/api/components/ApiTagSummary.svelte';
4+
import { getOpenApi } from '$lib/api/services/open-api';
5+
6+
const { tags } = getOpenApi();
7+
</script>
8+
9+
<ApiPageContent title="API Endpoints" description="An overview of all the available API endpoints">
10+
{#each tags as tag (tag.href)}
11+
<ApiTagSummary {tag} />
12+
{/each}
13+
</ApiPageContent>
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<script lang="ts">
2+
import ApiTagSummary from '$lib/api/components/ApiTagSummary.svelte';
3+
import { Text, Button, Heading, Stack, Icon } from '@immich/ui';
4+
import { type PageData } from './$types';
5+
import { ApiPage } from '$lib/utils/api';
6+
import { mdiArrowLeft, mdiArrowRight } from '@mdi/js';
7+
8+
type Props = {
9+
data: PageData;
10+
};
11+
12+
const { data }: Props = $props();
13+
14+
const tag = $derived(data.tag);
15+
</script>
16+
17+
<Stack gap={4}>
18+
<div class="flex justify-between items-center">
19+
<Heading size="giant">API Endpoints</Heading>
20+
<Button href={ApiPage.Endpoints} color="secondary">View All</Button>
21+
</div>
22+
23+
<ApiTagSummary {tag} />
24+
25+
<Stack gap={4}>
26+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
27+
{#if tag.previous}
28+
<Button href={tag.previous.href} size="giant" fullWidth color="secondary" variant="outline">
29+
<Stack gap={2} class="w-full">
30+
<Text size="small">Previous</Text>
31+
<div class="flex items-center gap-2">
32+
<Icon icon={mdiArrowLeft} />
33+
<Text>{tag.previous.name}</Text>
34+
</div>
35+
</Stack>
36+
</Button>
37+
{:else}
38+
<span></span>
39+
{/if}
40+
41+
{#if tag.next}
42+
<Button href={tag.next.href} size="giant" fullWidth color="secondary" variant="outline">
43+
<div class="w-full flex flex-col place-items-end">
44+
<Text size="small">Next</Text>
45+
<div class="flex items-center gap-2">
46+
<Text>{tag.next.name}</Text>
47+
<Icon icon={mdiArrowRight} />
48+
</div>
49+
</div>
50+
</Button>
51+
{:else}
52+
<span></span>
53+
{/if}
54+
</div>
55+
</Stack>
56+
</Stack>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { getOpenApi } from '$lib/api/services/open-api';
2+
import { redirect } from '@sveltejs/kit';
3+
import type { PageLoad } from './$types';
4+
import { ApiPage } from '$lib/utils/api';
5+
6+
export const load = (async ({ params, parent }) => {
7+
await parent();
8+
const { tags } = getOpenApi();
9+
const tag = tags.find((tag) => tag.href === `${ApiPage.Endpoints}/${params.tag}`);
10+
11+
if (!tag) {
12+
return redirect(307, '/api');
13+
}
14+
15+
return {
16+
tag,
17+
};
18+
}) satisfies PageLoad;

0 commit comments

Comments
 (0)