feat: Containerize services with Docker Compose (PostgreSQL, Strapi CMS, Express Backend, Nginx Frontend) on custom ports >9000
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
node_modules
|
||||
dist
|
||||
.git
|
||||
.gitignore
|
||||
.tmp
|
||||
.cache
|
||||
*.log
|
||||
Dockerfile
|
||||
@@ -0,0 +1,131 @@
|
||||
############################
|
||||
# OS X
|
||||
############################
|
||||
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
Icon
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
._*
|
||||
|
||||
|
||||
############################
|
||||
# Linux
|
||||
############################
|
||||
|
||||
*~
|
||||
|
||||
|
||||
############################
|
||||
# Windows
|
||||
############################
|
||||
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
$RECYCLE.BIN/
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
|
||||
############################
|
||||
# Packages
|
||||
############################
|
||||
|
||||
*.7z
|
||||
*.csv
|
||||
*.dat
|
||||
*.dmg
|
||||
*.gz
|
||||
*.iso
|
||||
*.jar
|
||||
*.rar
|
||||
*.tar
|
||||
*.zip
|
||||
*.com
|
||||
*.class
|
||||
*.dll
|
||||
*.exe
|
||||
*.o
|
||||
*.seed
|
||||
*.so
|
||||
*.swo
|
||||
*.swp
|
||||
*.swn
|
||||
*.swm
|
||||
*.out
|
||||
*.pid
|
||||
|
||||
|
||||
############################
|
||||
# Logs and databases
|
||||
############################
|
||||
|
||||
.tmp
|
||||
*.log
|
||||
*.sql
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
|
||||
|
||||
############################
|
||||
# Misc.
|
||||
############################
|
||||
|
||||
*#
|
||||
ssl
|
||||
.idea
|
||||
nbproject
|
||||
public/uploads/*
|
||||
!public/uploads/.gitkeep
|
||||
.tsbuildinfo
|
||||
.eslintcache
|
||||
|
||||
############################
|
||||
# Node.js
|
||||
############################
|
||||
|
||||
lib-cov
|
||||
lcov.info
|
||||
pids
|
||||
logs
|
||||
results
|
||||
node_modules
|
||||
.node_history
|
||||
|
||||
############################
|
||||
# Package managers
|
||||
############################
|
||||
|
||||
.yarn/*
|
||||
!.yarn/cache
|
||||
!.yarn/unplugged
|
||||
!.yarn/patches
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
.pnp.*
|
||||
yarn-error.log
|
||||
|
||||
############################
|
||||
# Tests
|
||||
############################
|
||||
|
||||
coverage
|
||||
|
||||
############################
|
||||
# Strapi
|
||||
############################
|
||||
|
||||
.env
|
||||
license.txt
|
||||
exports
|
||||
.strapi
|
||||
dist
|
||||
build
|
||||
.strapi-updater.json
|
||||
.strapi-cloud.json
|
||||
@@ -0,0 +1,21 @@
|
||||
FROM node:20-alpine
|
||||
|
||||
# Install native build tools for packages like better-sqlite3 / pg
|
||||
RUN apk add --no-cache python3 make g++ gcc libc-dev
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files and install dependencies
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
|
||||
# Copy rest of Strapi CMS source code
|
||||
COPY . .
|
||||
|
||||
# Build Strapi admin interface
|
||||
ENV NODE_ENV=production
|
||||
RUN npm run build
|
||||
|
||||
EXPOSE 1337
|
||||
|
||||
CMD ["npm", "run", "start"]
|
||||
@@ -0,0 +1,61 @@
|
||||
# 🚀 Getting started with Strapi
|
||||
|
||||
Strapi comes with a full featured [Command Line Interface](https://docs.strapi.io/dev-docs/cli) (CLI) which lets you scaffold and manage your project in seconds.
|
||||
|
||||
### `develop`
|
||||
|
||||
Start your Strapi application with autoReload enabled. [Learn more](https://docs.strapi.io/dev-docs/cli#strapi-develop)
|
||||
|
||||
```
|
||||
npm run develop
|
||||
# or
|
||||
yarn develop
|
||||
```
|
||||
|
||||
### `start`
|
||||
|
||||
Start your Strapi application with autoReload disabled. [Learn more](https://docs.strapi.io/dev-docs/cli#strapi-start)
|
||||
|
||||
```
|
||||
npm run start
|
||||
# or
|
||||
yarn start
|
||||
```
|
||||
|
||||
### `build`
|
||||
|
||||
Build your admin panel. [Learn more](https://docs.strapi.io/dev-docs/cli#strapi-build)
|
||||
|
||||
```
|
||||
npm run build
|
||||
# or
|
||||
yarn build
|
||||
```
|
||||
|
||||
## ⚙️ Deployment
|
||||
|
||||
Strapi gives you many possible deployment options for your project including [Strapi Cloud](https://cloud.strapi.io). Browse the [deployment section of the documentation](https://docs.strapi.io/dev-docs/deployment) to find the best solution for your use case.
|
||||
|
||||
```
|
||||
yarn strapi deploy
|
||||
```
|
||||
|
||||
## 📚 Learn more
|
||||
|
||||
- [Resource center](https://strapi.io/resource-center) - Strapi resource center.
|
||||
- [Strapi documentation](https://docs.strapi.io) - Official Strapi documentation.
|
||||
- [Strapi tutorials](https://strapi.io/tutorials) - List of tutorials made by the core team and the community.
|
||||
- [Strapi blog](https://strapi.io/blog) - Official Strapi blog containing articles made by the Strapi team and the community.
|
||||
- [Changelog](https://strapi.io/changelog) - Find out about the Strapi product updates, new features and general improvements.
|
||||
|
||||
Feel free to check out the [Strapi GitHub repository](https://github.com/strapi/strapi). Your feedback and contributions are welcome!
|
||||
|
||||
## ✨ Community
|
||||
|
||||
- [Discord](https://discord.strapi.io) - Come chat with the Strapi community including the core team.
|
||||
- [Forum](https://forum.strapi.io/) - Place to discuss, ask questions and find answers, show your Strapi project and get feedback or just talk with other Community members.
|
||||
- [Awesome Strapi](https://github.com/strapi/awesome-strapi) - A curated list of awesome things related to Strapi.
|
||||
|
||||
---
|
||||
|
||||
<sub>🤫 Psst! [Strapi is hiring](https://strapi.io/careers).</sub>
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { Core } from '@strapi/strapi';
|
||||
|
||||
const config = ({ env }: Core.Config.Shared.ConfigParams): Core.Config.Admin => ({
|
||||
rateLimit: {
|
||||
enabled: false,
|
||||
},
|
||||
auth: {
|
||||
secret: env('ADMIN_JWT_SECRET')!,
|
||||
},
|
||||
apiToken: {
|
||||
salt: env('API_TOKEN_SALT')!,
|
||||
},
|
||||
transfer: {
|
||||
token: {
|
||||
salt: env('TRANSFER_TOKEN_SALT')!,
|
||||
},
|
||||
},
|
||||
secrets: {
|
||||
encryptionKey: env('ENCRYPTION_KEY')!,
|
||||
},
|
||||
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,36 @@
|
||||
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', 'postgres');
|
||||
|
||||
const connections = {
|
||||
postgres: {
|
||||
client: 'postgres',
|
||||
connection: {
|
||||
host: env('DATABASE_HOST', '127.0.0.1'),
|
||||
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),
|
||||
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 as keyof typeof connections] || connections.postgres) as Core.Config.Database['connection'],
|
||||
};
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { Core } from '@strapi/strapi';
|
||||
|
||||
const config: Core.Config.Middlewares = [
|
||||
'strapi::logger',
|
||||
'strapi::errors',
|
||||
'strapi::security',
|
||||
'strapi::cors',
|
||||
'strapi::poweredBy',
|
||||
'strapi::query',
|
||||
'strapi::body',
|
||||
'strapi::session',
|
||||
'strapi::favicon',
|
||||
'strapi::public',
|
||||
];
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,44 @@
|
||||
import type { Core } from '@strapi/strapi';
|
||||
|
||||
const allowedMediaTypes = [
|
||||
'image/*',
|
||||
'video/*',
|
||||
'audio/*',
|
||||
'application/pdf',
|
||||
'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.*',
|
||||
'text/plain',
|
||||
'text/csv',
|
||||
];
|
||||
|
||||
const deniedExecutableTypes = [
|
||||
'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',
|
||||
];
|
||||
|
||||
const config = ({ env }: Core.Config.Shared.ConfigParams): Core.Config.Plugin => ({
|
||||
'users-permissions': {
|
||||
config: {
|
||||
jwtManagement: 'refresh',
|
||||
sessions: {
|
||||
httpOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
upload: {
|
||||
config: {
|
||||
security: {
|
||||
allowedTypes: allowedMediaTypes,
|
||||
deniedTypes: deniedExecutableTypes,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,14 @@
|
||||
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),
|
||||
app: {
|
||||
keys: env.array('APP_KEYS')!,
|
||||
},
|
||||
webhooks: {
|
||||
populateRelations: env.bool('WEBHOOKS_POPULATE_RELATIONS', false),
|
||||
},
|
||||
});
|
||||
|
||||
export default config;
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 497 B |
@@ -0,0 +1,161 @@
|
||||
const betterSqlite3 = require('better-sqlite3');
|
||||
const { Pool } = require('pg');
|
||||
const path = require('path');
|
||||
|
||||
const sqlitePath = path.join(__dirname, '.tmp/data.db');
|
||||
const sqliteDb = new betterSqlite3(sqlitePath);
|
||||
|
||||
const pgPool = new Pool({
|
||||
host: process.env.DATABASE_HOST || '127.0.0.1',
|
||||
port: parseInt(process.env.DATABASE_PORT || '9543', 10),
|
||||
user: process.env.DATABASE_USERNAME || 'strapi',
|
||||
password: process.env.DATABASE_PASSWORD || 'strapi_password_9000',
|
||||
database: process.env.DATABASE_NAME || 'strapi',
|
||||
});
|
||||
|
||||
async function migrate() {
|
||||
console.log('Connecting to PostgreSQL...');
|
||||
const client = await pgPool.connect();
|
||||
|
||||
try {
|
||||
console.log('Disabling constraints temporarily for migration...');
|
||||
await client.query("SET session_replication_role = 'replica';");
|
||||
|
||||
// 1. Get all public tables in Postgres and TRUNCATE them
|
||||
const allTablesRes = await client.query(`
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public' AND table_type = 'BASE TABLE';
|
||||
`);
|
||||
const pgTables = allTablesRes.rows.map(r => r.table_name);
|
||||
|
||||
for (const t of pgTables) {
|
||||
await client.query(`TRUNCATE TABLE "${t}" CASCADE;`);
|
||||
}
|
||||
console.log(`Truncated all ${pgTables.length} PostgreSQL tables.`);
|
||||
|
||||
// 2. Migrate tables from SQLite
|
||||
const tablesToMigrate = [
|
||||
'strapi_database_schema',
|
||||
'strapi_migrations_internal',
|
||||
'strapi_core_store_settings',
|
||||
'i18n_locale',
|
||||
'files',
|
||||
'files_related_mph',
|
||||
'job_roles',
|
||||
'blogs',
|
||||
'blogs_author_lnk',
|
||||
'blogs_editor_lnk',
|
||||
'admin_roles',
|
||||
'admin_users',
|
||||
'admin_users_roles_lnk',
|
||||
'admin_permissions',
|
||||
'admin_permissions_role_lnk',
|
||||
'up_roles',
|
||||
'up_permissions',
|
||||
'up_permissions_role_lnk',
|
||||
'strapi_api_tokens',
|
||||
'strapi_sessions',
|
||||
];
|
||||
|
||||
for (const table of tablesToMigrate) {
|
||||
if (!pgTables.includes(table)) {
|
||||
console.log(`Table ${table} does not exist in Postgres, skipping.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const colInfoRes = await client.query(
|
||||
`SELECT column_name, data_type FROM information_schema.columns WHERE table_name = $1;`,
|
||||
[table]
|
||||
);
|
||||
const colDataTypes = {};
|
||||
colInfoRes.rows.forEach(r => {
|
||||
colDataTypes[r.column_name] = r.data_type;
|
||||
});
|
||||
|
||||
const rows = sqliteDb.prepare(`SELECT * FROM "${table}"`).all();
|
||||
console.log(`Migrating table: ${table} (${rows.length} rows)`);
|
||||
|
||||
if (rows.length === 0) continue;
|
||||
|
||||
const columns = Object.keys(rows[0]);
|
||||
const colNames = columns.map((c) => `"${c}"`).join(', ');
|
||||
|
||||
for (let index = 0; index < rows.length; index++) {
|
||||
const row = rows[index];
|
||||
const values = columns.map((col) => {
|
||||
let val = row[col];
|
||||
const dt = colDataTypes[col];
|
||||
|
||||
if ((dt === 'timestamp with time zone' || dt === 'timestamp without time zone') && val !== null) {
|
||||
if (typeof val === 'number' || (typeof val === 'string' && /^\d+$/.test(val))) {
|
||||
val = new Date(Number(val)).toISOString();
|
||||
}
|
||||
}
|
||||
|
||||
if (dt === 'json' || dt === 'jsonb') {
|
||||
if (val === '' || val === undefined) {
|
||||
val = null;
|
||||
} else if (typeof val === 'object') {
|
||||
val = JSON.stringify(val);
|
||||
}
|
||||
}
|
||||
|
||||
if (dt === 'boolean' && val !== null) {
|
||||
if (val === 1 || val === '1' || val === 'true') val = true;
|
||||
if (val === 0 || val === '0' || val === 'false') val = false;
|
||||
}
|
||||
|
||||
return val;
|
||||
});
|
||||
|
||||
const placeholders = values.map((_, i) => `$${i + 1}`).join(', ');
|
||||
const query = `INSERT INTO "${table}" (${colNames}) VALUES (${placeholders});`;
|
||||
await client.query(query, values);
|
||||
}
|
||||
|
||||
if (columns.includes('id')) {
|
||||
const seqRes = await client.query(`
|
||||
SELECT pg_get_serial_sequence('"${table}"', 'id') AS seq;
|
||||
`);
|
||||
const seqName = seqRes.rows[0]?.seq;
|
||||
if (seqName) {
|
||||
await client.query(`
|
||||
SELECT setval('${seqName}', COALESCE((SELECT MAX(id) FROM "${table}"), 1));
|
||||
`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Fix ordinals for all link tables
|
||||
console.log('Fixing ordinals for link tables...');
|
||||
await client.query(`
|
||||
UPDATE public.admin_users_roles_lnk a SET user_ord = b.inv_order FROM (
|
||||
SELECT id, ROW_NUMBER() OVER (PARTITION BY role_id ORDER BY id) as inv_order FROM public.admin_users_roles_lnk
|
||||
) b WHERE a.id = b.id;
|
||||
`);
|
||||
await client.query(`
|
||||
UPDATE public.admin_permissions_role_lnk a SET permission_ord = b.inv_order FROM (
|
||||
SELECT id, ROW_NUMBER() OVER (PARTITION BY role_id ORDER BY id) as inv_order FROM public.admin_permissions_role_lnk
|
||||
) b WHERE a.id = b.id;
|
||||
`);
|
||||
await client.query(`
|
||||
UPDATE public.up_permissions_role_lnk a SET permission_ord = b.inv_order FROM (
|
||||
SELECT id, ROW_NUMBER() OVER (PARTITION BY role_id ORDER BY id) as inv_order FROM public.up_permissions_role_lnk
|
||||
) b WHERE a.id = b.id;
|
||||
`);
|
||||
|
||||
console.log('Re-enabling constraints...');
|
||||
await client.query("SET session_replication_role = 'origin';");
|
||||
|
||||
console.log('Migration completed successfully!');
|
||||
} catch (err) {
|
||||
console.error('Migration failed:', err);
|
||||
} finally {
|
||||
client.release();
|
||||
await pgPool.end();
|
||||
sqliteDb.close();
|
||||
}
|
||||
}
|
||||
|
||||
migrate();
|
||||
Generated
+21962
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "strapi-cms",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "A Strapi application",
|
||||
"scripts": {
|
||||
"build": "strapi build",
|
||||
"console": "strapi console",
|
||||
"deploy": "strapi deploy",
|
||||
"dev": "strapi develop",
|
||||
"develop": "strapi develop",
|
||||
"start": "strapi start",
|
||||
"strapi": "strapi",
|
||||
"upgrade": "npx @strapi/upgrade latest",
|
||||
"upgrade:dry": "npx @strapi/upgrade latest --dry"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/database": "5.51.1",
|
||||
"@strapi/plugin-cloud": "5.51.1",
|
||||
"@strapi/plugin-users-permissions": "5.51.1",
|
||||
"@strapi/strapi": "5.51.1",
|
||||
"better-sqlite3": "12.8.0",
|
||||
"bcryptjs": "^3.0.3",
|
||||
"pg": "^8.11.5",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"react-router-dom": "^6.30.3",
|
||||
"styled-components": "^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0 <=26.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"strapi": {
|
||||
"uuid": "25821e8c-5916-47a2-8e6d-77fa97960842",
|
||||
"installId": "9ed3e8ba9f17709af1a72fc11afdaf5363d3dbf1e159b1aa3056ef1e305cb60d"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 940 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 754 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
@@ -0,0 +1,3 @@
|
||||
# To prevent search engines from seeing the site altogether, uncomment the next two lines:
|
||||
# User-Agent: *
|
||||
# Disallow: /
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { StrapiApp } from '@strapi/strapi/admin';
|
||||
|
||||
export default {
|
||||
config: {
|
||||
locales: [
|
||||
// 'ar',
|
||||
// 'fr',
|
||||
// 'cs',
|
||||
// 'de',
|
||||
// 'da',
|
||||
// 'es',
|
||||
// 'he',
|
||||
// 'id',
|
||||
// 'it',
|
||||
// 'ja',
|
||||
// 'ko',
|
||||
// 'ms',
|
||||
// 'nl',
|
||||
// 'no',
|
||||
// 'pl',
|
||||
// 'pt-BR',
|
||||
// 'pt',
|
||||
// 'ru',
|
||||
// 'sk',
|
||||
// 'sv',
|
||||
// 'th',
|
||||
// 'tr',
|
||||
// 'uk',
|
||||
// 'vi',
|
||||
// 'zh-Hans',
|
||||
// 'zh',
|
||||
],
|
||||
},
|
||||
bootstrap(app: StrapiApp) {
|
||||
console.log(app);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["../plugins/**/admin/src/**/*", "./"],
|
||||
"exclude": ["node_modules/", "build/", "dist/", "**/*.test.ts"]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { mergeConfig, type UserConfig } from 'vite';
|
||||
|
||||
export default (config: UserConfig) => {
|
||||
// Important: always return the modified config
|
||||
return mergeConfig(config, {
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': '/src',
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
const designationMap: Record<string, string> = {
|
||||
'admin@cavininfotech.com': 'Superuser & Infrastructure Lead',
|
||||
'arvindh@cavininfotech.com': 'Chief AI Architect & Head of Data Engineering',
|
||||
'siddharth@cavininfotech.com': 'VP of Smart Factory & IIoT Solutions',
|
||||
'priya@cavininfotech.com': 'Lead Immersive UX & Spatial Computing Strategist',
|
||||
'vikram@cavininfotech.com': 'Senior Editor - Industrial Operations & OT Tech',
|
||||
'meera@cavininfotech.com': 'Senior Editor - Enterprise Software & Cloud',
|
||||
};
|
||||
|
||||
export default {
|
||||
async beforeCreate(event: any) {
|
||||
const { data } = event.params;
|
||||
const ctx = strapi.requestContext?.get();
|
||||
const user = ctx?.state?.user;
|
||||
|
||||
if (user) {
|
||||
// Auto-fill author relation if not explicitly set by user
|
||||
if (!data.author) {
|
||||
data.author = user.id;
|
||||
}
|
||||
|
||||
// Auto-fill author designation based on logged-in account
|
||||
if (!data.authorDesignation && user.email) {
|
||||
data.authorDesignation = designationMap[user.email] || `${user.firstname || ''} ${user.lastname || ''}`.trim();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async beforeUpdate(event: any) {
|
||||
const { data } = event.params;
|
||||
const ctx = strapi.requestContext?.get();
|
||||
const user = ctx?.state?.user;
|
||||
|
||||
if (user && data) {
|
||||
// Auto-fill author relation if empty
|
||||
if (!data.author) {
|
||||
data.author = user.id;
|
||||
}
|
||||
|
||||
// Auto-fill author designation if empty
|
||||
if (!data.authorDesignation && user.email) {
|
||||
data.authorDesignation = designationMap[user.email] || `${user.firstname || ''} ${user.lastname || ''}`.trim();
|
||||
}
|
||||
|
||||
// Auto-fill editor details if an Editor/Admin edits or approves
|
||||
const isEditor = user.roles?.some((r: any) => r.code === 'strapi-editor' || r.code === 'strapi-super-admin');
|
||||
if (isEditor) {
|
||||
if (!data.editor) {
|
||||
data.editor = user.id;
|
||||
}
|
||||
if (!data.editorDesignation && user.email) {
|
||||
data.editorDesignation = designationMap[user.email] || 'Senior Editor';
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"kind": "collectionType",
|
||||
"collectionName": "blogs",
|
||||
"info": {
|
||||
"singularName": "blog",
|
||||
"pluralName": "blogs",
|
||||
"displayName": "Blog",
|
||||
"description": "Blog posts with relational author & editor links to admin::user, media cover images, and job designations."
|
||||
},
|
||||
"options": {
|
||||
"draftAndPublish": true
|
||||
},
|
||||
"attributes": {
|
||||
"title": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"slug": {
|
||||
"type": "uid",
|
||||
"targetField": "title",
|
||||
"required": true
|
||||
},
|
||||
"category": {
|
||||
"type": "enumeration",
|
||||
"enum": [
|
||||
"AI & Data",
|
||||
"Industrial IoT",
|
||||
"AR/VR & Immersive Tech",
|
||||
"Operation Technology"
|
||||
],
|
||||
"required": true
|
||||
},
|
||||
"excerpt": {
|
||||
"type": "text",
|
||||
"required": true
|
||||
},
|
||||
"content": {
|
||||
"type": "richtext",
|
||||
"required": true
|
||||
},
|
||||
"coverImage": {
|
||||
"type": "media",
|
||||
"multiple": false,
|
||||
"required": false,
|
||||
"allowedTypes": [
|
||||
"images"
|
||||
]
|
||||
},
|
||||
"approvalStatus": {
|
||||
"type": "enumeration",
|
||||
"enum": [
|
||||
"Draft",
|
||||
"Pending Review",
|
||||
"In Review",
|
||||
"Needs Revision",
|
||||
"Approved",
|
||||
"Published",
|
||||
"Unpublished"
|
||||
],
|
||||
"default": "Draft",
|
||||
"required": true
|
||||
},
|
||||
"author": {
|
||||
"type": "relation",
|
||||
"relation": "oneToOne",
|
||||
"target": "admin::user"
|
||||
},
|
||||
"authorDesignation": {
|
||||
"type": "string"
|
||||
},
|
||||
"editor": {
|
||||
"type": "relation",
|
||||
"relation": "oneToOne",
|
||||
"target": "admin::user"
|
||||
},
|
||||
"editorDesignation": {
|
||||
"type": "string"
|
||||
},
|
||||
"reviewNotes": {
|
||||
"type": "text"
|
||||
},
|
||||
"approvalDate": {
|
||||
"type": "date"
|
||||
},
|
||||
"readTime": {
|
||||
"type": "string",
|
||||
"default": "5 min read"
|
||||
},
|
||||
"publishDate": {
|
||||
"type": "date"
|
||||
},
|
||||
"tags": {
|
||||
"type": "json"
|
||||
},
|
||||
"metaTitle": {
|
||||
"type": "string"
|
||||
},
|
||||
"metaDescription": {
|
||||
"type": "text"
|
||||
},
|
||||
"keywords": {
|
||||
"type": "string"
|
||||
},
|
||||
"canonicalURL": {
|
||||
"type": "string"
|
||||
},
|
||||
"ogImage": {
|
||||
"type": "media",
|
||||
"multiple": false,
|
||||
"required": false,
|
||||
"allowedTypes": [
|
||||
"images"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { factories } from '@strapi/strapi';
|
||||
|
||||
export default factories.createCoreController('api::blog.blog');
|
||||
@@ -0,0 +1,3 @@
|
||||
import { factories } from '@strapi/strapi';
|
||||
|
||||
export default factories.createCoreRouter('api::blog.blog');
|
||||
@@ -0,0 +1,3 @@
|
||||
import { factories } from '@strapi/strapi';
|
||||
|
||||
export default factories.createCoreService('api::blog.blog');
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"kind": "collectionType",
|
||||
"collectionName": "job_roles",
|
||||
"info": {
|
||||
"singularName": "job-role",
|
||||
"pluralName": "job-roles",
|
||||
"displayName": "Job Role",
|
||||
"description": "Available job roles and career opportunities"
|
||||
},
|
||||
"options": {
|
||||
"draftAndPublish": true
|
||||
},
|
||||
"attributes": {
|
||||
"title": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"department": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"location": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"description": {
|
||||
"type": "text"
|
||||
},
|
||||
"requirements": {
|
||||
"type": "text"
|
||||
},
|
||||
"isActive": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { factories } from '@strapi/strapi';
|
||||
|
||||
export default factories.createCoreController('api::job-role.job-role');
|
||||
@@ -0,0 +1,3 @@
|
||||
import { factories } from '@strapi/strapi';
|
||||
|
||||
export default factories.createCoreRouter('api::job-role.job-role');
|
||||
@@ -0,0 +1,3 @@
|
||||
import { factories } from '@strapi/strapi';
|
||||
|
||||
export default factories.createCoreService('api::job-role.job-role');
|
||||
@@ -0,0 +1,381 @@
|
||||
import type { Core } from '@strapi/strapi';
|
||||
// @ts-ignore
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
export default {
|
||||
register({ strapi }: { strapi: Core.Strapi }) {
|
||||
// Hide 'User' collection type from Content Manager sidebar
|
||||
if (strapi.contentTypes['plugin::users-permissions.user']) {
|
||||
strapi.contentTypes['plugin::users-permissions.user'].pluginOptions = {
|
||||
...(strapi.contentTypes['plugin::users-permissions.user'].pluginOptions || {}),
|
||||
'content-manager': { visible: false },
|
||||
};
|
||||
}
|
||||
|
||||
// Middleware to block Authors from publishing drafts while allowing them to unpublish
|
||||
strapi.server.use(async (ctx, next) => {
|
||||
const url = ctx.url || '';
|
||||
if (
|
||||
url.includes('/content-manager/') &&
|
||||
url.includes('api::blog.blog') &&
|
||||
(url.includes('/publish') || url.includes('/actions/publish')) &&
|
||||
ctx.method === 'POST'
|
||||
) {
|
||||
const user = ctx.state?.user;
|
||||
if (user && user.roles) {
|
||||
const isAuthor = user.roles.some((r: any) => r.code === 'strapi-author');
|
||||
const isSuperAdmin = user.roles.some((r: any) => r.code === 'strapi-super-admin');
|
||||
const isEditor = user.roles.some((r: any) => r.code === 'strapi-editor');
|
||||
|
||||
if (isAuthor && !isSuperAdmin && !isEditor) {
|
||||
const isUnpublish = ctx.request?.body?.discardDraft || url.includes('/unpublish') || ctx.query?.action === 'unpublish';
|
||||
if (!isUnpublish) {
|
||||
return ctx.forbidden('Authors are not permitted to publish draft articles. Please submit for Editor review.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
await next();
|
||||
});
|
||||
},
|
||||
|
||||
async bootstrap({ strapi }: { strapi: Core.Strapi }) {
|
||||
// 1. Enable Public Read Permissions for api::blog.blog & api::job-role.job-role
|
||||
try {
|
||||
const publicRole = await strapi.db.query('plugin::users-permissions.role').findOne({
|
||||
where: { type: 'public' },
|
||||
});
|
||||
|
||||
if (publicRole) {
|
||||
const permissionsToGrant = [
|
||||
'api::blog.blog.find',
|
||||
'api::blog.blog.findOne',
|
||||
'api::job-role.job-role.find',
|
||||
'api::job-role.job-role.findOne',
|
||||
];
|
||||
for (const action of permissionsToGrant) {
|
||||
const existingPermission = await strapi.db
|
||||
.query('plugin::users-permissions.permission')
|
||||
.findOne({
|
||||
where: { role: publicRole.id, action },
|
||||
});
|
||||
|
||||
if (!existingPermission) {
|
||||
await strapi.db.query('plugin::users-permissions.permission').create({
|
||||
data: {
|
||||
action,
|
||||
role: publicRole.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
strapi.log.info('Public permissions for Blog & Job Role APIs initialized.');
|
||||
}
|
||||
} catch (err) {
|
||||
strapi.log.warn('Could not set public permissions automatically:', err);
|
||||
}
|
||||
|
||||
// 2. Fetch Strapi Admin Roles
|
||||
const superAdminRole = await strapi.db.query('admin::role').findOne({ where: { code: 'strapi-super-admin' } });
|
||||
const editorRole = await strapi.db.query('admin::role').findOne({ where: { code: 'strapi-editor' } });
|
||||
const authorRole = await strapi.db.query('admin::role').findOne({ where: { code: 'strapi-author' } });
|
||||
|
||||
// Clean field list for Authors/Editors
|
||||
const contentVisibleFields = [
|
||||
'title',
|
||||
'slug',
|
||||
'category',
|
||||
'excerpt',
|
||||
'content',
|
||||
'coverImage',
|
||||
'approvalStatus',
|
||||
'reviewNotes',
|
||||
'approvalDate',
|
||||
'readTime',
|
||||
'publishDate',
|
||||
'tags',
|
||||
'metaTitle',
|
||||
'metaDescription',
|
||||
'keywords',
|
||||
'canonicalURL',
|
||||
'ogImage',
|
||||
];
|
||||
const propertiesObj = JSON.stringify({ fields: contentVisibleFields });
|
||||
|
||||
// 3. Grant Admin RBAC Permissions with Auto-Populated Fields Hidden from Form
|
||||
const grantRolePermissions = async (roleId: number, subject: string, actions: string[], isCreatorOnly = false, propObj = '{}') => {
|
||||
if (!roleId) return;
|
||||
for (const action of actions) {
|
||||
const conditions = isCreatorOnly ? ['admin::is-creator'] : [];
|
||||
const conditionsStr = JSON.stringify(conditions);
|
||||
const actionProperties = action === 'plugin::content-manager.explorer.delete' ? '{}' : propObj;
|
||||
|
||||
const existing = await strapi.db.connection('admin_permissions')
|
||||
.join('admin_permissions_role_lnk', 'admin_permissions.id', 'admin_permissions_role_lnk.permission_id')
|
||||
.where({
|
||||
'admin_permissions.action': action,
|
||||
'admin_permissions.subject': subject,
|
||||
'admin_permissions_role_lnk.role_id': roleId,
|
||||
})
|
||||
.first();
|
||||
|
||||
if (!existing) {
|
||||
const inserted = await strapi.db.connection('admin_permissions')
|
||||
.insert({
|
||||
action,
|
||||
subject,
|
||||
properties: actionProperties,
|
||||
conditions: conditionsStr,
|
||||
})
|
||||
.returning('id');
|
||||
|
||||
const rawId = Array.isArray(inserted) ? inserted[0] : inserted;
|
||||
const permId = (typeof rawId === 'object' && rawId !== null) ? (rawId.id || rawId) : rawId;
|
||||
|
||||
await strapi.db.connection('admin_permissions_role_lnk').insert({
|
||||
permission_id: Number(permId),
|
||||
role_id: roleId,
|
||||
});
|
||||
strapi.log.info(`Granted RBAC ${action} on ${subject} to role ID ${roleId}`);
|
||||
} else {
|
||||
await strapi.db.connection('admin_permissions')
|
||||
.where({ id: existing.permission_id })
|
||||
.update({
|
||||
properties: actionProperties,
|
||||
conditions: conditionsStr,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Grant Upload / Media Library permissions to Author & Editor roles
|
||||
const grantUploadPermissions = async (roleId: number) => {
|
||||
if (!roleId) return;
|
||||
const uploadActions = [
|
||||
'plugin::upload.assets.create',
|
||||
'plugin::upload.assets.read',
|
||||
'plugin::upload.assets.update',
|
||||
'plugin::upload.assets.download',
|
||||
'plugin::upload.read',
|
||||
];
|
||||
for (const action of uploadActions) {
|
||||
const existing = await strapi.db.connection('admin_permissions')
|
||||
.join('admin_permissions_role_lnk', 'admin_permissions.id', 'admin_permissions_role_lnk.permission_id')
|
||||
.where({
|
||||
'admin_permissions.action': action,
|
||||
'admin_permissions_role_lnk.role_id': roleId,
|
||||
})
|
||||
.first();
|
||||
|
||||
if (!existing) {
|
||||
const inserted = await strapi.db.connection('admin_permissions')
|
||||
.insert({
|
||||
action,
|
||||
subject: null,
|
||||
properties: '{}',
|
||||
conditions: '[]',
|
||||
})
|
||||
.returning('id');
|
||||
|
||||
const rawId = Array.isArray(inserted) ? inserted[0] : inserted;
|
||||
const permId = (typeof rawId === 'object' && rawId !== null) ? (rawId.id || rawId) : rawId;
|
||||
|
||||
await strapi.db.connection('admin_permissions_role_lnk').insert({
|
||||
permission_id: Number(permId),
|
||||
role_id: roleId,
|
||||
});
|
||||
strapi.log.info(`Granted Media Upload permission ${action} to role ID ${roleId}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (authorRole) {
|
||||
await grantRolePermissions(authorRole.id, 'api::blog.blog', [
|
||||
'plugin::content-manager.explorer.create',
|
||||
'plugin::content-manager.explorer.read',
|
||||
'plugin::content-manager.explorer.update',
|
||||
'plugin::content-manager.explorer.publish',
|
||||
], true, propertiesObj);
|
||||
await grantRolePermissions(authorRole.id, 'api::job-role.job-role', [
|
||||
'plugin::content-manager.explorer.create',
|
||||
'plugin::content-manager.explorer.read',
|
||||
'plugin::content-manager.explorer.update',
|
||||
'plugin::content-manager.explorer.publish',
|
||||
], false, '{}');
|
||||
await grantUploadPermissions(authorRole.id);
|
||||
}
|
||||
|
||||
if (editorRole) {
|
||||
await grantRolePermissions(editorRole.id, 'api::blog.blog', [
|
||||
'plugin::content-manager.explorer.create',
|
||||
'plugin::content-manager.explorer.read',
|
||||
'plugin::content-manager.explorer.update',
|
||||
'plugin::content-manager.explorer.publish',
|
||||
], false, propertiesObj);
|
||||
await grantRolePermissions(editorRole.id, 'api::job-role.job-role', [
|
||||
'plugin::content-manager.explorer.create',
|
||||
'plugin::content-manager.explorer.read',
|
||||
'plugin::content-manager.explorer.update',
|
||||
'plugin::content-manager.explorer.publish',
|
||||
], false, '{}');
|
||||
await grantUploadPermissions(editorRole.id);
|
||||
}
|
||||
|
||||
// 4. Provision Strapi Admin Users with Valid Bcrypt Hashes and Admin Roles
|
||||
const adminUserDefs = [
|
||||
{
|
||||
email: 'admin@cavininfotech.com',
|
||||
firstname: 'Cavin',
|
||||
lastname: 'Admin',
|
||||
designation: 'Superuser & Infrastructure Lead',
|
||||
roleId: superAdminRole?.id || 1,
|
||||
},
|
||||
{
|
||||
email: 'arvindh@cavininfotech.com',
|
||||
firstname: 'Dr. Arvindh',
|
||||
lastname: 'Ramachandran',
|
||||
designation: 'Chief AI Architect & Head of Data Engineering',
|
||||
roleId: authorRole?.id || 3,
|
||||
},
|
||||
{
|
||||
email: 'siddharth@cavininfotech.com',
|
||||
firstname: 'Siddharth',
|
||||
lastname: 'Narayanan',
|
||||
designation: 'VP of Smart Factory & IIoT Solutions',
|
||||
roleId: authorRole?.id || 3,
|
||||
},
|
||||
{
|
||||
email: 'priya@cavininfotech.com',
|
||||
firstname: 'Priya S.',
|
||||
lastname: 'Venkatesh',
|
||||
designation: 'Lead Immersive UX & Spatial Computing Strategist',
|
||||
roleId: authorRole?.id || 3,
|
||||
},
|
||||
{
|
||||
email: 'vikram@cavininfotech.com',
|
||||
firstname: 'Vikram',
|
||||
lastname: 'Sen',
|
||||
designation: 'Senior Editor - Industrial Operations & OT Tech',
|
||||
roleId: editorRole?.id || 2,
|
||||
},
|
||||
{
|
||||
email: 'meera@cavininfotech.com',
|
||||
firstname: 'Meera',
|
||||
lastname: 'Krishnan',
|
||||
designation: 'Senior Editor - Enterprise Software & Cloud',
|
||||
roleId: editorRole?.id || 2,
|
||||
},
|
||||
];
|
||||
|
||||
const hashedPassword = await bcrypt.hash('CavinPass2026!', 10);
|
||||
const userMap: Record<string, any> = {};
|
||||
|
||||
for (const uDef of adminUserDefs) {
|
||||
let user = await strapi.db.query('admin::user').findOne({
|
||||
where: { email: uDef.email },
|
||||
populate: ['roles'],
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
user = await strapi.db.query('admin::user').create({
|
||||
data: {
|
||||
email: uDef.email,
|
||||
firstname: uDef.firstname,
|
||||
lastname: uDef.lastname,
|
||||
password: hashedPassword,
|
||||
isActive: true,
|
||||
blocked: false,
|
||||
roles: [uDef.roleId],
|
||||
},
|
||||
});
|
||||
strapi.log.info(`Created Strapi Admin User: ${uDef.firstname} ${uDef.lastname} (${uDef.email})`);
|
||||
} else {
|
||||
if (!user.password.startsWith('$2')) {
|
||||
await strapi.db.query('admin::user').update({
|
||||
where: { id: user.id },
|
||||
data: { password: hashedPassword },
|
||||
});
|
||||
}
|
||||
if (!user.roles || user.roles.length === 0) {
|
||||
await strapi.db.query('admin::user').update({
|
||||
where: { id: user.id },
|
||||
data: { roles: [uDef.roleId] },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const linkCount = await strapi.db.connection('admin_users_roles_lnk').where({ user_id: user.id }).count('* as c');
|
||||
const countVal = Array.isArray(linkCount) ? (linkCount[0]?.c || linkCount[0]?.['count(*)']) : 0;
|
||||
if (Number(countVal) === 0) {
|
||||
await strapi.db.connection('admin_users_roles_lnk').insert({
|
||||
user_id: user.id,
|
||||
role_id: uDef.roleId,
|
||||
role_ord: 1,
|
||||
user_ord: 1,
|
||||
});
|
||||
}
|
||||
|
||||
userMap[uDef.email] = {
|
||||
...user,
|
||||
designation: uDef.designation,
|
||||
fullName: `${uDef.firstname} ${uDef.lastname}`,
|
||||
};
|
||||
}
|
||||
|
||||
// 5. Seed Default Available Job Roles if Collection is empty
|
||||
try {
|
||||
const existingRolesCount = await strapi.db.query('api::job-role.job-role').count();
|
||||
if (existingRolesCount === 0) {
|
||||
const jobRoleDefs = [
|
||||
{
|
||||
title: 'Lead AI & Data Architect',
|
||||
department: 'AI & Analytics',
|
||||
type: 'Full-Time',
|
||||
location: 'Hybrid / Chennai',
|
||||
description: 'Lead design and execution of scalable AI, machine learning pipelines, and data architectures.',
|
||||
requirements: '8+ years in AI/ML, PyTorch/TensorFlow, Cloud Architectures, Distributed Data Systems.',
|
||||
isActive: true,
|
||||
publishedAt: new Date(),
|
||||
},
|
||||
{
|
||||
title: 'Senior IIoT & Smart Factory Specialist',
|
||||
department: 'Industrial IoT',
|
||||
type: 'Full-Time',
|
||||
location: 'On-Site / Chennai',
|
||||
description: 'Build enterprise IIoT platforms, sensor networks, and edge computing for modern smart factories.',
|
||||
requirements: '5+ years in IIoT, MQTT/OPC-UA, SCADA integration, Edge AI.',
|
||||
isActive: true,
|
||||
publishedAt: new Date(),
|
||||
},
|
||||
{
|
||||
title: 'Spatial Computing & MR UX Designer',
|
||||
department: 'AR/VR & Immersive Tech',
|
||||
type: 'Full-Time',
|
||||
location: 'Hybrid',
|
||||
description: 'Design next-gen spatial UX, mixed reality interfaces, and 3D digital twin visualizations.',
|
||||
requirements: 'Spatial UX, Unity/Unreal, Figma 3D, WebXR.',
|
||||
isActive: true,
|
||||
publishedAt: new Date(),
|
||||
},
|
||||
{
|
||||
title: 'Cloud Infrastructure & DevOps Engineer',
|
||||
department: 'Enterprise Cloud',
|
||||
type: 'Full-Time',
|
||||
location: 'Remote / Hybrid',
|
||||
description: 'Manage high-availability multi-cloud infrastructure, Kubernetes clusters, and automated CI/CD pipelines.',
|
||||
requirements: 'Kubernetes, Terraform, AWS/GCP, Zero-Trust Security.',
|
||||
isActive: true,
|
||||
publishedAt: new Date(),
|
||||
},
|
||||
];
|
||||
|
||||
for (const roleData of jobRoleDefs) {
|
||||
await strapi.db.query('api::job-role.job-role').create({ data: roleData });
|
||||
strapi.log.info(`Seeded Job Role: ${roleData.title}`);
|
||||
}
|
||||
}
|
||||
} catch (jobSeedErr) {
|
||||
strapi.log.warn('Job Role seeding note:', jobSeedErr);
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "Node",
|
||||
"lib": ["ES2020"],
|
||||
"target": "ES2019",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"incremental": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"noEmitOnError": true,
|
||||
"noImplicitThis": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": [
|
||||
// Include root files
|
||||
"./",
|
||||
// Include all ts files
|
||||
"./**/*.ts",
|
||||
// Include all js files
|
||||
"./**/*.js",
|
||||
// Force the JSON files in the src folder to be included
|
||||
"src/**/*.json"
|
||||
],
|
||||
|
||||
"exclude": [
|
||||
"node_modules/",
|
||||
"build/",
|
||||
"dist/",
|
||||
".cache/",
|
||||
".tmp/",
|
||||
".strapi/",
|
||||
|
||||
// Do not include admin files in the server compilation
|
||||
"src/admin/",
|
||||
// Do not include test files
|
||||
"**/*.test.*",
|
||||
// Do not include plugins in the server compilation
|
||||
"src/plugins/**"
|
||||
]
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
/*
|
||||
* The app doesn't have any components yet.
|
||||
*/
|
||||
+1078
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user