diff --git a/packages/backend/src/prisma/migrations/20240522182248_add_legal_columns/migration.sql b/packages/backend/src/prisma/migrations/20240522182248_add_legal_columns/migration.sql
new file mode 100644
index 000000000..c7c8bfa2d
--- /dev/null
+++ b/packages/backend/src/prisma/migrations/20240522182248_add_legal_columns/migration.sql
@@ -0,0 +1,50 @@
+-- RedefineTables
+PRAGMA foreign_keys=OFF;
+CREATE TABLE "new_settings" (
+ "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ "rateLimitWindow" INTEGER NOT NULL,
+ "rateLimitMax" INTEGER NOT NULL,
+ "secret" TEXT NOT NULL,
+ "serviceName" TEXT NOT NULL,
+ "chunkSize" TEXT NOT NULL,
+ "chunkedUploadsTimeout" INTEGER NOT NULL,
+ "maxSize" TEXT NOT NULL,
+ "generateZips" BOOLEAN NOT NULL,
+ "generateOriginalFileNameWithIdentifier" BOOLEAN NOT NULL DEFAULT false,
+ "generatedFilenameLength" INTEGER NOT NULL,
+ "generatedAlbumLength" INTEGER NOT NULL,
+ "generatedLinksLength" INTEGER NOT NULL DEFAULT 8,
+ "blockedExtensions" TEXT NOT NULL,
+ "blockNoExtension" BOOLEAN NOT NULL,
+ "publicMode" BOOLEAN NOT NULL,
+ "userAccounts" BOOLEAN NOT NULL,
+ "disableStatisticsCron" BOOLEAN NOT NULL,
+ "disableUpdateCheck" BOOLEAN NOT NULL DEFAULT false,
+ "backgroundImageURL" TEXT NOT NULL,
+ "logoURL" TEXT NOT NULL,
+ "metaDescription" TEXT NOT NULL,
+ "metaKeywords" TEXT NOT NULL,
+ "metaTwitterHandle" TEXT NOT NULL,
+ "metaDomain" TEXT NOT NULL DEFAULT '',
+ "serveUploadsFrom" TEXT NOT NULL DEFAULT '',
+ "enableMixedCaseFilenames" BOOLEAN NOT NULL DEFAULT true,
+ "usersStorageQuota" TEXT NOT NULL DEFAULT '0',
+ "useNetworkStorage" BOOLEAN NOT NULL DEFAULT false,
+ "useMinimalHomepage" BOOLEAN NOT NULL DEFAULT false,
+ "useUrlShortener" BOOLEAN NOT NULL DEFAULT false,
+ "generateThumbnails" BOOLEAN NOT NULL DEFAULT true,
+ "privacyPolicyPageContent" TEXT NOT NULL DEFAULT '',
+ "termsOfServicePageContent" TEXT NOT NULL DEFAULT '',
+ "rulesPageContent" TEXT NOT NULL DEFAULT '',
+ "S3Region" TEXT NOT NULL DEFAULT '',
+ "S3Bucket" TEXT NOT NULL DEFAULT '',
+ "S3AccessKey" TEXT NOT NULL DEFAULT '',
+ "S3SecretKey" TEXT NOT NULL DEFAULT '',
+ "S3Endpoint" TEXT NOT NULL DEFAULT '',
+ "S3PublicUrl" TEXT NOT NULL DEFAULT ''
+);
+INSERT INTO "new_settings" ("S3AccessKey", "S3Bucket", "S3Endpoint", "S3PublicUrl", "S3Region", "S3SecretKey", "backgroundImageURL", "blockNoExtension", "blockedExtensions", "chunkSize", "chunkedUploadsTimeout", "disableStatisticsCron", "disableUpdateCheck", "enableMixedCaseFilenames", "generateOriginalFileNameWithIdentifier", "generateThumbnails", "generateZips", "generatedAlbumLength", "generatedFilenameLength", "generatedLinksLength", "id", "logoURL", "maxSize", "metaDescription", "metaDomain", "metaKeywords", "metaTwitterHandle", "publicMode", "rateLimitMax", "rateLimitWindow", "secret", "serveUploadsFrom", "serviceName", "useMinimalHomepage", "useNetworkStorage", "useUrlShortener", "userAccounts", "usersStorageQuota") SELECT "S3AccessKey", "S3Bucket", "S3Endpoint", "S3PublicUrl", "S3Region", "S3SecretKey", "backgroundImageURL", "blockNoExtension", "blockedExtensions", "chunkSize", "chunkedUploadsTimeout", "disableStatisticsCron", "disableUpdateCheck", "enableMixedCaseFilenames", "generateOriginalFileNameWithIdentifier", "generateThumbnails", "generateZips", "generatedAlbumLength", "generatedFilenameLength", "generatedLinksLength", "id", "logoURL", "maxSize", "metaDescription", "metaDomain", "metaKeywords", "metaTwitterHandle", "publicMode", "rateLimitMax", "rateLimitWindow", "secret", "serveUploadsFrom", "serviceName", "useMinimalHomepage", "useNetworkStorage", "useUrlShortener", "userAccounts", "usersStorageQuota" FROM "settings";
+DROP TABLE "settings";
+ALTER TABLE "new_settings" RENAME TO "settings";
+PRAGMA foreign_key_check;
+PRAGMA foreign_keys=ON;
diff --git a/packages/backend/src/prisma/schema.prisma b/packages/backend/src/prisma/schema.prisma
index 0c028da92..104a4297f 100644
--- a/packages/backend/src/prisma/schema.prisma
+++ b/packages/backend/src/prisma/schema.prisma
@@ -152,6 +152,9 @@ model settings {
useMinimalHomepage Boolean @default(false)
useUrlShortener Boolean @default(false)
generateThumbnails Boolean @default(true)
+ privacyPolicyPageContent String @default("")
+ termsOfServicePageContent String @default("")
+ rulesPageContent String @default("")
S3Region String @default("")
S3Bucket String @default("")
S3AccessKey String @default("")
diff --git a/packages/backend/src/routes/GetSetting.ts b/packages/backend/src/routes/GetSetting.ts
new file mode 100644
index 000000000..40f7a0f88
--- /dev/null
+++ b/packages/backend/src/routes/GetSetting.ts
@@ -0,0 +1,68 @@
+import type { FastifyReply } from 'fastify';
+import { z } from 'zod';
+import type { RequestWithUser } from '@/structures/interfaces.js';
+import { SETTINGS } from '@/structures/settings.js';
+
+const publicSettings = [
+ 'serviceName',
+ 'metaDescription',
+ 'metaKeywords',
+ 'metaTwitterHandle',
+ 'metaDomain',
+ 'chunkSize',
+ 'maxSize',
+ 'logoURL',
+ 'backgroundImageURL',
+ 'publicMode',
+ 'userAccounts',
+ 'blockedExtensions',
+ 'useNetworkStorage',
+ 'useMinimalHomepage',
+ 'serveUploadsFrom',
+ 'useUrlShortener',
+ 'privacyPolicyPageContent',
+ 'termsOfServicePageContent',
+ 'rulesPageContent'
+];
+
+export const schema = {
+ summary: 'Get setting',
+ describe: 'Get the current value of a specific setting of the instance',
+ tags: ['Server'],
+ response: {
+ 200: z.object({
+ value: z.any().optional().describe('The value of the requested setting key.')
+ })
+ }
+};
+
+export const options = {
+ url: '/settings/:key',
+ method: 'get',
+ middlewares: [
+ {
+ name: 'auth',
+ optional: true
+ },
+ {
+ name: 'apiKey',
+ optional: true
+ }
+ ]
+};
+
+export const run = (req: RequestWithUser, res: FastifyReply) => {
+ const { key } = req.params as { key: string };
+
+ if (publicSettings.includes(key)) {
+ return res.send({ value: SETTINGS[key] });
+ }
+
+ if (!req.user) return res.status(403).send({ message: 'Forbidden' });
+
+ if (!req.user.roles.some(role => role.name === 'admin' || role.name === 'owner'))
+ return res.status(403).send({ message: 'Forbidden' });
+
+ if (!SETTINGS[key]) return res.status(404).send({ message: 'Setting not found' });
+ return res.send({ value: SETTINGS[key] });
+};
diff --git a/packages/backend/src/routes/GetSettings.ts b/packages/backend/src/routes/GetSettings.ts
index 631e31eea..1801b31ae 100644
--- a/packages/backend/src/routes/GetSettings.ts
+++ b/packages/backend/src/routes/GetSettings.ts
@@ -27,7 +27,16 @@ export const schema = {
.optional()
.describe('Whether or not to use a minimal version of the homepage.'),
useUrlShortener: z.boolean().describe('Whether or not to use the URL shortener.'),
- serveUploadsFrom: z.string().optional().describe('The URL to serve uploads from.')
+ serveUploadsFrom: z.string().optional().describe('The URL to serve uploads from.'),
+ privacyPolicyPageContent: z
+ .boolean()
+ .optional()
+ .describe('Whether or not the privacy policy page is enabled.'),
+ termsOfServicePageContent: z
+ .boolean()
+ .optional()
+ .describe('Whether or not the terms of service page is enabled.'),
+ rulesPageContent: z.boolean().optional().describe('Whether or not the rules page is enabled.')
})
}
};
@@ -54,6 +63,9 @@ export const run = (_: RequestWithUser, res: FastifyReply) => {
useNetworkStorage: SETTINGS.useNetworkStorage,
useMinimalHomepage: SETTINGS.useMinimalHomepage,
serveUploadsFrom: SETTINGS.serveUploadsFrom,
- useUrlShortener: SETTINGS.useUrlShortener
+ useUrlShortener: SETTINGS.useUrlShortener,
+ privacyPolicyPageContent: Boolean(SETTINGS.privacyPolicyPageContent),
+ termsOfServicePageContent: Boolean(SETTINGS.termsOfServicePageContent),
+ rulesPageContent: Boolean(SETTINGS.rulesPageContent)
});
};
diff --git a/packages/backend/src/structures/interfaces.ts b/packages/backend/src/structures/interfaces.ts
index d417bd155..f00ad60ec 100644
--- a/packages/backend/src/structures/interfaces.ts
+++ b/packages/backend/src/structures/interfaces.ts
@@ -134,13 +134,16 @@ export interface Settings {
metaKeywords: string;
metaTwitterHandle: string;
port: number;
+ privacyPolicyPageContent: string;
publicMode: boolean;
rateLimitMax: number;
rateLimitWindow: number;
+ rulesPageContent: string;
secret: string;
serveUploadsFrom: string;
serviceName: string;
statisticsCron: string;
+ termsOfServicePageContent: string;
updateCheckCron: string;
useNetworkStorage: boolean;
useUrlShortener: boolean;
diff --git a/packages/backend/src/structures/settings.ts b/packages/backend/src/structures/settings.ts
index 21d705ade..345c26e12 100644
--- a/packages/backend/src/structures/settings.ts
+++ b/packages/backend/src/structures/settings.ts
@@ -74,6 +74,9 @@ export const loadSettings = async (force = false) => {
SETTINGS.S3SecretKey = settingsTable.S3SecretKey;
SETTINGS.S3Endpoint = settingsTable.S3Endpoint;
SETTINGS.S3PublicUrl = settingsTable.S3PublicUrl;
+ SETTINGS.privacyPolicyPageContent = settingsTable.privacyPolicyPageContent;
+ SETTINGS.termsOfServicePageContent = settingsTable.termsOfServicePageContent;
+ SETTINGS.rulesPageContent = settingsTable.rulesPageContent;
return;
}
@@ -116,7 +119,10 @@ export const loadSettings = async (force = false) => {
S3AccessKey: '',
S3SecretKey: '',
S3Endpoint: '',
- S3PublicUrl: ''
+ S3PublicUrl: '',
+ privacyPolicyPageContent: '',
+ termsOfServicePageContent: '',
+ rulesPageContent: ''
};
await prisma.settings.create({
@@ -390,5 +396,23 @@ const SETTINGS_META = {
description: 'Whether or not to use a minimal version of the homepage.',
name: 'Use Minimal Homepage',
category: 'customization'
+ },
+ privacyPolicyPageContent: {
+ type: 'text',
+ description: 'The markdown content for the privacy policy page. Leave empty to disable.',
+ name: 'Privacy Policy Page',
+ category: 'legal'
+ },
+ termsOfServicePageContent: {
+ type: 'text',
+ description: 'The markdown content for the terms of service page. Leave empty to disable.',
+ name: 'Terms of Service Page',
+ category: 'legal'
+ },
+ rulesPageContent: {
+ type: 'text',
+ description: 'The markdown content for the rules page. Leave empty to disable.',
+ name: 'Rules Page',
+ category: 'legal'
}
};
diff --git a/packages/next/public/meta-album.jpg b/packages/next/public/meta-album.jpg
deleted file mode 100644
index 2629c2c50..000000000
Binary files a/packages/next/public/meta-album.jpg and /dev/null differ
diff --git a/packages/next/public/meta-faq.jpg b/packages/next/public/meta-faq.jpg
deleted file mode 100644
index fcb2804db..000000000
Binary files a/packages/next/public/meta-faq.jpg and /dev/null differ
diff --git a/packages/next/public/meta-guides.jpg b/packages/next/public/meta-guides.jpg
deleted file mode 100644
index 48c733539..000000000
Binary files a/packages/next/public/meta-guides.jpg and /dev/null differ
diff --git a/packages/next/public/meta-nsfw-album.jpg b/packages/next/public/meta-nsfw-album.jpg
deleted file mode 100644
index 9e5bfae7b..000000000
Binary files a/packages/next/public/meta-nsfw-album.jpg and /dev/null differ
diff --git a/packages/next/public/meta-snippet.jpg b/packages/next/public/meta-snippet.jpg
deleted file mode 100644
index 24f828d16..000000000
Binary files a/packages/next/public/meta-snippet.jpg and /dev/null differ
diff --git a/packages/next/public/meta.jpg b/packages/next/public/meta.jpg
deleted file mode 100644
index aaa916e02..000000000
Binary files a/packages/next/public/meta.jpg and /dev/null differ
diff --git a/packages/next/src/app/(home)/(legal)/privacy-policy/page.tsx b/packages/next/src/app/(home)/(legal)/privacy-policy/page.tsx
new file mode 100644
index 000000000..adf97e159
--- /dev/null
+++ b/packages/next/src/app/(home)/(legal)/privacy-policy/page.tsx
@@ -0,0 +1,46 @@
+import { CustomMDX } from '@/components/mdx/Mdx';
+import request from '@/lib/request';
+import { notFound } from 'next/navigation';
+
+export function generateMetadata() {
+ const section = 'Privacy policy';
+ return {
+ title: section,
+ description: section,
+ openGraph: {
+ title: section,
+ description: section,
+ type: 'article',
+ images: ['/og']
+ },
+ twitter: {
+ card: 'summary_large_image',
+ title: section,
+ description: section,
+ images: ['/og']
+ }
+ };
+}
+
+export default async function PrivacyPolicy() {
+ const { data } = await request.get({
+ url: `settings/privacyPolicyPageContent`,
+ options: {
+ next: {
+ tags: ['settings']
+ }
+ }
+ });
+
+ if (!data) {
+ return notFound();
+ }
+
+ return (
+