├── src ├── api │ ├── .gitkeep │ ├── post │ │ ├── routes │ │ │ ├── like-post.js │ │ │ ├── custom-route.js │ │ │ └── post.js │ │ ├── policies │ │ │ └── check-role.js │ │ ├── content-types │ │ │ └── post │ │ │ │ ├── lifecycles.js │ │ │ │ └── schema.json │ │ ├── graphql │ │ │ └── post.js │ │ ├── services │ │ │ └── post.js │ │ └── controllers │ │ │ └── post.js │ ├── menu │ │ ├── routes │ │ │ └── menu.js │ │ ├── services │ │ │ └── menu.js │ │ ├── controllers │ │ │ └── menu.js │ │ └── content-types │ │ │ └── menu │ │ │ └── schema.json │ ├── tag │ │ ├── services │ │ │ └── tag.js │ │ ├── controllers │ │ │ └── tag.js │ │ ├── routes │ │ │ └── tag.js │ │ └── content-types │ │ │ └── tag │ │ │ └── schema.json │ ├── author │ │ ├── routes │ │ │ └── author.js │ │ ├── services │ │ │ └── author.js │ │ ├── controllers │ │ │ └── author.js │ │ └── content-types │ │ │ └── author │ │ │ └── schema.json │ ├── course │ │ ├── routes │ │ │ └── course.js │ │ ├── services │ │ │ └── course.js │ │ ├── controllers │ │ │ └── course.js │ │ └── content-types │ │ │ └── course │ │ │ └── schema.json │ ├── footer │ │ ├── routes │ │ │ └── footer.js │ │ ├── services │ │ │ └── footer.js │ │ ├── controllers │ │ │ └── footer.js │ │ └── content-types │ │ │ └── footer │ │ │ └── schema.json │ ├── header │ │ ├── routes │ │ │ └── header.js │ │ ├── services │ │ │ └── header.js │ │ ├── controllers │ │ │ └── header.js │ │ └── content-types │ │ │ └── header │ │ │ └── schema.json │ ├── service │ │ ├── routes │ │ │ └── service.js │ │ ├── services │ │ │ └── service.js │ │ ├── controllers │ │ │ └── service.js │ │ └── content-types │ │ │ └── service │ │ │ └── schema.json │ ├── blog-page │ │ ├── routes │ │ │ └── blog-page.js │ │ ├── services │ │ │ └── blog-page.js │ │ ├── controllers │ │ │ └── blog-page.js │ │ └── content-types │ │ │ └── blog-page │ │ │ └── schema.json │ ├── home-page │ │ ├── routes │ │ │ └── home-page.js │ │ ├── services │ │ │ └── home-page.js │ │ ├── controllers │ │ │ └── home-page.js │ │ └── content-types │ │ │ └── home-page │ │ │ └── schema.json │ ├── seo-config │ │ ├── routes │ │ │ └── seo-config.js │ │ ├── services │ │ │ └── seo-config.js │ │ ├── controllers │ │ │ └── seo-config.js │ │ └── content-types │ │ │ └── seo-config │ │ │ └── schema.json │ ├── static-page │ │ ├── routes │ │ │ └── static-page.js │ │ ├── services │ │ │ └── static-page.js │ │ ├── controllers │ │ │ └── static-page.js │ │ └── content-types │ │ │ └── static-page │ │ │ └── schema.json │ ├── company-info │ │ ├── routes │ │ │ └── company-info.js │ │ ├── services │ │ │ └── company-info.js │ │ ├── controllers │ │ │ └── company-info.js │ │ └── content-types │ │ │ └── company-info │ │ │ └── schema.json │ └── courses-page │ │ ├── routes │ │ └── courses-page.js │ │ ├── services │ │ └── courses-page.js │ │ ├── controllers │ │ └── courses-page.js │ │ └── content-types │ │ └── courses-page │ │ └── schema.json ├── extensions │ ├── .gitkeep │ └── users-permissions │ │ └── content-types │ │ └── user │ │ └── schema.json ├── admin │ ├── extensions │ │ ├── favicon.png │ │ └── logo.svg │ ├── webpack.config.example.js │ └── app.js ├── middlewares │ └── timer-header.js ├── components │ ├── seo │ │ └── seo-information.json │ ├── layout │ │ ├── link.json │ │ ├── services-preview.json │ │ ├── newsletter-form.json │ │ ├── featured-course.json │ │ ├── mission.json │ │ ├── page-info.json │ │ └── hero.json │ ├── blog │ │ └── posts-selection.json │ └── config │ │ └── social-link.json └── index.js ├── public ├── uploads │ └── .gitkeep └── robots.txt ├── .env.example ├── .eslintignore ├── favicon.png ├── config ├── env │ └── production │ │ ├── server.js │ │ ├── admin.js │ │ ├── database.js │ │ ├── middlewares.js │ │ └── plugins.js ├── api.js ├── server.js ├── plugins.js ├── admin.js ├── database.js └── middlewares.js ├── .editorconfig ├── .eslintrc ├── package.json ├── render.yaml ├── .gitignore └── README.md /src/api/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/uploads/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/extensions/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | HOST=0.0.0.0 2 | PORT=1337 3 | APP_KEYS= 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .cache 2 | build 3 | **/node_modules/** 4 | -------------------------------------------------------------------------------- /favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artcoded-net/strapi-devblog/HEAD/favicon.png -------------------------------------------------------------------------------- /config/env/production/server.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ env }) => ({ 2 | url: env("PUBLIC_SERVER_URL", ""), 3 | }); 4 | -------------------------------------------------------------------------------- /src/admin/extensions/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artcoded-net/strapi-devblog/HEAD/src/admin/extensions/favicon.png -------------------------------------------------------------------------------- /config/api.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rest: { 3 | defaultLimit: 25, 4 | maxLimit: 100, 5 | withCount: true, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # To prevent search engines from seeing the site altogether, uncomment the next two lines: 2 | # User-Agent: * 3 | # Disallow: / 4 | -------------------------------------------------------------------------------- /config/server.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ env }) => ({ 2 | host: env("HOST", "0.0.0.0"), 3 | port: env.int("PORT", 1337), 4 | app: { 5 | keys: env.array("APP_KEYS"), 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /config/plugins.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // ... 3 | // "github-projects": { 4 | // enabled: true, 5 | // resolve: "./src/plugins/github-projects", 6 | // }, 7 | // ... 8 | }; 9 | -------------------------------------------------------------------------------- /config/admin.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ env }) => ({ 2 | auth: { 3 | secret: env("ADMIN_JWT_SECRET", "3005f8e3851d45c9e8e012f5e63bfdf2"), 4 | }, 5 | url: "/dashboard", // localhost:1337/dashboard 6 | }); 7 | -------------------------------------------------------------------------------- /src/api/post/routes/like-post.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | routes: [ 3 | { 4 | method: "PUT", 5 | path: "/posts/:id/like", 6 | handler: "api::post.post.likePost", 7 | }, 8 | ], 9 | }; 10 | -------------------------------------------------------------------------------- /src/api/menu/routes/menu.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * menu router. 5 | */ 6 | 7 | const { createCoreRouter } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreRouter('api::menu.menu'); 10 | -------------------------------------------------------------------------------- /src/api/tag/services/tag.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * tag service. 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::tag.tag'); 10 | -------------------------------------------------------------------------------- /src/api/menu/services/menu.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * menu service. 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::menu.menu'); 10 | -------------------------------------------------------------------------------- /src/api/author/routes/author.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * author router 5 | */ 6 | 7 | const { createCoreRouter } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreRouter('api::author.author'); 10 | -------------------------------------------------------------------------------- /src/api/course/routes/course.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * course router. 5 | */ 6 | 7 | const { createCoreRouter } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreRouter('api::course.course'); 10 | -------------------------------------------------------------------------------- /src/api/footer/routes/footer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * footer router. 5 | */ 6 | 7 | const { createCoreRouter } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreRouter('api::footer.footer'); 10 | -------------------------------------------------------------------------------- /src/api/header/routes/header.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * header router. 5 | */ 6 | 7 | const { createCoreRouter } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreRouter('api::header.header'); 10 | -------------------------------------------------------------------------------- /src/api/author/services/author.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * author service 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::author.author'); 10 | -------------------------------------------------------------------------------- /src/api/course/services/course.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * course service. 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::course.course'); 10 | -------------------------------------------------------------------------------- /src/api/footer/services/footer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * footer service. 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::footer.footer'); 10 | -------------------------------------------------------------------------------- /src/api/header/services/header.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * header service. 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::header.header'); 10 | -------------------------------------------------------------------------------- /src/api/service/routes/service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * service router. 5 | */ 6 | 7 | const { createCoreRouter } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreRouter('api::service.service'); 10 | -------------------------------------------------------------------------------- /src/api/tag/controllers/tag.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * tag controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::tag.tag'); 10 | -------------------------------------------------------------------------------- /src/api/menu/controllers/menu.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * menu controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::menu.menu'); 10 | -------------------------------------------------------------------------------- /src/api/service/services/service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * service service. 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::service.service'); 10 | -------------------------------------------------------------------------------- /src/api/blog-page/routes/blog-page.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * blog-page router. 5 | */ 6 | 7 | const { createCoreRouter } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreRouter('api::blog-page.blog-page'); 10 | -------------------------------------------------------------------------------- /src/api/home-page/routes/home-page.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * home-page router. 5 | */ 6 | 7 | const { createCoreRouter } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreRouter('api::home-page.home-page'); 10 | -------------------------------------------------------------------------------- /src/api/author/controllers/author.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * author controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::author.author'); 10 | -------------------------------------------------------------------------------- /src/api/blog-page/services/blog-page.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * blog-page service. 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::blog-page.blog-page'); 10 | -------------------------------------------------------------------------------- /src/api/course/controllers/course.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * course controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::course.course'); 10 | -------------------------------------------------------------------------------- /src/api/footer/controllers/footer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * footer controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::footer.footer'); 10 | -------------------------------------------------------------------------------- /src/api/header/controllers/header.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * header controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::header.header'); 10 | -------------------------------------------------------------------------------- /src/api/home-page/services/home-page.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * home-page service. 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::home-page.home-page'); 10 | -------------------------------------------------------------------------------- /src/api/seo-config/routes/seo-config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * seo-config router. 5 | */ 6 | 7 | const { createCoreRouter } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreRouter('api::seo-config.seo-config'); 10 | -------------------------------------------------------------------------------- /src/api/seo-config/services/seo-config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * seo-config service. 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::seo-config.seo-config'); 10 | -------------------------------------------------------------------------------- /src/api/service/controllers/service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * service controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::service.service'); 10 | -------------------------------------------------------------------------------- /src/api/static-page/routes/static-page.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * static-page router. 5 | */ 6 | 7 | const { createCoreRouter } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreRouter('api::static-page.static-page'); 10 | -------------------------------------------------------------------------------- /src/api/company-info/routes/company-info.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * company-info router. 5 | */ 6 | 7 | const { createCoreRouter } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreRouter('api::company-info.company-info'); 10 | -------------------------------------------------------------------------------- /src/api/courses-page/routes/courses-page.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * courses-page router. 5 | */ 6 | 7 | const { createCoreRouter } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreRouter('api::courses-page.courses-page'); 10 | -------------------------------------------------------------------------------- /src/api/static-page/services/static-page.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * static-page service. 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::static-page.static-page'); 10 | -------------------------------------------------------------------------------- /src/api/blog-page/controllers/blog-page.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * blog-page controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::blog-page.blog-page'); 10 | -------------------------------------------------------------------------------- /src/api/company-info/services/company-info.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * company-info service. 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::company-info.company-info'); 10 | -------------------------------------------------------------------------------- /src/api/courses-page/services/courses-page.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * courses-page service. 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::courses-page.courses-page'); 10 | -------------------------------------------------------------------------------- /src/api/home-page/controllers/home-page.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * home-page controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::home-page.home-page'); 10 | -------------------------------------------------------------------------------- /src/api/seo-config/controllers/seo-config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * seo-config controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::seo-config.seo-config'); 10 | -------------------------------------------------------------------------------- /src/api/static-page/controllers/static-page.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * static-page controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::static-page.static-page'); 10 | -------------------------------------------------------------------------------- /src/api/company-info/controllers/company-info.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * company-info controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::company-info.company-info'); 10 | -------------------------------------------------------------------------------- /src/api/courses-page/controllers/courses-page.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * courses-page controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::courses-page.courses-page'); 10 | -------------------------------------------------------------------------------- /src/middlewares/timer-header.js: -------------------------------------------------------------------------------- 1 | module.exports = () => { 2 | return async (ctx, next) => { 3 | const start = Date.now(); 4 | 5 | await next(); 6 | 7 | const delta = Math.ceil(Date.now() - start); 8 | ctx.set("X-Response-Time", delta + "ms"); 9 | }; 10 | }; 11 | -------------------------------------------------------------------------------- /src/api/post/routes/custom-route.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | routes: [ 3 | { 4 | method: "GET", 5 | path: "/posts/example", 6 | handler: "api::post.post.exampleAction", 7 | config: { 8 | // some configuration 9 | }, 10 | }, 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /config/database.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = ({ env }) => ({ 4 | connection: { 5 | client: 'sqlite', 6 | connection: { 7 | filename: path.join(__dirname, '..', env('DATABASE_FILENAME', '.tmp/data.db')), 8 | }, 9 | useNullAsDefault: true, 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /config/middlewares.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | "global::timer-header", 3 | "strapi::errors", 4 | "strapi::security", 5 | "strapi::cors", 6 | "strapi::poweredBy", 7 | "strapi::logger", 8 | "strapi::query", 9 | "strapi::body", 10 | "strapi::session", 11 | "strapi::favicon", 12 | "strapi::public", 13 | ]; 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [{package.json,*.yml}] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /src/admin/webpack.config.example.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable no-unused-vars */ 4 | module.exports = (config, webpack) => { 5 | // Note: we provide webpack above so you should not `require` it 6 | // Perform customizations to webpack config 7 | // Important: return the modified config 8 | return config; 9 | }; 10 | -------------------------------------------------------------------------------- /config/env/production/admin.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ env }) => { 2 | // know if we want to serve the admin panel 3 | // if not, we want to know the absolute URL of the admin panel (admin.artcoded.net) 4 | 5 | return { 6 | url: env("PUBLIC_ADMIN_URL", "/dashboard"), 7 | serveAdminPanel: env("PUBLIC_ADMIN_URL") == undefined, 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /src/api/post/routes/post.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * post router. 5 | */ 6 | 7 | const { createCoreRouter } = require("@strapi/strapi").factories; 8 | 9 | module.exports = createCoreRouter("api::post.post", { 10 | config: { 11 | find: { 12 | policies: [{ name: "check-role", config: { userRole: "Author" } }], 13 | }, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /src/components/seo/seo-information.json: -------------------------------------------------------------------------------- 1 | { 2 | "collectionName": "components_seo_seo_informations", 3 | "info": { 4 | "displayName": "seoInformation", 5 | "icon": "search" 6 | }, 7 | "options": {}, 8 | "attributes": { 9 | "seoTitle": { 10 | "type": "string" 11 | }, 12 | "seoDescription": { 13 | "type": "text" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/layout/link.json: -------------------------------------------------------------------------------- 1 | { 2 | "collectionName": "components_layout_links", 3 | "info": { 4 | "displayName": "link", 5 | "icon": "arrow-right" 6 | }, 7 | "options": {}, 8 | "attributes": { 9 | "label": { 10 | "type": "string", 11 | "required": true 12 | }, 13 | "url": { 14 | "type": "string", 15 | "required": true 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/layout/services-preview.json: -------------------------------------------------------------------------------- 1 | { 2 | "collectionName": "components_layout_services_previews", 3 | "info": { 4 | "displayName": "servicesPreview", 5 | "icon": "barcode" 6 | }, 7 | "options": {}, 8 | "attributes": { 9 | "services": { 10 | "type": "relation", 11 | "relation": "oneToMany", 12 | "target": "api::service.service" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/components/layout/newsletter-form.json: -------------------------------------------------------------------------------- 1 | { 2 | "collectionName": "components_layout_newsletter_forms", 3 | "info": { 4 | "displayName": "newsletterForm", 5 | "icon": "envelope-open" 6 | }, 7 | "options": {}, 8 | "attributes": { 9 | "heading": { 10 | "type": "string", 11 | "required": true 12 | }, 13 | "subHeading": { 14 | "type": "text" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/api/post/policies/check-role.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * `check-role` policy. 5 | */ 6 | 7 | module.exports = (policyContext, config, { strapi }) => { 8 | const { userRole } = config; 9 | console.log(policyContext.state.user); 10 | const isEligible = 11 | policyContext.state.user && policyContext.state.user.role.name == userRole; 12 | 13 | if (isEligible) { 14 | return true; 15 | } 16 | 17 | return false; 18 | }; 19 | -------------------------------------------------------------------------------- /config/env/production/database.js: -------------------------------------------------------------------------------- 1 | const { parse } = require("pg-connection-string"); 2 | 3 | module.exports = ({ env }) => { 4 | const { host, port, database, user, password } = parse(env("DATABASE_URL")); 5 | return { 6 | connection: { 7 | client: "postgres", 8 | connection: { 9 | host, 10 | port, 11 | database, 12 | user, 13 | password, 14 | }, 15 | debug: false, 16 | }, 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /src/components/blog/posts-selection.json: -------------------------------------------------------------------------------- 1 | { 2 | "collectionName": "components_blog_posts_selections", 3 | "info": { 4 | "displayName": "postsSelection", 5 | "icon": "align-center" 6 | }, 7 | "options": {}, 8 | "attributes": { 9 | "heading": { 10 | "type": "string", 11 | "required": false 12 | }, 13 | "featuredPosts": { 14 | "type": "relation", 15 | "relation": "oneToMany", 16 | "target": "api::post.post" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/layout/featured-course.json: -------------------------------------------------------------------------------- 1 | { 2 | "collectionName": "components_layout_featured_courses", 3 | "info": { 4 | "displayName": "featuredCourse", 5 | "icon": "equals", 6 | "description": "" 7 | }, 8 | "options": {}, 9 | "attributes": { 10 | "course": { 11 | "type": "relation", 12 | "relation": "oneToOne", 13 | "target": "api::course.course" 14 | }, 15 | "heading": { 16 | "type": "string" 17 | }, 18 | "announcement": { 19 | "type": "text" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/components/layout/mission.json: -------------------------------------------------------------------------------- 1 | { 2 | "collectionName": "components_layout_missions", 3 | "info": { 4 | "displayName": "mission", 5 | "icon": "align-right" 6 | }, 7 | "options": {}, 8 | "attributes": { 9 | "heading": { 10 | "type": "string", 11 | "default": "Our Mission", 12 | "required": true 13 | }, 14 | "content": { 15 | "type": "text", 16 | "required": true 17 | }, 18 | "showLogo": { 19 | "type": "boolean", 20 | "default": true, 21 | "required": true 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/components/config/social-link.json: -------------------------------------------------------------------------------- 1 | { 2 | "collectionName": "components_config_social_links", 3 | "info": { 4 | "displayName": "socialLink", 5 | "icon": "arrow-circle-right" 6 | }, 7 | "options": {}, 8 | "attributes": { 9 | "socialMedia": { 10 | "type": "enumeration", 11 | "enum": [ 12 | "github", 13 | "youtube", 14 | "twitter", 15 | "facebook", 16 | "whatsapp" 17 | ], 18 | "required": true 19 | }, 20 | "link": { 21 | "type": "string", 22 | "required": true 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/layout/page-info.json: -------------------------------------------------------------------------------- 1 | { 2 | "collectionName": "components_layout_page_infos", 3 | "info": { 4 | "displayName": "PageInfo", 5 | "icon": "align-left" 6 | }, 7 | "options": {}, 8 | "attributes": { 9 | "content": { 10 | "type": "richtext" 11 | }, 12 | "cover": { 13 | "allowedTypes": [ 14 | "images" 15 | ], 16 | "type": "media", 17 | "multiple": true 18 | }, 19 | "seo": { 20 | "type": "component", 21 | "repeatable": false, 22 | "component": "seo.seo-information" 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/api/tag/routes/tag.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * tag router. 5 | */ 6 | 7 | const { createCoreRouter } = require("@strapi/strapi").factories; 8 | 9 | module.exports = createCoreRouter("api::tag.tag", { 10 | prefix: "", // /tags --> /test/tags 11 | only: ["find", "findOne"], 12 | except: ["create"], 13 | config: { 14 | find: { 15 | auth: false, // disabling the Strapi JWT auth system for this route 16 | policies: [], 17 | middlewares: [], 18 | }, 19 | findOne: {}, 20 | create: {}, 21 | update: {}, 22 | delete: {}, 23 | }, 24 | }); 25 | -------------------------------------------------------------------------------- /src/api/tag/content-types/tag/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "tags", 4 | "info": { 5 | "singularName": "tag", 6 | "pluralName": "tags", 7 | "displayName": "Tag", 8 | "description": "" 9 | }, 10 | "options": { 11 | "draftAndPublish": false 12 | }, 13 | "pluginOptions": {}, 14 | "attributes": { 15 | "name": { 16 | "type": "string", 17 | "required": true, 18 | "unique": true 19 | }, 20 | "slug": { 21 | "type": "uid", 22 | "targetField": "name", 23 | "required": true 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/api/footer/content-types/footer/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "singleType", 3 | "collectionName": "footers", 4 | "info": { 5 | "singularName": "footer", 6 | "pluralName": "footers", 7 | "displayName": "Footer" 8 | }, 9 | "options": { 10 | "draftAndPublish": false 11 | }, 12 | "pluginOptions": {}, 13 | "attributes": { 14 | "socialLinks": { 15 | "type": "component", 16 | "repeatable": true, 17 | "component": "config.social-link" 18 | }, 19 | "footerMenu": { 20 | "type": "relation", 21 | "relation": "oneToOne", 22 | "target": "api::menu.menu" 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "eslint:recommended", 4 | "env": { 5 | "commonjs": true, 6 | "es6": true, 7 | "node": true, 8 | "browser": false 9 | }, 10 | "parserOptions": { 11 | "ecmaFeatures": { 12 | "experimentalObjectRestSpread": true, 13 | "jsx": false 14 | }, 15 | "sourceType": "module" 16 | }, 17 | "globals": { 18 | "strapi": true 19 | }, 20 | "rules": { 21 | "indent": ["error", 2, { "SwitchCase": 1 }], 22 | "linebreak-style": ["error", "unix"], 23 | "no-console": 0, 24 | "quotes": ["error", "single"], 25 | "semi": ["error", "always"] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/api/seo-config/content-types/seo-config/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "singleType", 3 | "collectionName": "seo_configs", 4 | "info": { 5 | "singularName": "seo-config", 6 | "pluralName": "seo-configs", 7 | "displayName": "SeoConfig" 8 | }, 9 | "options": { 10 | "draftAndPublish": false 11 | }, 12 | "pluginOptions": {}, 13 | "attributes": { 14 | "defaultSeo": { 15 | "type": "component", 16 | "repeatable": false, 17 | "component": "seo.seo-information" 18 | }, 19 | "seoImage": { 20 | "allowedTypes": [ 21 | "images" 22 | ], 23 | "type": "media", 24 | "multiple": false 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/api/menu/content-types/menu/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "menus", 4 | "info": { 5 | "singularName": "menu", 6 | "pluralName": "menus", 7 | "displayName": "Menu" 8 | }, 9 | "options": { 10 | "draftAndPublish": false 11 | }, 12 | "pluginOptions": {}, 13 | "attributes": { 14 | "menuItems": { 15 | "type": "component", 16 | "repeatable": true, 17 | "component": "layout.link" 18 | }, 19 | "name": { 20 | "type": "string", 21 | "required": true, 22 | "unique": true 23 | }, 24 | "slug": { 25 | "type": "uid", 26 | "targetField": "name" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/post/content-types/post/lifecycles.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | beforeCreate: async ({ params }) => { 3 | // find the Admin User who is about to create the Post 4 | const adminUserId = params.data.createdBy; 5 | 6 | // find the corresponding Author 7 | const author = ( 8 | await strapi.entityService.findMany("api::author.author", { 9 | filters: { 10 | admin_user: [adminUserId], 11 | }, 12 | }) 13 | )[0]; 14 | 15 | // update the data payload of the request for creating the new post 16 | // by adding the Author to the 'authors' relation field 17 | params.data.authors.connect = [...params.data.authors.connect, author.id]; 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /src/api/static-page/content-types/static-page/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "static_pages", 4 | "info": { 5 | "singularName": "static-page", 6 | "pluralName": "static-pages", 7 | "displayName": "StaticPage" 8 | }, 9 | "options": { 10 | "draftAndPublish": true 11 | }, 12 | "pluginOptions": {}, 13 | "attributes": { 14 | "title": { 15 | "type": "string", 16 | "required": true, 17 | "unique": true 18 | }, 19 | "slug": { 20 | "type": "uid", 21 | "targetField": "title", 22 | "required": true 23 | }, 24 | "pageInfo": { 25 | "type": "component", 26 | "repeatable": false, 27 | "component": "layout.page-info" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/layout/hero.json: -------------------------------------------------------------------------------- 1 | { 2 | "collectionName": "components_layout_heroes", 3 | "info": { 4 | "displayName": "hero", 5 | "icon": "window-maximize", 6 | "description": "" 7 | }, 8 | "options": {}, 9 | "attributes": { 10 | "callToAction": { 11 | "type": "string", 12 | "required": true 13 | }, 14 | "image": { 15 | "type": "media", 16 | "multiple": true, 17 | "required": false, 18 | "allowedTypes": [ 19 | "images", 20 | "videos" 21 | ] 22 | }, 23 | "buttons": { 24 | "displayName": "link", 25 | "type": "component", 26 | "repeatable": true, 27 | "component": "layout.link" 28 | }, 29 | "subtitle": { 30 | "type": "string" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/api/service/content-types/service/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "services", 4 | "info": { 5 | "singularName": "service", 6 | "pluralName": "services", 7 | "displayName": "Service" 8 | }, 9 | "options": { 10 | "draftAndPublish": true 11 | }, 12 | "pluginOptions": {}, 13 | "attributes": { 14 | "name": { 15 | "type": "string", 16 | "required": true, 17 | "unique": true 18 | }, 19 | "description": { 20 | "type": "richtext" 21 | }, 22 | "slug": { 23 | "type": "uid", 24 | "targetField": "name" 25 | }, 26 | "cover": { 27 | "allowedTypes": [ 28 | "images" 29 | ], 30 | "type": "media", 31 | "multiple": false 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/admin/app.js: -------------------------------------------------------------------------------- 1 | import NewLogo from "./extensions/logo.svg"; 2 | import Favicon from "./extensions/favicon.png"; 3 | 4 | const myPrimaryColor = "#2C97AD"; 5 | 6 | export default { 7 | config: { 8 | locales: ["it"], 9 | // translations: { 10 | // it: { 11 | // "app.components.LeftMenuLinkContainer.installNewPlugin": "Negozio", 12 | // }, 13 | // }, 14 | auth: { 15 | logo: NewLogo, 16 | }, 17 | menu: { 18 | logo: NewLogo, 19 | }, 20 | head: { 21 | favicon: Favicon, 22 | }, 23 | tutorials: false, 24 | theme: { 25 | colors: { 26 | buttonPrimary600: myPrimaryColor, 27 | primary600: myPrimaryColor, 28 | }, 29 | }, 30 | }, 31 | bootstrap(app) { 32 | console.log(app); 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /src/api/header/content-types/header/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "singleType", 3 | "collectionName": "headers", 4 | "info": { 5 | "singularName": "header", 6 | "pluralName": "headers", 7 | "displayName": "Header" 8 | }, 9 | "options": { 10 | "draftAndPublish": false 11 | }, 12 | "pluginOptions": {}, 13 | "attributes": { 14 | "showLogo": { 15 | "type": "boolean", 16 | "default": true, 17 | "required": true 18 | }, 19 | "menu": { 20 | "type": "relation", 21 | "relation": "oneToOne", 22 | "target": "api::menu.menu" 23 | }, 24 | "socialLinks": { 25 | "type": "component", 26 | "repeatable": true, 27 | "component": "config.social-link" 28 | }, 29 | "showProfileLink": { 30 | "type": "boolean", 31 | "default": true, 32 | "required": true 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/api/author/content-types/author/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "authors", 4 | "info": { 5 | "singularName": "author", 6 | "pluralName": "authors", 7 | "displayName": "Author", 8 | "description": "" 9 | }, 10 | "options": { 11 | "draftAndPublish": false 12 | }, 13 | "pluginOptions": {}, 14 | "attributes": { 15 | "firstname": { 16 | "type": "string", 17 | "required": true 18 | }, 19 | "lastname": { 20 | "type": "string", 21 | "required": true 22 | }, 23 | "email": { 24 | "type": "email", 25 | "required": true 26 | }, 27 | "username": { 28 | "type": "string", 29 | "required": false 30 | }, 31 | "admin_user": { 32 | "type": "relation", 33 | "relation": "oneToOne", 34 | "target": "admin::user" 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/api/blog-page/content-types/blog-page/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "singleType", 3 | "collectionName": "blog_pages", 4 | "info": { 5 | "singularName": "blog-page", 6 | "pluralName": "blog-pages", 7 | "displayName": "BlogPage" 8 | }, 9 | "options": { 10 | "draftAndPublish": true 11 | }, 12 | "pluginOptions": {}, 13 | "attributes": { 14 | "title": { 15 | "type": "string", 16 | "default": "Our Blog", 17 | "required": true 18 | }, 19 | "slug": { 20 | "type": "uid", 21 | "targetField": "title", 22 | "required": true 23 | }, 24 | "pageInfo": { 25 | "type": "component", 26 | "repeatable": false, 27 | "component": "layout.page-info" 28 | }, 29 | "excludedTags": { 30 | "type": "relation", 31 | "relation": "oneToMany", 32 | "target": "api::tag.tag" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/api/courses-page/content-types/courses-page/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "singleType", 3 | "collectionName": "courses_pages", 4 | "info": { 5 | "singularName": "courses-page", 6 | "pluralName": "courses-pages", 7 | "displayName": "CoursesPage" 8 | }, 9 | "options": { 10 | "draftAndPublish": true 11 | }, 12 | "pluginOptions": {}, 13 | "attributes": { 14 | "title": { 15 | "type": "string", 16 | "default": "Our Courses", 17 | "required": true 18 | }, 19 | "slug": { 20 | "type": "uid", 21 | "targetField": "title", 22 | "required": true 23 | }, 24 | "pageInfo": { 25 | "type": "component", 26 | "repeatable": false, 27 | "component": "layout.page-info" 28 | }, 29 | "excludedCourses": { 30 | "type": "relation", 31 | "relation": "oneToMany", 32 | "target": "api::course.course" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /config/env/production/middlewares.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | "strapi::errors", 3 | { 4 | name: "strapi::security", 5 | config: { 6 | contentSecurityPolicy: { 7 | useDefaults: true, 8 | directives: { 9 | "connect-src": ["'self'", "https:"], 10 | "img-src": [ 11 | "'self'", 12 | "data:", 13 | "blob:", 14 | "dl.airtable.com", 15 | "res.cloudinary.com", 16 | ], 17 | "media-src": [ 18 | "'self'", 19 | "data:", 20 | "blob:", 21 | "dl.airtable.com", 22 | "res.cloudinary.com", 23 | ], 24 | upgradeInsecureRequests: null, 25 | }, 26 | }, 27 | }, 28 | }, 29 | "strapi::cors", 30 | "strapi::poweredBy", 31 | "strapi::logger", 32 | "strapi::query", 33 | "strapi::body", 34 | "strapi::session", 35 | "strapi::favicon", 36 | "strapi::public", 37 | ]; 38 | -------------------------------------------------------------------------------- /src/api/post/graphql/post.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | likePostMutation: ` 3 | type Mutation { 4 | likePost(id: ID!): PostEntityResponse 5 | } 6 | `, 7 | getLikePostResolver: (strapi) => { 8 | const resolverFunction = async (parent, args, ctx, info) => { 9 | // resolver implementation 10 | const { id: postId } = args; 11 | const userId = ctx.state.user.id; 12 | const likedPost = await strapi 13 | .service("api::post.post") 14 | .likePost({ postId, userId }); 15 | const { toEntityResponse } = strapi 16 | .plugin("graphql") 17 | .service("format").returnTypes; 18 | const formattedResponse = toEntityResponse(likedPost, { 19 | args, 20 | resourceUID: "api::post.post", 21 | }); 22 | return formattedResponse; 23 | }; 24 | return resolverFunction; 25 | }, 26 | likePostMutationConfig: { 27 | auth: { 28 | scope: ["api::post.post.likePost"], 29 | }, 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /src/api/course/content-types/course/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "courses", 4 | "info": { 5 | "singularName": "course", 6 | "pluralName": "courses", 7 | "displayName": "Course", 8 | "description": "" 9 | }, 10 | "options": { 11 | "draftAndPublish": true 12 | }, 13 | "pluginOptions": {}, 14 | "attributes": { 15 | "title": { 16 | "type": "string", 17 | "required": true, 18 | "unique": true 19 | }, 20 | "description": { 21 | "type": "richtext" 22 | }, 23 | "slug": { 24 | "type": "uid", 25 | "targetField": "title", 26 | "required": true 27 | }, 28 | "images": { 29 | "type": "media", 30 | "multiple": true, 31 | "required": false, 32 | "allowedTypes": [ 33 | "images", 34 | "videos" 35 | ] 36 | }, 37 | "tags": { 38 | "type": "relation", 39 | "relation": "oneToMany", 40 | "target": "api::tag.tag" 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /config/env/production/plugins.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ env }) => ({ 2 | // ... 3 | email: { 4 | config: { 5 | provider: "nodemailer", 6 | providerOptions: { 7 | host: env("SMTP_HOST", "smtp.example.com"), 8 | port: env("SMTP_PORT", 587), 9 | auth: { 10 | user: env("SMTP_USERNAME"), 11 | pass: env("SMTP_PASSWORD"), 12 | }, 13 | secure: true, 14 | // ... any custom nodemailer options 15 | }, 16 | settings: { 17 | defaultFrom: "info@artcoded.net", 18 | defaultReplyTo: "info@artcoded.net", 19 | }, 20 | }, 21 | }, 22 | upload: { 23 | config: { 24 | provider: "cloudinary", 25 | providerOptions: { 26 | cloud_name: env("CLOUDINARY_NAME"), 27 | api_key: env("CLOUDINARY_KEY"), 28 | api_secret: env("CLOUDINARY_SECRET"), 29 | }, 30 | actionOptions: { 31 | upload: {}, 32 | uploadStream: {}, 33 | delete: {}, 34 | }, 35 | }, 36 | }, 37 | }); 38 | -------------------------------------------------------------------------------- /src/api/company-info/content-types/company-info/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "singleType", 3 | "collectionName": "company_infos", 4 | "info": { 5 | "singularName": "company-info", 6 | "pluralName": "company-infos", 7 | "displayName": "CompanyInfo", 8 | "description": "" 9 | }, 10 | "options": { 11 | "draftAndPublish": false 12 | }, 13 | "pluginOptions": {}, 14 | "attributes": { 15 | "logo": { 16 | "type": "media", 17 | "multiple": false, 18 | "required": false, 19 | "allowedTypes": [ 20 | "images" 21 | ] 22 | }, 23 | "socialLinks": { 24 | "displayName": "socialLink", 25 | "type": "component", 26 | "repeatable": true, 27 | "component": "config.social-link" 28 | }, 29 | "companyEmail": { 30 | "type": "email", 31 | "required": true 32 | }, 33 | "companyName": { 34 | "type": "string", 35 | "required": true 36 | }, 37 | "vat": { 38 | "type": "string" 39 | }, 40 | "companyAddress": { 41 | "type": "string" 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dev-blog", 3 | "private": true, 4 | "version": "0.1.0", 5 | "description": "A Strapi application", 6 | "scripts": { 7 | "develop": "strapi develop", 8 | "start": "strapi start", 9 | "build": "strapi build", 10 | "strapi": "strapi" 11 | }, 12 | "devDependencies": {}, 13 | "dependencies": { 14 | "@artcoded/strapi-plugin-github-projects": "1.0.1", 15 | "@strapi/helper-plugin": "^4.10.5", 16 | "@strapi/plugin-graphql": "4.10.5", 17 | "@strapi/plugin-i18n": "4.10.5", 18 | "@strapi/plugin-users-permissions": "4.10.5", 19 | "@strapi/provider-email-nodemailer": "4.10.5", 20 | "@strapi/provider-upload-cloudinary": "4.10.5", 21 | "@strapi/strapi": "4.10.5", 22 | "add": "^2.0.6", 23 | "better-sqlite3": "latest", 24 | "pg": "^8.7.3", 25 | "pg-connection-string": "^2.5.0", 26 | "yarn": "^1.22.18" 27 | }, 28 | "author": { 29 | "name": "A Strapi developer" 30 | }, 31 | "strapi": { 32 | "uuid": "193bd07f-b0a1-4ddd-9d75-8eb8d8dbc52c" 33 | }, 34 | "engines": { 35 | "node": ">=12.x.x <=18.x.x", 36 | "npm": ">=6.0.0" 37 | }, 38 | "license": "MIT" 39 | } 40 | -------------------------------------------------------------------------------- /render.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | - type: web 3 | name: dev-blog 4 | env: node 5 | plan: free 6 | buildCommand: yarn && NODE_ENV=production yarn build 7 | startCommand: yarn start 8 | healthCheckPath: /_health 9 | envVars: 10 | - key: PUBLIC_SERVER_URL 11 | sync: false 12 | - key: NODE_VERSION 13 | value: ~16.13.0 14 | - key: NODE_ENV 15 | value: production 16 | - key: CLOUDINARY_NAME 17 | sync: false 18 | - key: CLOUDINARY_KEY 19 | sync: false 20 | - key: CLOUDINARY_SECRET 21 | sync: false 22 | - key: SMTP_HOST 23 | sync: false 24 | - key: SMTP_PORT 25 | sync: false 26 | - key: SMTP_USERNAME 27 | sync: false 28 | - key: SMTP_PASSWORD 29 | sync: false 30 | - key: GITHUB_TOKEN 31 | sync: false 32 | - key: DATABASE_URL 33 | fromDatabase: 34 | name: strapi-db 35 | property: connectionString 36 | - key: JWT_SECRET 37 | generateValue: true 38 | - key: ADMIN_JWT_SECRET 39 | generateValue: true 40 | - key: APP_KEYS 41 | generateValue: true 42 | - key: API_TOKEN_SALT 43 | generateValue: true 44 | 45 | databases: 46 | - name: strapi-db 47 | plan: free 48 | -------------------------------------------------------------------------------- /src/api/home-page/content-types/home-page/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "singleType", 3 | "collectionName": "home_pages", 4 | "info": { 5 | "singularName": "home-page", 6 | "pluralName": "home-pages", 7 | "displayName": "HomePage", 8 | "description": "" 9 | }, 10 | "options": { 11 | "draftAndPublish": true 12 | }, 13 | "pluginOptions": {}, 14 | "attributes": { 15 | "title": { 16 | "type": "string", 17 | "default": "Home", 18 | "required": true, 19 | "unique": true 20 | }, 21 | "hero": { 22 | "displayName": "hero", 23 | "type": "component", 24 | "repeatable": false, 25 | "component": "layout.hero" 26 | }, 27 | "postsSelection": { 28 | "displayName": "postsSelection", 29 | "type": "component", 30 | "repeatable": false, 31 | "component": "blog.posts-selection" 32 | }, 33 | "dynamicHomeSection": { 34 | "type": "dynamiczone", 35 | "components": [ 36 | "layout.mission", 37 | "layout.newsletter-form" 38 | ] 39 | }, 40 | "seo": { 41 | "type": "component", 42 | "repeatable": false, 43 | "component": "seo.seo-information" 44 | }, 45 | "servicesPreview": { 46 | "type": "component", 47 | "repeatable": false, 48 | "component": "layout.services-preview" 49 | }, 50 | "featuredCourse": { 51 | "type": "component", 52 | "repeatable": false, 53 | "component": "layout.featured-course" 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/api/post/content-types/post/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "posts", 4 | "info": { 5 | "singularName": "post", 6 | "pluralName": "posts", 7 | "displayName": "Post", 8 | "description": "" 9 | }, 10 | "options": { 11 | "draftAndPublish": true 12 | }, 13 | "pluginOptions": {}, 14 | "attributes": { 15 | "title": { 16 | "type": "string", 17 | "required": true, 18 | "unique": true, 19 | "minLength": 4, 20 | "maxLength": 150 21 | }, 22 | "content": { 23 | "type": "richtext", 24 | "required": true 25 | }, 26 | "slug": { 27 | "type": "uid", 28 | "targetField": "title", 29 | "required": true 30 | }, 31 | "cover": { 32 | "type": "media", 33 | "multiple": true, 34 | "required": false, 35 | "allowedTypes": [ 36 | "images" 37 | ] 38 | }, 39 | "authors": { 40 | "type": "relation", 41 | "relation": "oneToMany", 42 | "target": "api::author.author" 43 | }, 44 | "tags": { 45 | "type": "relation", 46 | "relation": "oneToMany", 47 | "target": "api::tag.tag" 48 | }, 49 | "seo": { 50 | "displayName": "seoInformation", 51 | "type": "component", 52 | "repeatable": false, 53 | "component": "seo.seo-information" 54 | }, 55 | "premium": { 56 | "type": "boolean", 57 | "default": false, 58 | "required": true, 59 | "private": true 60 | }, 61 | "likedBy": { 62 | "type": "relation", 63 | "relation": "manyToMany", 64 | "target": "plugin::users-permissions.user", 65 | "inversedBy": "likes" 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ############################ 2 | # OS X 3 | ############################ 4 | 5 | .DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | Icon 9 | .Spotlight-V100 10 | .Trashes 11 | ._* 12 | 13 | 14 | ############################ 15 | # Linux 16 | ############################ 17 | 18 | *~ 19 | 20 | 21 | ############################ 22 | # Windows 23 | ############################ 24 | 25 | Thumbs.db 26 | ehthumbs.db 27 | Desktop.ini 28 | $RECYCLE.BIN/ 29 | *.cab 30 | *.msi 31 | *.msm 32 | *.msp 33 | 34 | 35 | ############################ 36 | # Packages 37 | ############################ 38 | 39 | *.7z 40 | *.csv 41 | *.dat 42 | *.dmg 43 | *.gz 44 | *.iso 45 | *.jar 46 | *.rar 47 | *.tar 48 | *.zip 49 | *.com 50 | *.class 51 | *.dll 52 | *.exe 53 | *.o 54 | *.seed 55 | *.so 56 | *.swo 57 | *.swp 58 | *.swn 59 | *.swm 60 | *.out 61 | *.pid 62 | 63 | 64 | ############################ 65 | # Logs and databases 66 | ############################ 67 | 68 | .tmp 69 | *.log 70 | *.sql 71 | *.sqlite 72 | *.sqlite3 73 | 74 | 75 | ############################ 76 | # Misc. 77 | ############################ 78 | 79 | *# 80 | ssl 81 | .idea 82 | nbproject 83 | public/uploads/* 84 | !public/uploads/.gitkeep 85 | 86 | ############################ 87 | # Node.js 88 | ############################ 89 | 90 | lib-cov 91 | lcov.info 92 | pids 93 | logs 94 | results 95 | node_modules 96 | .node_history 97 | 98 | ############################ 99 | # Tests 100 | ############################ 101 | 102 | testApp 103 | coverage 104 | 105 | ############################ 106 | # Strapi 107 | ############################ 108 | 109 | .env 110 | license.txt 111 | exports 112 | *.cache 113 | build 114 | .strapi-updater.json 115 | 116 | dist -------------------------------------------------------------------------------- /src/extensions/users-permissions/content-types/user/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "up_users", 4 | "info": { 5 | "name": "user", 6 | "description": "", 7 | "singularName": "user", 8 | "pluralName": "users", 9 | "displayName": "User" 10 | }, 11 | "options": { 12 | "draftAndPublish": false, 13 | "timestamps": true 14 | }, 15 | "attributes": { 16 | "username": { 17 | "type": "string", 18 | "minLength": 3, 19 | "unique": true, 20 | "configurable": false, 21 | "required": true 22 | }, 23 | "email": { 24 | "type": "email", 25 | "minLength": 6, 26 | "configurable": false, 27 | "required": true 28 | }, 29 | "provider": { 30 | "type": "string", 31 | "configurable": false 32 | }, 33 | "password": { 34 | "type": "password", 35 | "minLength": 6, 36 | "configurable": false, 37 | "private": true 38 | }, 39 | "resetPasswordToken": { 40 | "type": "string", 41 | "configurable": false, 42 | "private": true 43 | }, 44 | "confirmationToken": { 45 | "type": "string", 46 | "configurable": false, 47 | "private": true 48 | }, 49 | "confirmed": { 50 | "type": "boolean", 51 | "default": false, 52 | "configurable": false 53 | }, 54 | "blocked": { 55 | "type": "boolean", 56 | "default": false, 57 | "configurable": false 58 | }, 59 | "role": { 60 | "type": "relation", 61 | "relation": "manyToOne", 62 | "target": "plugin::users-permissions.role", 63 | "inversedBy": "users", 64 | "configurable": false 65 | }, 66 | "likes": { 67 | "type": "relation", 68 | "relation": "manyToMany", 69 | "target": "api::post.post", 70 | "mappedBy": "likedBy" 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚀 Getting started with Strapi 2 | 3 | Strapi comes with a full featured [Command Line Interface](https://docs.strapi.io/developer-docs/latest/developer-resources/cli/CLI.html) (CLI) which lets you scaffold and manage your project in seconds. 4 | 5 | ### `develop` 6 | 7 | Start your Strapi application with autoReload enabled. [Learn more](https://docs.strapi.io/developer-docs/latest/developer-resources/cli/CLI.html#strapi-develop) 8 | 9 | ``` 10 | npm run develop 11 | # or 12 | yarn develop 13 | ``` 14 | 15 | ### `start` 16 | 17 | Start your Strapi application with autoReload disabled. [Learn more](https://docs.strapi.io/developer-docs/latest/developer-resources/cli/CLI.html#strapi-start) 18 | 19 | ``` 20 | npm run start 21 | # or 22 | yarn start 23 | ``` 24 | 25 | ### `build` 26 | 27 | Build your admin panel. [Learn more](https://docs.strapi.io/developer-docs/latest/developer-resources/cli/CLI.html#strapi-build) 28 | 29 | ``` 30 | npm run build 31 | # or 32 | yarn build 33 | ``` 34 | 35 | ## ⚙️ Deployment 36 | 37 | Strapi gives you many possible deployment options for your project. Find the one that suits you on the [deployment section of the documentation](https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/deployment.html). 38 | 39 | ## 📚 Learn more 40 | 41 | - [Resource center](https://strapi.io/resource-center) - Strapi resource center. 42 | - [Strapi documentation](https://docs.strapi.io) - Official Strapi documentation. 43 | - [Strapi tutorials](https://strapi.io/tutorials) - List of tutorials made by the core team and the community. 44 | - [Strapi blog](https://docs.strapi.io) - Official Strapi blog containing articles made by the Strapi team and the community. 45 | - [Changelog](https://strapi.io/changelog) - Find out about the Strapi product updates, new features and general improvements. 46 | 47 | Feel free to check out the [Strapi GitHub repository](https://github.com/strapi/strapi). Your feedback and contributions are welcome! 48 | 49 | ## ✨ Community 50 | 51 | - [Discord](https://discord.strapi.io) - Come chat with the Strapi community including the core team. 52 | - [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. 53 | - [Awesome Strapi](https://github.com/strapi/awesome-strapi) - A curated list of awesome things related to Strapi. 54 | 55 | --- 56 | 57 | 🤫 Psst! [Strapi is hiring](https://strapi.io/careers). 58 | -------------------------------------------------------------------------------- /src/api/post/services/post.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * post service. 5 | */ 6 | 7 | const { createCoreService } = require("@strapi/strapi").factories; 8 | 9 | module.exports = createCoreService("api::post.post", ({ strapi }) => ({ 10 | // Method 1: Creating an entirely custom service 11 | async exampleService(...args) { 12 | console.log(args); 13 | let response = { okay: true }; 14 | 15 | if (response.okay === false) { 16 | return { response, error: true }; 17 | } 18 | 19 | return response; 20 | }, 21 | 22 | // Method 2: Wrapping a core service (leaves core logic in place) 23 | async find(...args) { 24 | // Calling the default core controller 25 | const { results, pagination } = await super.find(...args); 26 | 27 | // some custom logic 28 | results.forEach((result) => { 29 | result.counter = 1; 30 | }); 31 | 32 | return { results, pagination }; 33 | }, 34 | 35 | // Method 3: Replacing a core service 36 | async findOne(entityId, params = {}) { 37 | return strapi.entityService.findOne( 38 | "api::post.post", 39 | entityId, 40 | this.getFetchParams(params) 41 | ); 42 | }, 43 | 44 | async findPublic(args) { 45 | const newQuery = { 46 | ...args, 47 | filters: { 48 | ...args.filters, 49 | premium: false, 50 | }, 51 | }; 52 | const publicPosts = await strapi.entityService.findMany( 53 | "api::post.post", 54 | this.getFetchParams(newQuery) 55 | ); 56 | return publicPosts; 57 | }, 58 | 59 | async findOneIfPublic(args) { 60 | const { id, query } = args; 61 | const post = await strapi.entityService.findOne( 62 | "api::post.post", 63 | id, 64 | this.getFetchParams(query) 65 | ); 66 | return post.premium ? null : post; 67 | }, 68 | 69 | async likePost(args) { 70 | const { postId, userId, query } = args; 71 | 72 | // use the underlying entity service API to fetch the post and, in particular, its likedBy property 73 | const postToLike = await strapi.entityService.findOne( 74 | "api::post.post", 75 | postId, 76 | { 77 | populate: ["likedBy"], 78 | } 79 | ); 80 | 81 | // use the underlying entity service API to update the current post with the new relation 82 | const updatedPost = await strapi.entityService.update( 83 | "api::post.post", 84 | postId, 85 | { 86 | data: { 87 | likedBy: [...postToLike.likedBy, userId], 88 | }, 89 | ...query, 90 | } 91 | ); 92 | return updatedPost; 93 | }, 94 | })); 95 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { 4 | likePostMutation, 5 | getLikePostResolver, 6 | likePostMutationConfig, 7 | } = require("./api/post/graphql/post"); 8 | 9 | module.exports = { 10 | /** 11 | * An asynchronous register function that runs before 12 | * your application is initialized. 13 | * 14 | * This gives you an opportunity to extend code. 15 | */ 16 | register({ strapi }) { 17 | const extensionService = strapi.plugin("graphql").service("extension"); 18 | const extension = ({ nexus }) => ({ 19 | // GraphQL SDL 20 | typeDefs: likePostMutation, 21 | resolvers: { 22 | Mutation: { 23 | likePost: getLikePostResolver(strapi), 24 | }, 25 | }, 26 | resolversConfig: { 27 | "Mutation.likePost": likePostMutationConfig, 28 | }, 29 | }); 30 | extensionService.use(extension); 31 | }, 32 | 33 | /** 34 | * An asynchronous bootstrap function that runs before 35 | * your application gets started. 36 | * 37 | * This gives you an opportunity to set up your data model, 38 | * run jobs, or perform some special logic. 39 | */ 40 | bootstrap({ strapi }) { 41 | // we listen to lifecycle events 42 | strapi.db.lifecycles.subscribe({ 43 | models: ["admin::user"], //only listen to events for this model 44 | afterCreate: async ({ result }) => { 45 | // create an Author instance from the fields of the Admin User 46 | // that has just been created 47 | 48 | // Exctract the fields from the newly created Admin User 49 | const { 50 | id, 51 | firstname, 52 | lastname, 53 | email, 54 | username, 55 | createdAt, 56 | updatedAt, 57 | } = result; 58 | await strapi.service("api::author.author").create({ 59 | data: { 60 | firstname, 61 | lastname, 62 | email, 63 | username, 64 | createdAt, 65 | updatedAt, 66 | admin_user: [id], 67 | }, 68 | }); 69 | }, 70 | afterUpdate: async ({ result }) => { 71 | // get the Author that corresponds 72 | // to the Admin User that's just been updated 73 | const correspondingAuthor = ( 74 | await strapi.entityService.findMany("api::author.author", { 75 | populate: ["admin_user"], 76 | filters: { 77 | admin_user: { 78 | id: result.id, 79 | }, 80 | }, 81 | }) 82 | )[0]; 83 | 84 | // update the Author accordingly 85 | const { firstname, lastname, email, username, updatedAt } = result; 86 | await strapi 87 | .service("api::author.author") 88 | .update(correspondingAuthor.id, { 89 | data: { 90 | firstname, 91 | lastname, 92 | email, 93 | username, 94 | updatedAt, 95 | }, 96 | }); 97 | }, 98 | }); 99 | }, 100 | }; 101 | -------------------------------------------------------------------------------- /src/admin/extensions/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | 11 | 15 | 16 | 17 | 21 | 22 | 23 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 54 | 55 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 75 | 76 | 77 | 78 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /src/api/post/controllers/post.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * post controller 5 | */ 6 | 7 | const { createCoreController } = require("@strapi/strapi").factories; 8 | 9 | module.exports = createCoreController("api::post.post", ({ strapi }) => ({ 10 | // Method 1: Creating an entirely custom action 11 | async exampleAction(ctx) { 12 | console.log("I was called"); 13 | await strapi 14 | .service("api::post.post") 15 | .exampleService({ myParam: "example" }); 16 | try { 17 | ctx.body = "ok"; 18 | } catch (err) { 19 | ctx.body = err; 20 | } 21 | }, 22 | 23 | // // Solution 1: fetched all posts and filtered them afterwards 24 | // async find(ctx) { 25 | // // fetch all posts (including premium ones) 26 | // const { data, meta } = await super.find(ctx); 27 | // if (ctx.state.user) return { data, meta }; 28 | // // not authenticated 29 | // const filteredData = data.filter((post) => !post.attributes.premium); 30 | // return { data: filteredData, meta }; 31 | // }, 32 | 33 | // Solution 2: rewrite the action to fetch only needed posts 34 | // async find(ctx) { 35 | // // if the request is authenticated 36 | // const isRequestingNonPremium = 37 | // ctx.query.filters && ctx.query.filters.premium == false; 38 | // if (ctx.state.user || isRequestingNonPremium) return await super.find(ctx); 39 | // // if the request is public... 40 | // // ... let's call the underlying service with an additional filter param: premium == false 41 | // // /posts?filters[premium]=false 42 | // const { query } = ctx; 43 | // const filteredPosts = await strapi.service("api::post.post").find({ 44 | // ...query, 45 | // filters: { 46 | // ...query.filters, 47 | // premium: false, 48 | // }, 49 | // }); 50 | // const sanitizedPosts = await this.sanitizeOutput(filteredPosts, ctx); 51 | // return this.transformResponse(sanitizedPosts); 52 | // }, 53 | 54 | async find(ctx) { 55 | // if the request is authenticated or explicitly asking for public content only 56 | const isRequestingNonPremium = 57 | ctx.query.filters && ctx.query.filters.premium == false; 58 | if (ctx.state.user || isRequestingNonPremium) return await super.find(ctx); 59 | // if the request is public... 60 | const publicPosts = await strapi 61 | .service("api::post.post") 62 | .findPublic(ctx.query); 63 | const sanitizedPosts = await this.sanitizeOutput(publicPosts, ctx); 64 | return this.transformResponse(sanitizedPosts); 65 | }, 66 | 67 | // // Method 3: Replacing a core action 68 | // async findOne(ctx) { 69 | // // '/posts/:id' /posts/1? 70 | // const { id } = ctx.params; 71 | // const { query } = ctx; 72 | 73 | // const entity = await strapi.service("api::post.post").findOne(id, query); 74 | // const sanitizedEntity = await this.sanitizeOutput(entity, ctx); 75 | 76 | // return this.transformResponse(sanitizedEntity); 77 | // }, 78 | 79 | // Method 3: Replacing a core action 80 | async findOne(ctx) { 81 | if (ctx.state.user) return await super.findOne(ctx); 82 | //else... 83 | const { id } = ctx.params; // /posts/:id 84 | const { query } = ctx; 85 | const postIfPublic = await strapi 86 | .service("api::post.post") 87 | .findOneIfPublic({ 88 | id, 89 | query, 90 | }); 91 | const sanitizedEntity = await this.sanitizeOutput(postIfPublic, ctx); 92 | return this.transformResponse(sanitizedEntity); 93 | }, 94 | 95 | async likePost(ctx) { 96 | // ctx.state.user 97 | const user = ctx.state.user; // user trying to like the post 98 | const postId = ctx.params.id; // the post that's being "liked" 99 | const { query } = ctx; 100 | const updatedPost = await strapi.service("api::post.post").likePost({ 101 | postId, 102 | userId: user.id, 103 | query, 104 | }); 105 | const sanitizedEntity = await this.sanitizeOutput(updatedPost, ctx); 106 | return this.transformResponse(sanitizedEntity); 107 | }, 108 | })); 109 | --------------------------------------------------------------------------------