作ってみるシリーズ:ヘッドレスCMSを作る

2021-05-25
CMS

おはようございます、開発担当のマユリです。

本日はCMSサービスを提供するだけでなく、作っている過程も紹介してコンテンツにしていこうという企画です。

ウェブサービスを提供する上で毎度必要となるのがCMS。簡易なものから高度なカスタマイズを要するものまでありますが、要するにコンテンツを検索、追加、更新、削除できればいいので基本となるプログラムを構築して公開していきたいと思います。

今では出力メディアを問わずに使えるヘッドレスCMSも人気がでてきているとのことで、フルAPIで作成してヘッドレスに行きたいと思います。

CMSの基本的なデータ項目はschema.orgが定義しているarticleがいいと思います。 詳細はこちらをご参照ください。 Article

それではまずは、articleというデータを作成、更新、削除、検索していきましょう。

Articleオブジェクト

Articleオブジェクトは下記のように定義しています。

/// <summary>
/// Article.
/// </summary>
[Table("Articles")]
public class ArticleModel : ArticleBaseModel
{
   [Key, MaxLength(64)]
   public string Id { get; set; }
   public string Title { get; set; }
   public string Description { get; set; }
        
   public string Text { get; set; }
   public string MarkdownText { get; set; }
   public string Url { get; set; }
   public DateTime Publish { get; set; }
   public long PublishUnixtime { get; set; }
   public DateTime Expire { get; set; }
   public long ExpireUnixtime { get; set; }
   [MaxLength(64)]
   public string ImageId { get; set; }
   public string ImageUrl { get; set; }
   [Required, MaxLength(64)]

   public string ChannelId { get; set; }
   [Required, MaxLength(64)]
   public string CategoryId { get; set; }
   [Required, MaxLength(64)]
   public string SubCategoryId { get; set; }
   public string Tags { get; set; }
   [Required]
   public string AuthorId { get; set; }
   [Required]
   public string Culture { get; set; }
   [Required]
   public string PermaName { get; set; }
 }

ArticleBaseModelというクラスをここでは継承しています。すべてのデータ共通する項目をArticleBaseModelに定義することによって共通性と効率化をします。

/// <summary>
/// Base, common model definition.
/// </summary>
public class ArticleBaseModel
{
   [MaxLength(64)]
   public string OwnerId { get; set; }
   public DateTime Modified { get; set; }
   public DateTime Created { get; set; }
   public bool IsDeleted { get; set; }
   public bool IsPublished { get; set; }
}

articleデータの作成と更新

データモデルは定義できたので、さっそくデータを作成していきます。

データを作成するメソッドはこちらです。

/// <summary>
/// Upsert article.
/// </summary>
/// <remarks>
/// channelId, categoryId, subCategoryId, authorId must be valid.
/// </remarks>
/// <param name="item"></param>
/// <returns></returns>
public async Task<UpsertResponseModel<ArticleModel>> UpsertArticleAsync(ArticleModel item)
{
   // We assume everything is OK first.
   var isValid = true;
   var responseStatus = "Success";
   var erros = new List<ErrorModel>();

   // Validate channel
   var channel = (from ch in _db.Channels where ch.Id == item.ChannelId select ch).FirstOrDefault();
   if (channel == null)
   {
      isValid = false;
      erros.Add(new ErrorModel()
      {
         ErrorCode = 500,
         ErrorMessage = "Channel is not found"
       });
    }

    // Validate category
    var category = (from ca in _db.Categories where ca.Id == item.CategoryId select ca).FirstOrDefault();
    if (category == null)
    {
       isValid = false;
       erros.Add(new ErrorModel()
       {
          ErrorCode = 500,
          ErrorMessage = "Category is not found"
       });
    }

    // Validate sub category
    var subCategory = (from sc in _db.SubCategories where sc.Id == item.SubCategoryId select sc).FirstOrDefault();
    if (subCategory == null)
    {
       isValid = false;
       erros.Add(new ErrorModel()
       {
          ErrorCode = 500,
          ErrorMessage = "Sub category is not found"
       });
    }

    // Validate author
    var author = (from au in _db.Author where au.Id == item.AuthorId select au).FirstOrDefault();
    if (author == null)
    {
       isValid = false;
       erros.Add(new ErrorModel()
       {
          ErrorCode = 500,
          ErrorMessage = "Author is not found"
       });
    }

    // If valid, then execute db operation
    if (isValid)
    {
       // Insert or Update?
       var original = (from o in _db.Articles where o.Id == item.Id select o).AsNoTracking().FirstOrDefault();

       if (original == null)
       {
          // Insert

          // Make sure perma name is unique.
          if (IsUniqueArticlePermaName(item.OwnerId, item.ChannelId, item.CategoryId, item.SubCategoryId, item.PermaName, item.Culture))
          {
             try
             {
                // Set timestamp
                item.ExpireUnixtime = DateTimeHelpers.GetUnixTime(item.Expire);
                item.PublishUnixtime = DateTimeHelpers.GetUnixTime(item.Publish);
                item.Modified = DateTime.Now;
                item.Created = DateTime.Now;
                            
                _db.Articles.Add(item);
                await _db.SaveChangesAsync();
              }
              catch (Exception e)
              {
                 isValid = false;
                 erros.Add(new ErrorModel()
                 {
                    ErrorCode = 500,
                    ErrorMessage = e.Message
                 });
              }
           }
           else
           {
              isValid = false;
              erros.Add(new ErrorModel()
              {
                 ErrorCode = 500,
                 ErrorMessage = "Perma name is not unique."
              });
           }

        }
        else
        {
           // Upsert
           // Make sure perma name is unique.
           if (item.PermaName != original.PermaName)
           {
              if (IsUniqueArticlePermaName(item.PermaName, item.ChannelId, item.CategoryId, item.SubCategoryId, item.OwnerId, item.Culture))
              {
                 try
                 {
                    item.ExpireUnixtime = DateTimeHelpers.GetUnixTime(item.Expire);
                    item.PublishUnixtime = DateTimeHelpers.GetUnixTime(item.Publish);
                    item.Modified = DateTime.Now;
                              
                    _db.Articles.Update(item);
                    await _db.SaveChangesAsync();
                 }
                 catch (Exception e)
                 {
                    isValid = false;
                    erros.Add(new ErrorModel()
                    {
                       ErrorCode = 500,
                       ErrorMessage = e.Message
                    });
                 }
              }
              else
              {
                 isValid = false;
                 erros.Add(new ErrorModel()
                 {
                    ErrorCode = 500,
                    ErrorMessage = "Perma name is not unique."
                 });
              }
           }
           else
           {
              try
              {
                 item.ExpireUnixtime = DateTimeHelpers.GetUnixTime(item.Expire);
                 item.PublishUnixtime = DateTimeHelpers.GetUnixTime(item.Publish);
                 item.Modified = DateTime.Now;

                 _db.Articles.Update(item);
                 await _db.SaveChangesAsync();
              }
              catch (Exception e)
               {
                  isValid = false;
                  erros.Add(new ErrorModel()
                  {
                     ErrorCode = 500,
                     ErrorMessage = e.Message
                  });
               }
            }
         }
      }

      if (!isValid)
      {
         responseStatus = "Bad Request";
       }

       return new UpsertResponseModel<ArticleModel>()
       {
          ResponseStatus = responseStatus,
          Item = item,
           Errors = erros
        };
   }
公開日: 2021-05-06