Skip to content

nicklima/strapi-blog-cms

Repository files navigation

Starter - Strapi CMS

Template base para criação de um CMS utilizando Strapi com Cloudinary e PostgreSQL.


Cloudinary

As configurações do Provider do Cloudinary estão no arquivo ./config/plugins.js e os dados sensíveis devem ser passados via variáveis de ambiente, como mostrado no .env.example

module.exports = ({ env }) => {
  if (env("NODE_ENV") === "production") {
    return {
      upload: {
        config: {
          provider: "cloudinary",
          providerOptions: {
            cloud_name: env("CLOUDINARY_NAME"),
            api_key: env("CLOUDINARY_KEY"),
            api_secret: env("CLOUDINARY_SECRET"),
          },
        },
      },
    };
  }

  return;
};

Para que as thumbnails possam ser vistas no painel devemos alterar as configurações de segurança no arquivo ./config/middlewares.js e incluir o domínio do GCS

module.exports = [
  "strapi::errors",
  "strapi::security",
  {
    name: "strapi::security",
    config: {
      contentSecurityPolicy: {
        useDefaults: true,
        directives: {
          "connect-src": ["'self'", "https:"],
          "img-src": ["'self'", "data:", "blob:", "res.cloudinary.com"],
          "media-src": ["'self'", "data:", "blob:", "res.cloudinary.com"],
          upgradeInsecureRequests: null,
        },
      },
    },
  },
  "strapi::cors",
  "strapi::poweredBy",
  "strapi::logger",
  "strapi::query",
  "strapi::body",
  "strapi::favicon",
  "strapi::public",
];

Database

Esse starter está configurado com PostgreSQL para configuração em ambiente de Produção e SQLite para o ambiente de desenvolvimento local.
Quando em ambiente local os dados ficam armazenados na pasta .tmp na raiz do projeto.

Para os detalhes de conexão do banco de produção, definir as informações nas variáveis de ambiente como demonstrado no .env.example

const path = require("path");

module.exports = ({ env }) => {
  if (env("NODE_ENV") === "production") {
    return {
      connection: {
        client: "postgres",
        connection: {
          host: env("DATABASE_HOST", "127.0.0.1"),
          port: env.int("DATABASE_PORT", 5432),
          database: env("DATABASE_NAME", "strapi"),
          user: env("DATABASE_USERNAME", "root"),
          password: env("DATABASE_PASSWORD", "root"),
          ssl: {
            rejectUnauthorized: env.bool("DATABASE_SSL", false), // For self-signed certificates
          },
        },
      },
    };
  }

  return {
    connection: {
      client: "sqlite",
      connection: {
        filename: path.join(
          __dirname,
          "../",
          env("DATABASE_FILENAME", ".tmp/data.db")
        ),
      },
      useNullAsDefault: true,
    },
  };
};

Inserção de dados na instalação do Strapi

O Strapi permite que utilizemos uma função para gerar conteúdo de marcação (dummy content) que pode ser aproveitada para subirmos uma nova LP com seus dados de forma automática.

Para isso devemos utilizar a função bootstrap do Strapi definida nos arquivos ./src/index.js e .src/bootstrap.js como no exemplo abaixo que cria automaticamente os artigos, categorias e autores desse stater e preenche também os dados de SEO com um conteúdo padrão para uso nas aplicações Frontend

./src/index.js

"use strict";

const fs = require("fs");
const mime = require("mime-types");
const set = require("lodash.set");
const { categories, writers, articles, global } = require("../data/data.json");

const isFirstRun = async () => {
  const pluginStore = strapi.store({
    environment: strapi.config.environment,
    type: "type",
    name: "setup",
  });
  const initHasRun = await pluginStore.get({ key: "initHasRun" });
  await pluginStore.set({ key: "initHasRun", value: true });
  return !initHasRun;
};

const setPublicPermissions = async (newPermissions) => {
  // Find the ID of the public role
  const publicRole = await strapi
    .query("plugin::users-permissions.role")
    .findOne({
      where: {
        type: "public",
      },
    });

  // Create the new permissions and link them to the public role
  const allPermissionsToCreate = [];
  Object.keys(newPermissions).map((controller) => {
    const actions = newPermissions[controller];
    const permissionsToCreate = actions.map((action) => {
      return strapi.query("plugin::users-permissions.permission").create({
        data: {
          action: `api::${controller}.${controller}.${action}`,
          role: publicRole.id,
        },
      });
    });
    allPermissionsToCreate.push(...permissionsToCreate);
  });
  await Promise.all(allPermissionsToCreate);
};

const getFileSizeInBytes = (filePath) => {
  const stats = fs.statSync(filePath);
  const fileSizeInBytes = stats["size"];
  return fileSizeInBytes;
};

const getFileData = (fileName) => {
  const filePath = `./data/uploads/${fileName}`;

  // Parse the file metadata
  const size = getFileSizeInBytes(filePath);
  const ext = fileName.split(".").pop();
  const mimeType = mime.lookup(ext);

  return {
    path: filePath,
    name: fileName,
    size,
    type: mimeType,
  };
};

// Create an entry and attach files if there are any
const createEntry = async ({ model, entry, files }) => {
  try {
    if (files) {
      for (const [key, file] of Object.entries(files)) {
        // Get file name without the extension
        const [fileName] = file.name.split(".");
        // Upload each individual file
        const uploadedFile = await strapi
          .plugin("upload")
          .service("upload")
          .upload({
            files: file,
            data: {
              fileInfo: {
                alternativeText: fileName,
                caption: fileName,
                name: fileName,
              },
            },
          });

        // Attach each file to its entry
        set(entry, key, uploadedFile[0].id);
      }
    }

    // Actually create the entry in Strapi
    const createdEntry = await strapi.entityService.create(
      `api::${model}.${model}`,
      {
        data: entry,
      }
    );
  } catch (e) {
    console.log("model", entry, e);
  }
};

// Import Data from ./data/data.json

async function importCategories() {
  return Promise.all(
    categories.map((category) => {
      return createEntry({ model: "category", entry: category });
    })
  );
}

async function importWriters() {
  return Promise.all(
    writers.map(async (writer) => {
      const files = {
        picture: getFileData(`${writer.email}.jpg`),
      };
      return createEntry({
        model: "writer",
        entry: writer,
        files,
      });
    })
  );
}

async function importArticles() {
  return Promise.all(
    articles.map((article) => {
      const files = {
        image: getFileData(`${article.slug}.jpg`),
      };

      return createEntry({
        model: "article",
        entry: {
          ...article,
          // Make sure it's not a draft
          publishedAt: Date.now(),
        },
        files,
      });
    })
  );
}

// Create Globals SEO data
const importGlobal = async () => {
  const files = {
    favicon: getFileData("favicon.png"),
    "defaultSeo.shareImage": getFileData("default-image.png"),
  };
  return createEntry({ model: "global", entry: global, files });
};

// Call Import data functions
const importSeedData = async () => {
  // Allow read of application content types
  await setPublicPermissions({
    global: ["find"],
    category: ["find", "findOne"],
    writer: ["find", "findOne"],
    article: ["find", "findOne"],
  });

  console.log("Bootstraping data...");
  // Create all entries
  await importCategories();
  await importWriters();
  await importArticles();
  await importGlobal();
};

// Run bootstrap functions
module.exports = async () => {
  const shouldImportSeedData = await isFirstRun();

  if (shouldImportSeedData) {
    console.log("First install, let's check if we have to create some data...");
    try {
      console.log("Setting up the template...");
      await importSeedData();
      console.log("Content imported!");
    } catch (error) {
      console.log("Could not import seed data...");
      console.error(error);
    }
  }
};

Nesse exemplo de código estamos utilizando os dados contidos no JSON data.json dentro da pasta ./data.
As imagens importadas dessa forma estão inseridas dentro da pasta ./data/uploads e serão tratadas no código.

Nesse código de exemplo as imagens precisam ser nomeadas com o mesmo slug definido no JSON.\

NOTA:

Essa função de bootstrap só irá ser executada na primeira instalação do painel.
Então não precisamos nos preocupar com sobrescrita dos dados.

Changelog

1.0.0

  • Criação da estrutura base do strapi na versão 4.1.0
  • Inclusão do modelo das variáveis de ambiente do projeto no .env.example
  • Criação dos Content-Types Articles, Categories, Writers e Globals/SEO que irá gerar os METADADOS de SEO para uso nas aplicações frontend
  • Inserção automática do conteúdo de POSTS, CATEGORIAS, GLOBALS/SEO e geração do ADMIN USER utilizando a função bootstrap() do Strapi no arquivo ./src/bootstrap.js

1.0.1

  • Ajuste na ordem das funções do ./src/bootstrap.js e remoção da variável de ambiente BOOTSTRAP_CONTENT
  • Ajuste dos tamanhos e pesos das imagens em ./data/uploads
  • Inclusão dos campos de redes sociais no bootstrap dos writers
  • Atualização do strapi para a versão 4.1.1
  • Removida a opção de criar usuário automaticamente com a função createAdminUser() para evitar bug com os roles



🚀 Sobre o Strapi

O Strapi vem com uma Interface de linha de comando (CLI) completa, que permite que você gerencie seu projeto em segundos.

develop

Inicie seu aplicativo Strapi com o autoReload ativado. Saiba mais

npm run develop
# or
yarn develop

start

Inicie seu aplicativo Strapi com o autoReload desativado. Saiba mais

npm run start
# or
yarn start

build

Construa seu painel de administração. Saiba mais

npm run build
# or
yarn build

⚙️ Deploy

O Strapi oferece muitas opções de implantação possíveis para o seu projeto. Encontre aquele que combina com você na seção de implantação da documentação.

📚 Documentação

Sinta-se à vontade para conferir o repositório Strapi GitHub. Seus comentários e contribuições são bem-vindos!

✨ Comunidade

  • Discord - Venha conversar com a comunidade Strapi, incluindo a equipe principal.
  • Fórum - Lugar para discutir, fazer perguntas e encontrar respostas, mostrar seu projeto Strapi e obter feedback ou apenas conversar com outros membros da Comunidade.
  • Awesome Strapi - Uma lista com curadoria de coisas incríveis relacionadas ao Strapi.

About

Backend application of a Headless blog using Strapi as CMS

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published