├── 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 |
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 |
--------------------------------------------------------------------------------