initial commit
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import type { Core } from '@strapi/strapi';
|
||||
|
||||
const config = ({ env }: Core.Config.Shared.ConfigParams): Core.Config.Admin => ({
|
||||
auth: {
|
||||
secret: env('ADMIN_JWT_SECRET')!,
|
||||
},
|
||||
apiToken: {
|
||||
salt: env('API_TOKEN_SALT')!,
|
||||
},
|
||||
transfer: {
|
||||
token: {
|
||||
salt: env('TRANSFER_TOKEN_SALT')!,
|
||||
},
|
||||
},
|
||||
secrets: {
|
||||
encryptionKey: env('ENCRYPTION_KEY')!,
|
||||
},
|
||||
/**
|
||||
* Path the admin panel is served from, relative to `server.url`.
|
||||
* Set ADMIN_URL to an absolute URL only when the panel is hosted separately
|
||||
* from the API (in which case also set SERVE_ADMIN_PANEL=false).
|
||||
*/
|
||||
url: env('ADMIN_URL', '/admin'),
|
||||
serveAdminPanel: env.bool('SERVE_ADMIN_PANEL', true),
|
||||
// Do not pop a browser open on `strapi develop` - agents and CI run headless.
|
||||
autoOpen: env.bool('ADMIN_AUTO_OPEN', false),
|
||||
flags: {
|
||||
nps: env.bool('FLAG_NPS', true),
|
||||
promoteEE: env.bool('FLAG_PROMOTE_EE', true),
|
||||
docLinks: env.bool('FLAG_DOC_LINKS', true),
|
||||
},
|
||||
});
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { Core } from '@strapi/strapi';
|
||||
|
||||
const config: Core.Config.Api = {
|
||||
rest: {
|
||||
defaultLimit: 25,
|
||||
maxLimit: 100,
|
||||
withCount: true,
|
||||
strictParams: true,
|
||||
},
|
||||
documents: {
|
||||
strictParams: true,
|
||||
strictRelations: true,
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,72 @@
|
||||
import path from 'path';
|
||||
import type { Core } from '@strapi/strapi';
|
||||
import { isDatabaseClientKind } from '@strapi/database';
|
||||
|
||||
const config = ({ env }: Core.Config.Shared.ConfigParams): Core.Config.Database => {
|
||||
const client = env('DATABASE_CLIENT', 'sqlite');
|
||||
|
||||
if (!isDatabaseClientKind(client)) {
|
||||
throw new Error(
|
||||
`Unsupported DATABASE_CLIENT: ${client}. Use "postgres", "mysql", or "sqlite".`
|
||||
);
|
||||
}
|
||||
|
||||
const connections: Record<Core.Config.Database.ClientKind, Core.Config.Database['connection']> = {
|
||||
mysql: {
|
||||
client: 'mysql',
|
||||
connection: {
|
||||
host: env('DATABASE_HOST', 'localhost'),
|
||||
port: env.int('DATABASE_PORT', 3306),
|
||||
database: env('DATABASE_NAME', 'strapi'),
|
||||
user: env('DATABASE_USERNAME', 'strapi'),
|
||||
password: env('DATABASE_PASSWORD', 'strapi'),
|
||||
ssl: env.bool('DATABASE_SSL', false) && {
|
||||
key: env('DATABASE_SSL_KEY', undefined),
|
||||
cert: env('DATABASE_SSL_CERT', undefined),
|
||||
ca: env('DATABASE_SSL_CA', undefined),
|
||||
capath: env('DATABASE_SSL_CAPATH', undefined),
|
||||
cipher: env('DATABASE_SSL_CIPHER', undefined),
|
||||
rejectUnauthorized: env.bool('DATABASE_SSL_REJECT_UNAUTHORIZED', true),
|
||||
},
|
||||
},
|
||||
pool: { min: env.int('DATABASE_POOL_MIN', 2), max: env.int('DATABASE_POOL_MAX', 10) },
|
||||
},
|
||||
postgres: {
|
||||
client: 'postgres',
|
||||
connection: {
|
||||
connectionString: env('DATABASE_URL'),
|
||||
host: env('DATABASE_HOST', 'localhost'),
|
||||
port: env.int('DATABASE_PORT', 5432),
|
||||
database: env('DATABASE_NAME', 'strapi'),
|
||||
user: env('DATABASE_USERNAME', 'strapi'),
|
||||
password: env('DATABASE_PASSWORD', 'strapi'),
|
||||
ssl: env.bool('DATABASE_SSL', false) && {
|
||||
key: env('DATABASE_SSL_KEY', undefined),
|
||||
cert: env('DATABASE_SSL_CERT', undefined),
|
||||
ca: env('DATABASE_SSL_CA', undefined),
|
||||
capath: env('DATABASE_SSL_CAPATH', undefined),
|
||||
cipher: env('DATABASE_SSL_CIPHER', undefined),
|
||||
rejectUnauthorized: env.bool('DATABASE_SSL_REJECT_UNAUTHORIZED', true),
|
||||
},
|
||||
schema: env('DATABASE_SCHEMA', 'public'),
|
||||
},
|
||||
pool: { min: env.int('DATABASE_POOL_MIN', 2), max: env.int('DATABASE_POOL_MAX', 10) },
|
||||
},
|
||||
sqlite: {
|
||||
client: 'sqlite',
|
||||
connection: {
|
||||
filename: path.join(__dirname, '..', '..', env('DATABASE_FILENAME', '.tmp/data.db')),
|
||||
},
|
||||
useNullAsDefault: true,
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
connection: {
|
||||
...connections[client],
|
||||
acquireConnectionTimeout: env.int('DATABASE_CONNECTION_TIMEOUT', 60000),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,63 @@
|
||||
import type { Core } from '@strapi/strapi';
|
||||
|
||||
/**
|
||||
* Origins that are always allowed outside production: the React Router dev
|
||||
* server (Vite) on both hostnames it is commonly reached on.
|
||||
*/
|
||||
const DEV_ORIGINS = [
|
||||
'http://localhost:5173',
|
||||
'http://127.0.0.1:5173',
|
||||
];
|
||||
|
||||
/**
|
||||
* The default Strapi middleware stack, in order, with CORS configured for the
|
||||
* SHARP-ER web app.
|
||||
*
|
||||
* Order matters: `strapi::errors` must wrap everything after it, `strapi::cors`
|
||||
* must run before routing so pre-flight requests are answered, and
|
||||
* `strapi::public` stays last so it only handles what nothing else claimed.
|
||||
*/
|
||||
const config = ({ env }: Core.Config.Shared.ConfigParams): Core.Config.Middlewares => {
|
||||
const isProduction = env('NODE_ENV', 'development') === 'production';
|
||||
|
||||
/**
|
||||
* Production origins come from the environment, e.g.
|
||||
* CORS_ORIGINS=https://sharper.ly,https://www.sharper.ly
|
||||
* Outside production the local dev server is allowed as well.
|
||||
*/
|
||||
const configuredOrigins = env.array('CORS_ORIGINS', []) as string[];
|
||||
const origin = Array.from(
|
||||
new Set([...(isProduction ? [] : DEV_ORIGINS), ...configuredOrigins].filter(Boolean))
|
||||
);
|
||||
|
||||
return [
|
||||
'strapi::logger',
|
||||
'strapi::errors',
|
||||
// If uploads move to S3/CDN, add the bucket host to img-src and media-src
|
||||
// here - see the S3 notes in config/plugins.ts.
|
||||
'strapi::security',
|
||||
{
|
||||
name: 'strapi::cors',
|
||||
config: {
|
||||
// An empty list blocks every cross-origin browser request, which is the
|
||||
// safe default if CORS_ORIGINS was forgotten in production.
|
||||
origin,
|
||||
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
|
||||
headers: ['Content-Type', 'Authorization', 'Origin', 'Accept'],
|
||||
// The web app sends the locale it wants and reads pagination metadata.
|
||||
expose: ['Content-Range', 'Content-Length'],
|
||||
credentials: true,
|
||||
maxAge: 31536000,
|
||||
keepHeadersOnError: false,
|
||||
},
|
||||
},
|
||||
'strapi::poweredBy',
|
||||
'strapi::query',
|
||||
'strapi::body',
|
||||
'strapi::session',
|
||||
'strapi::favicon',
|
||||
'strapi::public',
|
||||
];
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,143 @@
|
||||
import type { Core } from '@strapi/strapi';
|
||||
|
||||
/**
|
||||
* Media types the Media Library accepts.
|
||||
*
|
||||
* The site publishes reports, guidance notes and event material, so office
|
||||
* documents and PDFs are allowed next to the usual image/video/audio types.
|
||||
*/
|
||||
const allowedMediaTypes = [
|
||||
'image/*',
|
||||
'video/*',
|
||||
'audio/*',
|
||||
'application/pdf',
|
||||
'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.*',
|
||||
'text/plain',
|
||||
'text/csv',
|
||||
];
|
||||
|
||||
/**
|
||||
* Types that are never accepted, even if they slip past `allowedMediaTypes`.
|
||||
* SVG is denied on purpose: it can carry script and is served from the same
|
||||
* origin as the API.
|
||||
*/
|
||||
const deniedTypes = [
|
||||
'image/svg+xml',
|
||||
'application/vnd.microsoft.portable-executable',
|
||||
'application/x-msdownload',
|
||||
'application/x-msdos-program',
|
||||
'application/x-executable',
|
||||
'application/x-dosexec',
|
||||
'application/x-sh',
|
||||
'text/x-shellscript',
|
||||
'application/x-mach-binary',
|
||||
];
|
||||
|
||||
/**
|
||||
* The locales the site ships with.
|
||||
*
|
||||
* `en` is the default (LTR) and `ar` is the right-to-left translation. The web
|
||||
* app mirrors these codes in its URL prefixes (`/en/...`, `/ar/...`), so the
|
||||
* codes here are part of the public contract - do not rename them.
|
||||
*
|
||||
* The i18n plugin itself does not read this key; it is our own declaration and
|
||||
* is read back at boot by `src/bootstrap/locales.ts`, which creates any missing
|
||||
* locale and pins the default one. Adding a locale is therefore a one-line edit
|
||||
* here plus a restart.
|
||||
*/
|
||||
const locales = [
|
||||
{ code: 'en', name: 'English (en)', isDefault: true },
|
||||
{ code: 'ar', name: 'Arabic (ar)' },
|
||||
];
|
||||
|
||||
const config = ({ env }: Core.Config.Shared.ConfigParams): Core.Config.Plugin => ({
|
||||
/**
|
||||
* Internationalisation - core plugin, listed explicitly so the two supported
|
||||
* locales live in version control rather than only in the database.
|
||||
*/
|
||||
i18n: {
|
||||
enabled: true,
|
||||
config: {
|
||||
defaultLocale: 'en',
|
||||
locales,
|
||||
},
|
||||
},
|
||||
|
||||
'users-permissions': {
|
||||
config: {
|
||||
jwtSecret: env('JWT_SECRET'),
|
||||
/**
|
||||
* "refresh" uses the SessionManager (short-lived access token + refresh
|
||||
* token) instead of the legacy long-lived plugin JWT.
|
||||
*/
|
||||
jwtManagement: 'refresh',
|
||||
sessions: {
|
||||
// Refresh token is stored in a cookie that JavaScript cannot read.
|
||||
httpOnly: true,
|
||||
},
|
||||
/**
|
||||
* Rate limit on the auth endpoints (login, forgot-password, ...).
|
||||
* Tightened from the Strapi default of 10 requests per minute.
|
||||
*/
|
||||
ratelimit: {
|
||||
interval: env.int('AUTH_RATE_LIMIT_INTERVAL', 60_000),
|
||||
max: env.int('AUTH_RATE_LIMIT_MAX', 5),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Uploads stay on the default `local` provider: files land in
|
||||
* `apps/cms/public/uploads` and are served by `strapi::public`.
|
||||
*
|
||||
* TO SWITCH TO S3 IN PRODUCTION
|
||||
* -----------------------------
|
||||
* 1. Add the provider package to apps/cms/package.json:
|
||||
* "@strapi/provider-upload-aws-s3": "5.53.0"
|
||||
* 2. Replace the `provider` / `providerOptions` keys below with:
|
||||
*
|
||||
* provider: 'aws-s3',
|
||||
* providerOptions: {
|
||||
* baseUrl: env('CDN_URL'), // optional CloudFront/CDN origin
|
||||
* rootPath: env('S3_ROOT_PATH'), // optional key prefix
|
||||
* s3Options: {
|
||||
* credentials: {
|
||||
* accessKeyId: env('AWS_ACCESS_KEY_ID'),
|
||||
* secretAccessKey: env('AWS_ACCESS_SECRET'),
|
||||
* },
|
||||
* region: env('AWS_REGION'),
|
||||
* params: {
|
||||
* ACL: env('AWS_ACL', 'private'),
|
||||
* signedUrlExpires: env.int('AWS_SIGNED_URL_EXPIRES', 15 * 60),
|
||||
* Bucket: env('AWS_BUCKET'),
|
||||
* },
|
||||
* },
|
||||
* },
|
||||
* actionOptions: { upload: {}, uploadStream: {}, delete: {} },
|
||||
*
|
||||
* 3. Add the bucket/CDN host to the Content Security Policy in
|
||||
* `config/middlewares.ts` (`img-src` and `media-src`), otherwise the admin
|
||||
* panel shows broken thumbnails.
|
||||
* 4. Set the AWS_* / CDN_URL variables listed in `.env.example`.
|
||||
*/
|
||||
upload: {
|
||||
config: {
|
||||
provider: 'local',
|
||||
providerOptions: {
|
||||
// Cache-Control max-age (seconds) sent for locally served uploads.
|
||||
localServer: {
|
||||
maxage: env.int('UPLOAD_LOCAL_MAX_AGE', 300_000),
|
||||
},
|
||||
},
|
||||
// Hard ceiling per file, in bytes. Defaults to 50 MB.
|
||||
sizeLimit: env.int('UPLOAD_MAX_FILE_SIZE', 50 * 1024 * 1024),
|
||||
security: {
|
||||
allowedTypes: allowedMediaTypes,
|
||||
deniedTypes,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { Core } from '@strapi/strapi';
|
||||
|
||||
const config = ({ env }: Core.Config.Shared.ConfigParams): Core.Config.Server => ({
|
||||
host: env('HOST', '0.0.0.0'),
|
||||
port: env.int('PORT', 1337),
|
||||
/**
|
||||
* Public URL of the API. Required behind a reverse proxy / custom domain so
|
||||
* that generated links (media URLs, admin redirects) are absolute and https.
|
||||
* Leave unset in local development.
|
||||
*/
|
||||
url: env('PUBLIC_URL'),
|
||||
// Trust X-Forwarded-* headers when Strapi runs behind nginx / a load balancer.
|
||||
proxy: env.bool('IS_PROXIED', false),
|
||||
app: {
|
||||
keys: env.array('APP_KEYS')!,
|
||||
},
|
||||
logger: {
|
||||
config: {
|
||||
level: env('LOG_LEVEL', 'info'),
|
||||
},
|
||||
},
|
||||
cron: {
|
||||
enabled: env.bool('CRON_ENABLED', false),
|
||||
},
|
||||
webhooks: {
|
||||
populateRelations: env.bool('WEBHOOKS_POPULATE_RELATIONS', false),
|
||||
},
|
||||
});
|
||||
|
||||
export default config;
|
||||
Reference in New Issue
Block a user