commit
f4b3d43b8f
|
@ -41,7 +41,7 @@ services:
|
||||||
links:
|
links:
|
||||||
- postgres:postgres
|
- postgres:postgres
|
||||||
environment:
|
environment:
|
||||||
- DATABASE_URL=postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable
|
- PGWEB_DATABASE_URL=postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1 @@
|
||||||
* @movie-web/core
|
* @movie-web/project-leads
|
||||||
|
|
||||||
.github @binaryoverload
|
|
||||||
|
|
|
@ -14,16 +14,16 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v2
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 8
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 20
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Install packages
|
- name: Install packages
|
||||||
|
@ -38,16 +38,16 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@v2
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 8
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 20
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Install packages
|
- name: Install packages
|
||||||
|
@ -62,10 +62,10 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v5
|
||||||
|
|
|
@ -12,7 +12,7 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Get version
|
- name: Get version
|
||||||
id: package-version
|
id: package-version
|
||||||
|
@ -42,10 +42,10 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Docker buildx
|
- name: Setup Docker buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Get version
|
- name: Get version
|
||||||
id: package-version
|
id: package-version
|
||||||
|
@ -70,9 +70,12 @@ jobs:
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
id: build-and-push
|
id: build-and-push
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
context: .
|
context: .
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "backend",
|
"name": "backend",
|
||||||
"version": "1.3.0",
|
"version": "1.3.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"homepage": "https://github.com/movie-web/backend",
|
"homepage": "https://github.com/movie-web/backend",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { devFragment } from '@/config/fragments/dev';
|
||||||
import { dockerFragment } from '@/config/fragments/docker';
|
import { dockerFragment } from '@/config/fragments/docker';
|
||||||
import { createConfigLoader } from 'neat-config';
|
import { createConfigLoader } from 'neat-config';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
import { booleanSchema } from './schema';
|
||||||
|
|
||||||
const fragments = {
|
const fragments = {
|
||||||
dev: devFragment,
|
dev: devFragment,
|
||||||
|
@ -13,7 +14,7 @@ export const ormConfigSchema = z.object({
|
||||||
// connection URL for postgres database
|
// connection URL for postgres database
|
||||||
connection: z.string(),
|
connection: z.string(),
|
||||||
// whether to use SSL for the connection
|
// whether to use SSL for the connection
|
||||||
ssl: z.coerce.boolean().default(false),
|
ssl: booleanSchema.default(false),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const booleanSchema = z.preprocess((val) => val === 'true', z.boolean());
|
||||||
|
|
||||||
export const configSchema = z.object({
|
export const configSchema = z.object({
|
||||||
server: z
|
server: z
|
||||||
.object({
|
.object({
|
||||||
|
@ -11,13 +13,13 @@ export const configSchema = z.object({
|
||||||
|
|
||||||
// disable cross origin restrictions, allow any site.
|
// disable cross origin restrictions, allow any site.
|
||||||
// overwrites the cors option above
|
// overwrites the cors option above
|
||||||
allowAnySite: z.coerce.boolean().default(false),
|
allowAnySite: booleanSchema.default(false),
|
||||||
|
|
||||||
// should it trust reverse proxy headers? (for ip gathering)
|
// should it trust reverse proxy headers? (for ip gathering)
|
||||||
trustProxy: z.coerce.boolean().default(false),
|
trustProxy: booleanSchema.default(false),
|
||||||
|
|
||||||
// should it trust cloudflare headers? (for ip gathering, cloudflare has priority)
|
// should it trust cloudflare headers? (for ip gathering, cloudflare has priority)
|
||||||
trustCloudflare: z.coerce.boolean().default(false),
|
trustCloudflare: booleanSchema.default(false),
|
||||||
|
|
||||||
// prefix for where the instance is run on. for example set it to /backend if you're hosting it on example.com/backend
|
// prefix for where the instance is run on. for example set it to /backend if you're hosting it on example.com/backend
|
||||||
// if this is set, do not apply url rewriting before proxing
|
// if this is set, do not apply url rewriting before proxing
|
||||||
|
@ -30,7 +32,7 @@ export const configSchema = z.object({
|
||||||
format: z.enum(['json', 'pretty']).default('pretty'),
|
format: z.enum(['json', 'pretty']).default('pretty'),
|
||||||
|
|
||||||
// show debug logs?
|
// show debug logs?
|
||||||
debug: z.coerce.boolean().default(false),
|
debug: booleanSchema.default(false),
|
||||||
})
|
})
|
||||||
.default({}),
|
.default({}),
|
||||||
postgres: z.object({
|
postgres: z.object({
|
||||||
|
@ -38,19 +40,19 @@ export const configSchema = z.object({
|
||||||
connection: z.string(),
|
connection: z.string(),
|
||||||
|
|
||||||
// run all migrations on boot of the application
|
// run all migrations on boot of the application
|
||||||
migrateOnBoot: z.coerce.boolean().default(false),
|
migrateOnBoot: booleanSchema.default(false),
|
||||||
|
|
||||||
// try to sync the schema on boot, useful for development
|
// try to sync the schema on boot, useful for development
|
||||||
// will always keep the database schema in sync with the connected database
|
// will always keep the database schema in sync with the connected database
|
||||||
// it is extremely destructive, do not use it EVER in production
|
// it is extremely destructive, do not use it EVER in production
|
||||||
syncSchema: z.coerce.boolean().default(false),
|
syncSchema: booleanSchema.default(false),
|
||||||
|
|
||||||
// Enable debug logging for MikroORM - Outputs queries and entity management logs
|
// Enable debug logging for MikroORM - Outputs queries and entity management logs
|
||||||
// Do NOT use in production, leaks all sensitive data
|
// Do NOT use in production, leaks all sensitive data
|
||||||
debugLogging: z.coerce.boolean().default(false),
|
debugLogging: booleanSchema.default(false),
|
||||||
|
|
||||||
// Enable SSL for the postgres connection
|
// Enable SSL for the postgres connection
|
||||||
ssl: z.coerce.boolean().default(false),
|
ssl: booleanSchema.default(false),
|
||||||
}),
|
}),
|
||||||
crypto: z.object({
|
crypto: z.object({
|
||||||
// session secret. used for signing session tokens
|
// session secret. used for signing session tokens
|
||||||
|
@ -65,7 +67,7 @@ export const configSchema = z.object({
|
||||||
captcha: z
|
captcha: z
|
||||||
.object({
|
.object({
|
||||||
// enabled captchas on register
|
// enabled captchas on register
|
||||||
enabled: z.coerce.boolean().default(false),
|
enabled: booleanSchema.default(false),
|
||||||
|
|
||||||
// captcha secret
|
// captcha secret
|
||||||
secret: z.string().min(1).optional(),
|
secret: z.string().min(1).optional(),
|
||||||
|
@ -76,7 +78,7 @@ export const configSchema = z.object({
|
||||||
ratelimits: z
|
ratelimits: z
|
||||||
.object({
|
.object({
|
||||||
// enabled captchas on register
|
// enabled captchas on register
|
||||||
enabled: z.coerce.boolean().default(false),
|
enabled: booleanSchema.default(false),
|
||||||
redisUrl: z.string().optional(),
|
redisUrl: z.string().optional(),
|
||||||
})
|
})
|
||||||
.default({}),
|
.default({}),
|
||||||
|
|
|
@ -13,6 +13,7 @@ export type Metrics = {
|
||||||
providerHostnames: Counter<'hostname'>;
|
providerHostnames: Counter<'hostname'>;
|
||||||
providerStatuses: Counter<'provider_id' | 'status'>;
|
providerStatuses: Counter<'provider_id' | 'status'>;
|
||||||
watchMetrics: Counter<'title' | 'tmdb_full_id' | 'provider_id' | 'success'>;
|
watchMetrics: Counter<'title' | 'tmdb_full_id' | 'provider_id' | 'success'>;
|
||||||
|
toolMetrics: Counter<'tool'>;
|
||||||
};
|
};
|
||||||
|
|
||||||
let metrics: null | Metrics = null;
|
let metrics: null | Metrics = null;
|
||||||
|
@ -59,6 +60,11 @@ export async function setupMetrics(app: FastifyInstance) {
|
||||||
help: 'mw_media_watch_count',
|
help: 'mw_media_watch_count',
|
||||||
labelNames: ['title', 'tmdb_full_id', 'provider_id', 'success'],
|
labelNames: ['title', 'tmdb_full_id', 'provider_id', 'success'],
|
||||||
}),
|
}),
|
||||||
|
toolMetrics: new Counter({
|
||||||
|
name: 'mw_provider_tool_count',
|
||||||
|
help: 'mw_provider_tool_count',
|
||||||
|
labelNames: ['tool'],
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
const promClient = app.metrics.client;
|
const promClient = app.metrics.client;
|
||||||
|
@ -68,6 +74,7 @@ export async function setupMetrics(app: FastifyInstance) {
|
||||||
promClient.register.registerMetric(metrics.providerStatuses);
|
promClient.register.registerMetric(metrics.providerStatuses);
|
||||||
promClient.register.registerMetric(metrics.watchMetrics);
|
promClient.register.registerMetric(metrics.watchMetrics);
|
||||||
promClient.register.registerMetric(metrics.captchaSolves);
|
promClient.register.registerMetric(metrics.captchaSolves);
|
||||||
|
promClient.register.registerMetric(metrics.toolMetrics);
|
||||||
|
|
||||||
const orm = getORM();
|
const orm = getORM();
|
||||||
const em = orm.em.fork();
|
const em = orm.em.fork();
|
||||||
|
|
|
@ -19,6 +19,7 @@ const metricsProviderSchema = z.object({
|
||||||
|
|
||||||
const metricsProviderInputSchema = z.object({
|
const metricsProviderInputSchema = z.object({
|
||||||
items: z.array(metricsProviderSchema).max(10).min(1),
|
items: z.array(metricsProviderSchema).max(10).min(1),
|
||||||
|
tool: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const metricsRouter = makeRouter((app) => {
|
export const metricsRouter = makeRouter((app) => {
|
||||||
|
@ -65,6 +66,12 @@ export const metricsRouter = makeRouter((app) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (body.tool) {
|
||||||
|
getMetrics().toolMetrics.inc({
|
||||||
|
tool: body.tool,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,16 +4,14 @@ import { StatusError } from '@/services/error';
|
||||||
export async function isValidCaptcha(token: string): Promise<boolean> {
|
export async function isValidCaptcha(token: string): Promise<boolean> {
|
||||||
if (!conf.captcha.secret)
|
if (!conf.captcha.secret)
|
||||||
throw new Error('isValidCaptcha() is called but no secret set');
|
throw new Error('isValidCaptcha() is called but no secret set');
|
||||||
|
const formData = new URLSearchParams();
|
||||||
|
formData.append('secret', conf.captcha.secret);
|
||||||
|
formData.append('response', token);
|
||||||
const res = await fetch('https://www.google.com/recaptcha/api/siteverify', {
|
const res = await fetch('https://www.google.com/recaptcha/api/siteverify', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: formData,
|
||||||
secret: conf.captcha.secret,
|
|
||||||
response: token,
|
|
||||||
}),
|
|
||||||
headers: {
|
|
||||||
'content-type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
return !!json.success;
|
return !!json.success;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue