mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-11-21 05:53:11 -05:00
Handle cookies in keibi + fix database/env stuff (#1135)
This commit is contained in:
commit
1db4dea56f
62
.env.example
62
.env.example
@ -21,8 +21,8 @@ GOCODER_PRESET=fast
|
|||||||
|
|
||||||
|
|
||||||
# Keep those empty to use kyoo's default api key. You can also specify a custom API key if you want.
|
# Keep those empty to use kyoo's default api key. You can also specify a custom API key if you want.
|
||||||
# go to https://www.themoviedb.org/settings/api and copy the api key (not the read access token, the api key)
|
# go to https://www.themoviedb.org/settings/api and copy the read access token (not the api key)
|
||||||
THEMOVIEDB_APIKEY=
|
THEMOVIEDB_API_ACCESS_TOKEN=""
|
||||||
# go to https://thetvdb.com/api-information/signup and copy the api key
|
# go to https://thetvdb.com/api-information/signup and copy the api key
|
||||||
TVDB_APIKEY=
|
TVDB_APIKEY=
|
||||||
# you can also input your subscriber's pin to support TVDB
|
# you can also input your subscriber's pin to support TVDB
|
||||||
@ -32,41 +32,45 @@ TVDB_PIN=
|
|||||||
# The url you can use to reach your kyoo instance. This is used during oidc to redirect users to your instance.
|
# The url you can use to reach your kyoo instance. This is used during oidc to redirect users to your instance.
|
||||||
PUBLIC_URL=http://localhost:8901
|
PUBLIC_URL=http://localhost:8901
|
||||||
|
|
||||||
# Use a builtin oidc service (google, discord, trakt, or simkl):
|
# Default permissions of new users. They are able to browse & play videos.
|
||||||
# When you create a client_id, secret combo you may be asked for a redirect url. You need to specify https://YOUR-PUBLIC-URL/api/auth/logged/YOUR-SERVICE-NAME
|
# Set `verified` to true if you don't wanna manually verify users.
|
||||||
OIDC_DISCORD_CLIENTID=
|
EXTRA_CLAIMS='{"permissions": ["core.read", "core.play"], "verified": false}'
|
||||||
OIDC_DISCORD_SECRET=
|
# This is the permissions of the first user (aka the first user is admin)
|
||||||
# Or add your custom one:
|
FIRST_USER_CLAIMS='{"permissions": ["users.read", "users.write", "apikeys.read", "apikeys.write", "users.delete", "core.read", "core.write", "core.play", "scanner.trigger"], "verified": true}'
|
||||||
OIDC_SERVICE_NAME=YourPrettyName
|
|
||||||
OIDC_SERVICE_LOGO=https://url-of-your-logo.com
|
# Guest (meaning unlogged in users) can be:
|
||||||
OIDC_SERVICE_CLIENTID=
|
# unauthorized (they need to connect before doing anything)
|
||||||
OIDC_SERVICE_SECRET=
|
# GUEST_CLAIMS=""
|
||||||
OIDC_SERVICE_AUTHORIZATION=https://url-of-the-authorization-endpoint-of-the-oidc-service.com/auth
|
# able to browse & see what you have but not able to play
|
||||||
OIDC_SERVICE_TOKEN=https://url-of-the-token-endpoint-of-the-oidc-service.com/token
|
GUEST_CLAIMS='{"permissions": ["core.read"], "verified": true}'
|
||||||
OIDC_SERVICE_PROFILE=https://url-of-the-profile-endpoint-of-the-oidc-service.com/userinfo
|
# or have browse & play permissions
|
||||||
OIDC_SERVICE_SCOPE="the list of scopes space separeted like email identity"
|
GUEST_CLAIMS='{"permissions": ["core.read", "core.play"], "verified": true}'
|
||||||
# Token authentication method as seen in https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication
|
|
||||||
# Supported values: ClientSecretBasic (default) or ClientSecretPost
|
# DO NOT change this.
|
||||||
# If in doubt, leave this empty.
|
PROTECTED_CLAIMS="permissions,verified"
|
||||||
OIDC_SERVICE_AUTHMETHOD=ClientSecretBasic
|
|
||||||
# on the previous list, service is the internal name of your service, you can add as many as you want.
|
|
||||||
|
|
||||||
|
|
||||||
# Following options are optional and only useful for debugging.
|
# You can create apikeys at runtime via POST /keys but you can also have some defined in the env.
|
||||||
|
# Replace $YOURNAME with the name of the key you want (only alpha are valid)
|
||||||
|
# The value will be the apikey (max 128 bytes)
|
||||||
|
KEIBI_APIKEY_SCANNER=EJqUB8robwKwLNt37SuHqdcsNGrtwpfYxeExfiAbokpxZVd4WctWr7gnSZ
|
||||||
|
KEIBI_APIKEY_SCANNER_CLAIMS='{"permissions": ["core.read", "core.write"]}'
|
||||||
|
|
||||||
# To debug the front end, you can set the following to an external backend
|
# To debug the front end, you can set the following to an external backend
|
||||||
KYOO_URL=
|
KYOO_URL=
|
||||||
|
|
||||||
# Database things
|
# It is recommended to use the below PG environment variables when possible.
|
||||||
|
# POSTGRES_URL=postgres://user:password@hostname:port/dbname?sslmode=verify-full&sslrootcert=/path/to/server.crt&sslcert=/path/to/client.crt&sslkey=/path/to/client.key
|
||||||
|
# The behavior of the below variables match what is documented here:
|
||||||
|
# https://www.postgresql.org/docs/current/libpq-envars.html
|
||||||
PGUSER=kyoo
|
PGUSER=kyoo
|
||||||
PGPASSWORD=password
|
PGPASSWORD=password
|
||||||
PGDATABASE=kyoo
|
PGDATABASE=kyoo
|
||||||
PGHOST=postgres
|
PGHOST=postgres
|
||||||
PGPORT=5432
|
PGPORT=5432
|
||||||
|
# PGOPTIONS=-c search_path=kyoo,public
|
||||||
# v5 stuff, does absolutely nothing on master (aka: you can delete this)
|
# PGPASSFILE=/my/password # Takes precedence over PGPASSWORD. New line characters are not trimmed.
|
||||||
EXTRA_CLAIMS='{"permissions": ["core.read"], "verified": false}'
|
# PGSSLMODE=verify-full
|
||||||
FIRST_USER_CLAIMS='{"permissions": ["users.read", "users.write", "apikeys.read", "apikeys.write", "users.delete", "core.read", "core.write", "scanner.trigger"], "verified": true}'
|
# PGSSLROOTCERT=/my/serving.crt
|
||||||
GUEST_CLAIMS='{"permissions": ["users.read", "users.write", "apikeys.read", "apikeys.write", "users.delete", "core.read", "core.write", "scanner.trigger"], "verified": true}'
|
# PGSSLCERT=/my/client.crt
|
||||||
# GUEST_CLAIMS='{"permissions": ["core.read"]}'
|
# PGSSLKEY=/my/client.key=password
|
||||||
PROTECTED_CLAIMS="permissions,verified"
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { series } from "./controllers/shows/series";
|
|||||||
import { showsH } from "./controllers/shows/shows";
|
import { showsH } from "./controllers/shows/shows";
|
||||||
import { staffH } from "./controllers/staff";
|
import { staffH } from "./controllers/staff";
|
||||||
import { studiosH } from "./controllers/studios";
|
import { studiosH } from "./controllers/studios";
|
||||||
import { videosH } from "./controllers/videos";
|
import { videosReadH, videosWriteH } from "./controllers/videos";
|
||||||
import type { KError } from "./models/error";
|
import type { KError } from "./models/error";
|
||||||
|
|
||||||
export const base = new Elysia({ name: "base" })
|
export const base = new Elysia({ name: "base" })
|
||||||
@ -90,7 +90,8 @@ export const handlers = new Elysia({ prefix })
|
|||||||
.use(imagesH)
|
.use(imagesH)
|
||||||
.use(watchlistH)
|
.use(watchlistH)
|
||||||
.use(historyH)
|
.use(historyH)
|
||||||
.use(nextup),
|
.use(nextup)
|
||||||
|
.use(videosReadH),
|
||||||
)
|
)
|
||||||
.guard(
|
.guard(
|
||||||
{
|
{
|
||||||
@ -104,5 +105,5 @@ export const handlers = new Elysia({ prefix })
|
|||||||
// },
|
// },
|
||||||
permissions: ["core.write"],
|
permissions: ["core.write"],
|
||||||
},
|
},
|
||||||
(app) => app.use(videosH).use(seed),
|
(app) => app.use(videosWriteH).use(seed),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -99,13 +99,15 @@ export const processImages = async () => {
|
|||||||
const column = sql.raw(img.column);
|
const column = sql.raw(img.column);
|
||||||
|
|
||||||
await tx.execute(sql`
|
await tx.execute(sql`
|
||||||
update ${table} set ${column} = ${ret} where ${column}->'id' = ${sql.raw(`'"${img.id}"'::jsonb`)}
|
update ${table} set ${column} = ${ret}
|
||||||
`);
|
where ${column}->'id' = ${sql.raw(`'"${img.id}"'::jsonb`)}
|
||||||
|
`);
|
||||||
|
|
||||||
await tx.delete(mqueue).where(eq(mqueue.id, item.id));
|
await tx.delete(mqueue).where(eq(mqueue.id, item.id));
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error("Failed to download image", img.url, err.message);
|
console.error("Failed to download image", img.url, err.message);
|
||||||
await tx
|
// don't use the transaction here, it can be aborted.
|
||||||
|
await db
|
||||||
.update(mqueue)
|
.update(mqueue)
|
||||||
.set({ attempt: sql`${mqueue.attempt}+1` })
|
.set({ attempt: sql`${mqueue.attempt}+1` })
|
||||||
.where(eq(mqueue.id, item.id));
|
.where(eq(mqueue.id, item.id));
|
||||||
|
|||||||
@ -439,10 +439,9 @@ function getNextVideoEntry({
|
|||||||
.as("next");
|
.as("next");
|
||||||
}
|
}
|
||||||
|
|
||||||
export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
|
export const videosReadH = new Elysia({ prefix: "/videos", tags: ["videos"] })
|
||||||
.model({
|
.model({
|
||||||
video: Video,
|
video: Video,
|
||||||
"created-videos": t.Array(CreatedVideo),
|
|
||||||
error: t.Object({}),
|
error: t.Object({}),
|
||||||
})
|
})
|
||||||
.use(auth)
|
.use(auth)
|
||||||
@ -483,7 +482,7 @@ export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
|
|||||||
message: `No video found with id or slug '${id}'`,
|
message: `No video found with id or slug '${id}'`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return video;
|
return video as any;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
detail: {
|
detail: {
|
||||||
@ -805,7 +804,15 @@ export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
|
|||||||
422: KError,
|
422: KError,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
|
export const videosWriteH = new Elysia({ prefix: "/videos", tags: ["videos"] })
|
||||||
|
.model({
|
||||||
|
video: Video,
|
||||||
|
"created-videos": t.Array(CreatedVideo),
|
||||||
|
error: t.Object({}),
|
||||||
|
})
|
||||||
|
.use(auth)
|
||||||
.post(
|
.post(
|
||||||
"",
|
"",
|
||||||
async ({ body, status }) => {
|
async ({ body, status }) => {
|
||||||
|
|||||||
@ -19,7 +19,7 @@ PROTECTED_CLAIMS="permissions"
|
|||||||
# The url you can use to reach your kyoo instance. This is used during oidc to redirect users to your instance.
|
# The url you can use to reach your kyoo instance. This is used during oidc to redirect users to your instance.
|
||||||
PUBLIC_URL=http://localhost:8901
|
PUBLIC_URL=http://localhost:8901
|
||||||
|
|
||||||
# You can create apikeys at runtime via POST /apikey but you can also have some defined in the env.
|
# You can create apikeys at runtime via POST /key but you can also have some defined in the env.
|
||||||
# Replace $YOURNAME with the name of the key you want (only alpha are valid)
|
# Replace $YOURNAME with the name of the key you want (only alpha are valid)
|
||||||
# The value will be the apikey (max 128 bytes)
|
# The value will be the apikey (max 128 bytes)
|
||||||
# KEIBI_APIKEY_$YOURNAME=oaeushtaoesunthoaensuth
|
# KEIBI_APIKEY_$YOURNAME=oaeushtaoesunthoaensuth
|
||||||
@ -43,8 +43,3 @@ PGPORT=5432
|
|||||||
# PGSSLROOTCERT=/my/serving.crt
|
# PGSSLROOTCERT=/my/serving.crt
|
||||||
# PGSSLCERT=/my/client.crt
|
# PGSSLCERT=/my/client.crt
|
||||||
# PGSSLKEY=/my/client.key
|
# PGSSLKEY=/my/client.key
|
||||||
|
|
||||||
# Default is keibi, you can specify "disabled" to use the default search_path of the user.
|
|
||||||
# If this is not "disabled", the schema will be created (if it does not exists) and
|
|
||||||
# the search_path of the user will be ignored (only the schema specified will be used).
|
|
||||||
POSTGRES_SCHEMA=keibi
|
|
||||||
|
|||||||
@ -60,8 +60,8 @@ GET `/users/$id/sessions` can be used by admins to list others session
|
|||||||
### Api keys
|
### Api keys
|
||||||
|
|
||||||
```
|
```
|
||||||
Get `/apikeys`
|
Get `/keys`
|
||||||
Post `/apikeys` {...claims} Create a new api keys with given claims
|
Post `/keys` {...claims} Create a new api keys with given claims
|
||||||
```
|
```
|
||||||
|
|
||||||
An api key can be used like an opaque token, calling /jwt with it will return a valid jwt with the claims you specified during the post request to create it.
|
An api key can be used like an opaque token, calling /jwt with it will return a valid jwt with the claims you specified during the post request to create it.
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.30.0
|
||||||
// source: apikeys.sql
|
// source: apikeys.sql
|
||||||
|
|
||||||
package dbc
|
package dbc
|
||||||
@ -13,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const createApiKey = `-- name: CreateApiKey :one
|
const createApiKey = `-- name: CreateApiKey :one
|
||||||
insert into apikeys(name, token, claims, created_by)
|
insert into keibi.apikeys(name, token, claims, created_by)
|
||||||
values ($1, $2, $3, $4)
|
values ($1, $2, $3, $4)
|
||||||
returning
|
returning
|
||||||
pk, id, name, token, claims, created_by, created_at, last_used
|
pk, id, name, token, claims, created_by, created_at, last_used
|
||||||
@ -48,7 +48,7 @@ func (q *Queries) CreateApiKey(ctx context.Context, arg CreateApiKeyParams) (Api
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deleteApiKey = `-- name: DeleteApiKey :one
|
const deleteApiKey = `-- name: DeleteApiKey :one
|
||||||
delete from apikeys
|
delete from keibi.apikeys
|
||||||
where id = $1
|
where id = $1
|
||||||
returning
|
returning
|
||||||
pk, id, name, token, claims, created_by, created_at, last_used
|
pk, id, name, token, claims, created_by, created_at, last_used
|
||||||
@ -74,7 +74,7 @@ const getApiKey = `-- name: GetApiKey :one
|
|||||||
select
|
select
|
||||||
pk, id, name, token, claims, created_by, created_at, last_used
|
pk, id, name, token, claims, created_by, created_at, last_used
|
||||||
from
|
from
|
||||||
apikeys
|
keibi.apikeys
|
||||||
where
|
where
|
||||||
name = $1
|
name = $1
|
||||||
and token = $2
|
and token = $2
|
||||||
@ -105,7 +105,7 @@ const listApiKeys = `-- name: ListApiKeys :many
|
|||||||
select
|
select
|
||||||
pk, id, name, token, claims, created_by, created_at, last_used
|
pk, id, name, token, claims, created_by, created_at, last_used
|
||||||
from
|
from
|
||||||
apikeys
|
keibi.apikeys
|
||||||
order by
|
order by
|
||||||
last_used
|
last_used
|
||||||
`
|
`
|
||||||
@ -141,7 +141,7 @@ func (q *Queries) ListApiKeys(ctx context.Context) ([]Apikey, error) {
|
|||||||
|
|
||||||
const touchApiKey = `-- name: TouchApiKey :exec
|
const touchApiKey = `-- name: TouchApiKey :exec
|
||||||
update
|
update
|
||||||
apikeys
|
keibi.apikeys
|
||||||
set
|
set
|
||||||
last_used = now()::timestamptz
|
last_used = now()::timestamptz
|
||||||
where
|
where
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.30.0
|
||||||
|
|
||||||
package dbc
|
package dbc
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.30.0
|
||||||
|
|
||||||
package dbc
|
package dbc
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.30.0
|
||||||
// source: sessions.sql
|
// source: sessions.sql
|
||||||
|
|
||||||
package dbc
|
package dbc
|
||||||
@ -13,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const clearOtherSessions = `-- name: ClearOtherSessions :exec
|
const clearOtherSessions = `-- name: ClearOtherSessions :exec
|
||||||
delete from sessions as s using users as u
|
delete from keibi.sessions as s using keibi.users as u
|
||||||
where s.user_pk = u.pk
|
where s.user_pk = u.pk
|
||||||
and s.id != $1
|
and s.id != $1
|
||||||
and u.id = $2
|
and u.id = $2
|
||||||
@ -30,7 +30,7 @@ func (q *Queries) ClearOtherSessions(ctx context.Context, arg ClearOtherSessions
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createSession = `-- name: CreateSession :one
|
const createSession = `-- name: CreateSession :one
|
||||||
insert into sessions(token, user_pk, device)
|
insert into keibi.sessions(token, user_pk, device)
|
||||||
values ($1, $2, $3)
|
values ($1, $2, $3)
|
||||||
returning
|
returning
|
||||||
pk, id, token, user_pk, created_date, last_used, device
|
pk, id, token, user_pk, created_date, last_used, device
|
||||||
@ -58,7 +58,7 @@ func (q *Queries) CreateSession(ctx context.Context, arg CreateSessionParams) (S
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deleteSession = `-- name: DeleteSession :one
|
const deleteSession = `-- name: DeleteSession :one
|
||||||
delete from sessions as s using users as u
|
delete from keibi.sessions as s using keibi.users as u
|
||||||
where s.user_pk = u.pk
|
where s.user_pk = u.pk
|
||||||
and s.id = $1
|
and s.id = $1
|
||||||
and u.id = $2
|
and u.id = $2
|
||||||
@ -93,8 +93,8 @@ select
|
|||||||
s.last_used,
|
s.last_used,
|
||||||
u.pk, u.id, u.username, u.email, u.password, u.claims, u.created_date, u.last_seen
|
u.pk, u.id, u.username, u.email, u.password, u.claims, u.created_date, u.last_seen
|
||||||
from
|
from
|
||||||
users as u
|
keibi.users as u
|
||||||
inner join sessions as s on u.pk = s.user_pk
|
inner join keibi.sessions as s on u.pk = s.user_pk
|
||||||
where
|
where
|
||||||
s.token = $1
|
s.token = $1
|
||||||
limit 1
|
limit 1
|
||||||
@ -130,8 +130,8 @@ const getUserSessions = `-- name: GetUserSessions :many
|
|||||||
select
|
select
|
||||||
s.pk, s.id, s.token, s.user_pk, s.created_date, s.last_used, s.device
|
s.pk, s.id, s.token, s.user_pk, s.created_date, s.last_used, s.device
|
||||||
from
|
from
|
||||||
sessions as s
|
keibi.sessions as s
|
||||||
inner join users as u on u.pk = s.user_pk
|
inner join keibi.users as u on u.pk = s.user_pk
|
||||||
where
|
where
|
||||||
u.pk = $1
|
u.pk = $1
|
||||||
order by
|
order by
|
||||||
@ -168,7 +168,7 @@ func (q *Queries) GetUserSessions(ctx context.Context, pk int32) ([]Session, err
|
|||||||
|
|
||||||
const touchSession = `-- name: TouchSession :exec
|
const touchSession = `-- name: TouchSession :exec
|
||||||
update
|
update
|
||||||
sessions
|
keibi.sessions
|
||||||
set
|
set
|
||||||
last_used = now()::timestamptz
|
last_used = now()::timestamptz
|
||||||
where
|
where
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.30.0
|
||||||
// source: users.sql
|
// source: users.sql
|
||||||
|
|
||||||
package dbc
|
package dbc
|
||||||
@ -13,12 +13,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const createUser = `-- name: CreateUser :one
|
const createUser = `-- name: CreateUser :one
|
||||||
insert into users(username, email, password, claims)
|
insert into keibi.users(username, email, password, claims)
|
||||||
values ($1, $2, $3, case when not exists (
|
values ($1, $2, $3, case when not exists (
|
||||||
select
|
select
|
||||||
pk, id, username, email, password, claims, created_date, last_seen
|
pk, id, username, email, password, claims, created_date, last_seen
|
||||||
from
|
from
|
||||||
users) then
|
keibi.users) then
|
||||||
$4::jsonb
|
$4::jsonb
|
||||||
else
|
else
|
||||||
$5::jsonb
|
$5::jsonb
|
||||||
@ -58,7 +58,7 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deleteUser = `-- name: DeleteUser :one
|
const deleteUser = `-- name: DeleteUser :one
|
||||||
delete from users
|
delete from keibi.users
|
||||||
where id = $1
|
where id = $1
|
||||||
returning
|
returning
|
||||||
pk, id, username, email, password, claims, created_date, last_seen
|
pk, id, username, email, password, claims, created_date, last_seen
|
||||||
@ -84,7 +84,7 @@ const getAllUsers = `-- name: GetAllUsers :many
|
|||||||
select
|
select
|
||||||
pk, id, username, email, password, claims, created_date, last_seen
|
pk, id, username, email, password, claims, created_date, last_seen
|
||||||
from
|
from
|
||||||
users
|
keibi.users
|
||||||
order by
|
order by
|
||||||
id
|
id
|
||||||
limit $1
|
limit $1
|
||||||
@ -123,7 +123,7 @@ const getAllUsersAfter = `-- name: GetAllUsersAfter :many
|
|||||||
select
|
select
|
||||||
pk, id, username, email, password, claims, created_date, last_seen
|
pk, id, username, email, password, claims, created_date, last_seen
|
||||||
from
|
from
|
||||||
users
|
keibi.users
|
||||||
where
|
where
|
||||||
id >= $2
|
id >= $2
|
||||||
order by
|
order by
|
||||||
@ -173,8 +173,8 @@ select
|
|||||||
h.username,
|
h.username,
|
||||||
h.profile_url
|
h.profile_url
|
||||||
from
|
from
|
||||||
users as u
|
keibi.users as u
|
||||||
left join oidc_handle as h on u.pk = h.user_pk
|
left join keibi.oidc_handle as h on u.pk = h.user_pk
|
||||||
where ($1::boolean
|
where ($1::boolean
|
||||||
and u.id = $2)
|
and u.id = $2)
|
||||||
or (not $1
|
or (not $1
|
||||||
@ -232,7 +232,7 @@ const getUserByLogin = `-- name: GetUserByLogin :one
|
|||||||
select
|
select
|
||||||
pk, id, username, email, password, claims, created_date, last_seen
|
pk, id, username, email, password, claims, created_date, last_seen
|
||||||
from
|
from
|
||||||
users
|
keibi.users
|
||||||
where
|
where
|
||||||
email = $1
|
email = $1
|
||||||
or username = $1
|
or username = $1
|
||||||
@ -257,7 +257,7 @@ func (q *Queries) GetUserByLogin(ctx context.Context, login string) (User, error
|
|||||||
|
|
||||||
const touchUser = `-- name: TouchUser :exec
|
const touchUser = `-- name: TouchUser :exec
|
||||||
update
|
update
|
||||||
users
|
keibi.users
|
||||||
set
|
set
|
||||||
last_used = now()::timestamptz
|
last_used = now()::timestamptz
|
||||||
where
|
where
|
||||||
@ -271,7 +271,7 @@ func (q *Queries) TouchUser(ctx context.Context, pk int32) error {
|
|||||||
|
|
||||||
const updateUser = `-- name: UpdateUser :one
|
const updateUser = `-- name: UpdateUser :one
|
||||||
update
|
update
|
||||||
users
|
keibi.users
|
||||||
set
|
set
|
||||||
username = coalesce($2, username),
|
username = coalesce($2, username),
|
||||||
email = coalesce($3, email),
|
email = coalesce($3, email),
|
||||||
|
|||||||
19
auth/jwt.go
19
auth/jwt.go
@ -34,19 +34,30 @@ func (h *Handler) CreateJwt(c echo.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
c.Response().Header().Add("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||||
return c.JSON(http.StatusOK, Jwt{
|
return c.JSON(http.StatusOK, Jwt{
|
||||||
Token: &token,
|
Token: &token,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
auth := c.Request().Header.Get("Authorization")
|
auth := c.Request().Header.Get("Authorization")
|
||||||
var jwt *string
|
var token string
|
||||||
|
|
||||||
if !strings.HasPrefix(auth, "Bearer ") {
|
if auth == "" {
|
||||||
|
c, _ := c.Request().Cookie("X-Bearer")
|
||||||
|
if c != nil {
|
||||||
|
token = c.Value
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(auth, "Bearer ") {
|
||||||
|
token = auth[len("Bearer "):]
|
||||||
|
} else if auth != "" {
|
||||||
|
return echo.NewHTTPError(http.StatusUnauthorized, "Invalid bearer format.")
|
||||||
|
}
|
||||||
|
|
||||||
|
var jwt *string
|
||||||
|
if token == "" {
|
||||||
jwt = h.createGuestJwt()
|
jwt = h.createGuestJwt()
|
||||||
} else {
|
} else {
|
||||||
token := auth[len("Bearer "):]
|
|
||||||
|
|
||||||
tkn, err := h.createJwt(token)
|
tkn, err := h.createJwt(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
17
auth/main.go
17
auth/main.go
@ -106,29 +106,20 @@ func OpenDatabase() (*pgxpool.Pool, error) {
|
|||||||
config.ConnConfig.RuntimeParams["application_name"] = "keibi"
|
config.ConnConfig.RuntimeParams["application_name"] = "keibi"
|
||||||
}
|
}
|
||||||
|
|
||||||
schema := GetenvOr("POSTGRES_SCHEMA", "keibi")
|
|
||||||
if _, ok := config.ConnConfig.RuntimeParams["search_path"]; !ok {
|
|
||||||
config.ConnConfig.RuntimeParams["search_path"] = schema
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err := pgxpool.NewWithConfig(ctx, config)
|
db, err := pgxpool.NewWithConfig(ctx, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Could not connect to database, check your env variables!\n")
|
fmt.Printf("Could not connect to database, check your env variables!\n")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if schema != "disabled" {
|
|
||||||
_, err = db.Exec(ctx, fmt.Sprintf("create schema if not exists %s", schema))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Migrating database")
|
fmt.Println("Migrating database")
|
||||||
dbi := stdlib.OpenDBFromPool(db)
|
dbi := stdlib.OpenDBFromPool(db)
|
||||||
defer dbi.Close()
|
defer dbi.Close()
|
||||||
|
|
||||||
driver, err := pgxd.WithInstance(dbi, &pgxd.Config{})
|
dbi.Exec("create schema if not exists keibi")
|
||||||
|
driver, err := pgxd.WithInstance(dbi, &pgxd.Config{
|
||||||
|
SchemaName: "keibi",
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
begin;
|
begin;
|
||||||
|
|
||||||
drop table oidc_handle;
|
drop table keibi.oidc_handle;
|
||||||
drop table users;
|
drop table keibi.users;
|
||||||
|
|
||||||
commit;
|
commit;
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
begin;
|
begin;
|
||||||
|
|
||||||
create table users(
|
create schema if not exists keibi;
|
||||||
|
|
||||||
|
create table keibi.users(
|
||||||
pk serial primary key,
|
pk serial primary key,
|
||||||
id uuid not null default gen_random_uuid(),
|
id uuid not null default gen_random_uuid(),
|
||||||
username varchar(256) not null unique,
|
username varchar(256) not null unique,
|
||||||
@ -12,8 +14,8 @@ create table users(
|
|||||||
last_seen timestamptz not null default now()::timestamptz
|
last_seen timestamptz not null default now()::timestamptz
|
||||||
);
|
);
|
||||||
|
|
||||||
create table oidc_handle(
|
create table keibi.oidc_handle(
|
||||||
user_pk integer not null references users(pk) on delete cascade,
|
user_pk integer not null references keibi.users(pk) on delete cascade,
|
||||||
provider varchar(256) not null,
|
provider varchar(256) not null,
|
||||||
|
|
||||||
id text not null,
|
id text not null,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
begin;
|
begin;
|
||||||
|
|
||||||
drop table sessions;
|
drop table keibi.sessions;
|
||||||
|
|
||||||
commit;
|
commit;
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
begin;
|
begin;
|
||||||
|
|
||||||
create table sessions(
|
create table keibi.sessions(
|
||||||
pk serial primary key,
|
pk serial primary key,
|
||||||
id uuid not null default gen_random_uuid(),
|
id uuid not null default gen_random_uuid(),
|
||||||
token varchar(128) not null unique,
|
token varchar(128) not null unique,
|
||||||
user_pk integer not null references users(pk) on delete cascade,
|
user_pk integer not null references keibi.users(pk) on delete cascade,
|
||||||
created_date timestamptz not null default now()::timestamptz,
|
created_date timestamptz not null default now()::timestamptz,
|
||||||
last_used timestamptz not null default now()::timestamptz,
|
last_used timestamptz not null default now()::timestamptz,
|
||||||
device varchar(1024)
|
device varchar(1024)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
begin;
|
begin;
|
||||||
|
|
||||||
drop table apikeys;
|
drop table keibi.apikeys;
|
||||||
|
|
||||||
commit;
|
commit;
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
begin;
|
begin;
|
||||||
|
|
||||||
create table apikeys(
|
create table keibi.apikeys(
|
||||||
pk serial primary key,
|
pk serial primary key,
|
||||||
id uuid not null default gen_random_uuid(),
|
id uuid not null default gen_random_uuid(),
|
||||||
name varchar(256) not null unique,
|
name varchar(256) not null unique,
|
||||||
token varchar(128) not null unique,
|
token varchar(128) not null unique,
|
||||||
claims jsonb not null,
|
claims jsonb not null,
|
||||||
|
|
||||||
created_by integer references users(pk) on delete cascade,
|
created_by integer references keibi.users(pk) on delete cascade,
|
||||||
created_at timestamptz not null default now()::timestamptz,
|
created_at timestamptz not null default now()::timestamptz,
|
||||||
last_used timestamptz not null default now()::timestamptz
|
last_used timestamptz not null default now()::timestamptz
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,14 +2,14 @@
|
|||||||
select
|
select
|
||||||
*
|
*
|
||||||
from
|
from
|
||||||
apikeys
|
keibi.apikeys
|
||||||
where
|
where
|
||||||
name = $1
|
name = $1
|
||||||
and token = $2;
|
and token = $2;
|
||||||
|
|
||||||
-- name: TouchApiKey :exec
|
-- name: TouchApiKey :exec
|
||||||
update
|
update
|
||||||
apikeys
|
keibi.apikeys
|
||||||
set
|
set
|
||||||
last_used = now()::timestamptz
|
last_used = now()::timestamptz
|
||||||
where
|
where
|
||||||
@ -19,18 +19,18 @@ where
|
|||||||
select
|
select
|
||||||
*
|
*
|
||||||
from
|
from
|
||||||
apikeys
|
keibi.apikeys
|
||||||
order by
|
order by
|
||||||
last_used;
|
last_used;
|
||||||
|
|
||||||
-- name: CreateApiKey :one
|
-- name: CreateApiKey :one
|
||||||
insert into apikeys(name, token, claims, created_by)
|
insert into keibi.apikeys(name, token, claims, created_by)
|
||||||
values ($1, $2, $3, $4)
|
values ($1, $2, $3, $4)
|
||||||
returning
|
returning
|
||||||
*;
|
*;
|
||||||
|
|
||||||
-- name: DeleteApiKey :one
|
-- name: DeleteApiKey :one
|
||||||
delete from apikeys
|
delete from keibi.apikeys
|
||||||
where id = $1
|
where id = $1
|
||||||
returning
|
returning
|
||||||
*;
|
*;
|
||||||
|
|||||||
@ -5,15 +5,15 @@ select
|
|||||||
s.last_used,
|
s.last_used,
|
||||||
sqlc.embed(u)
|
sqlc.embed(u)
|
||||||
from
|
from
|
||||||
users as u
|
keibi.users as u
|
||||||
inner join sessions as s on u.pk = s.user_pk
|
inner join keibi.sessions as s on u.pk = s.user_pk
|
||||||
where
|
where
|
||||||
s.token = $1
|
s.token = $1
|
||||||
limit 1;
|
limit 1;
|
||||||
|
|
||||||
-- name: TouchSession :exec
|
-- name: TouchSession :exec
|
||||||
update
|
update
|
||||||
sessions
|
keibi.sessions
|
||||||
set
|
set
|
||||||
last_used = now()::timestamptz
|
last_used = now()::timestamptz
|
||||||
where
|
where
|
||||||
@ -23,21 +23,21 @@ where
|
|||||||
select
|
select
|
||||||
s.*
|
s.*
|
||||||
from
|
from
|
||||||
sessions as s
|
keibi.sessions as s
|
||||||
inner join users as u on u.pk = s.user_pk
|
inner join keibi.users as u on u.pk = s.user_pk
|
||||||
where
|
where
|
||||||
u.pk = $1
|
u.pk = $1
|
||||||
order by
|
order by
|
||||||
last_used;
|
last_used;
|
||||||
|
|
||||||
-- name: CreateSession :one
|
-- name: CreateSession :one
|
||||||
insert into sessions(token, user_pk, device)
|
insert into keibi.sessions(token, user_pk, device)
|
||||||
values ($1, $2, $3)
|
values ($1, $2, $3)
|
||||||
returning
|
returning
|
||||||
*;
|
*;
|
||||||
|
|
||||||
-- name: DeleteSession :one
|
-- name: DeleteSession :one
|
||||||
delete from sessions as s using users as u
|
delete from keibi.sessions as s using keibi.users as u
|
||||||
where s.user_pk = u.pk
|
where s.user_pk = u.pk
|
||||||
and s.id = $1
|
and s.id = $1
|
||||||
and u.id = sqlc.arg(user_id)
|
and u.id = sqlc.arg(user_id)
|
||||||
@ -45,7 +45,7 @@ returning
|
|||||||
s.*;
|
s.*;
|
||||||
|
|
||||||
-- name: ClearOtherSessions :exec
|
-- name: ClearOtherSessions :exec
|
||||||
delete from sessions as s using users as u
|
delete from keibi.sessions as s using keibi.users as u
|
||||||
where s.user_pk = u.pk
|
where s.user_pk = u.pk
|
||||||
and s.id != @session_id
|
and s.id != @session_id
|
||||||
and u.id = @user_id;
|
and u.id = @user_id;
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
select
|
select
|
||||||
*
|
*
|
||||||
from
|
from
|
||||||
users
|
keibi.users
|
||||||
order by
|
order by
|
||||||
id
|
id
|
||||||
limit $1;
|
limit $1;
|
||||||
@ -11,7 +11,7 @@ limit $1;
|
|||||||
select
|
select
|
||||||
*
|
*
|
||||||
from
|
from
|
||||||
users
|
keibi.users
|
||||||
where
|
where
|
||||||
id >= sqlc.arg(after_id)
|
id >= sqlc.arg(after_id)
|
||||||
order by
|
order by
|
||||||
@ -26,8 +26,8 @@ select
|
|||||||
h.username,
|
h.username,
|
||||||
h.profile_url
|
h.profile_url
|
||||||
from
|
from
|
||||||
users as u
|
keibi.users as u
|
||||||
left join oidc_handle as h on u.pk = h.user_pk
|
left join keibi.oidc_handle as h on u.pk = h.user_pk
|
||||||
where (@use_id::boolean
|
where (@use_id::boolean
|
||||||
and u.id = @id)
|
and u.id = @id)
|
||||||
or (not @use_id
|
or (not @use_id
|
||||||
@ -37,7 +37,7 @@ where (@use_id::boolean
|
|||||||
select
|
select
|
||||||
*
|
*
|
||||||
from
|
from
|
||||||
users
|
keibi.users
|
||||||
where
|
where
|
||||||
email = sqlc.arg(login)
|
email = sqlc.arg(login)
|
||||||
or username = sqlc.arg(login)
|
or username = sqlc.arg(login)
|
||||||
@ -45,19 +45,19 @@ limit 1;
|
|||||||
|
|
||||||
-- name: TouchUser :exec
|
-- name: TouchUser :exec
|
||||||
update
|
update
|
||||||
users
|
keibi.users
|
||||||
set
|
set
|
||||||
last_used = now()::timestamptz
|
last_used = now()::timestamptz
|
||||||
where
|
where
|
||||||
pk = $1;
|
pk = $1;
|
||||||
|
|
||||||
-- name: CreateUser :one
|
-- name: CreateUser :one
|
||||||
insert into users(username, email, password, claims)
|
insert into keibi.users(username, email, password, claims)
|
||||||
values ($1, $2, $3, case when not exists (
|
values ($1, $2, $3, case when not exists (
|
||||||
select
|
select
|
||||||
*
|
*
|
||||||
from
|
from
|
||||||
users) then
|
keibi.users) then
|
||||||
sqlc.arg(first_claims)::jsonb
|
sqlc.arg(first_claims)::jsonb
|
||||||
else
|
else
|
||||||
sqlc.arg(claims)::jsonb
|
sqlc.arg(claims)::jsonb
|
||||||
@ -67,7 +67,7 @@ returning
|
|||||||
|
|
||||||
-- name: UpdateUser :one
|
-- name: UpdateUser :one
|
||||||
update
|
update
|
||||||
users
|
keibi.users
|
||||||
set
|
set
|
||||||
username = coalesce(sqlc.narg(username), username),
|
username = coalesce(sqlc.narg(username), username),
|
||||||
email = coalesce(sqlc.narg(email), email),
|
email = coalesce(sqlc.narg(email), email),
|
||||||
@ -79,7 +79,7 @@ returning
|
|||||||
*;
|
*;
|
||||||
|
|
||||||
-- name: DeleteUser :one
|
-- name: DeleteUser :one
|
||||||
delete from users
|
delete from keibi.users
|
||||||
where id = $1
|
where id = $1
|
||||||
returning
|
returning
|
||||||
*;
|
*;
|
||||||
|
|||||||
@ -30,15 +30,20 @@ sql:
|
|||||||
- db_type: "jsonb"
|
- db_type: "jsonb"
|
||||||
go_type:
|
go_type:
|
||||||
type: "interface{}"
|
type: "interface{}"
|
||||||
- column: "users.claims"
|
- column: "keibi.users.claims"
|
||||||
go_type:
|
go_type:
|
||||||
import: "github.com/golang-jwt/jwt/v5"
|
import: "github.com/golang-jwt/jwt/v5"
|
||||||
package: "jwt"
|
package: "jwt"
|
||||||
type: "MapClaims"
|
type: "MapClaims"
|
||||||
- column: "apikeys.claims"
|
- column: "keibi.apikeys.claims"
|
||||||
go_type:
|
go_type:
|
||||||
import: "github.com/golang-jwt/jwt/v5"
|
import: "github.com/golang-jwt/jwt/v5"
|
||||||
package: "jwt"
|
package: "jwt"
|
||||||
type: "MapClaims"
|
type: "MapClaims"
|
||||||
|
overrides:
|
||||||
|
go:
|
||||||
|
rename:
|
||||||
|
keibi_apikey: Apikey
|
||||||
|
keibi_oidc_handle: OidcHandle
|
||||||
|
keibi_session: Session
|
||||||
|
keibi_user: User
|
||||||
|
|||||||
@ -23,7 +23,7 @@ x-transcoder: &transcoder-base
|
|||||||
- "traefik.http.routers.transcoder.rule=PathPrefix(`/video`)"
|
- "traefik.http.routers.transcoder.rule=PathPrefix(`/video`)"
|
||||||
- "traefik.http.routers.transcoder.middlewares=phantom-token"
|
- "traefik.http.routers.transcoder.middlewares=phantom-token"
|
||||||
- "traefik.http.middlewares.phantom-token.forwardauth.address=http://auth:4568/auth/jwt"
|
- "traefik.http.middlewares.phantom-token.forwardauth.address=http://auth:4568/auth/jwt"
|
||||||
- "traefik.http.middlewares.phantom-token.forwardauth.authRequestHeaders=Authorization,X-Api-Key"
|
- "traefik.http.middlewares.phantom-token.forwardauth.authRequestHeaders=Authorization,Cookie,X-Api-Key"
|
||||||
- "traefik.http.middlewares.phantom-token.forwardauth.authResponseHeaders=Authorization"
|
- "traefik.http.middlewares.phantom-token.forwardauth.authResponseHeaders=Authorization"
|
||||||
develop:
|
develop:
|
||||||
watch:
|
watch:
|
||||||
@ -94,7 +94,7 @@ services:
|
|||||||
- "traefik.http.routers.api.rule=PathPrefix(`/api/`) || PathPrefix(`/swagger`)"
|
- "traefik.http.routers.api.rule=PathPrefix(`/api/`) || PathPrefix(`/swagger`)"
|
||||||
- "traefik.http.routers.api.middlewares=phantom-token"
|
- "traefik.http.routers.api.middlewares=phantom-token"
|
||||||
- "traefik.http.middlewares.phantom-token.forwardauth.address=http://auth:4568/auth/jwt"
|
- "traefik.http.middlewares.phantom-token.forwardauth.address=http://auth:4568/auth/jwt"
|
||||||
- "traefik.http.middlewares.phantom-token.forwardauth.authRequestHeaders=Authorization,X-Api-Key"
|
- "traefik.http.middlewares.phantom-token.forwardauth.authRequestHeaders=Authorization,Cookie,X-Api-Key"
|
||||||
- "traefik.http.middlewares.phantom-token.forwardauth.authResponseHeaders=Authorization"
|
- "traefik.http.middlewares.phantom-token.forwardauth.authResponseHeaders=Authorization"
|
||||||
develop:
|
develop:
|
||||||
watch:
|
watch:
|
||||||
@ -120,6 +120,7 @@ services:
|
|||||||
# Use this env var once we use mTLS for auth
|
# Use this env var once we use mTLS for auth
|
||||||
# - KYOO_URL=${KYOO_URL:-http://api:3567/api}
|
# - KYOO_URL=${KYOO_URL:-http://api:3567/api}
|
||||||
- KYOO_URL=${KYOO_URL:-http://traefik:8901/api}
|
- KYOO_URL=${KYOO_URL:-http://traefik:8901/api}
|
||||||
|
- KYOO_APIKEY=scanner-$KEIBI_APIKEY_SCANNER
|
||||||
- JWKS_URL=http://auth:4568/.well-known/jwks.json
|
- JWKS_URL=http://auth:4568/.well-known/jwks.json
|
||||||
- JWT_ISSUER=${PUBLIC_URL}
|
- JWT_ISSUER=${PUBLIC_URL}
|
||||||
volumes:
|
volumes:
|
||||||
@ -129,7 +130,7 @@ services:
|
|||||||
- "traefik.http.routers.scanner.rule=PathPrefix(`/scanner/`)"
|
- "traefik.http.routers.scanner.rule=PathPrefix(`/scanner/`)"
|
||||||
- "traefik.http.routers.scanner.middlewares=phantom-token"
|
- "traefik.http.routers.scanner.middlewares=phantom-token"
|
||||||
- "traefik.http.middlewares.phantom-token.forwardauth.address=http://auth:4568/auth/jwt"
|
- "traefik.http.middlewares.phantom-token.forwardauth.address=http://auth:4568/auth/jwt"
|
||||||
- "traefik.http.middlewares.phantom-token.forwardauth.authRequestHeaders=Authorization,X-Api-Key"
|
- "traefik.http.middlewares.phantom-token.forwardauth.authRequestHeaders=Authorization,Cookie,X-Api-Key"
|
||||||
- "traefik.http.middlewares.phantom-token.forwardauth.authResponseHeaders=Authorization"
|
- "traefik.http.middlewares.phantom-token.forwardauth.authResponseHeaders=Authorization"
|
||||||
command: fastapi dev scanner --host 0.0.0.0 --port 4389
|
command: fastapi dev scanner --host 0.0.0.0 --port 4389
|
||||||
develop:
|
develop:
|
||||||
|
|||||||
@ -19,7 +19,7 @@ x-transcoder: &transcoder-base
|
|||||||
- "traefik.http.routers.transcoder.rule=PathPrefix(`/video`)"
|
- "traefik.http.routers.transcoder.rule=PathPrefix(`/video`)"
|
||||||
- "traefik.http.routers.transcoder.middlewares=phantom-token"
|
- "traefik.http.routers.transcoder.middlewares=phantom-token"
|
||||||
- "traefik.http.middlewares.phantom-token.forwardauth.address=http://auth:4568/auth/jwt"
|
- "traefik.http.middlewares.phantom-token.forwardauth.address=http://auth:4568/auth/jwt"
|
||||||
- "traefik.http.middlewares.phantom-token.forwardauth.authRequestHeaders=Authorization,X-Api-Key"
|
- "traefik.http.middlewares.phantom-token.forwardauth.authRequestHeaders=Authorization,Cookie,X-Api-Key"
|
||||||
- "traefik.http.middlewares.phantom-token.forwardauth.authResponseHeaders=Authorization"
|
- "traefik.http.middlewares.phantom-token.forwardauth.authResponseHeaders=Authorization"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
@ -64,7 +64,7 @@ services:
|
|||||||
- "traefik.http.routers.api.rule=PathPrefix(`/api/`) || PathPrefix(`/swagger`)"
|
- "traefik.http.routers.api.rule=PathPrefix(`/api/`) || PathPrefix(`/swagger`)"
|
||||||
- "traefik.http.routers.api.middlewares=phantom-token"
|
- "traefik.http.routers.api.middlewares=phantom-token"
|
||||||
- "traefik.http.middlewares.phantom-token.forwardauth.address=http://auth:4568/auth/jwt"
|
- "traefik.http.middlewares.phantom-token.forwardauth.address=http://auth:4568/auth/jwt"
|
||||||
- "traefik.http.middlewares.phantom-token.forwardauth.authRequestHeaders=Authorization,X-Api-Key"
|
- "traefik.http.middlewares.phantom-token.forwardauth.authRequestHeaders=Authorization,Cookie,X-Api-Key"
|
||||||
- "traefik.http.middlewares.phantom-token.forwardauth.authResponseHeaders=Authorization"
|
- "traefik.http.middlewares.phantom-token.forwardauth.authResponseHeaders=Authorization"
|
||||||
|
|
||||||
scanner:
|
scanner:
|
||||||
@ -77,6 +77,7 @@ services:
|
|||||||
# Use this env var once we use mTLS for auth
|
# Use this env var once we use mTLS for auth
|
||||||
# - KYOO_URL=${KYOO_URL:-http://api:3567/api}
|
# - KYOO_URL=${KYOO_URL:-http://api:3567/api}
|
||||||
- KYOO_URL=${KYOO_URL:-http://traefik:8901/api}
|
- KYOO_URL=${KYOO_URL:-http://traefik:8901/api}
|
||||||
|
- KYOO_APIKEY=scanner-$KEIBI_APIKEY_SCANNER
|
||||||
- JWKS_URL=http://auth:4568/.well-known/jwks.json
|
- JWKS_URL=http://auth:4568/.well-known/jwks.json
|
||||||
- JWT_ISSUER=${PUBLIC_URL}
|
- JWT_ISSUER=${PUBLIC_URL}
|
||||||
volumes:
|
volumes:
|
||||||
@ -86,7 +87,7 @@ services:
|
|||||||
- "traefik.http.routers.scanner.rule=PathPrefix(`/scanner/`)"
|
- "traefik.http.routers.scanner.rule=PathPrefix(`/scanner/`)"
|
||||||
- "traefik.http.routers.scanner.middlewares=phantom-token"
|
- "traefik.http.routers.scanner.middlewares=phantom-token"
|
||||||
- "traefik.http.middlewares.phantom-token.forwardauth.address=http://auth:4568/auth/jwt"
|
- "traefik.http.middlewares.phantom-token.forwardauth.address=http://auth:4568/auth/jwt"
|
||||||
- "traefik.http.middlewares.phantom-token.forwardauth.authRequestHeaders=Authorization,X-Api-Key"
|
- "traefik.http.middlewares.phantom-token.forwardauth.authRequestHeaders=Authorization,Cookie,X-Api-Key"
|
||||||
- "traefik.http.middlewares.phantom-token.forwardauth.authResponseHeaders=Authorization"
|
- "traefik.http.middlewares.phantom-token.forwardauth.authResponseHeaders=Authorization"
|
||||||
|
|
||||||
transcoder:
|
transcoder:
|
||||||
|
|||||||
@ -7,10 +7,9 @@ const writeAccounts = (accounts: Account[]) => {
|
|||||||
storeValue("accounts", accounts);
|
storeValue("accounts", accounts);
|
||||||
if (Platform.OS === "web") {
|
if (Platform.OS === "web") {
|
||||||
const selected = accounts.find((x) => x.selected);
|
const selected = accounts.find((x) => x.selected);
|
||||||
if (!selected) return;
|
|
||||||
setCookie("account", selected);
|
setCookie("account", selected);
|
||||||
// cookie used for images and videos since we can't add Authorization headers in img or video tags.
|
// cookie used for images and videos since we can't add Authorization headers in img or video tags.
|
||||||
setCookie("X-Bearer", selected?.token);
|
setCookie("X-Bearer", selected?.token, { skipBase64: true });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -15,8 +15,14 @@ function fromBase64(b64: string) {
|
|||||||
return Buffer.from(b64, "base64").toString("utf8");
|
return Buffer.from(b64, "base64").toString("utf8");
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setCookie = (key: string, val?: unknown) => {
|
export const setCookie = (
|
||||||
const value = toBase64(typeof val !== "string" ? JSON.stringify(val) : val);
|
key: string,
|
||||||
|
val?: unknown,
|
||||||
|
opts?: { skipBase64?: boolean },
|
||||||
|
) => {
|
||||||
|
const value = opts?.skipBase64
|
||||||
|
? val
|
||||||
|
: toBase64(typeof val !== "string" ? JSON.stringify(val) : val);
|
||||||
const d = new Date();
|
const d = new Date();
|
||||||
// A year
|
// A year
|
||||||
d.setTime(d.getTime() + 365 * 24 * 60 * 60 * 1000);
|
d.setTime(d.getTime() + 365 * 24 * 60 * 60 * 1000);
|
||||||
|
|||||||
@ -11,7 +11,7 @@ LIBRARY_IGNORE_PATTERN=".*/[dD]ownloads?/.*"
|
|||||||
THEMOVIEDB_API_ACCESS_TOKEN=""
|
THEMOVIEDB_API_ACCESS_TOKEN=""
|
||||||
|
|
||||||
KYOO_URL="http://api:3567/api"
|
KYOO_URL="http://api:3567/api"
|
||||||
KYOO_APIKEY=""
|
KYOO_APIKEY=scanner-$KEIBI_APIKEY_SCANNER
|
||||||
|
|
||||||
JWKS_URL="http://auth:4568/.well-known/jwks.json"
|
JWKS_URL="http://auth:4568/.well-known/jwks.json"
|
||||||
JWT_ISSUER=$PUBLIC_URL
|
JWT_ISSUER=$PUBLIC_URL
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
from asyncio import CancelledError, TaskGroup, create_task
|
from asyncio import CancelledError, TaskGroup, create_task, sleep
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
@ -48,11 +48,16 @@ async def background_startup(
|
|||||||
processor: RequestProcessor,
|
processor: RequestProcessor,
|
||||||
is_master: bool | None,
|
is_master: bool | None,
|
||||||
):
|
):
|
||||||
|
async def scan():
|
||||||
|
# wait for everything to startup & resume before scanning
|
||||||
|
await sleep(30)
|
||||||
|
await scanner.scan(remove_deleted=True)
|
||||||
|
|
||||||
async with TaskGroup() as tg:
|
async with TaskGroup() as tg:
|
||||||
_ = tg.create_task(processor.listen(tg))
|
_ = tg.create_task(processor.listen(tg))
|
||||||
if is_master:
|
if is_master:
|
||||||
_ = tg.create_task(scanner.monitor())
|
_ = tg.create_task(scanner.monitor())
|
||||||
_ = tg.create_task(scanner.scan(remove_deleted=True))
|
_ = tg.create_task(scan())
|
||||||
|
|
||||||
|
|
||||||
async def cancel():
|
async def cancel():
|
||||||
|
|||||||
@ -7,7 +7,7 @@ from statistics import mean
|
|||||||
from types import TracebackType
|
from types import TracebackType
|
||||||
from typing import Any, cast, override
|
from typing import Any, cast, override
|
||||||
|
|
||||||
from aiohttp import ClientSession
|
from aiohttp import ClientResponseError, ClientSession
|
||||||
from langcodes import Language
|
from langcodes import Language
|
||||||
|
|
||||||
from ..models.collection import Collection, CollectionTranslation
|
from ..models.collection import Collection, CollectionTranslation
|
||||||
@ -643,7 +643,21 @@ class TheMovieDatabase(Provider):
|
|||||||
async with self._client.get(path, params=params) as r:
|
async with self._client.get(path, params=params) as r:
|
||||||
if not_found_fail and r.status == 404:
|
if not_found_fail and r.status == 404:
|
||||||
raise ProviderError(not_found_fail)
|
raise ProviderError(not_found_fail)
|
||||||
r.raise_for_status()
|
if r.status == 429:
|
||||||
|
retry_after = r.headers.get("Retry-After")
|
||||||
|
delay = float(retry_after) if retry_after else 2.0
|
||||||
|
await asyncio.sleep(delay)
|
||||||
|
return await self._get(
|
||||||
|
path, params=params, not_found_fail=not_found_fail
|
||||||
|
)
|
||||||
|
if r.status >= 400:
|
||||||
|
raise ClientResponseError(
|
||||||
|
r.request_info,
|
||||||
|
r.history,
|
||||||
|
status=r.status,
|
||||||
|
message=await r.text(),
|
||||||
|
headers=r.headers,
|
||||||
|
)
|
||||||
return await r.json()
|
return await r.json()
|
||||||
|
|
||||||
def _map_genres(self, genres: Generator[int]) -> list[Genre]:
|
def _map_genres(self, genres: Generator[int]) -> list[Genre]:
|
||||||
|
|||||||
@ -34,10 +34,6 @@ POSTGRES_SERVER=
|
|||||||
POSTGRES_PORT=5432
|
POSTGRES_PORT=5432
|
||||||
# can also be "require" ("prefer" is not supported)
|
# can also be "require" ("prefer" is not supported)
|
||||||
POSTGRES_SSLMODE="disable"
|
POSTGRES_SSLMODE="disable"
|
||||||
# Default is gocoder, you can specify "disabled" to use the default search_path of the user.
|
|
||||||
# If this is not "disabled", the schema will be created (if it does not exists) and
|
|
||||||
# the search_path of the user will be ignored (only the schema specified will be used).
|
|
||||||
POSTGRES_SCHEMA=gocoder
|
|
||||||
|
|
||||||
# Storage backend
|
# Storage backend
|
||||||
# There are two currently supported backends: local filesystem and s3.
|
# There are two currently supported backends: local filesystem and s3.
|
||||||
|
|||||||
@ -3,14 +3,14 @@ module github.com/zoriya/kyoo/transcoder
|
|||||||
go 1.24.2
|
go 1.24.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/asticode/go-astisub v0.35.0
|
github.com/asticode/go-astisub v0.36.0
|
||||||
github.com/aws/aws-sdk-go-v2 v1.39.3
|
github.com/aws/aws-sdk-go-v2 v1.39.5
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.88.6
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.89.1
|
||||||
github.com/disintegration/imaging v1.6.2
|
github.com/disintegration/imaging v1.6.2
|
||||||
github.com/golang-migrate/migrate/v4 v4.19.0
|
github.com/golang-migrate/migrate/v4 v4.19.0
|
||||||
|
github.com/jackc/pgx/v5 v5.7.6
|
||||||
github.com/labstack/echo-jwt/v4 v4.3.1
|
github.com/labstack/echo-jwt/v4 v4.3.1
|
||||||
github.com/labstack/echo/v4 v4.13.4
|
github.com/labstack/echo/v4 v4.13.4
|
||||||
github.com/lib/pq v1.10.9
|
|
||||||
github.com/swaggo/echo-swagger v1.4.1
|
github.com/swaggo/echo-swagger v1.4.1
|
||||||
github.com/swaggo/swag v1.16.6
|
github.com/swaggo/swag v1.16.6
|
||||||
gitlab.com/opennota/screengen v1.0.2
|
gitlab.com/opennota/screengen v1.0.2
|
||||||
@ -20,38 +20,46 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
github.com/asticode/go-astikit v0.20.0 // indirect
|
github.com/asticode/go-astikit v0.56.0 // indirect
|
||||||
github.com/asticode/go-astits v1.8.0 // indirect
|
github.com/asticode/go-astits v1.14.0 // indirect
|
||||||
github.com/ghodss/yaml v1.0.0 // indirect
|
github.com/ghodss/yaml v1.0.0 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
github.com/go-openapi/jsonpointer v0.22.1 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
github.com/go-openapi/jsonreference v0.21.2 // indirect
|
||||||
github.com/go-openapi/spec v0.21.0 // indirect
|
github.com/go-openapi/spec v0.22.0 // indirect
|
||||||
github.com/go-openapi/swag v0.23.1 // indirect
|
github.com/go-openapi/swag/conv v0.25.1 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/go-openapi/swag/jsonname v0.25.1 // indirect
|
||||||
github.com/mailru/easyjson v0.9.0 // indirect
|
github.com/go-openapi/swag/jsonutils v0.25.1 // indirect
|
||||||
|
github.com/go-openapi/swag/loading v0.25.1 // indirect
|
||||||
|
github.com/go-openapi/swag/stringutils v0.25.1 // indirect
|
||||||
|
github.com/go-openapi/swag/typeutils v0.25.1 // indirect
|
||||||
|
github.com/go-openapi/swag/yamlutils v0.25.1 // indirect
|
||||||
|
github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
github.com/swaggo/files/v2 v2.0.2 // indirect
|
github.com/swaggo/files/v2 v2.0.2 // indirect
|
||||||
golang.org/x/mod v0.28.0 // indirect
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
golang.org/x/tools v0.37.0 // indirect
|
golang.org/x/mod v0.29.0 // indirect
|
||||||
|
golang.org/x/tools v0.38.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2 // indirect
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.31.14
|
github.com/aws/aws-sdk-go-v2/config v1.31.16
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.18 // indirect
|
github.com/aws/aws-sdk-go-v2/credentials v1.18.20 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.10 // indirect
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.12 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.10 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.12 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.10 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.12 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.10 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.12 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.1 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.3 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.10 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.12 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.10 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.12 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.29.7 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sso v1.30.0 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.2 // indirect
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.4 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.38.8 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sts v1.39.0 // indirect
|
||||||
github.com/aws/smithy-go v1.23.1 // indirect
|
github.com/aws/smithy-go v1.23.1 // indirect
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
@ -62,18 +70,18 @@ require (
|
|||||||
github.com/lestrrat-go/blackmagic v1.0.4 // indirect
|
github.com/lestrrat-go/blackmagic v1.0.4 // indirect
|
||||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||||
github.com/lestrrat-go/httprc/v3 v3.0.1
|
github.com/lestrrat-go/httprc/v3 v3.0.1
|
||||||
github.com/lestrrat-go/jwx/v3 v3.0.10
|
github.com/lestrrat-go/jwx/v3 v3.0.12
|
||||||
github.com/lestrrat-go/option v1.0.1 // indirect
|
github.com/lestrrat-go/option v1.0.1 // indirect
|
||||||
github.com/lestrrat-go/option/v2 v2.0.0 // indirect
|
github.com/lestrrat-go/option/v2 v2.0.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/segmentio/asm v1.2.0 // indirect
|
github.com/segmentio/asm v1.2.1 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||||
golang.org/x/crypto v0.42.0 // indirect
|
golang.org/x/crypto v0.43.0 // indirect
|
||||||
golang.org/x/image v0.29.0 // indirect
|
golang.org/x/image v0.32.0 // indirect
|
||||||
golang.org/x/net v0.44.0 // indirect
|
golang.org/x/net v0.46.0 // indirect
|
||||||
golang.org/x/sys v0.36.0 // indirect
|
golang.org/x/sys v0.37.0 // indirect
|
||||||
golang.org/x/text v0.30.0
|
golang.org/x/text v0.30.0
|
||||||
golang.org/x/time v0.12.0 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@ -4,46 +4,49 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc
|
|||||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/asticode/go-astikit v0.20.0 h1:+7N+J4E4lWx2QOkRdOf6DafWJMv6O4RRfgClwQokrH8=
|
|
||||||
github.com/asticode/go-astikit v0.20.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0=
|
github.com/asticode/go-astikit v0.20.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0=
|
||||||
github.com/asticode/go-astisub v0.35.0 h1:wnELGJMeJbavW//X7nLTy97L3iblub7tO1VSeHnZBdA=
|
github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0=
|
||||||
github.com/asticode/go-astisub v0.35.0/go.mod h1:WTkuSzFB+Bp7wezuSf2Oxulj5A8zu2zLRVFf6bIFQK8=
|
github.com/asticode/go-astikit v0.56.0 h1:DmD2p7YnvxiPdF0h+dRmos3bsejNEXbycENsY5JfBqw=
|
||||||
github.com/asticode/go-astits v1.8.0 h1:rf6aiiGn/QhlFjNON1n5plqF3Fs025XLUwiQ0NB6oZg=
|
github.com/asticode/go-astikit v0.56.0/go.mod h1:fV43j20UZYfXzP9oBn33udkvCvDvCDhzjVqoLFuuYZE=
|
||||||
|
github.com/asticode/go-astisub v0.36.0 h1:AatpRp9xZSv/pUoCnsx/NmKEhyjkyHFwrkkon4kgDBI=
|
||||||
|
github.com/asticode/go-astisub v0.36.0/go.mod h1:WTkuSzFB+Bp7wezuSf2Oxulj5A8zu2zLRVFf6bIFQK8=
|
||||||
github.com/asticode/go-astits v1.8.0/go.mod h1:DkOWmBNQpnr9mv24KfZjq4JawCFX1FCqjLVGvO0DygQ=
|
github.com/asticode/go-astits v1.8.0/go.mod h1:DkOWmBNQpnr9mv24KfZjq4JawCFX1FCqjLVGvO0DygQ=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.39.3 h1:h7xSsanJ4EQJXG5iuW4UqgP7qBopLpj84mpkNx3wPjM=
|
github.com/asticode/go-astits v1.14.0 h1:zkgnZzipx2XX5mWycqsSBeEyDH58+i4HtyF4j2ROb00=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.39.3/go.mod h1:yWSxrnioGUZ4WVv9TgMrNUeLV3PFESn/v+6T/Su8gnM=
|
github.com/asticode/go-astits v1.14.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI=
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.39.5 h1:e/SXuia3rkFtapghJROrydtQpfQaaUgd1cUvyO1mp2w=
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.39.5/go.mod h1:yWSxrnioGUZ4WVv9TgMrNUeLV3PFESn/v+6T/Su8gnM=
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2 h1:t9yYsydLYNBk9cJ73rgPhPWqOh/52fcWDQB5b1JsKSY=
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2 h1:t9yYsydLYNBk9cJ73rgPhPWqOh/52fcWDQB5b1JsKSY=
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2/go.mod h1:IusfVNTmiSN3t4rhxWFaBAqn+mcNdwKtPcV16eYdgko=
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2/go.mod h1:IusfVNTmiSN3t4rhxWFaBAqn+mcNdwKtPcV16eYdgko=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.31.14 h1:kj/KpDqvt0UqcEL3WOvCykE9QUpBb6b23hQdnXe+elo=
|
github.com/aws/aws-sdk-go-v2/config v1.31.16 h1:E4Tz+tJiPc7kGnXwIfCyUj6xHJNpENlY11oKpRTgsjc=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.31.14/go.mod h1:X5PaY6QCzViihn/ru7VxnIamcJQrG9NSeTxuSKm2YtU=
|
github.com/aws/aws-sdk-go-v2/config v1.31.16/go.mod h1:2S9hBElpCyGMifv14WxQ7EfPumgoeCPZUpuPX8VtW34=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.18 h1:5AfxTvDN0AJoA7rg/yEc0sHhl6/B9fZ+NtiQuOjWGQM=
|
github.com/aws/aws-sdk-go-v2/credentials v1.18.20 h1:KFndAnHd9NUuzikHjQ8D5CfFVO+bgELkmcGY8yAw98Q=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.18/go.mod h1:m9mE1mJ1s7zI6rrt7V3RQU2SCgUbNaphlfqEksLp+Fs=
|
github.com/aws/aws-sdk-go-v2/credentials v1.18.20/go.mod h1:9mCi28a+fmBHSQ0UM79omkz6JtN+PEsvLrnG36uoUv0=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.10 h1:UuGVOX48oP4vgQ36oiKmW9RuSeT8jlgQgBFQD+HUiHY=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.12 h1:VO3FIM2TDbm0kqp6sFNR0PbioXJb/HzCDW6NtIZpIWE=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.10/go.mod h1:vM/Ini41PzvudT4YkQyE/+WiQJiQ6jzeDyU8pQKwCac=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.12/go.mod h1:6C39gB8kg82tx3r72muZSrNhHia9rjGkX7ORaS2GKNE=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.10 h1:mj/bdWleWEh81DtpdHKkw41IrS+r3uw1J/VQtbwYYp8=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.12 h1:p/9flfXdoAnwJnuW9xHEAFY22R3A6skYkW19JFF9F+8=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.10/go.mod h1:7+oEMxAZWP8gZCyjcm9VicI0M61Sx4DJtcGfKYv2yKQ=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.12/go.mod h1:ZTLHakoVCTtW8AaLGSwJ3LXqHD9uQKnOcv1TrpO6u2k=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.10 h1:wh+/mn57yhUrFtLIxyFPh2RgxgQz/u+Yrf7hiHGHqKY=
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.12 h1:2lTWFvRcnWFFLzHWmtddu5MTchc5Oj2OOey++99tPZ0=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.10/go.mod h1:7zirD+ryp5gitJJ2m1BBux56ai8RIRDykXZrJSp540w=
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.12/go.mod h1:hI92pK+ho8HVcWMHKHrK3Uml4pfG7wvL86FzO0LVtQQ=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.10 h1:FHw90xCTsofzk6vjU808TSuDtDfOOKPNdz5Weyc3tUI=
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.12 h1:itu4KHu8JK/N6NcLIISlf3LL1LccMqruLUXZ9y7yBZw=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.10/go.mod h1:n8jdIE/8F3UYkg8O4IGkQpn2qUmapg/1K1yl29/uf/c=
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.12/go.mod h1:i+6vTU3xziikTY3vcox23X8pPGW5X3wVgd1VZ7ha+x8=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 h1:xtuxji5CS0JknaXoACOunXOYOQzgfTvGAc9s2QdCJA4=
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 h1:xtuxji5CS0JknaXoACOunXOYOQzgfTvGAc9s2QdCJA4=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2/go.mod h1:zxwi0DIR0rcRcgdbl7E2MSOvxDyyXGBlScvBkARFaLQ=
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2/go.mod h1:zxwi0DIR0rcRcgdbl7E2MSOvxDyyXGBlScvBkARFaLQ=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.1 h1:ne+eepnDB2Wh5lHKzELgEncIqeVlQ1rSF9fEa4r5I+A=
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.3 h1:NEe7FaViguRQEm8zl8Ay/kC/QRsMtWUiCGZajQIsLdc=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.1/go.mod h1:u0Jkg0L+dcG1ozUq21uFElmpbmjBnhHR5DELHIme4wg=
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.3/go.mod h1:JLuCKu5VfiLBBBl/5IzZILU7rxS0koQpHzMOCzycOJU=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.10 h1:DRND0dkCKtJzCj4Xl4OpVbXZgfttY5q712H9Zj7qc/0=
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.12 h1:MM8imH7NZ0ovIVX7D2RxfMDv7Jt9OiUXkcQ+GqywA7M=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.10/go.mod h1:tGGNmJKOTernmR2+VJ0fCzQRurcPZj9ut60Zu5Fi6us=
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.12/go.mod h1:gf4OGwdNkbEsb7elw2Sy76odfhwNktWII3WgvQgQQ6w=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.10 h1:DA+Hl5adieRyFvE7pCvBWm3VOZTRexGVkXw33SUqNoY=
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.12 h1:R3uW0iKl8rgNEXNjVGliW/oMEh9fO/LlUEV8RvIFr1I=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.10/go.mod h1:L+A89dH3/gr8L4ecrdzuXUYd1znoko6myzndVGZx/DA=
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.12/go.mod h1:XEttbEr5yqsw8ebi7vlDoGJJjMXRez4/s9pibpJyL5s=
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.88.6 h1:Hcb4yllr4GTOHC/BKjEklxWhciWMHIqzeCI9oYf1OIk=
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.89.1 h1:Dq82AV+Qxpno/fG162eAhnD8d48t9S+GZCfz7yv1VeA=
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.88.6/go.mod h1:N/iojY+8bW3MYol9NUMuKimpSbPEur75cuI1SmtonFM=
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.89.1/go.mod h1:MbKLznDKpf7PnSonNRUVYZzfP0CeLkRIUexeblgKcU4=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.29.7 h1:fspVFg6qMx0svs40YgRmE7LZXh9VRZvTT35PfdQR6FM=
|
github.com/aws/aws-sdk-go-v2/service/sso v1.30.0 h1:xHXvxst78wBpJFgDW07xllOx0IAzbryrSdM4nMVQ4Dw=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.29.7/go.mod h1:BQTKL3uMECaLaUV3Zc2L4Qybv8C6BIXjuu1dOPyxTQs=
|
github.com/aws/aws-sdk-go-v2/service/sso v1.30.0/go.mod h1:/e8m+AO6HNPPqMyfKRtzZ9+mBF5/x1Wk8QiDva4m07I=
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.2 h1:scVnW+NLXasGOhy7HhkdT9AGb6kjgW7fJ5xYkUaqHs0=
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.4 h1:tBw2Qhf0kj4ZwtsVpDiVRU3zKLvjvjgIjHMKirxXg8M=
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.2/go.mod h1:FRNCY3zTEWZXBKm2h5UBUPvCVDOecTad9KhynDyGBc0=
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.4/go.mod h1:Deq4B7sRM6Awq/xyOBlxBdgW8/Z926KYNNaGMW2lrkA=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.38.8 h1:xSL4IV19pKDASL2fjWXRfTGmZddPiPPZNPpbv6uZQZY=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.39.0 h1:C+BRMnasSYFcgDw8o9H5hzehKzXyAb9GY5v/8bP9DUY=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.38.8/go.mod h1:L1xxV3zAdB+qVrVW/pBIrIAnHFWHo6FBbFe4xOGsG/o=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.39.0/go.mod h1:4EjU+4mIx6+JqKQkruye+CaigV7alL3thVPfDd9VlMs=
|
||||||
github.com/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M=
|
github.com/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M=
|
||||||
github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
|
github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
|
||||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||||
@ -75,14 +78,29 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
|||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
|
github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk=
|
||||||
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
|
github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM=
|
||||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU=
|
||||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ=
|
||||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
github.com/go-openapi/spec v0.22.0 h1:xT/EsX4frL3U09QviRIZXvkh80yibxQmtoEvyqug0Tw=
|
||||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
github.com/go-openapi/spec v0.22.0/go.mod h1:K0FhKxkez8YNS94XzF8YKEMULbFrRw4m15i2YUht4L0=
|
||||||
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
|
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||||
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
|
github.com/go-openapi/swag/conv v0.25.1 h1:+9o8YUg6QuqqBM5X6rYL/p1dpWeZRhoIt9x7CCP+he0=
|
||||||
|
github.com/go-openapi/swag/conv v0.25.1/go.mod h1:Z1mFEGPfyIKPu0806khI3zF+/EUXde+fdeksUl2NiDs=
|
||||||
|
github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU=
|
||||||
|
github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo=
|
||||||
|
github.com/go-openapi/swag/jsonutils v0.25.1 h1:AihLHaD0brrkJoMqEZOBNzTLnk81Kg9cWr+SPtxtgl8=
|
||||||
|
github.com/go-openapi/swag/jsonutils v0.25.1/go.mod h1:JpEkAjxQXpiaHmRO04N1zE4qbUEg3b7Udll7AMGTNOo=
|
||||||
|
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1 h1:DSQGcdB6G0N9c/KhtpYc71PzzGEIc/fZ1no35x4/XBY=
|
||||||
|
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1/go.mod h1:kjmweouyPwRUEYMSrbAidoLMGeJ5p6zdHi9BgZiqmsg=
|
||||||
|
github.com/go-openapi/swag/loading v0.25.1 h1:6OruqzjWoJyanZOim58iG2vj934TysYVptyaoXS24kw=
|
||||||
|
github.com/go-openapi/swag/loading v0.25.1/go.mod h1:xoIe2EG32NOYYbqxvXgPzne989bWvSNoWoyQVWEZicc=
|
||||||
|
github.com/go-openapi/swag/stringutils v0.25.1 h1:Xasqgjvk30eUe8VKdmyzKtjkVjeiXx1Iz0zDfMNpPbw=
|
||||||
|
github.com/go-openapi/swag/stringutils v0.25.1/go.mod h1:JLdSAq5169HaiDUbTvArA2yQxmgn4D6h4A+4HqVvAYg=
|
||||||
|
github.com/go-openapi/swag/typeutils v0.25.1 h1:rD/9HsEQieewNt6/k+JBwkxuAHktFtH3I3ysiFZqukA=
|
||||||
|
github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8=
|
||||||
|
github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91oSJLDPF1bmGk=
|
||||||
|
github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg=
|
||||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
@ -98,8 +116,16 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY
|
|||||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6 h1:D/V0gu4zQ3cL2WKeVNVM4r2gLxGGf6McLwgXzRTo2RQ=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
|
||||||
|
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
@ -112,20 +138,22 @@ github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0
|
|||||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||||
github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=
|
github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=
|
||||||
github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw=
|
github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw=
|
||||||
|
github.com/lestrrat-go/dsig v1.0.0 h1:OE09s2r9Z81kxzJYRn07TFM9XA4akrUdoMwr0L8xj38=
|
||||||
|
github.com/lestrrat-go/dsig v1.0.0/go.mod h1:dEgoOYYEJvW6XGbLasr8TFcAxoWrKlbQvmJgCR0qkDo=
|
||||||
|
github.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY=
|
||||||
|
github.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU=
|
||||||
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
|
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
|
||||||
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
|
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
|
||||||
github.com/lestrrat-go/httprc/v3 v3.0.1 h1:3n7Es68YYGZb2Jf+k//llA4FTZMl3yCwIjFIk4ubevI=
|
github.com/lestrrat-go/httprc/v3 v3.0.1 h1:3n7Es68YYGZb2Jf+k//llA4FTZMl3yCwIjFIk4ubevI=
|
||||||
github.com/lestrrat-go/httprc/v3 v3.0.1/go.mod h1:2uAvmbXE4Xq8kAUjVrZOq1tZVYYYs5iP62Cmtru00xk=
|
github.com/lestrrat-go/httprc/v3 v3.0.1/go.mod h1:2uAvmbXE4Xq8kAUjVrZOq1tZVYYYs5iP62Cmtru00xk=
|
||||||
github.com/lestrrat-go/jwx/v3 v3.0.10 h1:XuoCBhZBncRIjMQ32HdEc76rH0xK/Qv2wq5TBouYJDw=
|
github.com/lestrrat-go/jwx/v3 v3.0.12 h1:p25r68Y4KrbBdYjIsQweYxq794CtGCzcrc5dGzJIRjg=
|
||||||
github.com/lestrrat-go/jwx/v3 v3.0.10/go.mod h1:kNMedLgTpHvPJkK5EMVa1JFz+UVyY2dMmZKu3qjl/Pk=
|
github.com/lestrrat-go/jwx/v3 v3.0.12/go.mod h1:HiUSaNmMLXgZ08OmGBaPVvoZQgJVOQphSrGr5zMamS8=
|
||||||
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
|
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
|
||||||
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||||
github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss=
|
github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss=
|
||||||
github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg=
|
github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg=
|
||||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
|
||||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
@ -147,14 +175,16 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
|
||||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/swaggo/echo-swagger v1.4.1 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk=
|
github.com/swaggo/echo-swagger v1.4.1 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk=
|
||||||
github.com/swaggo/echo-swagger v1.4.1/go.mod h1:C8bSi+9yH2FLZsnhqMZLIZddpUxZdBYuNHbtaS1Hljc=
|
github.com/swaggo/echo-swagger v1.4.1/go.mod h1:C8bSi+9yH2FLZsnhqMZLIZddpUxZdBYuNHbtaS1Hljc=
|
||||||
github.com/swaggo/files/v2 v2.0.2 h1:Bq4tgS/yxLB/3nwOMcul5oLEUKa877Ykgz3CJMVbQKU=
|
github.com/swaggo/files/v2 v2.0.2 h1:Bq4tgS/yxLB/3nwOMcul5oLEUKa877Ykgz3CJMVbQKU=
|
||||||
@ -179,36 +209,38 @@ go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/Wgbsd
|
|||||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.29.0 h1:HcdsyR4Gsuys/Axh0rDEmlBmB68rW1U9BUdB3UVHsas=
|
golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ=
|
||||||
golang.org/x/image v0.29.0/go.mod h1:RVJROnf3SLK8d26OW91j4FrIHGbsJ8QnbEocVTOWQDA=
|
golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc=
|
||||||
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||||
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"slices"
|
||||||
|
|
||||||
_ "github.com/zoriya/kyoo/transcoder/docs"
|
_ "github.com/zoriya/kyoo/transcoder/docs"
|
||||||
|
|
||||||
@ -37,6 +38,35 @@ func ErrorHandler(err error, c echo.Context) {
|
|||||||
}{Errors: []string{message}})
|
}{Errors: []string{message}})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RequireCorePlayPermission(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
user := c.Get("user")
|
||||||
|
if user == nil {
|
||||||
|
return echo.NewHTTPError(http.StatusForbidden, "missing jwt")
|
||||||
|
}
|
||||||
|
token, ok := user.(*jwt.Token)
|
||||||
|
if !ok {
|
||||||
|
return echo.NewHTTPError(http.StatusForbidden, "invalid jwt")
|
||||||
|
}
|
||||||
|
claims, ok := token.Claims.(jwt.MapClaims)
|
||||||
|
if !ok {
|
||||||
|
return echo.NewHTTPError(http.StatusForbidden, "invalid jwt claims")
|
||||||
|
}
|
||||||
|
permissions, ok := claims["permissions"]
|
||||||
|
if !ok {
|
||||||
|
return echo.NewHTTPError(http.StatusForbidden, "missing permissions claim")
|
||||||
|
}
|
||||||
|
perms, ok := permissions.([]any)
|
||||||
|
if !ok {
|
||||||
|
return echo.NewHTTPError(http.StatusForbidden, "permissions claim is not an array")
|
||||||
|
}
|
||||||
|
if !slices.Contains(perms, "core.play") {
|
||||||
|
return echo.NewHTTPError(http.StatusForbidden, "missing core.play permission")
|
||||||
|
}
|
||||||
|
return next(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// @title gocoder - Kyoo's transcoder
|
// @title gocoder - Kyoo's transcoder
|
||||||
// @version 1.0
|
// @version 1.0
|
||||||
// @description Real time transcoder.
|
// @description Real time transcoder.
|
||||||
@ -103,7 +133,7 @@ func main() {
|
|||||||
return nil, fmt.Errorf("unable to find key %q", kid)
|
return nil, fmt.Errorf("unable to find key %q", kid)
|
||||||
}
|
}
|
||||||
|
|
||||||
var pubkey interface{}
|
var pubkey any
|
||||||
if err := jwk.Export(key, &pubkey); err != nil {
|
if err := jwk.Export(key, &pubkey); err != nil {
|
||||||
return nil, fmt.Errorf("Unable to get the public key. Error: %s", err.Error())
|
return nil, fmt.Errorf("Unable to get the public key. Error: %s", err.Error())
|
||||||
}
|
}
|
||||||
@ -111,6 +141,8 @@ func main() {
|
|||||||
return pubkey, nil
|
return pubkey, nil
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
g.Use(RequireCorePlayPermission)
|
||||||
}
|
}
|
||||||
|
|
||||||
api.RegisterStreamHandlers(g, transcoder)
|
api.RegisterStreamHandlers(g, transcoder)
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
begin;
|
begin;
|
||||||
|
|
||||||
drop table info;
|
drop table gocoder.info;
|
||||||
drop table videos;
|
drop table gocoder.videos;
|
||||||
drop table audios;
|
drop table gocoder.audios;
|
||||||
drop table subtitles;
|
drop table gocoder.subtitles;
|
||||||
drop table chapters;
|
drop table gocoder.chapters;
|
||||||
drop type chapter_type;
|
drop type gocoder.chapter_type;
|
||||||
|
|
||||||
commit;
|
commit;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
begin;
|
begin;
|
||||||
|
|
||||||
create table info(
|
create table gocoder.info(
|
||||||
sha varchar(40) not null primary key,
|
sha varchar(40) not null primary key,
|
||||||
path varchar(4096) not null unique,
|
path varchar(4096) not null unique,
|
||||||
extension varchar(16),
|
extension varchar(16),
|
||||||
@ -15,8 +15,8 @@ create table info(
|
|||||||
ver_keyframes integer not null
|
ver_keyframes integer not null
|
||||||
);
|
);
|
||||||
|
|
||||||
create table videos(
|
create table gocoder.videos(
|
||||||
sha varchar(40) not null references info(sha) on delete cascade,
|
sha varchar(40) not null references gocoder.info(sha) on delete cascade,
|
||||||
idx integer not null,
|
idx integer not null,
|
||||||
title varchar(1024),
|
title varchar(1024),
|
||||||
language varchar(256),
|
language varchar(256),
|
||||||
@ -32,8 +32,8 @@ create table videos(
|
|||||||
constraint videos_pk primary key (sha, idx)
|
constraint videos_pk primary key (sha, idx)
|
||||||
);
|
);
|
||||||
|
|
||||||
create table audios(
|
create table gocoder.audios(
|
||||||
sha varchar(40) not null references info(sha) on delete cascade,
|
sha varchar(40) not null references gocoder.info(sha) on delete cascade,
|
||||||
idx integer not null,
|
idx integer not null,
|
||||||
title varchar(1024),
|
title varchar(1024),
|
||||||
language varchar(256),
|
language varchar(256),
|
||||||
@ -47,8 +47,8 @@ create table audios(
|
|||||||
constraint audios_pk primary key (sha, idx)
|
constraint audios_pk primary key (sha, idx)
|
||||||
);
|
);
|
||||||
|
|
||||||
create table subtitles(
|
create table gocoder.subtitles(
|
||||||
sha varchar(40) not null references info(sha) on delete cascade,
|
sha varchar(40) not null references gocoder.info(sha) on delete cascade,
|
||||||
idx integer not null,
|
idx integer not null,
|
||||||
title varchar(1024),
|
title varchar(1024),
|
||||||
language varchar(256),
|
language varchar(256),
|
||||||
@ -60,14 +60,14 @@ create table subtitles(
|
|||||||
constraint subtitle_pk primary key (sha, idx)
|
constraint subtitle_pk primary key (sha, idx)
|
||||||
);
|
);
|
||||||
|
|
||||||
create type chapter_type as enum('content', 'recap', 'intro', 'credits', 'preview');
|
create type gocoder.chapter_type as enum('content', 'recap', 'intro', 'credits', 'preview');
|
||||||
|
|
||||||
create table chapters(
|
create table gocoder.chapters(
|
||||||
sha varchar(40) not null references info(sha) on delete cascade,
|
sha varchar(40) not null references gocoder.info(sha) on delete cascade,
|
||||||
start_time real not null,
|
start_time real not null,
|
||||||
end_time real not null,
|
end_time real not null,
|
||||||
name varchar(1024),
|
name varchar(1024),
|
||||||
type chapter_type,
|
type gocoder.chapter_type,
|
||||||
|
|
||||||
constraint chapter_pk primary key (sha, start_time)
|
constraint chapter_pk primary key (sha, start_time)
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
begin;
|
begin;
|
||||||
|
|
||||||
alter table subtitles drop column is_hearing_impaired;
|
alter table gocoder.subtitles drop column is_hearing_impaired;
|
||||||
|
|
||||||
commit;
|
commit;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
begin;
|
begin;
|
||||||
|
|
||||||
alter table subtitles add column is_hearing_impaired boolean not null default false;
|
alter table gocoder.subtitles add column is_hearing_impaired boolean not null default false;
|
||||||
|
|
||||||
commit;
|
commit;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
begin;
|
begin;
|
||||||
|
|
||||||
alter table subtitles drop column mime_codec;
|
alter table gocoder.subtitles drop column mime_codec;
|
||||||
|
|
||||||
commit;
|
commit;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
begin;
|
begin;
|
||||||
|
|
||||||
alter table subtitles add column mime_codec varchar(256) default null;
|
alter table gocoder.subtitles add column mime_codec varchar(256) default null;
|
||||||
|
|
||||||
commit;
|
commit;
|
||||||
|
|||||||
@ -27,7 +27,7 @@ func (s *MetadataService) ExtractSubs(ctx context.Context, info *MediaInfo) (any
|
|||||||
log.Printf("Couldn't extract subs: %v", err)
|
log.Printf("Couldn't extract subs: %v", err)
|
||||||
return set(nil, err)
|
return set(nil, err)
|
||||||
}
|
}
|
||||||
_, err = s.database.Exec(`update info set ver_extract = $2 where sha = $1`, info.Sha, ExtractVersion)
|
_, err = s.database.Exec(ctx, `update gocoder.info set ver_extract = $2 where sha = $1`, info.Sha, ExtractVersion)
|
||||||
return set(nil, err)
|
return set(nil, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,125 +20,133 @@ import (
|
|||||||
const InfoVersion = 3
|
const InfoVersion = 3
|
||||||
|
|
||||||
type Versions struct {
|
type Versions struct {
|
||||||
Info int32 `json:"info"`
|
Info int32 `json:"info" db:"ver_info"`
|
||||||
Extract int32 `json:"extract"`
|
Extract int32 `json:"extract" db:"ver_extract"`
|
||||||
Thumbs int32 `json:"thumbs"`
|
Thumbs int32 `json:"thumbs" db:"ver_thumbs"`
|
||||||
Keyframes int32 `json:"keyframes"`
|
Keyframes int32 `json:"keyframes" db:"ver_keyframes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MediaInfo struct {
|
type MediaInfo struct {
|
||||||
// The sha1 of the video file.
|
// The sha1 of the video file.
|
||||||
Sha string `json:"sha"`
|
Sha string `json:"sha" db:"sha"`
|
||||||
/// The internal path of the video file.
|
/// The internal path of the video file.
|
||||||
Path string `json:"path"`
|
Path string `json:"path" db:"path"`
|
||||||
/// The extension currently used to store this video file
|
/// The extension currently used to store this video file
|
||||||
Extension string `json:"extension"`
|
Extension string `json:"extension" db:"extension"`
|
||||||
/// The whole mimetype (defined as the RFC 6381). ex: `video/mp4; codecs="avc1.640028, mp4a.40.2"`
|
/// The whole mimetype (defined as the RFC 6381). ex: `video/mp4; codecs=\"avc1.640028, mp4a.40.2\"`
|
||||||
MimeCodec *string `json:"mimeCodec"`
|
MimeCodec *string `json:"mimeCodec" db:"mime_codec"`
|
||||||
/// The file size of the video file.
|
/// The file size of the video file.
|
||||||
Size int64 `json:"size"`
|
Size int64 `json:"size" db:"size"`
|
||||||
/// The length of the media in seconds.
|
/// The length of the media in seconds.
|
||||||
Duration float64 `json:"duration"`
|
Duration float64 `json:"duration" db:"duration"`
|
||||||
/// The container of the video file of this episode.
|
/// The container of the video file of this episode.
|
||||||
Container *string `json:"container"`
|
Container *string `json:"container" db:"container"`
|
||||||
/// Version of the metadata. This can be used to invalidate older metadata from db if the extraction code has changed.
|
/// Version of the metadata. This can be used to invalidate older metadata from db if the extraction code has changed.
|
||||||
Versions Versions `json:"versions"`
|
Versions Versions `json:"versions" db:"versions"`
|
||||||
|
|
||||||
/// The list of videos if there are multiples.
|
/// The list of videos if there are multiples.
|
||||||
Videos []Video `json:"videos"`
|
Videos []Video `json:"videos" db:"-"`
|
||||||
/// The list of audio tracks.
|
/// The list of audio tracks.
|
||||||
Audios []Audio `json:"audios"`
|
Audios []Audio `json:"audios" db:"-"`
|
||||||
/// The list of subtitles tracks.
|
/// The list of subtitles tracks.
|
||||||
Subtitles []Subtitle `json:"subtitles"`
|
Subtitles []Subtitle `json:"subtitles" db:"-"`
|
||||||
/// The list of fonts that can be used to display subtitles.
|
/// The list of fonts that can be used to display subtitles.
|
||||||
Fonts []string `json:"fonts"`
|
Fonts []string `json:"fonts" db:"fonts"`
|
||||||
/// The list of chapters. See Chapter for more information.
|
/// The list of chapters. See Chapter for more information.
|
||||||
Chapters []Chapter `json:"chapters"`
|
Chapters []Chapter `json:"chapters" db:"-"`
|
||||||
|
|
||||||
/// lock used to read/set keyframes of video/audio
|
/// lock used to read/set keyframes of video/audio
|
||||||
lock sync.Mutex
|
lock sync.Mutex `json:"-" db:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Video struct {
|
type Video struct {
|
||||||
|
Sha string `json:"-" db:"sha"`
|
||||||
|
|
||||||
/// The index of this track on the media.
|
/// The index of this track on the media.
|
||||||
Index uint32 `json:"index"`
|
Index uint32 `json:"index" db:"idx"`
|
||||||
/// The title of the stream.
|
/// The title of the stream.
|
||||||
Title *string `json:"title"`
|
Title *string `json:"title" db:"title"`
|
||||||
/// The language of this stream (as a ISO-639-2 language code)
|
/// The language of this stream (as a ISO-639-2 language code)
|
||||||
Language *string `json:"language"`
|
Language *string `json:"language" db:"language"`
|
||||||
/// The human readable codec name.
|
/// The human readable codec name.
|
||||||
Codec string `json:"codec"`
|
Codec string `json:"codec" db:"codec"`
|
||||||
/// The codec of this stream (defined as the RFC 6381).
|
/// The codec of this stream (defined as the RFC 6381).
|
||||||
MimeCodec *string `json:"mimeCodec"`
|
MimeCodec *string `json:"mimeCodec" db:"mime_codec"`
|
||||||
/// The width of the video stream
|
/// The width of the video stream
|
||||||
Width uint32 `json:"width"`
|
Width uint32 `json:"width" db:"width"`
|
||||||
/// The height of the video stream
|
/// The height of the video stream
|
||||||
Height uint32 `json:"height"`
|
Height uint32 `json:"height" db:"height"`
|
||||||
/// The average bitrate of the video in bytes/s
|
/// The average bitrate of the video in bytes/s
|
||||||
Bitrate uint32 `json:"bitrate"`
|
Bitrate uint32 `json:"bitrate" db:"bitrate"`
|
||||||
/// Is this stream the default one of it's type?
|
/// Is this stream the default one of it's type?
|
||||||
IsDefault bool `json:"isDefault"`
|
IsDefault bool `json:"isDefault" db:"is_default"`
|
||||||
|
|
||||||
/// Keyframes of this video
|
/// Keyframes of this video
|
||||||
Keyframes *Keyframe `json:"-"`
|
Keyframes *Keyframe `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Audio struct {
|
type Audio struct {
|
||||||
|
Sha string `json:"-" db:"sha"`
|
||||||
|
|
||||||
/// The index of this track on the media.
|
/// The index of this track on the media.
|
||||||
Index uint32 `json:"index"`
|
Index uint32 `json:"index" db:"idx"`
|
||||||
/// The title of the stream.
|
/// The title of the stream.
|
||||||
Title *string `json:"title"`
|
Title *string `json:"title" db:"title"`
|
||||||
/// The language of this stream (as a IETF-BCP-47 language code)
|
/// The language of this stream (as a IETF-BCP-47 language code)
|
||||||
Language *string `json:"language"`
|
Language *string `json:"language" db:"language"`
|
||||||
/// The human readable codec name.
|
/// The human readable codec name.
|
||||||
Codec string `json:"codec"`
|
Codec string `json:"codec" db:"codec"`
|
||||||
/// The codec of this stream (defined as the RFC 6381).
|
/// The codec of this stream (defined as the RFC 6381).
|
||||||
MimeCodec *string `json:"mimeCodec"`
|
MimeCodec *string `json:"mimeCodec" db:"mime_codec"`
|
||||||
/// The average bitrate of the audio in bytes/s
|
/// The average bitrate of the audio in bytes/s
|
||||||
Bitrate uint32 `json:"bitrate"`
|
Bitrate uint32 `json:"bitrate" db:"bitrate"`
|
||||||
/// Is this stream the default one of it's type?
|
/// Is this stream the default one of it's type?
|
||||||
IsDefault bool `json:"isDefault"`
|
IsDefault bool `json:"isDefault" db:"is_default"`
|
||||||
|
|
||||||
/// Keyframes of this video
|
/// Keyframes of this video
|
||||||
Keyframes *Keyframe `json:"-"`
|
Keyframes *Keyframe `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Subtitle struct {
|
type Subtitle struct {
|
||||||
|
Sha string `json:"-" db:"sha"`
|
||||||
|
|
||||||
/// The index of this track on the media.
|
/// The index of this track on the media.
|
||||||
Index *uint32 `json:"index"`
|
Index *uint32 `json:"index" db:"idx"`
|
||||||
/// The title of the stream.
|
/// The title of the stream.
|
||||||
Title *string `json:"title"`
|
Title *string `json:"title" db:"title"`
|
||||||
/// The language of this stream (as a IETF-BCP-47 language code)
|
/// The language of this stream (as a IETF-BCP-47 language code)
|
||||||
Language *string `json:"language"`
|
Language *string `json:"language" db:"language"`
|
||||||
/// The codec of this stream.
|
/// The codec of this stream.
|
||||||
Codec string `json:"codec"`
|
Codec string `json:"codec" db:"codec"`
|
||||||
/// The codec of this stream (defined as the RFC 6381).
|
/// The codec of this stream (defined as the RFC 6381).
|
||||||
MimeCodec *string `json:"mimeCodec"`
|
MimeCodec *string `json:"mimeCodec" db:"mime_codec"`
|
||||||
/// The extension for the codec.
|
/// The extension for the codec.
|
||||||
Extension *string `json:"extension"`
|
Extension *string `json:"extension" db:"extension"`
|
||||||
/// Is this stream the default one of it's type?
|
/// Is this stream the default one of it's type?
|
||||||
IsDefault bool `json:"isDefault"`
|
IsDefault bool `json:"isDefault" db:"is_default"`
|
||||||
/// Is this stream tagged as forced?
|
/// Is this stream tagged as forced?
|
||||||
IsForced bool `json:"isForced"`
|
IsForced bool `json:"isForced" db:"is_forced"`
|
||||||
/// Is this stream tagged as hearing impaired?
|
/// Is this stream tagged as hearing impaired?
|
||||||
IsHearingImpaired bool `json:"isHearingImpaired"`
|
IsHearingImpaired bool `json:"isHearingImpaired" db:"is_hearing_impaired"`
|
||||||
/// Is this an external subtitle (as in stored in a different file)
|
/// Is this an external subtitle (as in stored in a different file)
|
||||||
IsExternal bool `json:"isExternal"`
|
IsExternal bool `json:"isExternal" db:"-"`
|
||||||
/// Where the subtitle is stored (null if stored inside the video)
|
/// Where the subtitle is stored (null if stored inside the video)
|
||||||
Path *string `json:"path"`
|
Path *string `json:"path" db:"-"`
|
||||||
/// The link to access this subtitle.
|
/// The link to access this subtitle.
|
||||||
Link *string `json:"link"`
|
Link *string `json:"link" db:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Chapter struct {
|
type Chapter struct {
|
||||||
|
Sha string `json:"-" db:"sha"`
|
||||||
|
|
||||||
/// The start time of the chapter (in second from the start of the episode).
|
/// The start time of the chapter (in second from the start of the episode).
|
||||||
StartTime float32 `json:"startTime"`
|
StartTime float32 `json:"startTime" db:"start_time"`
|
||||||
/// The end time of the chapter (in second from the start of the episode).
|
/// The end time of the chapter (in second from the start of the episode).
|
||||||
EndTime float32 `json:"endTime"`
|
EndTime float32 `json:"endTime" db:"end_time"`
|
||||||
/// The name of this chapter. This should be a human-readable name that could be presented to the user.
|
/// The name of this chapter. This should be a human-readable name that could be presented to the user.
|
||||||
Name string `json:"name"`
|
Name string `json:"name" db:"name"`
|
||||||
/// The type value is used to mark special chapters (openning/credits...)
|
/// The type value is used to mark special chapters (openning/credits...)
|
||||||
Type ChapterType `json:"type"`
|
Type ChapterType `json:"type" db:"type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChapterType string
|
type ChapterType string
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package src
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
@ -10,7 +11,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/lib/pq"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
"github.com/zoriya/kyoo/transcoder/src/utils"
|
"github.com/zoriya/kyoo/transcoder/src/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -75,12 +76,20 @@ func (kf *Keyframe) AddListener(callback func(keyframes []float64)) {
|
|||||||
kf.info.listeners = append(kf.info.listeners, callback)
|
kf.info.listeners = append(kf.info.listeners, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (kf *Keyframe) Scan(src interface{}) error {
|
func (kf *Keyframe) Scan(src any) error {
|
||||||
var arr pq.Float64Array
|
var arr []float64
|
||||||
err := arr.Scan(src)
|
|
||||||
|
m := pgtype.NewMap()
|
||||||
|
t, ok := m.TypeForValue(&arr)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return errors.New("failed to parse keyframes")
|
||||||
|
}
|
||||||
|
err := m.Scan(t.OID, pgtype.BinaryFormatCode, src.([]byte), &arr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
kf.Keyframes = arr
|
kf.Keyframes = arr
|
||||||
kf.IsDone = true
|
kf.IsDone = true
|
||||||
kf.info = &KeyframeInfo{}
|
kf.info = &KeyframeInfo{}
|
||||||
@ -131,13 +140,14 @@ func (s *MetadataService) GetKeyframes(info *MediaInfo, isVideo bool, idx uint32
|
|||||||
info.lock.Unlock()
|
info.lock.Unlock()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
ctx := context.Background()
|
||||||
var table string
|
var table string
|
||||||
var err error
|
var err error
|
||||||
if isVideo {
|
if isVideo {
|
||||||
table = "videos"
|
table = "gocoder.videos"
|
||||||
err = getVideoKeyframes(info.Path, idx, kf)
|
err = getVideoKeyframes(info.Path, idx, kf)
|
||||||
} else {
|
} else {
|
||||||
table = "audios"
|
table = "gocoder.audios"
|
||||||
err = getAudioKeyframes(info, idx, kf)
|
err = getAudioKeyframes(info, idx, kf)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,15 +157,16 @@ func (s *MetadataService) GetKeyframes(info *MediaInfo, isVideo bool, idx uint32
|
|||||||
}
|
}
|
||||||
|
|
||||||
kf.info.ready.Wait()
|
kf.info.ready.Wait()
|
||||||
tx, _ := s.database.Begin()
|
tx, _ := s.database.Begin(ctx)
|
||||||
tx.Exec(
|
tx.Exec(
|
||||||
|
ctx,
|
||||||
fmt.Sprintf(`update %s set keyframes = $3 where sha = $1 and idx = $2`, table),
|
fmt.Sprintf(`update %s set keyframes = $3 where sha = $1 and idx = $2`, table),
|
||||||
info.Sha,
|
info.Sha,
|
||||||
idx,
|
idx,
|
||||||
pq.Array(kf.Keyframes),
|
kf.Keyframes,
|
||||||
)
|
)
|
||||||
tx.Exec(`update info set ver_keyframes = $2 where sha = $1`, info.Sha, KeyframeVersion)
|
tx.Exec(ctx, `update gocoder.info set ver_keyframes = $2 where sha = $1`, info.Sha, KeyframeVersion)
|
||||||
err = tx.Commit()
|
err = tx.Commit(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Couldn't store keyframes on database: %v", err)
|
log.Printf("Couldn't store keyframes on database: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,24 +2,26 @@ package src
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/config"
|
"github.com/aws/aws-sdk-go-v2/config"
|
||||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
"github.com/golang-migrate/migrate/v4"
|
"github.com/golang-migrate/migrate/v4"
|
||||||
"github.com/golang-migrate/migrate/v4/database/postgres"
|
pgxd "github.com/golang-migrate/migrate/v4/database/pgx/v5"
|
||||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||||
"github.com/lib/pq"
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
"github.com/jackc/pgx/v5/stdlib"
|
||||||
"github.com/zoriya/kyoo/transcoder/src/storage"
|
"github.com/zoriya/kyoo/transcoder/src/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MetadataService struct {
|
type MetadataService struct {
|
||||||
database *sql.DB
|
database *pgxpool.Pool
|
||||||
lock RunLock[string, *MediaInfo]
|
lock RunLock[string, *MediaInfo]
|
||||||
thumbLock RunLock[string, any]
|
thumbLock RunLock[string, any]
|
||||||
extractLock RunLock[string, any]
|
extractLock RunLock[string, any]
|
||||||
@ -53,63 +55,69 @@ func NewMetadataService() (*MetadataService, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *MetadataService) Close() error {
|
func (s *MetadataService) Close() error {
|
||||||
cleanupErrs := make([]error, 0, 2)
|
|
||||||
if s.database != nil {
|
if s.database != nil {
|
||||||
err := s.database.Close()
|
s.database.Close()
|
||||||
if err != nil {
|
|
||||||
cleanupErrs = append(cleanupErrs, fmt.Errorf("failed to close database: %w", err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.storage != nil {
|
if s.storage != nil {
|
||||||
if storageCloser, ok := s.storage.(storage.StorageBackendCloser); ok {
|
if storageCloser, ok := s.storage.(storage.StorageBackendCloser); ok {
|
||||||
err := storageCloser.Close()
|
err := storageCloser.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cleanupErrs = append(cleanupErrs, fmt.Errorf("failed to close storage: %w", err))
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := errors.Join(cleanupErrs...); err != nil {
|
|
||||||
return fmt.Errorf("failed to cleanup resources: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MetadataService) setupDb() (*sql.DB, error) {
|
func (s *MetadataService) setupDb() (*pgxpool.Pool, error) {
|
||||||
schema := GetEnvOr("POSTGRES_SCHEMA", "gocoder")
|
ctx := context.Background()
|
||||||
|
|
||||||
connectionString := os.Getenv("POSTGRES_URL")
|
connectionString := os.Getenv("POSTGRES_URL")
|
||||||
if connectionString == "" {
|
config, err := pgxpool.ParseConfig(connectionString)
|
||||||
connectionString = fmt.Sprintf(
|
if err != nil {
|
||||||
"postgresql://%v:%v@%v:%v/%v?application_name=gocoder&sslmode=%s",
|
return nil, errors.New("failed to create postgres config from environment variables")
|
||||||
url.QueryEscape(os.Getenv("POSTGRES_USER")),
|
|
||||||
url.QueryEscape(os.Getenv("POSTGRES_PASSWORD")),
|
|
||||||
url.QueryEscape(os.Getenv("POSTGRES_SERVER")),
|
|
||||||
url.QueryEscape(os.Getenv("POSTGRES_PORT")),
|
|
||||||
url.QueryEscape(os.Getenv("POSTGRES_DB")),
|
|
||||||
url.QueryEscape(GetEnvOr("POSTGRES_SSLMODE", "disable")),
|
|
||||||
)
|
|
||||||
if schema != "disabled" {
|
|
||||||
connectionString = fmt.Sprintf("%s&search_path=%s", connectionString, url.QueryEscape(schema))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
db, err := sql.Open("postgres", connectionString)
|
// Set default values
|
||||||
|
if config.ConnConfig.Host == "/tmp" {
|
||||||
|
config.ConnConfig.Host = "postgres"
|
||||||
|
}
|
||||||
|
if config.ConnConfig.Database == "" {
|
||||||
|
config.ConnConfig.Database = "kyoo"
|
||||||
|
}
|
||||||
|
// The pgx library will set the username to the name of the current user if not provided via
|
||||||
|
// environment variable or connection string. Make a best-effort attempt to see if the user
|
||||||
|
// was explicitly specified, without implementing full connection string parsing. If not, set
|
||||||
|
// the username to the default value of "kyoo".
|
||||||
|
if os.Getenv("PGUSER") == "" {
|
||||||
|
currentUserName, _ := user.Current()
|
||||||
|
// If the username matches the current user and it's not in the connection string, then it was set
|
||||||
|
// by the pgx library. This doesn't cover the case where the system username happens to be in some other part
|
||||||
|
// of the connection string, but this cannot be checked without full connection string parsing.
|
||||||
|
if currentUserName.Username == config.ConnConfig.User && !strings.Contains(connectionString, currentUserName.Username) {
|
||||||
|
config.ConnConfig.User = "kyoo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := config.ConnConfig.RuntimeParams["application_name"]; !ok {
|
||||||
|
config.ConnConfig.RuntimeParams["application_name"] = "gocoder"
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := pgxpool.NewWithConfig(ctx, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Could not connect to database, check your env variables!")
|
fmt.Printf("Could not connect to database, check your env variables!\n")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if schema != "disabled" {
|
fmt.Println("Migrating database")
|
||||||
_, err = db.Exec(fmt.Sprintf("create schema if not exists %s", schema))
|
dbi := stdlib.OpenDBFromPool(db)
|
||||||
if err != nil {
|
defer dbi.Close()
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
driver, err := postgres.WithInstance(db, &postgres.Config{})
|
dbi.Exec("create schema if not exists gocoder")
|
||||||
|
driver, err := pgxd.WithInstance(dbi, &pgxd.Config{
|
||||||
|
SchemaName: "gocoder",
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -118,6 +126,7 @@ func (s *MetadataService) setupDb() (*sql.DB, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
m.Up()
|
m.Up()
|
||||||
|
fmt.Println("Migrating finished")
|
||||||
|
|
||||||
return db, nil
|
return db, nil
|
||||||
}
|
}
|
||||||
@ -147,7 +156,7 @@ func (s *MetadataService) setupStorage(ctx context.Context) (storage.StorageBack
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *MetadataService) GetMetadata(ctx context.Context, path string, sha string) (*MediaInfo, error) {
|
func (s *MetadataService) GetMetadata(ctx context.Context, path string, sha string) (*MediaInfo, error) {
|
||||||
ret, err := s.getMetadata(path, sha)
|
ret, err := s.getMetadata(ctx, path, sha)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -165,14 +174,14 @@ func (s *MetadataService) GetMetadata(ctx context.Context, path string, sha stri
|
|||||||
for _, audio := range ret.Audios {
|
for _, audio := range ret.Audios {
|
||||||
audio.Keyframes = nil
|
audio.Keyframes = nil
|
||||||
}
|
}
|
||||||
tx, err := s.database.Begin()
|
tx, err := s.database.Begin(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tx.Exec(`update videos set keyframes = null where sha = $1`, sha)
|
tx.Exec(ctx, `update gocoder.videos set keyframes = null where sha = $1`, sha)
|
||||||
tx.Exec(`update audios set keyframes = null where sha = $1`, sha)
|
tx.Exec(ctx, `update gocoder.audios set keyframes = null where sha = $1`, sha)
|
||||||
tx.Exec(`update info set ver_keyframes = 0 where sha = $1`, sha)
|
tx.Exec(ctx, `update gocoder.info set ver_keyframes = 0 where sha = $1`, sha)
|
||||||
err = tx.Commit()
|
err = tx.Commit(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("error deleting old keyframes from database: %v", err)
|
fmt.Printf("error deleting old keyframes from database: %v", err)
|
||||||
}
|
}
|
||||||
@ -181,79 +190,60 @@ func (s *MetadataService) GetMetadata(ctx context.Context, path string, sha stri
|
|||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MetadataService) getMetadata(path string, sha string) (*MediaInfo, error) {
|
func (s *MetadataService) getMetadata(ctx context.Context, path string, sha string) (*MediaInfo, error) {
|
||||||
var ret MediaInfo
|
rows, _ := s.database.Query(
|
||||||
var fonts pq.StringArray
|
ctx,
|
||||||
err := s.database.QueryRow(
|
`select
|
||||||
`select i.sha, i.path, i.extension, i.mime_codec, i.size, i.duration, i.container,
|
i.sha, i.path, i.extension, i.mime_codec, i.size, i.duration, i.container, i.fonts,
|
||||||
i.fonts, i.ver_info, i.ver_extract, i.ver_thumbs, i.ver_keyframes
|
jsonb_build_object(
|
||||||
from info as i where i.sha=$1`,
|
'info', i.ver_info,
|
||||||
|
'extract', i.ver_extract,
|
||||||
|
'thumbs', i.ver_thumbs,
|
||||||
|
'keyframes', i.ver_keyframes
|
||||||
|
) as versions
|
||||||
|
from gocoder.info as i
|
||||||
|
where i.sha=$1 limit 1`,
|
||||||
sha,
|
sha,
|
||||||
).Scan(
|
|
||||||
&ret.Sha, &ret.Path, &ret.Extension, &ret.MimeCodec, &ret.Size, &ret.Duration, &ret.Container,
|
|
||||||
&fonts, &ret.Versions.Info, &ret.Versions.Extract, &ret.Versions.Thumbs, &ret.Versions.Keyframes,
|
|
||||||
)
|
)
|
||||||
ret.Fonts = fonts
|
ret, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[MediaInfo])
|
||||||
ret.Videos = make([]Video, 0)
|
|
||||||
ret.Audios = make([]Audio, 0)
|
|
||||||
ret.Subtitles = make([]Subtitle, 0)
|
|
||||||
ret.Chapters = make([]Chapter, 0)
|
|
||||||
|
|
||||||
if err == sql.ErrNoRows || (ret.Versions.Info < InfoVersion && ret.Versions.Info != 0) {
|
if errors.Is(err, pgx.ErrNoRows) || (ret.Versions.Info < InfoVersion && ret.Versions.Info != 0) {
|
||||||
return s.storeFreshMetadata(path, sha)
|
return s.storeFreshMetadata(ctx, path, sha)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rows, err := s.database.Query(
|
rows, _ = s.database.Query(
|
||||||
`select v.idx, v.title, v.language, v.codec, v.mime_codec, v.width, v.height, v.bitrate, v.is_default, v.keyframes
|
ctx,
|
||||||
from videos as v where v.sha=$1`,
|
`select * from gocoder.videos as v where v.sha=$1`,
|
||||||
sha,
|
sha,
|
||||||
)
|
)
|
||||||
|
ret.Videos, err = pgx.CollectRows(rows, pgx.RowToStructByName[Video])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for rows.Next() {
|
|
||||||
var v Video
|
|
||||||
err := rows.Scan(&v.Index, &v.Title, &v.Language, &v.Codec, &v.MimeCodec, &v.Width, &v.Height, &v.Bitrate, &v.IsDefault, &v.Keyframes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ret.Videos = append(ret.Videos, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err = s.database.Query(
|
rows, _ = s.database.Query(
|
||||||
`select a.idx, a.title, a.language, a.codec, a.mime_codec, a.bitrate, a.is_default, a.keyframes
|
ctx,
|
||||||
from audios as a where a.sha=$1`,
|
`select * from gocoder.audios as a where a.sha=$1`,
|
||||||
sha,
|
sha,
|
||||||
)
|
)
|
||||||
|
ret.Audios, err = pgx.CollectRows(rows, pgx.RowToStructByName[Audio])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for rows.Next() {
|
|
||||||
var a Audio
|
|
||||||
err := rows.Scan(&a.Index, &a.Title, &a.Language, &a.Codec, &a.MimeCodec, &a.Bitrate, &a.IsDefault, &a.Keyframes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ret.Audios = append(ret.Audios, a)
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err = s.database.Query(
|
rows, _ = s.database.Query(
|
||||||
`select s.idx, s.title, s.language, s.codec, s.mime_codec, s.extension, s.is_default, s.is_forced, s.is_hearing_impaired
|
ctx,
|
||||||
from subtitles as s where s.sha=$1`,
|
`select * from gocoder.subtitles as s where s.sha=$1`,
|
||||||
sha,
|
sha,
|
||||||
)
|
)
|
||||||
|
ret.Subtitles, err = pgx.CollectRows(rows, pgx.RowToStructByName[Subtitle])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for rows.Next() {
|
for i, s := range ret.Subtitles {
|
||||||
var s Subtitle
|
|
||||||
err := rows.Scan(&s.Index, &s.Title, &s.Language, &s.Codec, &s.MimeCodec, &s.Extension, &s.IsDefault, &s.IsForced, &s.IsHearingImpaired)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if s.Extension != nil {
|
if s.Extension != nil {
|
||||||
link := fmt.Sprintf(
|
link := fmt.Sprintf(
|
||||||
"/video/%s/subtitle/%d.%s",
|
"/video/%s/subtitle/%d.%s",
|
||||||
@ -261,35 +251,27 @@ func (s *MetadataService) getMetadata(path string, sha string) (*MediaInfo, erro
|
|||||||
*s.Index,
|
*s.Index,
|
||||||
*s.Extension,
|
*s.Extension,
|
||||||
)
|
)
|
||||||
s.Link = &link
|
ret.Subtitles[i].Link = &link
|
||||||
}
|
}
|
||||||
ret.Subtitles = append(ret.Subtitles, s)
|
|
||||||
}
|
}
|
||||||
err = ret.SearchExternalSubtitles()
|
err = ret.SearchExternalSubtitles()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Couldn't find external subtitles: %v", err)
|
fmt.Printf("Couldn't find external subtitles: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rows, err = s.database.Query(
|
rows, _ = s.database.Query(
|
||||||
`select c.start_time, c.end_time, c.name, c.type
|
ctx,
|
||||||
from chapters as c where c.sha=$1`,
|
`select * from gocoder.chapters as c where c.sha=$1`,
|
||||||
sha,
|
sha,
|
||||||
)
|
)
|
||||||
|
ret.Chapters, err = pgx.CollectRows(rows, pgx.RowToStructByName[Chapter])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for rows.Next() {
|
|
||||||
var c Chapter
|
|
||||||
err := rows.Scan(&c.StartTime, &c.EndTime, &c.Name, &c.Type)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ret.Chapters = append(ret.Chapters, c)
|
|
||||||
}
|
|
||||||
return &ret, nil
|
return &ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MetadataService) storeFreshMetadata(path string, sha string) (*MediaInfo, error) {
|
func (s *MetadataService) storeFreshMetadata(ctx context.Context, path string, sha string) (*MediaInfo, error) {
|
||||||
get_running, set := s.lock.Start(sha)
|
get_running, set := s.lock.Start(sha)
|
||||||
if get_running != nil {
|
if get_running != nil {
|
||||||
return get_running()
|
return get_running()
|
||||||
@ -300,26 +282,29 @@ func (s *MetadataService) storeFreshMetadata(path string, sha string) (*MediaInf
|
|||||||
return set(nil, err)
|
return set(nil, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tx, err := s.database.Begin()
|
tx, err := s.database.Begin(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return set(ret, err)
|
return set(ret, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// it needs to be a delete instead of a on conflict do update because we want to trigger delete casquade for
|
// it needs to be a delete instead of a on conflict do update because we want to trigger delete casquade for
|
||||||
// videos/audios & co.
|
// videos/audios & co.
|
||||||
tx.Exec(`delete from info where path = $1`, path)
|
tx.Exec(ctx, `delete from gocoder.info where path = $1`, path)
|
||||||
tx.Exec(`
|
tx.Exec(ctx,
|
||||||
insert into info(sha, path, extension, mime_codec, size, duration, container,
|
`
|
||||||
|
insert into gocoder.info(sha, path, extension, mime_codec, size, duration, container,
|
||||||
fonts, ver_info, ver_extract, ver_thumbs, ver_keyframes)
|
fonts, ver_info, ver_extract, ver_thumbs, ver_keyframes)
|
||||||
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||||
`,
|
`,
|
||||||
// on conflict do not update versions of extract/thumbs/keyframes
|
// on conflict do not update versions of extract/thumbs/keyframes
|
||||||
ret.Sha, ret.Path, ret.Extension, ret.MimeCodec, ret.Size, ret.Duration, ret.Container,
|
ret.Sha, ret.Path, ret.Extension, ret.MimeCodec, ret.Size, ret.Duration, ret.Container,
|
||||||
pq.Array(ret.Fonts), ret.Versions.Info, ret.Versions.Extract, ret.Versions.Thumbs, ret.Versions.Keyframes,
|
ret.Fonts, ret.Versions.Info, ret.Versions.Extract, ret.Versions.Thumbs, ret.Versions.Keyframes,
|
||||||
)
|
)
|
||||||
for _, v := range ret.Videos {
|
for _, v := range ret.Videos {
|
||||||
tx.Exec(`
|
tx.Exec(
|
||||||
insert into videos(sha, idx, title, language, codec, mime_codec, width, height, is_default, bitrate)
|
ctx,
|
||||||
|
`
|
||||||
|
insert into gocoder.videos(sha, idx, title, language, codec, mime_codec, width, height, is_default, bitrate)
|
||||||
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||||
on conflict (sha, idx) do update set
|
on conflict (sha, idx) do update set
|
||||||
sha = excluded.sha,
|
sha = excluded.sha,
|
||||||
@ -337,8 +322,10 @@ func (s *MetadataService) storeFreshMetadata(path string, sha string) (*MediaInf
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
for _, a := range ret.Audios {
|
for _, a := range ret.Audios {
|
||||||
tx.Exec(`
|
tx.Exec(
|
||||||
insert into audios(sha, idx, title, language, codec, mime_codec, is_default, bitrate)
|
ctx,
|
||||||
|
`
|
||||||
|
insert into gocoder.audios(sha, idx, title, language, codec, mime_codec, is_default, bitrate)
|
||||||
values ($1, $2, $3, $4, $5, $6, $7, $8)
|
values ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||||
on conflict (sha, idx) do update set
|
on conflict (sha, idx) do update set
|
||||||
sha = excluded.sha,
|
sha = excluded.sha,
|
||||||
@ -354,8 +341,10 @@ func (s *MetadataService) storeFreshMetadata(path string, sha string) (*MediaInf
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
for _, s := range ret.Subtitles {
|
for _, s := range ret.Subtitles {
|
||||||
tx.Exec(`
|
tx.Exec(
|
||||||
insert into subtitles(sha, idx, title, language, codec, mime_codec, extension, is_default, is_forced, is_hearing_impaired)
|
ctx,
|
||||||
|
`
|
||||||
|
insert into gocoder.subtitles(sha, idx, title, language, codec, mime_codec, extension, is_default, is_forced, is_hearing_impaired)
|
||||||
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||||
on conflict (sha, idx) do update set
|
on conflict (sha, idx) do update set
|
||||||
sha = excluded.sha,
|
sha = excluded.sha,
|
||||||
@ -373,8 +362,10 @@ func (s *MetadataService) storeFreshMetadata(path string, sha string) (*MediaInf
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
for _, c := range ret.Chapters {
|
for _, c := range ret.Chapters {
|
||||||
tx.Exec(`
|
tx.Exec(
|
||||||
insert into chapters(sha, start_time, end_time, name, type)
|
ctx,
|
||||||
|
`
|
||||||
|
insert into gocoder.chapters(sha, start_time, end_time, name, type)
|
||||||
values ($1, $2, $3, $4, $5)
|
values ($1, $2, $3, $4, $5)
|
||||||
on conflict (sha, start_time) do update set
|
on conflict (sha, start_time) do update set
|
||||||
sha = excluded.sha,
|
sha = excluded.sha,
|
||||||
@ -386,7 +377,7 @@ func (s *MetadataService) storeFreshMetadata(path string, sha string) (*MediaInf
|
|||||||
ret.Sha, c.StartTime, c.EndTime, c.Name, c.Type,
|
ret.Sha, c.StartTime, c.EndTime, c.Name, c.Type,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
err = tx.Commit()
|
err = tx.Commit(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return set(ret, err)
|
return set(ret, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -77,7 +77,7 @@ func (s *MetadataService) ExtractThumbs(ctx context.Context, path string, sha st
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return set(nil, err)
|
return set(nil, err)
|
||||||
}
|
}
|
||||||
_, err = s.database.Exec(`update info set ver_thumbs = $2 where sha = $1`, sha, ThumbsVersion)
|
_, err = s.database.Exec(ctx, `update gocoder.info set ver_thumbs = $2 where sha = $1`, sha, ThumbsVersion)
|
||||||
return set(nil, err)
|
return set(nil, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user