agentsclimarketplace

Nnews guide

Skill landim32/awesome-ai-skills/skills/nnews-guide

Claude AI Skills repository

Install
npx -y skills add landim32/awesome-ai-skills --skill nnews-guide

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Guides how to integrate the NNews NuGet package for consuming the NNews CMS API in a .NET 8 project. Use when the user wants to consume articles, categories, tags, images, or AI-powered content generation from the NNews API.

SKILL.md

22.2 KB, as published. Nobody here has run it

NNews NuGet Package Integration Guide

You are an expert assistant that helps developers integrate the NNews NuGet package for consuming the NNews CMS API in .NET 8 projects.

Input

The user may provide a specific question or context as argument: $ARGUMENTS

If no argument is provided, present a complete overview of the NNews integration.

When the user asks about NNews, use this knowledge base to provide accurate, contextual guidance.


NNews — Data Transfer Objects

Install: dotnet add package NNews

NNewsSetting

public class NNewsSetting
{
    public string ApiUrl { get; set; } = string.Empty;  // NNews API base URL
}

ArticleInfo

public class ArticleInfo
{
    public long ArticleId { get; set; }
    public long CategoryId { get; set; }
    public long? AuthorId { get; set; }
    public string? ImageName { get; set; }       // Max 560 chars
    public string Title { get; set; }            // Required, max 255 chars
    public string Content { get; set; }          // Required
    public int Status { get; set; }              // 0=Draft, 1=Published, 2=Archived, 3=Scheduled
    public int ContentType { get; set; } = 2;    // 1=PlainText, 2=Html, 3=MarkDown
    public DateTime DateAt { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime UpdatedAt { get; set; }
    public CategoryInfo? Category { get; set; }
    public List<TagInfo> Tags { get; set; } = new();
    public List<RoleInfo> Roles { get; set; } = new();
}

ArticleInsertedInfo

public class ArticleInsertedInfo
{
    public long CategoryId { get; set; }            // Required
    public long? AuthorId { get; set; }
    public string? ImageName { get; set; }          // Max 560 chars
    public string Title { get; set; }               // Required, max 255 chars
    public string Content { get; set; }             // Required
    public int Status { get; set; }                 // 0=Draft, 1=Published, 2=Archived, 3=Scheduled
    public int ContentType { get; set; } = 2;       // Default: Html
    public DateTime DateAt { get; set; }            // Required
    public string TagList { get; set; } = string.Empty;  // Comma-separated tags
    public List<string> Roles { get; set; } = new();     // Role slugs
}

ArticleUpdatedInfo

public class ArticleUpdatedInfo
{
    public long ArticleId { get; set; }
    public long CategoryId { get; set; }
    public long? AuthorId { get; set; }
    public string? ImageName { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public int Status { get; set; }
    public int ContentType { get; set; } = 2;
    public DateTime DateAt { get; set; }
    public string TagList { get; set; } = string.Empty;
    public List<string> Roles { get; set; } = new();
}

CategoryInfo

public class CategoryInfo
{
    public long CategoryId { get; set; }
    public long? ParentId { get; set; }      // For hierarchical categories
    public string Title { get; set; }        // Required, max 240 chars
    public DateTime CreatedAt { get; set; }
    public DateTime UpdatedAt { get; set; }
    public int ArticleCount { get; set; }    // Read-only counter
}

TagInfo

public class TagInfo
{
    public long TagId { get; set; }
    public string Title { get; set; }        // Required, max 120 chars
    public string? Slug { get; set; }        // Max 120 chars
    public int ArticleCount { get; set; }    // Read-only counter
}

RoleInfo

public class RoleInfo
{
    public string Slug { get; set; } = string.Empty;
    public string Name { get; set; } = string.Empty;
}

PagedResult<T>

public class PagedResult<T>
{
    public IList<T> Items { get; set; } = new List<T>();
    public int Page { get; set; }
    public int PageSize { get; set; }
    public int TotalCount { get; set; }
    public int TotalPages { get; set; }
    public bool HasPrevious => Page > 1;
    public bool HasNext => Page < TotalPages;
}

AI DTOs

// Request for AI content generation
public class AIArticleRequest
{
    public long? ArticleId { get; set; }
    public string Prompt { get; set; }              // Required, 10-2000 chars
    public bool GenerateImage { get; set; } = false;
}

// Response from AI generation
public class AIArticleResponse
{
    public string Title { get; set; } = string.Empty;
    public string Content { get; set; } = string.Empty;
    public long CategoryId { get; set; }
    public string TagList { get; set; } = string.Empty;
    public string? ImagePrompt { get; set; }
}

// Response from AI article update
public class AIArticleUpdateResponse
{
    public long ArticleId { get; set; }
    public string Title { get; set; } = string.Empty;
    public string Content { get; set; } = string.Empty;
    public long CategoryId { get; set; }
    public string TagList { get; set; } = string.Empty;
    public string? ImagePrompt { get; set; }
}

// Category summary for AI context
public class AICategorySummary
{
    public long CategoryId { get; set; }
    public string Title { get; set; } = string.Empty;
    public long? ParentId { get; set; }
}

NNews — Anti-Corruption Layer (ACL)

IArticleClient

public interface IArticleClient
{
    Task<PagedResult<ArticleInfo>> GetAllAsync(long? categoryId = null, int? status = null, int page = 1, int pageSize = 10, CancellationToken cancellationToken = default);
    Task<PagedResult<ArticleInfo>> ListByCategoryAsync(long categoryId, int page = 1, int pageSize = 10, CancellationToken cancellationToken = default);
    Task<PagedResult<ArticleInfo>> ListByRolesAsync(int page = 1, int pageSize = 10, CancellationToken cancellationToken = default);
    Task<PagedResult<ArticleInfo>> ListByTagAsync(string tagSlug, int page = 1, int pageSize = 10, CancellationToken cancellationToken = default);
    Task<PagedResult<ArticleInfo>> SearchAsync(string keyword, int page = 1, int pageSize = 10, CancellationToken cancellationToken = default);
    Task<ArticleInfo> GetByIdAsync(int id, CancellationToken cancellationToken = default);
    Task<ArticleInfo> CreateAsync(ArticleInsertedInfo article, CancellationToken cancellationToken = default);
    Task<ArticleInfo> UpdateAsync(ArticleUpdatedInfo article, CancellationToken cancellationToken = default);
    Task DeleteAsync(int id, CancellationToken cancellationToken = default);
}

IArticleAIClient

public interface IArticleAIClient
{
    Task<ArticleInfo> CreateWithAIAsync(string prompt, bool generateImage = false, CancellationToken cancellationToken = default);
    Task<ArticleInfo> UpdateWithAIAsync(int articleId, string prompt, bool generateImage = false, CancellationToken cancellationToken = default);
}

ICategoryClient

public interface ICategoryClient
{
    Task<IList<CategoryInfo>> GetAllAsync(CancellationToken cancellationToken = default);
    Task<IList<CategoryInfo>> ListByParentAsync(long? parentId = null, CancellationToken cancellationToken = default);
    Task<CategoryInfo> GetByIdAsync(int id, CancellationToken cancellationToken = default);
    Task<CategoryInfo> CreateAsync(CategoryInfo category, CancellationToken cancellationToken = default);
    Task<CategoryInfo> UpdateAsync(CategoryInfo category, CancellationToken cancellationToken = default);
    Task DeleteAsync(int id, CancellationToken cancellationToken = default);
}

ITagClient

public interface ITagClient
{
    Task<IList<TagInfo>> GetAllAsync(CancellationToken cancellationToken = default);
    Task<IList<TagInfo>> ListByRolesAsync(CancellationToken cancellationToken = default);
    Task<TagInfo> GetByIdAsync(int id, CancellationToken cancellationToken = default);
    Task<TagInfo> CreateAsync(TagInfo tag, CancellationToken cancellationToken = default);
    Task<TagInfo> UpdateAsync(TagInfo tag, CancellationToken cancellationToken = default);
    Task DeleteAsync(int id, CancellationToken cancellationToken = default);
    Task MergeTagsAsync(long sourceTagId, long targetTagId, CancellationToken cancellationToken = default);
}

IImageClient

public interface IImageClient
{
    Task<string> UploadImageAsync(IFormFile file, CancellationToken cancellationToken = default);
}

ITenantResolver

public interface ITenantResolver
{
    string TenantId { get; }
    string ConnectionString { get; }
    string JwtSecret { get; }
}

NNews REST API Endpoints

All endpoints are prefixed by the controller name (e.g., /Article, /Category). Authenticated endpoints require a Bearer JWT token. Tenant is resolved via X-Tenant-Id header or JWT tenant_id claim.

Article (/Article)

MethodRouteQuery ParamsBodyAuthResponse
GET/ArticlecategoryId?, status?, page, pageSizeYesPagedResult<ArticleInfo>
GET/Article/ListByCategorycategoryId, page, pageSizeNoPagedResult<ArticleInfo>
GET/Article/ListByRolespage, pageSizeNoPagedResult<ArticleInfo>
GET/Article/ListByTagtagSlug, page, pageSizeNoPagedResult<ArticleInfo>
GET/Article/Searchkeyword, page, pageSizeNoPagedResult<ArticleInfo>
GET/Article/{id}NoArticleInfo
POST/ArticleArticleInsertedInfoYesArticleInfo (201)
POST/Article/insertWithAIAIArticleRequestYesArticleInfo (201)
PUT/ArticleArticleUpdatedInfoYesArticleInfo
PUT/Article/updateWithAIAIArticleRequestYesArticleInfo
DELETE/Article/{id}Yes204 No Content

Status filter values: 0 = Draft, 1 = Published, 2 = Archived, 3 = Scheduled

Category (/Category)

MethodRouteQuery ParamsBodyAuthResponse
GET/CategoryYesIList<CategoryInfo>
GET/Category/listByParentroles?, parentId?NoIList<CategoryInfo>
GET/Category/{id}NoCategoryInfo
POST/CategoryCategoryInfoYesCategoryInfo (201)
PUT/CategoryCategoryInfoYesCategoryInfo
DELETE/Category/{id}Yes204 No Content

Tag (/Tag)

MethodRouteQuery ParamsBodyAuthResponse
GET/TagYesIList<TagInfo>
GET/Tag/ListByRolesNoIList<TagInfo>
GET/Tag/{id}NoTagInfo
POST/TagTagInfoYesTagInfo (201)
PUT/TagTagInfoYesTagInfo
DELETE/Tag/{id}Yes204 No Content
POST/Tag/merge/{sourceTagId}/{targetTagId}Yes200 OK

Image (/Image)

MethodRouteQuery ParamsBodyAuthResponse
POST/Image/uploadImageIFormFile (multipart, max 100MB)Yesstring (image URL)

Multi-Tenancy

NNews supports multi-tenancy via the X-Tenant-Id HTTP header.

TenantHeaderHandler

A DelegatingHandler that automatically adds the X-Tenant-Id header to all outgoing HTTP requests. It reads the tenant ID from the configuration key Tenant:DefaultTenantId.

TenantResolver

Implements ITenantResolver. Reads tenant configuration from:

  • Tenant:DefaultTenantId — the default tenant ID
  • Tenants:{TenantId}:ConnectionString — database connection string
  • Tenants:{TenantId}:JwtSecret — JWT secret for the tenant

Step-by-Step Integration

1. Install Package

dotnet add package NNews

2. Configure appsettings.json

{
  "NNews": {
    "ApiUrl": "http://localhost:5007"
  },
  "Tenant": {
    "DefaultTenantId": "my-tenant"
  }
}

Docker: use "ApiUrl": "http://nnews-api:80".

3. Register Services (DI)

using NNews.ACL;
using NNews.ACL.Interfaces;
using NNews.ACL.Handlers;
using NNews.ACL.Services;
using NNews.DTO.Settings;

// Settings
services.Configure<NNewsSetting>(configuration.GetSection("NNews"));

// Tenant resolver
services.AddScoped<ITenantResolver, TenantResolver>();

// Register TenantHeaderHandler for automatic X-Tenant-Id header
services.AddTransient<TenantHeaderHandler>();

// Register HttpClients with TenantHeaderHandler
services.AddHttpClient<IArticleClient, ArticleClient>()
    .AddHttpMessageHandler<TenantHeaderHandler>();

services.AddHttpClient<IArticleAIClient, ArticleAIClient>()
    .AddHttpMessageHandler<TenantHeaderHandler>();

services.AddHttpClient<ICategoryClient, CategoryClient>()
    .AddHttpMessageHandler<TenantHeaderHandler>();

services.AddHttpClient<ITagClient, TagClient>()
    .AddHttpMessageHandler<TenantHeaderHandler>();

services.AddHttpClient<IImageClient, ImageClient>()
    .AddHttpMessageHandler<TenantHeaderHandler>();

4. Register Only What You Need

If you only need to read articles (e.g., a public blog frontend), register only the necessary clients:

services.Configure<NNewsSetting>(configuration.GetSection("NNews"));
services.AddTransient<TenantHeaderHandler>();

services.AddHttpClient<IArticleClient, ArticleClient>()
    .AddHttpMessageHandler<TenantHeaderHandler>();

services.AddHttpClient<ICategoryClient, CategoryClient>()
    .AddHttpMessageHandler<TenantHeaderHandler>();

services.AddHttpClient<ITagClient, TagClient>()
    .AddHttpMessageHandler<TenantHeaderHandler>();

Usage Examples

List Articles (Paginated)

public class BlogService
{
    private readonly IArticleClient _articleClient;

    public BlogService(IArticleClient articleClient)
    {
        _articleClient = articleClient;
    }

    public async Task<PagedResult<ArticleInfo>> GetLatestArticles(int page = 1, int pageSize = 12)
    {
        return await _articleClient.GetAllAsync(page: page, pageSize: pageSize);
    }

    public async Task<PagedResult<ArticleInfo>> GetPublishedArticles(int page = 1, int pageSize = 12)
    {
        // Filter by status: 0=Draft, 1=Published, 2=Archived, 3=Scheduled
        return await _articleClient.GetAllAsync(status: 1, page: page, pageSize: pageSize);
    }
}

List Articles by Category

public async Task<PagedResult<ArticleInfo>> GetByCategory(long categoryId, int page = 1)
{
    return await _articleClient.ListByCategoryAsync(categoryId, page: page, pageSize: 10);
}

List Articles by Tag

public async Task<PagedResult<ArticleInfo>> GetByTag(string tagSlug, int page = 1)
{
    return await _articleClient.ListByTagAsync(tagSlug, page: page, pageSize: 10);
}

Search Articles

public async Task<PagedResult<ArticleInfo>> Search(string keyword, int page = 1)
{
    return await _articleClient.SearchAsync(keyword, page: page, pageSize: 10);
}

Get Single Article

public async Task<ArticleInfo> GetArticle(int id)
{
    return await _articleClient.GetByIdAsync(id);
}

Create Article

public async Task<ArticleInfo> CreateArticle()
{
    var article = new ArticleInsertedInfo
    {
        Title = "My First Article",
        Content = "<p>Hello World!</p>",
        CategoryId = 1,
        Status = 1,           // Published
        ContentType = 2,       // Html
        DateAt = DateTime.UtcNow,
        TagList = "dotnet,csharp,webapi",
        Roles = new List<string> { "admin", "editor" }
    };

    return await _articleClient.CreateAsync(article);
}

Update Article

public async Task<ArticleInfo> UpdateArticle(long articleId)
{
    var article = new ArticleUpdatedInfo
    {
        ArticleId = articleId,
        Title = "Updated Title",
        Content = "<p>Updated content</p>",
        CategoryId = 1,
        Status = 1,
        ContentType = 2,
        DateAt = DateTime.UtcNow,
        TagList = "dotnet,updated"
    };

    return await _articleClient.UpdateAsync(article);
}

Delete Article

public async Task DeleteArticle(int articleId)
{
    await _articleClient.DeleteAsync(articleId);
}

Create Article with AI

public async Task<ArticleInfo> CreateWithAI(string prompt, bool withImage = false)
{
    return await _articleAIClient.CreateWithAIAsync(prompt, generateImage: withImage);
}

// Example usage:
// var article = await CreateWithAI("Write an article about clean architecture in .NET 8", withImage: true);

Update Article with AI

public async Task<ArticleInfo> UpdateWithAI(int articleId, string prompt)
{
    return await _articleAIClient.UpdateWithAIAsync(articleId, prompt, generateImage: false);
}

Category Management

public async Task CategoryExamples()
{
    // List all categories
    var categories = await _categoryClient.GetAllAsync();

    // List root categories (no parent)
    var rootCategories = await _categoryClient.ListByParentAsync(parentId: null);

    // List subcategories
    var subCategories = await _categoryClient.ListByParentAsync(parentId: 1);

    // Create category
    var newCategory = await _categoryClient.CreateAsync(new CategoryInfo
    {
        Title = "Technology",
        ParentId = null  // Root category
    });

    // Create subcategory
    var subCategory = await _categoryClient.CreateAsync(new CategoryInfo
    {
        Title = "Web Development",
        ParentId = newCategory.CategoryId
    });

    // Delete category
    await _categoryClient.DeleteAsync((int)newCategory.CategoryId);
}

Tag Management

public async Task TagExamples()
{
    // List all tags
    var tags = await _tagClient.GetAllAsync();

    // List tags filtered by user roles
    var roleTags = await _tagClient.ListByRolesAsync();

    // Create tag
    var newTag = await _tagClient.CreateAsync(new TagInfo { Title = "C#" });

    // Merge tags (move all articles from source to target, then delete source)
    await _tagClient.MergeTagsAsync(sourceTagId: 5, targetTagId: 2);

    // Delete tag
    await _tagClient.DeleteAsync((int)newTag.TagId);
}

Image Upload

[HttpPost("upload")]
[Authorize]
public async Task<ActionResult<string>> UploadImage(IFormFile file)
{
    if (file == null || file.Length == 0)
        return BadRequest("No file uploaded");

    var imageUrl = await _imageClient.UploadImageAsync(file);
    return Ok(imageUrl);
}

Controller Example — Blog API

using Microsoft.AspNetCore.Mvc;
using NNews.ACL.Interfaces;
using NNews.DTO;

[Route("api/[controller]")]
[ApiController]
public class BlogController : ControllerBase
{
    private readonly IArticleClient _articleClient;
    private readonly ICategoryClient _categoryClient;
    private readonly ITagClient _tagClient;

    public BlogController(
        IArticleClient articleClient,
        ICategoryClient categoryClient,
        ITagClient tagClient)
    {
        _articleClient = articleClient;
        _categoryClient = categoryClient;
        _tagClient = tagClient;
    }

    [HttpGet("articles")]
    public async Task<ActionResult<PagedResult<ArticleInfo>>> GetArticles(
        [FromQuery] int page = 1,
        [FromQuery] int pageSize = 12,
        [FromQuery] long? categoryId = null,
        [FromQuery] int? status = null)
    {
        var result = await _articleClient.GetAllAsync(categoryId, status, page, pageSize);
        return Ok(result);
    }

    [HttpGet("articles/{id}")]
    public async Task<ActionResult<ArticleInfo>> GetArticle(int id)
    {
        var article = await _articleClient.GetByIdAsync(id);
        return Ok(article);
    }

    [HttpGet("articles/search")]
    public async Task<ActionResult<PagedResult<ArticleInfo>>> Search(
        [FromQuery] string keyword,
        [FromQuery] int page = 1)
    {
        var result = await _articleClient.SearchAsync(keyword, page);
        return Ok(result);
    }

    [HttpDelete("articles/{id}")]
    public async Task<IActionResult> DeleteArticle(int id)
    {
        await _articleClient.DeleteAsync(id);
        return NoContent();
    }

    [HttpGet("categories")]
    public async Task<ActionResult<IList<CategoryInfo>>> GetCategories()
    {
        var categories = await _categoryClient.GetAllAsync();
        return Ok(categories);
    }

    [HttpGet("tags")]
    public async Task<ActionResult<IList<TagInfo>>> GetTags()
    {
        var tags = await _tagClient.GetAllAsync();
        return Ok(tags);
    }
}

Troubleshooting

IssueCauseSolution
HTTP 500 on all requestsNNews API unreachableVerify NNews:ApiUrl in appsettings
HTTP 401 UnauthorizedMissing or invalid auth tokenEnsure Bearer token is forwarded or endpoint is public
Empty PagedResultWrong tenant or no dataCheck Tenant:DefaultTenantId configuration
DI error for IArticleClientMissing registrationAdd services.AddHttpClient<IArticleClient, ArticleClient>()
Missing X-Tenant-Id headerTenantHeaderHandler not registeredAdd .AddHttpMessageHandler<TenantHeaderHandler>()
NNewsSetting.ApiUrl emptyMissing configuration sectionAdd "NNews": { "ApiUrl": "..." } to appsettings
CreateAsync returns errorMissing required fieldsEnsure Title, Content, CategoryId, and DateAt are set
Tags not appliedWrong formatUse comma-separated string in TagList (e.g., "tag1,tag2,tag3")

Response Guidelines

  1. Be specific: Reference exact class names, interfaces, and method signatures
  2. Show code: Always include working code examples based on the patterns above
  3. Context-aware: If the user is working in a project that consumes NNews, reference their existing DI setup and configuration
  4. Minimal changes: Only suggest what's needed for the user's specific question
  5. Multi-tenancy: Always remind about configuring Tenant:DefaultTenantId and registering TenantHeaderHandler
  6. Prerequisites: Mention required NuGet package and configuration if starting fresh

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.