Compare commits

...

69 Commits
0.1.0 ... main

Author SHA1 Message Date
jtom38 d1408ce6a4 Merge pull request 'features/role-updates' (#18) from features/role-updates into main
continuous-integration/drone/push Build is passing Details
Reviewed-on: #18
2023-08-06 13:39:45 -07:00
James Tombleson bb6d36cfd8 Updated article to use UTC time
continuous-integration/drone/pr Build is passing Details
2023-08-06 13:37:59 -07:00
James Tombleson ff272ab146 Added user role on creation. Updated Unixtime call. Updated how to add a role. Roles are now added to the claim 2023-08-06 13:37:36 -07:00
James Tombleson 97fc34481c ISourcesRepository.cs updated to return total rows 2023-08-06 13:35:26 -07:00
James Tombleson 338edf8d4e Added TotalByTypeAsync to see if we need to pull multiple pages 2023-08-06 13:34:54 -07:00
James Tombleson 22e9638f88 namespace updated and using 2023-08-06 13:33:45 -07:00
James Tombleson 3e1a8e8419 Using updates 2023-08-06 13:32:35 -07:00
James Tombleson bd05c45e6e Updated startup to reflect the correct consts and roles 2023-08-06 13:31:00 -07:00
James Tombleson a89c80ed3c Updated Authorization.cs namespace and added the claim value. 2023-08-06 13:30:18 -07:00
jtom38 7c88967f7f Merge pull request 'features/add-authors-table' (#17) from features/add-authors-table into main
continuous-integration/drone/push Build is passing Details
Reviewed-on: #17
2023-08-04 23:09:43 -07:00
James Tombleson a3085bb27f Article controller was updated and tests updated
continuous-integration/drone/pr Build is passing Details
2023-08-04 23:07:04 -07:00
James Tombleson 751bbd3a7b IAuthorTable.cs was added 2023-08-04 23:05:49 -07:00
James Tombleson 2aa14548aa DTO was updated to reflect author changes 2023-08-04 23:05:26 -07:00
James Tombleson 0d751cc571 Collector jobs have been updated to handle authors 2023-08-04 23:04:32 -07:00
James Tombleson 56a655e4f1 author table was added 2023-08-04 23:04:09 -07:00
jtom38 2ea971af54 Merge pull request 'Adding the calls to track authors for metrics but also to attempt to find AI writers.' (#16) from features/authortable into main
continuous-integration/drone/push Build is passing Details
Reviewed-on: #16
2023-07-30 08:56:51 -07:00
James Tombleson dcb81315bf Adding the calls to track authors for metrics but also to attempt to find AI writers.
continuous-integration/drone/pr Build is passing Details
2023-07-29 10:02:43 -07:00
jtom38 06427d53f3 Merge pull request 'features/api-cleanup' (#15) from features/api-cleanup into main
continuous-integration/drone/push Build is passing Details
Reviewed-on: #15
2023-07-29 09:47:27 -07:00
James Tombleson 82dea60126 Updated DiscordWebHooks to handle userId
continuous-integration/drone/pr Build is passing Details
2023-07-29 09:44:15 -07:00
James Tombleson c92d13797f minor adjustments 2023-07-23 22:58:25 -07:00
James Tombleson 463cb893e8 Logging update 2023-07-23 22:57:54 -07:00
James Tombleson a416746269 DiscordNotificationController.cs got a new route, moved over to ActionResults, records are now tracking the userId and returning common result classes 2023-07-23 22:57:35 -07:00
James Tombleson 7344d2bdd2 CodeProjectController.cs was updated to support ActionResults and new route 2023-07-23 22:56:26 -07:00
James Tombleson 19d8e3e925 ArticlesController.cs was cleaned up and supports results with details now 2023-07-23 22:56:06 -07:00
James Tombleson fa14562fd4 Updated namespaces, consts, startup and warnings are now errors in the api project 2023-07-23 22:55:20 -07:00
James Tombleson 2bc99afe63 Moved controllers to v1 namespace and updated routes 2023-07-23 22:54:00 -07:00
James Tombleson 5df2996947 Database project now has warnings converted to errors 2023-07-23 22:52:59 -07:00
James Tombleson 7b24ba16f7 moved the IdentityService.cs to services and warnings are now errors 2023-07-23 22:52:18 -07:00
James Tombleson d7242c12c8 Created a master config to match the appsettings 2023-07-23 22:51:22 -07:00
James Tombleson 9b86f9e84d Cleaned up the consts and moved the Authorization.cs to the correct namespace 2023-07-23 22:50:41 -07:00
James Tombleson 83203f967d Moved domain objects from the API to Domain 2023-07-23 22:50:03 -07:00
jtom38 5b8f0beb6e Merge pull request 'features/identity-roles' (#14) from features/identity-roles into main
continuous-integration/drone/push Build is passing Details
Reviewed-on: #14
2023-07-23 16:24:21 -07:00
James Tombleson 3697093df1 Moved AccountController.cs to IdentityController.cs and defined roles on a route
continuous-integration/drone/pr Build is passing Details
2023-07-23 16:22:49 -07:00
James Tombleson c4a84e9adc moved some rest actions over to roles 2023-07-23 16:22:11 -07:00
James Tombleson 64f167c0c5 added global.json to pin dotnet version 2023-07-23 16:21:00 -07:00
James Tombleson 16e63aa4a1 Added the Authorization.cs file to contain policy, claims and roles 2023-07-23 16:20:47 -07:00
James Tombleson b9c07eda7d Refactored how the start up works... everything in one file was getting very messy to read 2023-07-23 16:20:16 -07:00
James Tombleson 2bc20fccc8 docker-compose.example.yaml was updated
continuous-integration/drone/pr Build is failing Details
2023-07-14 22:25:53 -07:00
James Tombleson 71319c05ef Controllers have been updated to support Authorize 2023-07-14 22:25:44 -07:00
James Tombleson bc79b507ac Added a API Key Attribute but not used yet 2023-07-14 22:25:11 -07:00
James Tombleson d5278a8be9 Added consts to lookup things in config easier 2023-07-14 22:24:53 -07:00
James Tombleson 0aa6c1489d Adding roles into the Identity side 2023-07-14 22:24:32 -07:00
James Tombleson 712ce4f4da Added notes, db migrations on startup, and injecting admin and user roles 2023-07-14 22:23:45 -07:00
jtom38 5c06865b1f Merge pull request 'Getting things setup to support token refresh' (#13) from features/token-refresh into main
continuous-integration/drone/push Build is passing Details
Reviewed-on: #13
2023-07-14 22:11:07 -07:00
James Tombleson 558f9d352c Updating CI to not build latest on a pr.
continuous-integration/drone/pr Build is passing Details
2023-07-14 22:01:13 -07:00
James Tombleson 4cacc03fb8 Getting things setup to support token refresh
continuous-integration/drone/pr Build is failing Details
2023-07-10 22:41:39 -07:00
James Tombleson 226a4876ff Another change attempting to get main building
continuous-integration/drone/push Build is passing Details
2023-07-10 18:12:14 -07:00
jtom38 59d3306364 Merge pull request 'features/ci-update' (#12) from features/ci-update into main
Reviewed-on: #12
2023-07-10 18:06:54 -07:00
James Tombleson 788124dfa0 changed the name
continuous-integration/drone/pr Build is passing Details
2023-07-10 18:01:31 -07:00
James Tombleson 6af7fb75be Split out the jobs to attempt to get things to run correctly again. 2023-07-10 17:58:32 -07:00
jtom38 42258ec56f Merge pull request 'Migration to EF and starting to add Identity' (#11) from features/ef-identity-migration into main
Reviewed-on: #11
2023-07-09 22:18:59 -07:00
James Tombleson df348bea5a Test updated for ef
continuous-integration/drone/pr Build is passing Details
2023-07-09 22:12:41 -07:00
James Tombleson 008d79cf3d added try catch for when a site could be down or blocking requests 2023-07-09 22:12:13 -07:00
James Tombleson fd60906a20 Adding the auth layers, not 100% finished yet 2023-07-09 22:11:30 -07:00
James Tombleson d28ad7424f User defined source subscriptions have been added and starting to let users take ownership of discord Notifications 2023-07-09 22:10:51 -07:00
James Tombleson 597470fd69 Renamed the Discord Notification client to help with naming issues 2023-07-09 21:30:23 -07:00
James Tombleson 58229b1348 Moving the Subscriptions over to DiscordNotifications as this will be the automated posting compared to user feeds 2023-07-09 21:29:33 -07:00
James Tombleson 7b1407c2cb JwtSettings has been added to config 2023-07-06 22:26:49 -07:00
James Tombleson 9e2b44df7c Controllers have been updated with Authorize calls and tables getting loaded from DI 2023-07-06 22:26:19 -07:00
James Tombleson 3f4de34115 new identity service was added to assist with auth 2023-07-06 22:25:33 -07:00
James Tombleson 06218af516 Api domain items have been added but subject to change 2023-07-06 22:24:50 -07:00
James Tombleson dff5556e06 jwt package was added 2023-07-06 22:23:22 -07:00
James Tombleson f081a59229 startup was updated to inject ef, tables, jwt, and update swagger with bearer token auth 2023-07-06 22:22:55 -07:00
James Tombleson f388d642be Tables have been updated to support databaseContext or connection string passed in 2023-07-06 22:21:53 -07:00
James Tombleson 9d5c5903bd Migrations have been added with identity support 2023-07-06 22:21:26 -07:00
James Tombleson d15ae49ff8 Minor config update to add a new namespace 2023-07-06 22:20:13 -07:00
James Tombleson bfd185906c Context was updated to support identity 2023-07-06 22:19:40 -07:00
jtom38 c25cafb0a7 Merge pull request 'Should now build when a new tag is made' (#10) from features/ci/build-image-on-tag into main
Reviewed-on: #10
2023-06-25 22:39:30 -07:00
James Tombleson 1aeb96db5b Should now build when a new tag is made
continuous-integration/drone/pr Build is passing Details
2023-06-25 22:38:07 -07:00
106 changed files with 6228 additions and 795 deletions

View File

@ -1,10 +1,10 @@
---
kind: pipeline
type: docker
name: build
name: buildLatestImage
steps:
- name: build image
- name: buildLatestImage
image: plugins/docker
settings:
repo: jtom38/newsbot-collector
@ -15,15 +15,39 @@ trigger:
branch:
include:
- main
event:
exclude:
- pull_request
---
kind: pipeline
type: docker
name: buildReleaseImage
steps:
- name: buildReleaseImage
image: plugins/docker
settings:
repo: jtom38/newsbot-collector
username: jtom38
password:
from_secret: DockerPushPat
trigger:
branch:
include:
- releases/*
ref:
include:
- refs/tags/**
event:
exclude:
- pull_request
---
kind: pipeline
type: docker
name: compile
name: PullRequestCompileTest
steps:
- name: Compile project
image: mcr.microsoft.com/dotnet/sdk:7.0.103

View File

@ -1,58 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Newsbot.Collector.Database.Repositories;
using Newsbot.Collector.Domain.Dto;
using Newsbot.Collector.Domain.Interfaces;
using Newsbot.Collector.Domain.Models;
namespace Newsbot.Collector.Api.Controllers;
[ApiController]
[Route("api/articles")]
public class ArticlesController : ControllerBase
{
private readonly IArticlesRepository _articles;
private readonly ILogger<ArticlesController> _logger;
private readonly ISourcesRepository _sources;
public ArticlesController(ILogger<ArticlesController> logger, IOptions<ConnectionStrings> settings)
{
_logger = logger;
_articles = new ArticlesTable(settings.Value.Database);
_sources = new SourcesTable(settings.Value.Database);
}
[HttpGet(Name = "GetArticles")]
public IEnumerable<ArticleDto> Get()
{
var res = new List<ArticleDto>();
var items = _articles.List(0, 25);
foreach (var item in items) res.Add(ArticleDto.Convert(item));
return res;
}
[HttpGet("{id:guid}")]
[EndpointDescription("Returns the article based on the Id value given.")]
public ArticleDto GetById(Guid id)
{
var item = _articles.GetById(id);
return ArticleDto.Convert(item);
}
[HttpGet("{id:guid}/details")]
public ArticleDetailsDto GetDetailsById(Guid id)
{
var item = _articles.GetById(id);
var sourceItem = _sources.GetById(item.SourceId);
return ArticleDetailsDto.Convert(item, sourceItem);
}
[HttpGet("by/{sourceId:guid}")]
public IEnumerable<ArticleDto> GetBySourceId(Guid sourceId, int page = 0, int count = 25)
{
var res = new List<ArticleDto>();
var items = _articles.ListBySourceId(sourceId, page, count);
foreach (var item in items) res.Add(ArticleDto.Convert(item));
return res;
}
}

View File

@ -1,91 +0,0 @@
using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Newsbot.Collector.Database.Repositories;
using Newsbot.Collector.Domain.Dto;
using Newsbot.Collector.Domain.Entities;
using Newsbot.Collector.Domain.Interfaces;
using Newsbot.Collector.Domain.Models;
namespace Newsbot.Collector.Api.Controllers;
[ApiController]
[Route("api/discord/webhooks")]
public class DiscordWebHookController : ControllerBase
{
private readonly ILogger<DiscordWebHookController> _logger;
private readonly ConnectionStrings _settings;
private readonly IDiscordWebHooksRepository _webhooks;
public DiscordWebHookController(ILogger<DiscordWebHookController> logger, IOptions<ConnectionStrings> settings)
{
_logger = logger;
_settings = settings.Value;
_webhooks = new DiscordWebhooksTable(_settings.Database);
}
[HttpGet(Name = "GetDiscordWebhooks")]
public IEnumerable<DiscordWebHookDto> Get(int page)
{
var items = new List<DiscordWebHookDto>();
var res = _webhooks.List(page);
foreach (var item in res)
{
items.Add(DiscordWebHookDto.Convert(item));
}
return items;
}
[HttpPost(Name = "New")]
public DiscordWebHookDto New(string url, string server, string channel)
{
var exists = _webhooks.GetByUrl(url);
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (exists.Id != Guid.Empty)
{
return DiscordWebHookDto.Convert(exists);
}
var res = _webhooks.New(new DiscordWebhookEntity
{
Url = url,
Server = server,
Channel = channel,
Enabled = true,
});
return DiscordWebHookDto.Convert(res);
}
[HttpGet("by/serverAndChannel")]
public IEnumerable<DiscordWebHookDto> GetByServerAndChannel(string server, string channel)
{
var items = new List<DiscordWebHookDto>();
var res = _webhooks.ListByServerAndChannel(server, channel, 25);
foreach (var item in res)
{
items.Add(DiscordWebHookDto.Convert(item));
}
return items;
}
[HttpGet("{id}")]
public DiscordWebHookDto GetById(Guid id)
{
var res = _webhooks.GetById(id);
return DiscordWebHookDto.Convert(res);
}
[HttpPost("{id}/disable")]
public void DisableById(Guid id)
{
_webhooks.Disable(id);
}
[HttpPost("{id}/enable")]
public void EnableById(Guid id)
{
_webhooks.Enable(id);
}
}

View File

@ -1,130 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Newsbot.Collector.Database.Repositories;
using Newsbot.Collector.Domain.Dto;
using Newsbot.Collector.Domain.Entities;
using Newsbot.Collector.Domain.Interfaces;
using Newsbot.Collector.Domain.Models;
namespace Newsbot.Collector.Api.Controllers;
[ApiController]
[Route("api/subscriptions")]
public class SubscriptionsController : ControllerBase
{
private readonly IDiscordWebHooksRepository _discord;
private readonly ILogger<SubscriptionsController> _logger;
private readonly ISourcesRepository _sources;
private readonly ISubscriptionRepository _subscription;
public SubscriptionsController(ILogger<SubscriptionsController> logger, IOptions<ConnectionStrings> settings)
{
_logger = logger;
_subscription = new SubscriptionsTable(settings.Value.Database);
_discord = new DiscordWebhooksTable(settings.Value.Database);
_sources = new SourcesTable(settings.Value.Database);
}
[HttpGet(Name = "ListSubscriptions")]
public IEnumerable<SubscriptionDto> List(int page)
{
var res = new List<SubscriptionDto>();
var items = _subscription.List(page);
foreach (var item in items) res.Add(SubscriptionDto.Convert(item));
return res;
}
[HttpGet("{id}")]
public SubscriptionDto GetById(Guid id)
{
return SubscriptionDto.Convert(_subscription.GetById(id));
}
[HttpGet("{id}/details")]
public SubscriptionDetailsDto GetDetailsById(Guid id)
{
var sub = _subscription.GetById(id);
var webhook = _discord.GetById(sub.DiscordWebHookId);
var source = _sources.GetById(sub.SourceId);
return SubscriptionDetailsDto.Convert(sub, source, webhook);
}
[HttpPost("{id}/delete")]
public void DeleteById(Guid id)
{
_subscription.Delete(id);
}
[HttpGet("by/discordid/{id}")]
public IEnumerable<SubscriptionDto> GetByDiscordId(Guid id)
{
var res = new List<SubscriptionDto>();
var items = _subscription.ListByWebhook(id);
foreach (var item in items) res.Add(SubscriptionDto.Convert(item));
return res;
}
[HttpGet("by/sourceId/{id}")]
public IEnumerable<SubscriptionDto> GetBySourceId(Guid id)
{
var res = new List<SubscriptionDto>();
var items = _subscription.ListBySourceId(id);
foreach (var item in items) res.Add(SubscriptionDto.Convert(item));
return res;
}
[HttpPost(Name = "New Subscription")]
public ActionResult<SubscriptionDto> New(Guid sourceId, Guid discordId)
{
if (sourceId == Guid.Empty) return new BadRequestResult();
if (discordId == Guid.Empty) return new BadRequestResult();
var exists = _subscription.GetByWebhookAndSource(discordId, sourceId);
if (exists.Id != Guid.Empty) return SubscriptionDto.Convert(exists);
var discord = _discord.GetById(discordId);
if (discord.Id == Guid.Empty) return new BadRequestResult();
var source = _sources.GetById(sourceId);
if (source.Id == Guid.Empty) return new BadRequestResult();
var item = _subscription.New(new SubscriptionEntity
{
Id = Guid.NewGuid(),
SourceId = sourceId,
DiscordWebHookId = discordId,
CodeAllowCommits = false,
CodeAllowReleases = false
});
return SubscriptionDto.Convert(item);
}
[HttpPost("new/codeproject")]
public ActionResult<SubscriptionDto> NewCodeProjectSubscription(Guid sourceId, Guid discordId, bool allowReleases,
bool allowCommits)
{
if (sourceId == Guid.Empty) return new BadRequestResult();
if (discordId == Guid.Empty) return new BadRequestResult();
var exists = _subscription.GetByWebhookAndSource(discordId, sourceId);
if (exists.Id != Guid.Empty) return SubscriptionDto.Convert(exists);
var discord = _discord.GetById(discordId);
if (discord.Id == Guid.Empty) return new BadRequestResult();
var source = _sources.GetById(sourceId);
if (source.Id == Guid.Empty) return new BadRequestResult();
var sub = _subscription.New(new SubscriptionEntity
{
DiscordWebHookId = discordId,
SourceId = sourceId,
CodeAllowCommits = allowCommits,
CodeAllowReleases = allowReleases
});
return new SubscriptionDto();
}
}

View File

@ -0,0 +1,89 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Newsbot.Collector.Domain.Dto;
using Newsbot.Collector.Domain.Entities;
using Newsbot.Collector.Domain.Interfaces;
using Newsbot.Collector.Domain.Results;
namespace Newsbot.Collector.Api.Controllers.v1;
[ApiController]
[Route("api/v1/articles")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class ArticlesController : ControllerBase
{
//private readonly ILogger<ArticlesController> _logger;
private readonly IArticlesRepository _articles;
private readonly ISourcesRepository _sources;
private readonly IAuthorTable _author;
public ArticlesController(IArticlesRepository articles, ISourcesRepository sources, IAuthorTable author)
{
_articles = articles;
_sources = sources;
_author = author;
}
[HttpGet(Name = "GetArticles")]
public ActionResult<ArticleResult> Get()
{
var res = new List<ArticleDto>();
var items = _articles.List(0);
foreach (var item in items)
{
res.Add(ArticleDto.Convert(item));
}
return new OkObjectResult(new ArticleResult
{
IsSuccessful = true,
Items = res
});
}
[HttpGet("{id:guid}")]
[EndpointDescription("Returns the article based on the Id value given.")]
public ActionResult<ArticleResult> GetById(Guid id)
{
var item = _articles.GetById(id);
return new OkObjectResult(new ArticleResult
{
IsSuccessful = true,
Items = new List<ArticleDto>
{
ArticleDto.Convert(item)
}
});
}
[HttpGet("{id:guid}/details")]
public ActionResult<ArticleDetailsResult> GetDetailsById(Guid id)
{
var item = _articles.GetById(id);
var sourceItem = _sources.GetById(item.SourceId);
var author = _author.GetBySourceIdAndNameAsync(sourceItem.Id, sourceItem.Name);
author.Wait();
return new OkObjectResult(new ArticleDetailsResult
{
IsSuccessful = true,
Item = ArticleDetailsDto.Convert(item, sourceItem, author.Result)
});
}
[HttpGet("by/{sourceId:guid}")]
public ActionResult<ArticleResult> GetBySourceId(Guid sourceId, int page = 0)
{
var res = new List<ArticleDto>();
var items = _articles.ListBySourceId(sourceId, page);
foreach (var item in items) res.Add(ArticleDto.Convert(item));
return new OkObjectResult(new ArticleResult
{
IsSuccessful = true,
Items = res
});
}
}

View File

@ -1,13 +1,17 @@
using Hangfire;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Newsbot.Collector.Api.Domain.Consts;
using Newsbot.Collector.Domain.Models.Config;
using Newsbot.Collector.Services.Jobs;
namespace Newsbot.Collector.Api.Controllers;
namespace Newsbot.Collector.Api.Controllers.v1;
[ApiController]
[Route("api/codeprojects")]
[Route("api/v1/codeprojects")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class CodeProjectController
{
private readonly ConfigSectionConnectionStrings _connectionStrings;
@ -21,7 +25,8 @@ public class CodeProjectController
}
[HttpPost("check")]
public void PullNow()
[Authorize(Roles = Authorization.AdministratorsRole)]
public ActionResult PullNow()
{
BackgroundJob.Enqueue<CodeProjectWatcherJob>(x => x.InitAndExecute(new CodeProjectWatcherJobOptions
{
@ -29,5 +34,7 @@ public class CodeProjectController
FeaturePullReleases = true,
FeaturePullCommits = true
}));
return new AcceptedResult();
}
}

View File

@ -0,0 +1,239 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Newsbot.Collector.Api.Middleware;
using Newsbot.Collector.Domain.Dto;
using Newsbot.Collector.Domain.Entities;
using Newsbot.Collector.Domain.Interfaces;
using Newsbot.Collector.Domain.Requests;
using Newsbot.Collector.Domain.Results;
namespace Newsbot.Collector.Api.Controllers.v1;
[ApiController]
[Route("api/v1/subscriptions")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class DiscordNotificationController : ControllerBase
{
private readonly ILogger<DiscordNotificationController> _logger;
private readonly IDiscordWebHooksRepository _discord;
private readonly ISourcesRepository _sources;
private readonly IDiscordNotificationRepository _discordNotification;
public DiscordNotificationController(ILogger<DiscordNotificationController> logger, IDiscordWebHooksRepository discord, ISourcesRepository sources, IDiscordNotificationRepository discordNotification)
{
_logger = logger;
_discord = discord;
_sources = sources;
_discordNotification = discordNotification;
}
[HttpGet(Name = "ListSubscriptions")]
public ActionResult<DiscordNotificationResult> List(int page)
{
var userId = HttpContext.GetUserId();
if (userId.Equals(string.Empty))
{
return new BadRequestObjectResult(new DiscordNotificationResult
{
IsSuccessful = false,
ErrorMessage = new List<string> { "User Id is missing from the request." }
});
}
var res = new List<DiscordNotificationDto>();
var items = _discordNotification.List(userId, page);
foreach (var item in items) res.Add(DiscordNotificationDto.Convert(item));
return new OkObjectResult(new DiscordNotificationResult
{
IsSuccessful = true,
Items = res
});
}
[HttpGet("{id}")]
public ActionResult<DiscordNotificationResult> GetById(Guid id)
{
var userId = HttpContext.GetUserId();
if (userId.Equals(string.Empty))
{
return new BadRequestObjectResult(new DiscordNotificationResult
{
IsSuccessful = false,
ErrorMessage = new List<string> { "User Id is missing from the request." }
});
}
var res = DiscordNotificationDto.Convert(_discordNotification.GetById(userId, id));
return new OkObjectResult(new DiscordNotificationResult
{
IsSuccessful = true,
Items = new List<DiscordNotificationDto>
{
res
}
});
}
[HttpGet("{id}/details")]
public ActionResult<DiscordNotificationDetailsResult> GetDetailsById(Guid id)
{
var userId = HttpContext.GetUserId();
if (userId.Equals(string.Empty))
{
return new BadRequestObjectResult(new DiscordNotificationResult
{
IsSuccessful = false,
ErrorMessage = new List<string> { "User Id is missing from the request." }
});
}
var sub = _discordNotification.GetById(userId, id);
var webhook = _discord.GetById(userId, sub.DiscordWebHookId);
var source = _sources.GetById(sub.SourceId);
return new OkObjectResult(new DiscordNotificationDetailsResult
{
IsSuccessful = true,
Item = DiscordNotificationDetailsDto.Convert(sub, source, webhook)
});
}
[HttpPost("{id}/delete")]
public ActionResult<DiscordNotificationResult> DeleteById(Guid id)
{
var userId = HttpContext.GetUserId();
if (userId.Equals(string.Empty))
{
return new BadRequestObjectResult(new DiscordNotificationResult
{
IsSuccessful = false,
ErrorMessage = new List<string> { "User Id is missing from the request." }
});
}
var rowsUpdated = _discordNotification.Delete(userId, id);
if (rowsUpdated == -1)
{
return new OkObjectResult(new DiscordNotificationResult
{
IsSuccessful = false,
ErrorMessage = new List<string> { "Record was not own by requested user." }
});
}
return new OkObjectResult(new DiscordNotificationResult
{
IsSuccessful = true
});
}
[HttpGet("by/discordId/{id}")]
public ActionResult<DiscordNotificationResult> GetByDiscordId(Guid id)
{
var userId = HttpContext.GetUserId();
if (userId.Equals(string.Empty))
{
return new BadRequestObjectResult(new DiscordNotificationResult
{
IsSuccessful = false,
ErrorMessage = new List<string> { "User Id is missing from the request." }
});
}
var res = new List<DiscordNotificationDto>();
var items = _discordNotification.ListByWebhook(userId, id);
foreach (var item in items) res.Add(DiscordNotificationDto.Convert(item));
return new OkObjectResult(new DiscordNotificationResult
{
IsSuccessful = true,
Items = res
});
}
[HttpGet("by/sourceId/{id}")]
public ActionResult<DiscordNotificationResult> GetBySourceId(Guid id)
{
var userId = HttpContext.GetUserId();
if (userId.Equals(string.Empty))
{
return new BadRequestObjectResult(new DiscordNotificationResult
{
IsSuccessful = false,
ErrorMessage = new List<string> { "User Id is missing from the request." }
});
}
var res = new List<DiscordNotificationDto>();
var items = _discordNotification.ListBySourceId(userId, id);
foreach (var item in items) res.Add(DiscordNotificationDto.Convert(item));
return new OkObjectResult(new DiscordNotificationResult
{
IsSuccessful = true,
Items = res
});
}
[HttpPost(Name = "New Subscription")]
public ActionResult<DiscordNotificationDto> New([FromBody] NewDiscordNotificationRequest request)
{
var userId = HttpContext.GetUserId();
if (userId.Equals(string.Empty))
{
return new BadRequestObjectResult(new DiscordNotificationResult
{
IsSuccessful = false,
ErrorMessage = new List<string> { "User Id is missing from the request." }
});
}
if (request.SourceId == Guid.Empty) return new BadRequestObjectResult(new DiscordNotificationResult
{
IsSuccessful = false,
ErrorMessage = new List<string> { "SourceId is missing from the request." }
});
if (request.DiscordId == Guid.Empty) return new BadRequestObjectResult(new DiscordNotificationResult
{
IsSuccessful = false,
ErrorMessage = new List<string> { "DiscordId is missing from the request." }
});
var exists = _discordNotification.GetByWebhookAndSource(userId, request.DiscordId, request.SourceId);
if (exists.Id != Guid.Empty) return DiscordNotificationDto.Convert(exists);
var discord = _discord.GetById(userId, request.DiscordId);
if (discord.Id == Guid.Empty) return new BadRequestObjectResult(new DiscordNotificationResult
{
IsSuccessful = false,
ErrorMessage = new List<string> { "Unable to find the requested DiscordId in the database." }
});
var source = _sources.GetById(request.SourceId);
if (source.Id == Guid.Empty) return new BadRequestObjectResult(new DiscordNotificationResult
{
IsSuccessful = false,
ErrorMessage = new List<string> { "Unable to find the requested SourceId in the database." }
});
var item = _discordNotification.New(new DiscordNotificationEntity
{
Id = Guid.NewGuid(),
SourceId = request.SourceId,
DiscordWebHookId = request.DiscordId,
CodeAllowCommits = request.AllowCommits,
CodeAllowReleases = request.AllowReleases,
UserId = userId
});
return new OkObjectResult(new DiscordNotificationResult
{
IsSuccessful = true,
Items = new List<DiscordNotificationDto> { DiscordNotificationDto.Convert(item) }
});
}
}

View File

@ -0,0 +1,114 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Newsbot.Collector.Api.Middleware;
using Newsbot.Collector.Domain.Dto;
using Newsbot.Collector.Domain.Entities;
using Newsbot.Collector.Domain.Interfaces;
using Newsbot.Collector.Domain.Models;
using Newsbot.Collector.Domain.Requests;
using Newsbot.Collector.Domain.Results;
namespace Newsbot.Collector.Api.Controllers.v1;
[ApiController]
[Route("api/v1/discord/webhooks")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class DiscordWebHookController : ControllerBase
{
private readonly ILogger<DiscordWebHookController> _logger;
private readonly IDiscordWebHooksRepository _webhooks;
public DiscordWebHookController(ILogger<DiscordWebHookController> logger, IOptions<ConnectionStrings> settings, IDiscordWebHooksRepository webhooks)
{
_logger = logger;
_webhooks = webhooks;
}
[HttpGet(Name = "GetDiscordWebhooks")]
public ActionResult<IEnumerable<DiscordWebHookDto>> Get(int page)
{
var userId = HttpContext.GetUserId();
if (userId.Equals(string.Empty))
{
_logger.LogWarning("Unable to find the user ID in the JWD Token");
return new BadRequestResult();
}
var items = new List<DiscordWebHookDto>();
var res = _webhooks.ListByUserId(userId, page);
foreach (var item in res)
{
items.Add(DiscordWebHookDto.Convert(item));
}
return new OkObjectResult(items);
}
[HttpPost(Name = "New")]
public ActionResult<DiscordWebhookResult> New([FromBody] NewDiscordWebhookRequest request)
{
var userId = HttpContext.GetUserId();
var exists = _webhooks.GetByUrl(request.Url ?? "");
if (exists.Id != Guid.Empty)
{
return new BadRequestObjectResult(new DiscordWebhookResult
{
IsSuccessful = true,
Items = new List<DiscordWebHookDto> { DiscordWebHookDto.Convert(exists) }
});
}
var res = _webhooks.New(new DiscordWebhookEntity
{
UserId = userId,
Url = request.Url ?? "",
Server = request.Server ?? "",
Channel = request.Channel ?? "",
Enabled = true,
});
return new OkObjectResult(new DiscordWebhookResult
{
IsSuccessful = true,
Items = new List<DiscordWebHookDto>
{
DiscordWebHookDto.Convert(res)
}
});
}
[HttpGet("by/serverAndChannel")]
public IEnumerable<DiscordWebHookDto> GetByServerAndChannel(string server, string channel)
{
var items = new List<DiscordWebHookDto>();
var res = _webhooks.ListByServerAndChannel(HttpContext.GetUserId(), server, channel, 25);
foreach (var item in res)
{
items.Add(DiscordWebHookDto.Convert(item));
}
return items;
}
[HttpGet("{id}")]
public DiscordWebHookDto GetById(string userId, Guid id)
{
var res = _webhooks.GetById(userId, id);
return DiscordWebHookDto.Convert(res);
}
[HttpPost("{id}/disable")]
public void DisableById(Guid id)
{
_webhooks.Disable(HttpContext.GetUserId(), id);
}
[HttpPost("{id}/enable")]
public void EnableById(Guid id)
{
_webhooks.Enable(HttpContext.GetUserId(), id);
}
}

View File

@ -0,0 +1,107 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Newsbot.Collector.Api.Domain.Consts;
using Newsbot.Collector.Domain.Requests;
using Newsbot.Collector.Domain.Response;
using Newsbot.Collector.Domain.Results;
using Newsbot.Collector.Services;
namespace Newsbot.Collector.Api.Controllers.v1;
[ApiController]
[Route("/api/v1/account")]
public class IdentityController : ControllerBase
{
private readonly ILogger<IdentityController> _logger;
private readonly IIdentityService _identityService;
public IdentityController(IIdentityService identityService, ILogger<IdentityController> logger)
{
_identityService = identityService;
_logger = logger;
}
[HttpPost("register")]
public IActionResult Register([FromBody] RegisterUserRequest user)
{
if (!ModelState.IsValid)
{
return new BadRequestObjectResult(new AuthFailedResponse
{
Errors = ModelState.Values
.Select(x => x.Errors
.Select(y => y.ErrorMessage).FirstOrDefault())
});
}
if (user.Email is null)
{
return new BadRequestResult();
}
if (user.Password is null)
{
return new BadRequestResult();
}
var response = _identityService.Register(user.Email, user.Password);
return CheckIfSuccessful(response);
}
[HttpPost("login")]
public IActionResult Login([FromBody] UserLoginRequest request)
{
if (request.Email is null)
{
return new BadRequestResult();
}
if (request.Password is null)
{
return new BadRequestResult();
}
var response = _identityService.Login(request.Email, request.Password);
return CheckIfSuccessful(response);
}
[HttpPost("refresh")]
public ActionResult RefreshToken([FromBody] UserRefreshTokenRequest request)
{
var response = _identityService.RefreshToken(request.Token ?? "", request.RefreshToken ?? "");
return CheckIfSuccessful(response);
}
[HttpPost("addRole")]
[Authorize(Roles = Authorization.AdministratorsRole)]
public ActionResult AddRole([FromBody] AddRoleRequest request)
{
try
{
_identityService.AddRole(request.RoleName ?? "", request.UserId ?? "");
return new OkResult();
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to add role to user");
return new BadRequestResult();
}
}
private ActionResult CheckIfSuccessful(AuthenticationResult result)
{
if (!result.IsSuccessful)
{
return new BadRequestObjectResult( new AuthFailedResponse
{
Errors = result.ErrorMessage
});
}
return new OkObjectResult(new AuthSuccessfulResponse
{
Token = result.Token,
RefreshToken = result.RefreshToken
});
}
}

View File

@ -1,13 +1,18 @@
using Hangfire;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Newsbot.Collector.Api.Domain.Consts;
using Newsbot.Collector.Domain.Models.Config;
using Newsbot.Collector.Domain.Models.Config.Sources;
using Newsbot.Collector.Services.Jobs;
namespace Newsbot.Collector.Api.Controllers;
namespace Newsbot.Collector.Api.Controllers.v1;
[ApiController]
[Route("api/rss")]
[Route("api/v1/rss")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class RssController
{
private readonly ConfigSectionConnectionStrings _connectionStrings;
@ -23,6 +28,7 @@ public class RssController
}
[HttpPost("check")]
[Authorize(Roles = Authorization.AdministratorsRole)]
public void CheckReddit()
{
BackgroundJob.Enqueue<RssWatcherJob>(x => x.InitAndExecute(new RssWatcherJobOptions

View File

@ -1,33 +1,29 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Newsbot.Collector.Database.Repositories;
using Newsbot.Collector.Api.Domain.Consts;
using Newsbot.Collector.Domain.Consts;
using Newsbot.Collector.Domain.Dto;
using Newsbot.Collector.Domain.Entities;
using Newsbot.Collector.Domain.Interfaces;
using Newsbot.Collector.Domain.Models;
using Newsbot.Collector.Services.HtmlParser;
namespace Newsbot.Collector.Api.Controllers;
namespace Newsbot.Collector.Api.Controllers.v1;
[ApiController]
[Route("api/sources")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class SourcesController : ControllerBase
{
private readonly IArticlesRepository _articles;
//private readonly ConnectionStrings _settings;
private readonly IIconsRepository _icons;
private readonly ILogger<SourcesController> _logger;
private readonly IIconsRepository _icons;
private readonly ISourcesRepository _sources;
public SourcesController(ILogger<SourcesController> logger, IOptions<ConnectionStrings> settings)
public SourcesController(ILogger<SourcesController> logger, IIconsRepository icons, ISourcesRepository sources)
{
_logger = logger;
//_settings = settings.Value;
_sources = new SourcesTable(settings.Value.Database);
_icons = new IconsTable(settings.Value.Database);
_articles = new ArticlesTable(settings.Value.Database);
_icons = icons;
_sources = sources;
}
[HttpGet(Name = "GetSources")]
@ -201,12 +197,14 @@ public class SourcesController : ControllerBase
return SourceDto.Convert(item);
}
[Authorize(Roles = Authorization.AdministratorsRole)]
[HttpPost("{id}/disable")]
public void Disable(Guid id)
{
_sources.Disable(id);
}
[Authorize(Roles = Authorization.AdministratorsRole)]
[HttpPost("{id}/enable")]
public void Enable(Guid id)
{
@ -214,6 +212,7 @@ public class SourcesController : ControllerBase
}
[HttpDelete("{id}")]
[Authorize(Roles = Authorization.AdministratorsRole)]
public void Delete(Guid id, bool purgeOrphanedRecords)
{
_sources.Delete(id);

View File

@ -0,0 +1,49 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Newsbot.Collector.Api.Authentication;
using Newsbot.Collector.Api.Middleware;
using Newsbot.Collector.Domain.Entities;
using Newsbot.Collector.Domain.Interfaces;
namespace Newsbot.Collector.Api.Controllers.v1;
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[ApiController]
[Route("api/v1/user")]
public class UserController : Controller
{
private readonly ILogger<UserController> _logger;
private readonly IUserSourceSubscription _subscription;
public UserController(ILogger<UserController> logger, IUserSourceSubscription subscription)
{
_logger = logger;
_subscription = subscription;
}
[HttpPost("listSubscriptions")]
public ActionResult ListSubscriptions()
{
_logger.LogInformation("'/api/v1/user/listSubscriptions' was requested");
var userId = HttpContext.GetUserId();
if (userId.Equals(string.Empty))
{
_logger.LogWarning("Unable to find the user ID in the JWD Token");
return new BadRequestResult();
}
try
{
var results = _subscription.ListUserSubscriptions(Guid.Parse(userId));
return new OkObjectResult(results);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to pull subscriptions for userId \'{UserId}\'", userId);
return new NoContentResult();
}
}
}

View File

@ -1,14 +1,18 @@
using Hangfire;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Newsbot.Collector.Api.Domain.Consts;
using Newsbot.Collector.Domain.Models.Config;
using Newsbot.Collector.Domain.Models.Config.Sources;
using Newsbot.Collector.Services.Jobs;
using ILogger = Grpc.Core.Logging.ILogger;
namespace Newsbot.Collector.Api.Controllers;
namespace Newsbot.Collector.Api.Controllers.v1;
[ApiController]
[Route("api/youtube")]
[Route("api/v1/youtube")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class YoutubeController
{
private readonly ILogger<YoutubeController> _logger;
@ -23,6 +27,7 @@ public class YoutubeController
}
[HttpPost("check")]
[Authorize(Policy = Authorization.AdministratorsRole)]
public void CheckYoutube()
{
BackgroundJob.Enqueue<YoutubeWatcherJob>(x => x.InitAndExecute(new YoutubeWatcherJobOptions

View File

@ -0,0 +1,32 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Newsbot.Collector.Domain.Consts;
namespace Newsbot.Collector.Api.Filters;
[AttributeUsage(AttributeTargets.Class| AttributeTargets.Method)]
public class ApiKeyAuthAttribute : Attribute, IAsyncActionFilter
{
private const string ApiKeyHeaderName = "X-API-KEY";
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!context.HttpContext.Request.Headers.TryGetValue(ApiKeyHeaderName, out var foundKey))
{
context.Result = new BadRequestResult();
return;
}
var config = context.HttpContext.RequestServices.GetRequiredService<IConfiguration>();
var apiKeys = config.GetValue<string[]>(ConfigConst.ApiKeys);
foreach (var key in apiKeys ?? Array.Empty<string?>())
{
if (key != foundKey) continue;
await next();
return;
}
context.Result = new BadRequestResult();
}
}

View File

@ -0,0 +1,9 @@
namespace Newsbot.Collector.Api.Middleware;
public static class JwtUserIdExtension
{
public static string GetUserId(this HttpContext context)
{
return context.User.Claims.Single(x => x.Type == "id").Value;
}
}

View File

@ -4,6 +4,8 @@
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
@ -13,6 +15,7 @@
<PackageReference Include="AspNetCore.HealthChecks.UI.Core" Version="6.0.5" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.7.33" />
<PackageReference Include="Hangfire.MemoryStorage" Version="1.7.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.8" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.8">

View File

@ -2,15 +2,12 @@ using Hangfire;
using Hangfire.MemoryStorage;
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.OpenApi.Models;
using Newsbot.Collector.Api;
using Newsbot.Collector.Api.Authentication;
using Newsbot.Collector.Database;
using Newsbot.Collector.Api.Startup;
using Newsbot.Collector.Domain.Consts;
using Newsbot.Collector.Domain.Models;
using Newsbot.Collector.Domain.Models.Config;
using Newsbot.Collector.Domain.Models.Config.Sources;
using Serilog;
using Serilog.Events;
using ILogger = Serilog.ILogger;
var builder = WebApplication.CreateBuilder(args);
@ -23,61 +20,41 @@ var config = GetConfiguration();
builder.Configuration.AddConfiguration(config);
Log.Logger = GetLogger(config);
Log.Information("Starting up");
// configure Entity Framework
// Allow the controllers to access all the table repositories based on the interface
DatabaseStartup.BuildDatabase(builder.Services, config);
DatabaseStartup.InjectTableClasses(builder.Services);
DatabaseStartup.InjectIdentityService(builder.Services);
// Configure Hangfire
builder.Services.AddHangfire(f => f.UseMemoryStorage());
builder.Services.AddHangfireServer();
GlobalConfiguration.Configuration.UseSerilogLogProvider();
// Build Health Checks
builder.Services.AddHealthChecks()
.AddNpgSql(config.GetValue<string>(ConfigConnectionStringConst.Database) ?? "");
.AddNpgSql(config.GetValue<string>(ConfigConst.ConnectionStringDatabase) ?? "");
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
SwaggerStartup.ConfigureSwagger(builder.Services);
builder.Services.Configure<ConnectionStrings>(config.GetSection("ConnectionStrings"));
builder.Services.Configure<ConfigSectionConnectionStrings>(config.GetSection(ConfigSectionsConst.ConnectionStrings));
builder.Services.Configure<ConfigSectionRssModel>(config.GetSection(ConfigSectionsConst.Rss));
builder.Services.Configure<ConfigSectionYoutubeModel>(config.GetSection(ConfigSectionsConst.Youtube));
//builder.Services.Configure<
builder.Services.AddSwaggerGen(cfg =>
{
cfg.AddSecurityDefinition("ApiKey", new OpenApiSecurityScheme
{
Description = "The API key to access the API",
Type = SecuritySchemeType.ApiKey,
Name = "x-api-key",
In = ParameterLocation.Header,
Scheme = "ApiKeyScheme"
});
var scheme = new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "ApiKey"
},
In = ParameterLocation.Header
};
var requirement = new OpenApiSecurityRequirement
{
{ scheme, new List<string>() }
};
cfg.AddSecurityRequirement(requirement);
});
builder.Services.AddDbContext<DatabaseContext>();
IdentityStartup.DefineJwtRequirements(builder.Services, config);
var app = builder.Build();
// Configure the HTTP request pipeline.
if (config.GetValue<bool>("EnableSwagger"))
// Enable Swagger if requested based on config
if (config.GetValue<bool>(ConfigConst.EnableSwagger))
{
app.UseSwagger();
app.UseSwaggerUI();
@ -85,13 +62,17 @@ if (config.GetValue<bool>("EnableSwagger"))
app.UseHttpsRedirection();
// Enable Hangfire background jobs
app.UseHangfireDashboard();
BackgroundJobs.SetupRecurringJobs(config);
app.UseAuthorization();
app.UseAuthentication();
app.UseMiddleware<ApiKeyAuthAuthentication>();
// Add middleware
//app.UseMiddleware<ApiKeyAuthAuthentication>();
// Add HealthChecks
app.MapHealthChecks("/health", new HealthCheckOptions
{
Predicate = _ => true,
@ -99,6 +80,21 @@ app.MapHealthChecks("/health", new HealthCheckOptions
});
app.MapControllers();
// Run Database Migrations if requested
using var serviceScope = app.Services.CreateScope();
if (config.GetValue<bool>(ConfigConst.RunDatabaseMigrationsOnStartup))
{
await DatabaseStartup.RunDatabaseMigrationsAsync(serviceScope);
}
else
{
Log.Warning("Database Migrations have been skipped. Make sure you run them on your own");
}
// Inject the roles
await DatabaseStartup.InjectIdentityRolesAsync(serviceScope);
// Start the application
app.Run();
@ -113,7 +109,7 @@ static IConfiguration GetConfiguration()
static ILogger GetLogger(IConfiguration configuration)
{
var otel = configuration.GetValue<string>(ConfigConnectionStringConst.OpenTelemetry) ?? "";
var otel = configuration.GetValue<string>(ConfigConst.ConnectionStringOpenTelemetry) ?? "";
if (otel == "")
return Log.Logger = new LoggerConfiguration()
@ -132,4 +128,4 @@ static ILogger GetLogger(IConfiguration configuration)
{ "service.name", "newsbot-collector-api" }
})
.CreateLogger();
}
}

View File

@ -3,7 +3,7 @@ using Newsbot.Collector.Domain.Consts;
using Newsbot.Collector.Domain.Models.Config;
using Newsbot.Collector.Services.Jobs;
namespace Newsbot.Collector.Api;
namespace Newsbot.Collector.Api.Startup;
public static class BackgroundJobs
{

View File

@ -0,0 +1,62 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Newsbot.Collector.Api.Domain.Consts;
using Newsbot.Collector.Database;
using Newsbot.Collector.Database.Repositories;
using Newsbot.Collector.Domain.Interfaces;
using Newsbot.Collector.Services;
namespace Newsbot.Collector.Api.Startup;
public class DatabaseStartup
{
public static void BuildDatabase(IServiceCollection services, IConfiguration config)
{
var dbconn = config.GetConnectionString("Database");
services.AddDbContext<DatabaseContext>(o => o.UseNpgsql(dbconn ?? ""));
// Add identity to our ef connection
services.AddIdentity<IdentityUser, IdentityRole>()
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<DatabaseContext>();
}
public static void InjectTableClasses(IServiceCollection services)
{
services.AddScoped<IArticlesRepository, ArticlesTable>();
services.AddScoped<IDiscordQueueRepository, DiscordQueueTable>();
services.AddScoped<IDiscordWebHooksRepository, DiscordWebhooksTable>();
services.AddScoped<IIconsRepository, IconsTable>();
services.AddScoped<ISourcesRepository, SourcesTable>();
services.AddScoped<IDiscordNotificationRepository, DiscordNotificationTable>();
services.AddScoped<IUserSourceSubscription, UserSourceSubscriptionTable>();
services.AddScoped<IRefreshTokenRepository, RefreshTokenTable>();
services.AddScoped<IAuthorTable, AuthorsTable>();
}
public static void InjectIdentityService(IServiceCollection services)
{
// Configure Identity
services.AddScoped<IIdentityService, IdentityService>();
}
public static async Task RunDatabaseMigrationsAsync(IServiceScope serviceScope)
{
var dbContext = serviceScope.ServiceProvider.GetRequiredService<DatabaseContext>();
await dbContext.Database.MigrateAsync();
}
public static async Task InjectIdentityRolesAsync(IServiceScope serviceScope)
{
var roleManager = serviceScope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
if (!await roleManager.RoleExistsAsync(Authorization.AdministratorsRole))
{
await roleManager.CreateAsync(new IdentityRole(Authorization.AdministratorsRole));
}
if (!await roleManager.RoleExistsAsync(Authorization.UsersRole))
{
await roleManager.CreateAsync(new IdentityRole(Authorization.UsersRole));
}
}
}

View File

@ -0,0 +1,52 @@
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Newsbot.Collector.Api.Domain.Consts;
using Newsbot.Collector.Domain.Models.Config;
namespace Newsbot.Collector.Api.Startup;
public static class IdentityStartup
{
public static void DefineJwtRequirements(IServiceCollection services, IConfiguration config)
{
// Configure JWT for auth and load it into DI so we can use it in the controllers
var jwtSettings = new JwtSettings();
config.Bind(nameof(jwtSettings), jwtSettings);
services.AddSingleton(jwtSettings);
// Configure how the Token Validation will be handled
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtSettings.Secret ?? "")),
ValidateIssuer = false,
ValidateAudience = false,
RequireExpirationTime = false,
ValidateLifetime = true
};
services.AddSingleton(tokenValidationParameters);
// Build the Authentication that will be used
services.AddAuthentication(x =>
{
x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
x.SaveToken = true;
x.TokenValidationParameters = tokenValidationParameters;
});
// Build the Authorization Policy that the users will conform to.
services.AddAuthorization(options =>
{
options.AddPolicy(Authorization.AdministratorPolicy,
b => b.RequireRole(Authorization.AdministratorsRole, "true"));
options.AddPolicy(Authorization.UserPolicy,
b => b.RequireRole(Authorization.UsersRole, "true"));
});
}
}

View File

@ -0,0 +1,67 @@
using Microsoft.OpenApi.Models;
namespace Newsbot.Collector.Api.Startup;
public static class SwaggerStartup
{
public static void ConfigureSwagger(IServiceCollection services)
{
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
services.AddEndpointsApiExplorer();
services.AddSwaggerGen();
services.AddEndpointsApiExplorer();
services.AddSwaggerGen();
// Configure swagger authentication
services.AddSwaggerGen(cfg =>
{
cfg.AddSecurityDefinition("ApiKey", new OpenApiSecurityScheme
{
Description = "The API key to access the API",
Type = SecuritySchemeType.ApiKey,
Name = "x-api-key",
In = ParameterLocation.Header,
Scheme = "ApiKeyScheme"
});
cfg.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization Header using the bearer scheme",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey
});
cfg.AddSecurityRequirement(new OpenApiSecurityRequirement
{
//{
// new OpenApiSecurityScheme
// {
// Reference = new OpenApiReference
// {
// Type = ReferenceType.SecurityScheme,
// Id = "ApiKey"
// },
// In = ParameterLocation.Header
// },
// new List<string>()
//},
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Scheme = "oauth2",
Name = "Bearer",
In = ParameterLocation.Header
},
new List<string>()
}
});
});
}
}

View File

@ -1,3 +1,5 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Newsbot.Collector.Domain.Consts;
@ -5,46 +7,62 @@ using Newsbot.Collector.Domain.Entities;
namespace Newsbot.Collector.Database;
public class DatabaseContext : DbContext
public class DatabaseContext : IdentityDbContext
{
public DbSet<ArticlesEntity> Articles { get; set; } = null!;
public DbSet<DiscordNotificationEntity> DiscordNotification { get; set; } = null!;
public DbSet<DiscordQueueEntity> DiscordQueue { get; set; } = null!;
public DbSet<DiscordWebhookEntity> DiscordWebhooks { get; set; } = null!;
public DbSet<IconEntity> Icons { get; set; } = null!;
public DbSet<SourceEntity> Sources { get; set; } = null!;
public DbSet<SubscriptionEntity> Subscriptions { get; set; } = null!;
private string ConnectionString { get; set; }
public DbSet<AuthorEntity> Authors { get; set; } = null!;
public DatabaseContext(IConfiguration appsettings, string connectionString)
{
var connString = appsettings.GetConnectionString(ConfigConnectionStringConst.Database);
ConnectionString = connString ?? "";
}
public DbSet<UserSourceSubscriptionEntity> UserSourceSubscription { get; set; } = null!;
public DbSet<RefreshTokenEntity> RefreshTokens { get; set; } = null!;
private string ConnectionString { get; set; } = "";
//public DatabaseContext(IConfiguration appsettings, string connectionString)
//{
// var connString = appsettings.GetConnectionString(ConfigConnectionStringConst.Database);
// ConnectionString = connString ?? "";
//}
public DatabaseContext(string connectionString)
{
ConnectionString = connectionString;
}
public DatabaseContext(DbContextOptions<DatabaseContext> connectionString)
{
ConnectionString = "";
}
public DatabaseContext()
{
ConnectionString = "";
}
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseNpgsql(ConnectionString);
if (ConnectionString != "")
{
options.UseNpgsql(ConnectionString);
}
}
//public DatabaseContext(DbContextOptions<DatabaseContext> connectionString)
//{
// ConnectionString = "";
//}
//public DatabaseContext()
//{
// ConnectionString = "";
//}
public DatabaseContext(DbContextOptions<DatabaseContext> options)
: base(options)
{
//ConnectionString = "";
}
public DatabaseContext(DbContextOptions<DatabaseContext> options, string connectionString)
: base(options)
{
ConnectionString = connectionString;
//ConnectionString = connectionString;
}
}

View File

@ -0,0 +1,470 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Newsbot.Collector.Database;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Newsbot.Collector.Database.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20230629222603_identity")]
partial class identity
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<int>("AccessFailedCount")
.HasColumnType("integer");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("boolean");
b.Property<bool>("LockoutEnabled")
.HasColumnType("boolean");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("timestamp with time zone");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("PasswordHash")
.HasColumnType("text");
b.Property<string>("PhoneNumber")
.HasColumnType("text");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("boolean");
b.Property<string>("SecurityStamp")
.HasColumnType("text");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("boolean");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("ProviderKey")
.HasColumnType("text");
b.Property<string>("ProviderDisplayName")
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<string>("RoleId")
.HasColumnType("text");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<string>("Value")
.HasColumnType("text");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.ArticlesEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("AuthorImage")
.HasColumnType("text");
b.Property<string>("AuthorName")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("CodeIsCommit")
.HasColumnType("boolean");
b.Property<bool>("CodeIsRelease")
.HasColumnType("boolean");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("PubDate")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.Property<string>("Tags")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Thumbnail")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Url")
.HasColumnType("text");
b.Property<string>("Video")
.IsRequired()
.HasColumnType("text");
b.Property<int>("VideoHeight")
.HasColumnType("integer");
b.Property<int>("VideoWidth")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("Articles");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordQueueEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("ArticleId")
.HasColumnType("uuid");
b.HasKey("Id");
b.ToTable("DiscordQueue");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordWebhookEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Channel")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("Enabled")
.HasColumnType("boolean");
b.Property<string>("Server")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("DiscordWebhooks");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.IconEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("FileName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Site")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.HasKey("Id");
b.ToTable("Icons");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.SourceEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("Deleted")
.HasColumnType("boolean");
b.Property<bool>("Enabled")
.HasColumnType("boolean");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Site")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Source")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Tags")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("text");
b.Property<string>("YoutubeId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Sources");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.SubscriptionEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("CodeAllowCommits")
.HasColumnType("boolean");
b.Property<bool>("CodeAllowReleases")
.HasColumnType("boolean");
b.Property<Guid>("DiscordWebHookId")
.HasColumnType("uuid");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.HasKey("Id");
b.ToTable("Subscriptions");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,223 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Newsbot.Collector.Database.Migrations
{
/// <inheritdoc />
public partial class identity : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AspNetRoles",
columns: table => new
{
Id = table.Column<string>(type: "text", nullable: false),
Name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
NormalizedName = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
ConcurrencyStamp = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetUsers",
columns: table => new
{
Id = table.Column<string>(type: "text", nullable: false),
UserName = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
NormalizedUserName = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
Email = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
NormalizedEmail = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
EmailConfirmed = table.Column<bool>(type: "boolean", nullable: false),
PasswordHash = table.Column<string>(type: "text", nullable: true),
SecurityStamp = table.Column<string>(type: "text", nullable: true),
ConcurrencyStamp = table.Column<string>(type: "text", nullable: true),
PhoneNumber = table.Column<string>(type: "text", nullable: true),
PhoneNumberConfirmed = table.Column<bool>(type: "boolean", nullable: false),
TwoFactorEnabled = table.Column<bool>(type: "boolean", nullable: false),
LockoutEnd = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
LockoutEnabled = table.Column<bool>(type: "boolean", nullable: false),
AccessFailedCount = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUsers", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetRoleClaims",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
RoleId = table.Column<string>(type: "text", nullable: false),
ClaimType = table.Column<string>(type: "text", nullable: true),
ClaimValue = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserClaims",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
UserId = table.Column<string>(type: "text", nullable: false),
ClaimType = table.Column<string>(type: "text", nullable: true),
ClaimValue = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetUserClaims_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserLogins",
columns: table => new
{
LoginProvider = table.Column<string>(type: "text", nullable: false),
ProviderKey = table.Column<string>(type: "text", nullable: false),
ProviderDisplayName = table.Column<string>(type: "text", nullable: true),
UserId = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
table.ForeignKey(
name: "FK_AspNetUserLogins_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserRoles",
columns: table => new
{
UserId = table.Column<string>(type: "text", nullable: false),
RoleId = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserTokens",
columns: table => new
{
UserId = table.Column<string>(type: "text", nullable: false),
LoginProvider = table.Column<string>(type: "text", nullable: false),
Name = table.Column<string>(type: "text", nullable: false),
Value = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
table.ForeignKey(
name: "FK_AspNetUserTokens_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_AspNetRoleClaims_RoleId",
table: "AspNetRoleClaims",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "RoleNameIndex",
table: "AspNetRoles",
column: "NormalizedName",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_AspNetUserClaims_UserId",
table: "AspNetUserClaims",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserLogins_UserId",
table: "AspNetUserLogins",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserRoles_RoleId",
table: "AspNetUserRoles",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "EmailIndex",
table: "AspNetUsers",
column: "NormalizedEmail");
migrationBuilder.CreateIndex(
name: "UserNameIndex",
table: "AspNetUsers",
column: "NormalizedUserName",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AspNetRoleClaims");
migrationBuilder.DropTable(
name: "AspNetUserClaims");
migrationBuilder.DropTable(
name: "AspNetUserLogins");
migrationBuilder.DropTable(
name: "AspNetUserRoles");
migrationBuilder.DropTable(
name: "AspNetUserTokens");
migrationBuilder.DropTable(
name: "AspNetRoles");
migrationBuilder.DropTable(
name: "AspNetUsers");
}
}
}

View File

@ -0,0 +1,501 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Newsbot.Collector.Database;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Newsbot.Collector.Database.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20230710050618_SourcesRenamed")]
partial class SourcesRenamed
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<int>("AccessFailedCount")
.HasColumnType("integer");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("boolean");
b.Property<bool>("LockoutEnabled")
.HasColumnType("boolean");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("timestamp with time zone");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("PasswordHash")
.HasColumnType("text");
b.Property<string>("PhoneNumber")
.HasColumnType("text");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("boolean");
b.Property<string>("SecurityStamp")
.HasColumnType("text");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("boolean");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("ProviderKey")
.HasColumnType("text");
b.Property<string>("ProviderDisplayName")
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<string>("RoleId")
.HasColumnType("text");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<string>("Value")
.HasColumnType("text");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.ArticlesEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("AuthorImage")
.HasColumnType("text");
b.Property<string>("AuthorName")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("CodeIsCommit")
.HasColumnType("boolean");
b.Property<bool>("CodeIsRelease")
.HasColumnType("boolean");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("PubDate")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.Property<string>("Tags")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Thumbnail")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Url")
.HasColumnType("text");
b.Property<string>("Video")
.IsRequired()
.HasColumnType("text");
b.Property<int>("VideoHeight")
.HasColumnType("integer");
b.Property<int>("VideoWidth")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("Articles");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordNotificationEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("CodeAllowCommits")
.HasColumnType("boolean");
b.Property<bool>("CodeAllowReleases")
.HasColumnType("boolean");
b.Property<Guid>("DiscordWebHookId")
.HasColumnType("uuid");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.HasKey("Id");
b.ToTable("DiscordNotification");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordQueueEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("ArticleId")
.HasColumnType("uuid");
b.HasKey("Id");
b.ToTable("DiscordQueue");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordWebhookEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Channel")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("Enabled")
.HasColumnType("boolean");
b.Property<string>("Server")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("DiscordWebhooks");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.IconEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("FileName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Site")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.HasKey("Id");
b.ToTable("Icons");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.SourceEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("Deleted")
.HasColumnType("boolean");
b.Property<bool>("Enabled")
.HasColumnType("boolean");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Site")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Source")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Tags")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("text");
b.Property<string>("YoutubeId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Sources");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.UserSourceSubscriptionEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTimeOffset>("DateAdded")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.Property<string>("UserId")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("UserSourceSubscription");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.UserSourceSubscriptionEntity", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User")
.WithMany()
.HasForeignKey("UserId");
b.Navigation("User");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,82 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Newsbot.Collector.Database.Migrations
{
/// <inheritdoc />
public partial class SourcesRenamed : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Subscriptions");
migrationBuilder.CreateTable(
name: "DiscordNotification",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
CodeAllowReleases = table.Column<bool>(type: "boolean", nullable: false),
CodeAllowCommits = table.Column<bool>(type: "boolean", nullable: false),
SourceId = table.Column<Guid>(type: "uuid", nullable: false),
DiscordWebHookId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_DiscordNotification", x => x.Id);
});
migrationBuilder.CreateTable(
name: "UserSourceSubscription",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
SourceId = table.Column<Guid>(type: "uuid", nullable: false),
DateAdded = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
UserId = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_UserSourceSubscription", x => x.Id);
table.ForeignKey(
name: "FK_UserSourceSubscription_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id");
});
migrationBuilder.CreateIndex(
name: "IX_UserSourceSubscription_UserId",
table: "UserSourceSubscription",
column: "UserId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "DiscordNotification");
migrationBuilder.DropTable(
name: "UserSourceSubscription");
migrationBuilder.CreateTable(
name: "Subscriptions",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
CodeAllowCommits = table.Column<bool>(type: "boolean", nullable: false),
CodeAllowReleases = table.Column<bool>(type: "boolean", nullable: false),
DiscordWebHookId = table.Column<Guid>(type: "uuid", nullable: false),
SourceId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Subscriptions", x => x.Id);
});
}
}
}

View File

@ -0,0 +1,504 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Newsbot.Collector.Database;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Newsbot.Collector.Database.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20230710050913_DiscordNotificationOwnerAdded")]
partial class DiscordNotificationOwnerAdded
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<int>("AccessFailedCount")
.HasColumnType("integer");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("boolean");
b.Property<bool>("LockoutEnabled")
.HasColumnType("boolean");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("timestamp with time zone");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("PasswordHash")
.HasColumnType("text");
b.Property<string>("PhoneNumber")
.HasColumnType("text");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("boolean");
b.Property<string>("SecurityStamp")
.HasColumnType("text");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("boolean");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("ProviderKey")
.HasColumnType("text");
b.Property<string>("ProviderDisplayName")
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<string>("RoleId")
.HasColumnType("text");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<string>("Value")
.HasColumnType("text");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.ArticlesEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("AuthorImage")
.HasColumnType("text");
b.Property<string>("AuthorName")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("CodeIsCommit")
.HasColumnType("boolean");
b.Property<bool>("CodeIsRelease")
.HasColumnType("boolean");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("PubDate")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.Property<string>("Tags")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Thumbnail")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Url")
.HasColumnType("text");
b.Property<string>("Video")
.IsRequired()
.HasColumnType("text");
b.Property<int>("VideoHeight")
.HasColumnType("integer");
b.Property<int>("VideoWidth")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("Articles");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordNotificationEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("CodeAllowCommits")
.HasColumnType("boolean");
b.Property<bool>("CodeAllowReleases")
.HasColumnType("boolean");
b.Property<Guid>("DiscordWebHookId")
.HasColumnType("uuid");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.Property<string>("UserId")
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("DiscordNotification");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordQueueEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("ArticleId")
.HasColumnType("uuid");
b.HasKey("Id");
b.ToTable("DiscordQueue");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordWebhookEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Channel")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("Enabled")
.HasColumnType("boolean");
b.Property<string>("Server")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("DiscordWebhooks");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.IconEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("FileName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Site")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.HasKey("Id");
b.ToTable("Icons");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.SourceEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("Deleted")
.HasColumnType("boolean");
b.Property<bool>("Enabled")
.HasColumnType("boolean");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Site")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Source")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Tags")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("text");
b.Property<string>("YoutubeId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Sources");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.UserSourceSubscriptionEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTimeOffset>("DateAdded")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.Property<string>("UserId")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("UserSourceSubscription");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.UserSourceSubscriptionEntity", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User")
.WithMany()
.HasForeignKey("UserId");
b.Navigation("User");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Newsbot.Collector.Database.Migrations
{
/// <inheritdoc />
public partial class DiscordNotificationOwnerAdded : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "UserId",
table: "DiscordNotification",
type: "text",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "UserId",
table: "DiscordNotification");
}
}
}

View File

@ -0,0 +1,543 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Newsbot.Collector.Database;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Newsbot.Collector.Database.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20230711033558_add_jwt_token_refresh_table")]
partial class add_jwt_token_refresh_table
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<int>("AccessFailedCount")
.HasColumnType("integer");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("boolean");
b.Property<bool>("LockoutEnabled")
.HasColumnType("boolean");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("timestamp with time zone");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("PasswordHash")
.HasColumnType("text");
b.Property<string>("PhoneNumber")
.HasColumnType("text");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("boolean");
b.Property<string>("SecurityStamp")
.HasColumnType("text");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("boolean");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("ProviderKey")
.HasColumnType("text");
b.Property<string>("ProviderDisplayName")
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<string>("RoleId")
.HasColumnType("text");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<string>("Value")
.HasColumnType("text");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.ArticlesEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("AuthorImage")
.HasColumnType("text");
b.Property<string>("AuthorName")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("CodeIsCommit")
.HasColumnType("boolean");
b.Property<bool>("CodeIsRelease")
.HasColumnType("boolean");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("PubDate")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.Property<string>("Tags")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Thumbnail")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Url")
.HasColumnType("text");
b.Property<string>("Video")
.IsRequired()
.HasColumnType("text");
b.Property<int>("VideoHeight")
.HasColumnType("integer");
b.Property<int>("VideoWidth")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("Articles");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordNotificationEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("CodeAllowCommits")
.HasColumnType("boolean");
b.Property<bool>("CodeAllowReleases")
.HasColumnType("boolean");
b.Property<Guid>("DiscordWebHookId")
.HasColumnType("uuid");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.Property<string>("UserId")
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("DiscordNotification");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordQueueEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("ArticleId")
.HasColumnType("uuid");
b.HasKey("Id");
b.ToTable("DiscordQueue");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordWebhookEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Channel")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("Enabled")
.HasColumnType("boolean");
b.Property<string>("Server")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("DiscordWebhooks");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.IconEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("FileName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Site")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.HasKey("Id");
b.ToTable("Icons");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.RefreshTokenEntity", b =>
{
b.Property<string>("Token")
.HasColumnType("text");
b.Property<DateTime>("CreatedDate")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("ExpiryDate")
.HasColumnType("timestamp with time zone");
b.Property<bool>("Invalidated")
.HasColumnType("boolean");
b.Property<string>("JwtId")
.HasColumnType("text");
b.Property<bool>("Used")
.HasColumnType("boolean");
b.Property<string>("UserId")
.HasColumnType("text");
b.HasKey("Token");
b.HasIndex("UserId");
b.ToTable("RefreshTokens");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.SourceEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("Deleted")
.HasColumnType("boolean");
b.Property<bool>("Enabled")
.HasColumnType("boolean");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Site")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Source")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Tags")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("text");
b.Property<string>("YoutubeId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Sources");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.UserSourceSubscriptionEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTimeOffset>("DateAdded")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.Property<string>("UserId")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("UserSourceSubscription");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.RefreshTokenEntity", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User")
.WithMany()
.HasForeignKey("UserId");
b.Navigation("User");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.UserSourceSubscriptionEntity", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User")
.WithMany()
.HasForeignKey("UserId");
b.Navigation("User");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,49 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Newsbot.Collector.Database.Migrations
{
/// <inheritdoc />
public partial class add_jwt_token_refresh_table : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "RefreshTokens",
columns: table => new
{
Token = table.Column<string>(type: "text", nullable: false),
JwtId = table.Column<string>(type: "text", nullable: true),
CreatedDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
ExpiryDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
Used = table.Column<bool>(type: "boolean", nullable: false),
Invalidated = table.Column<bool>(type: "boolean", nullable: false),
UserId = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_RefreshTokens", x => x.Token);
table.ForeignKey(
name: "FK_RefreshTokens_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id");
});
migrationBuilder.CreateIndex(
name: "IX_RefreshTokens_UserId",
table: "RefreshTokens",
column: "UserId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "RefreshTokens");
}
}
}

View File

@ -0,0 +1,579 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Newsbot.Collector.Database;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Newsbot.Collector.Database.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20230729170139_Adding Author Table")]
partial class AddingAuthorTable
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<int>("AccessFailedCount")
.HasColumnType("integer");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("boolean");
b.Property<bool>("LockoutEnabled")
.HasColumnType("boolean");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("timestamp with time zone");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("PasswordHash")
.HasColumnType("text");
b.Property<string>("PhoneNumber")
.HasColumnType("text");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("boolean");
b.Property<string>("SecurityStamp")
.HasColumnType("text");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("boolean");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("ProviderKey")
.HasColumnType("text");
b.Property<string>("ProviderDisplayName")
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<string>("RoleId")
.HasColumnType("text");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<string>("Value")
.HasColumnType("text");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.ArticlesEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("AuthorImage")
.HasColumnType("text");
b.Property<string>("AuthorName")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("CodeIsCommit")
.HasColumnType("boolean");
b.Property<bool>("CodeIsRelease")
.HasColumnType("boolean");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("PubDate")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.Property<string>("Tags")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Thumbnail")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Url")
.HasColumnType("text");
b.Property<string>("Video")
.IsRequired()
.HasColumnType("text");
b.Property<int>("VideoHeight")
.HasColumnType("integer");
b.Property<int>("VideoWidth")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("Articles");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.AuthorEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Image")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.HasKey("Id");
b.ToTable("Authors");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordNotificationEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("CodeAllowCommits")
.HasColumnType("boolean");
b.Property<bool>("CodeAllowReleases")
.HasColumnType("boolean");
b.Property<Guid>("DiscordWebHookId")
.HasColumnType("uuid");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.Property<string>("UserId")
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("DiscordNotification");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordQueueEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("ArticleId")
.HasColumnType("uuid");
b.HasKey("Id");
b.ToTable("DiscordQueue");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordWebhookEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Channel")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("Enabled")
.HasColumnType("boolean");
b.Property<string>("Server")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("text");
b.Property<string>("UserId")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("DiscordWebhooks");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.IconEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("FileName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Site")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.HasKey("Id");
b.ToTable("Icons");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.RefreshTokenEntity", b =>
{
b.Property<string>("Token")
.HasColumnType("text");
b.Property<DateTime>("CreatedDate")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("ExpiryDate")
.HasColumnType("timestamp with time zone");
b.Property<bool>("Invalidated")
.HasColumnType("boolean");
b.Property<string>("JwtId")
.HasColumnType("text");
b.Property<bool>("Used")
.HasColumnType("boolean");
b.Property<string>("UserId")
.HasColumnType("text");
b.HasKey("Token");
b.HasIndex("UserId");
b.ToTable("RefreshTokens");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.SourceEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("Deleted")
.HasColumnType("boolean");
b.Property<bool>("Enabled")
.HasColumnType("boolean");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Site")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Source")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Tags")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("text");
b.Property<string>("YoutubeId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Sources");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.UserSourceSubscriptionEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTimeOffset>("DateAdded")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.Property<string>("UserId")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("UserSourceSubscription");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordWebhookEntity", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User")
.WithMany()
.HasForeignKey("UserId");
b.Navigation("User");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.RefreshTokenEntity", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User")
.WithMany()
.HasForeignKey("UserId");
b.Navigation("User");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.UserSourceSubscriptionEntity", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User")
.WithMany()
.HasForeignKey("UserId");
b.Navigation("User");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,66 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Newsbot.Collector.Database.Migrations
{
/// <inheritdoc />
public partial class AddingAuthorTable : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "UserId",
table: "DiscordWebhooks",
type: "text",
nullable: true);
migrationBuilder.CreateTable(
name: "Authors",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
SourceId = table.Column<Guid>(type: "uuid", nullable: false),
Name = table.Column<string>(type: "text", nullable: false),
Image = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Authors", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_DiscordWebhooks_UserId",
table: "DiscordWebhooks",
column: "UserId");
migrationBuilder.AddForeignKey(
name: "FK_DiscordWebhooks_AspNetUsers_UserId",
table: "DiscordWebhooks",
column: "UserId",
principalTable: "AspNetUsers",
principalColumn: "Id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_DiscordWebhooks_AspNetUsers_UserId",
table: "DiscordWebhooks");
migrationBuilder.DropTable(
name: "Authors");
migrationBuilder.DropIndex(
name: "IX_DiscordWebhooks_UserId",
table: "DiscordWebhooks");
migrationBuilder.DropColumn(
name: "UserId",
table: "DiscordWebhooks");
}
}
}

View File

@ -0,0 +1,575 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Newsbot.Collector.Database;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Newsbot.Collector.Database.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20230805060324_add author table")]
partial class addauthortable
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<int>("AccessFailedCount")
.HasColumnType("integer");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("boolean");
b.Property<bool>("LockoutEnabled")
.HasColumnType("boolean");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("timestamp with time zone");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("PasswordHash")
.HasColumnType("text");
b.Property<string>("PhoneNumber")
.HasColumnType("text");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("boolean");
b.Property<string>("SecurityStamp")
.HasColumnType("text");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("boolean");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("ProviderKey")
.HasColumnType("text");
b.Property<string>("ProviderDisplayName")
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<string>("RoleId")
.HasColumnType("text");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<string>("Value")
.HasColumnType("text");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.ArticlesEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("AuthorId")
.HasColumnType("uuid");
b.Property<bool>("CodeIsCommit")
.HasColumnType("boolean");
b.Property<bool>("CodeIsRelease")
.HasColumnType("boolean");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("PubDate")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.Property<string>("Tags")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Thumbnail")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Url")
.HasColumnType("text");
b.Property<string>("Video")
.IsRequired()
.HasColumnType("text");
b.Property<int>("VideoHeight")
.HasColumnType("integer");
b.Property<int>("VideoWidth")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("Articles");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.AuthorEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Image")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.HasKey("Id");
b.ToTable("Authors");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordNotificationEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("CodeAllowCommits")
.HasColumnType("boolean");
b.Property<bool>("CodeAllowReleases")
.HasColumnType("boolean");
b.Property<Guid>("DiscordWebHookId")
.HasColumnType("uuid");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.Property<string>("UserId")
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("DiscordNotification");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordQueueEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("ArticleId")
.HasColumnType("uuid");
b.HasKey("Id");
b.ToTable("DiscordQueue");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordWebhookEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Channel")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("Enabled")
.HasColumnType("boolean");
b.Property<string>("Server")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("text");
b.Property<string>("UserId")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("DiscordWebhooks");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.IconEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("FileName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Site")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.HasKey("Id");
b.ToTable("Icons");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.RefreshTokenEntity", b =>
{
b.Property<string>("Token")
.HasColumnType("text");
b.Property<DateTime>("CreatedDate")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("ExpiryDate")
.HasColumnType("timestamp with time zone");
b.Property<bool>("Invalidated")
.HasColumnType("boolean");
b.Property<string>("JwtId")
.HasColumnType("text");
b.Property<bool>("Used")
.HasColumnType("boolean");
b.Property<string>("UserId")
.HasColumnType("text");
b.HasKey("Token");
b.HasIndex("UserId");
b.ToTable("RefreshTokens");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.SourceEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("Deleted")
.HasColumnType("boolean");
b.Property<bool>("Enabled")
.HasColumnType("boolean");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Site")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Source")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Tags")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("text");
b.Property<string>("YoutubeId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Sources");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.UserSourceSubscriptionEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTimeOffset>("DateAdded")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.Property<string>("UserId")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("UserSourceSubscription");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordWebhookEntity", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User")
.WithMany()
.HasForeignKey("UserId");
b.Navigation("User");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.RefreshTokenEntity", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User")
.WithMany()
.HasForeignKey("UserId");
b.Navigation("User");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.UserSourceSubscriptionEntity", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User")
.WithMany()
.HasForeignKey("UserId");
b.Navigation("User");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,51 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Newsbot.Collector.Database.Migrations
{
/// <inheritdoc />
public partial class addauthortable : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "AuthorImage",
table: "Articles");
migrationBuilder.DropColumn(
name: "AuthorName",
table: "Articles");
migrationBuilder.AddColumn<Guid>(
name: "AuthorId",
table: "Articles",
type: "uuid",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "AuthorId",
table: "Articles");
migrationBuilder.AddColumn<string>(
name: "AuthorImage",
table: "Articles",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "AuthorName",
table: "Articles",
type: "text",
nullable: false,
defaultValue: "");
}
}
}

View File

@ -17,23 +17,215 @@ namespace Newsbot.Collector.Database.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.7")
.HasAnnotation("ProductVersion", "7.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<int>("AccessFailedCount")
.HasColumnType("integer");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("boolean");
b.Property<bool>("LockoutEnabled")
.HasColumnType("boolean");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("timestamp with time zone");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("PasswordHash")
.HasColumnType("text");
b.Property<string>("PhoneNumber")
.HasColumnType("text");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("boolean");
b.Property<string>("SecurityStamp")
.HasColumnType("text");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("boolean");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("ProviderKey")
.HasColumnType("text");
b.Property<string>("ProviderDisplayName")
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<string>("RoleId")
.HasColumnType("text");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<string>("Value")
.HasColumnType("text");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.ArticlesEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("AuthorImage")
.HasColumnType("text");
b.Property<string>("AuthorName")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("AuthorId")
.HasColumnType("uuid");
b.Property<bool>("CodeIsCommit")
.HasColumnType("boolean");
@ -81,6 +273,54 @@ namespace Newsbot.Collector.Database.Migrations
b.ToTable("Articles");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.AuthorEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Image")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.HasKey("Id");
b.ToTable("Authors");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordNotificationEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("CodeAllowCommits")
.HasColumnType("boolean");
b.Property<bool>("CodeAllowReleases")
.HasColumnType("boolean");
b.Property<Guid>("DiscordWebHookId")
.HasColumnType("uuid");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.Property<string>("UserId")
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("DiscordNotification");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordQueueEntity", b =>
{
b.Property<Guid>("Id")
@ -116,8 +356,13 @@ namespace Newsbot.Collector.Database.Migrations
.IsRequired()
.HasColumnType("text");
b.Property<string>("UserId")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("DiscordWebhooks");
});
@ -143,6 +388,36 @@ namespace Newsbot.Collector.Database.Migrations
b.ToTable("Icons");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.RefreshTokenEntity", b =>
{
b.Property<string>("Token")
.HasColumnType("text");
b.Property<DateTime>("CreatedDate")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("ExpiryDate")
.HasColumnType("timestamp with time zone");
b.Property<bool>("Invalidated")
.HasColumnType("boolean");
b.Property<string>("JwtId")
.HasColumnType("text");
b.Property<bool>("Used")
.HasColumnType("boolean");
b.Property<string>("UserId")
.HasColumnType("text");
b.HasKey("Token");
b.HasIndex("UserId");
b.ToTable("RefreshTokens");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.SourceEntity", b =>
{
b.Property<Guid>("Id")
@ -192,27 +467,104 @@ namespace Newsbot.Collector.Database.Migrations
b.ToTable("Sources");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.SubscriptionEntity", b =>
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.UserSourceSubscriptionEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("CodeAllowCommits")
.HasColumnType("boolean");
b.Property<bool>("CodeAllowReleases")
.HasColumnType("boolean");
b.Property<Guid>("DiscordWebHookId")
.HasColumnType("uuid");
b.Property<DateTimeOffset>("DateAdded")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("SourceId")
.HasColumnType("uuid");
b.Property<string>("UserId")
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Subscriptions");
b.HasIndex("UserId");
b.ToTable("UserSourceSubscription");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordWebhookEntity", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User")
.WithMany()
.HasForeignKey("UserId");
b.Navigation("User");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.RefreshTokenEntity", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User")
.WithMany()
.HasForeignKey("UserId");
b.Navigation("User");
});
modelBuilder.Entity("Newsbot.Collector.Domain.Entities.UserSourceSubscriptionEntity", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User")
.WithMany()
.HasForeignKey("UserId");
b.Navigation("User");
});
#pragma warning restore 612, 618
}

View File

@ -6,6 +6,7 @@
<ItemGroup>
<PackageReference Include="dapper" Version="2.0.123" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.8">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@ -20,6 +21,9 @@
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>8981</NoWarn>
</PropertyGroup>
</Project>

View File

@ -11,29 +11,34 @@ namespace Newsbot.Collector.Database.Repositories;
public class ArticlesTable : IArticlesRepository
{
private readonly string _connectionString;
//private readonly string _connectionString;
private DatabaseContext _context;
public ArticlesTable(string connectionString)
{
_connectionString = connectionString;
//_connectionString = connectionString;
_context = new DatabaseContext(connectionString);
}
public ArticlesTable(IConfiguration configuration)
public ArticlesTable(DatabaseContext context)
{
var conn = configuration.GetConnectionString("database");
if (conn is null) conn = "";
_connectionString = conn;
_context = new DatabaseContext(conn);
_context = context;
}
//public ArticlesTable(IConfiguration configuration)
//{
// var conn = configuration.GetConnectionString("database");
// if (conn is null) conn = "";
//
// _context = new DatabaseContext(conn);
//}
public List<ArticlesEntity> List(int page = 0, int count = 25)
{
using var context = new DatabaseContext(_connectionString);
var query = context.Articles
//using var context = new DatabaseContext(_connectionString);
var query = _context.Articles
.Skip(page * count)
.OrderBy(d => d.PubDate)
.Take(25);
@ -43,8 +48,8 @@ public class ArticlesTable : IArticlesRepository
public ArticlesEntity GetById(Guid id)
{
using var context = new DatabaseContext(_connectionString);
var query = context.Articles
//using var context = new DatabaseContext(_connectionString);
var query = _context.Articles
.FirstOrDefault(d => d.Id.Equals(id));
query ??= new ArticlesEntity();
return query;
@ -52,16 +57,16 @@ public class ArticlesTable : IArticlesRepository
public ArticlesEntity GetByUrl(string url)
{
using var context = new DatabaseContext(_connectionString);
var res = context.Articles.FirstOrDefault(d => d.Url!.Equals(url));
//using var context = new DatabaseContext(_connectionString);
var res = _context.Articles.FirstOrDefault(d => d.Url!.Equals(url));
res ??= new ArticlesEntity();
return res;
}
public List<ArticlesEntity> ListBySourceId(Guid id, int page, int count)
{
using var context = new DatabaseContext(_connectionString);
var res = context.Articles
//using var context = new DatabaseContext(_connectionString);
var res = _context.Articles
.Skip(page * count)
.Where(d => d.SourceId.Equals(id));
return res.ToList();
@ -69,13 +74,14 @@ public class ArticlesTable : IArticlesRepository
public ArticlesEntity New(ArticlesEntity model)
{
using var context = new DatabaseContext(_connectionString);
//using var context = new DatabaseContext(_connectionString);
model.Id = new Guid();
var query = context.Articles.Add(model);
context.SaveChanges();
var query = _context.Articles.Add(model);
_context.SaveChanges();
return model;
}
/*
public ArticlesModel NewDapper(ArticlesModel model)
{
model.ID = Guid.NewGuid();
@ -103,26 +109,27 @@ public class ArticlesTable : IArticlesRepository
});
return model;
}
*/
public void DeleteAllBySourceId(Guid sourceId)
{
using var context = new DatabaseContext(_connectionString);
var res = context.Articles
//using var context = new DatabaseContext(_connectionString);
var res = _context.Articles
.Where(d => d.SourceId.Equals(sourceId))
.ToList();
foreach (var item in res)
{
context.Articles.Remove(item);
_context.Articles.Remove(item);
}
context.SaveChanges();
_context.SaveChanges();
}
private IDbConnection OpenConnection(string connectionString)
{
var conn = new NpgsqlConnection(_connectionString);
conn.Open();
return conn;
}
//private IDbConnection OpenConnection(string connectionString)
//{
// //var conn = new NpgsqlConnection(_connectionString);
// //conn.Open();
// //return conn;
//}
}

View File

@ -0,0 +1,67 @@
using Microsoft.EntityFrameworkCore;
using Newsbot.Collector.Domain.Entities;
using Newsbot.Collector.Domain.Interfaces;
namespace Newsbot.Collector.Database.Repositories;
public class AuthorsTable : IAuthorTable
{
private readonly DatabaseContext _context;
public AuthorsTable(string connectionString)
{
//_connectionString = connectionString;
_context = new DatabaseContext(connectionString);
}
public AuthorsTable(DatabaseContext context)
{
_context = context;
}
public async Task<AuthorEntity> NewAsync(AuthorEntity entity)
{
entity.Id = Guid.NewGuid();
_context.Authors.Add(entity);
await _context.SaveChangesAsync();
return entity;
}
public async Task<AuthorEntity> CreateIfMissingAsync(AuthorEntity entity)
{
var res = await GetBySourceIdAndNameAsync(entity.SourceId, entity.Name);
if (res is null)
{
entity.Id = Guid.NewGuid();
_context.Authors.Add(entity);
await _context.SaveChangesAsync();
return entity;
}
return res;
}
public async Task<List<AuthorEntity>> ListBySourceIdAsync(Guid sourceId)
{
return await _context.Authors
.Where(s => s.SourceId.Equals(sourceId)).ToListAsync();
}
public async Task<int> TotalPostsAsync(Guid id)
{
return await _context.Authors.CountAsync(x => x.Id.Equals(id));
}
public async Task<AuthorEntity?> GetBySourceIdAndNameAsync(Guid sourceId, string name)
{
return await _context.Authors
.Where(s => s.SourceId.Equals(sourceId))
.FirstOrDefaultAsync(n => n.Name.Equals(name));
}
public async Task<AuthorEntity?> GetById(Guid id)
{
return await _context.Authors.FirstOrDefaultAsync(q => q.Id.Equals(id));
}
}

View File

@ -0,0 +1,97 @@
using Newsbot.Collector.Domain.Entities;
using Newsbot.Collector.Domain.Interfaces;
namespace Newsbot.Collector.Database.Repositories;
public class DiscordNotificationTable : IDiscordNotificationRepository
{
//private readonly string _connectionString;
private DatabaseContext _context;
public DiscordNotificationTable(string connectionString)
{
//_connectionString = connectionString;
_context = new DatabaseContext(connectionString);
}
public DiscordNotificationTable(DatabaseContext context)
{
_context = context;
}
public DiscordNotificationEntity New(DiscordNotificationEntity model)
{
model.Id = new Guid();
_context.DiscordNotification.Add(model);
_context.SaveChanges();
return model;
}
public List<DiscordNotificationEntity> List(string userId, int page = 0, int count = 25)
{
return _context.DiscordNotification
.Where(x => x.UserId != null && x.UserId.Equals(userId))
.Skip(page * count)
.Take(count)
.ToList();
}
public List<DiscordNotificationEntity> ListBySourceId(string userId, Guid id, int page = 0, int count = 25)
{
return _context.DiscordNotification
.Where(f => f.SourceId.Equals(id))
.Where(f => f.UserId != null && f.UserId.Equals(userId))
.Skip(page * count)
.ToList();
}
public List<DiscordNotificationEntity> ListBySourceId(Guid id, int page = 0, int count = 25)
{
return _context.DiscordNotification
.Where(f => f.SourceId.Equals(id))
.Skip(page * count)
.ToList();
}
public List<DiscordNotificationEntity> ListByWebhook(string userId, Guid id, int page = 0, int count = 25)
{
return _context.DiscordNotification
.Where(f => f.DiscordWebHookId.Equals(id))
.Where(f => f.UserId != null && f.UserId.Equals(userId))
.Skip(page * count)
.ToList();
}
public DiscordNotificationEntity GetById(string userId, Guid id)
{
var res = _context.DiscordNotification
.Where(f => f.UserId != null && f.UserId.Equals(userId))
.FirstOrDefault(f => f.Id.Equals(id));
return res ??= new DiscordNotificationEntity();
}
public DiscordNotificationEntity GetByWebhookAndSource(string userId, Guid webhookId, Guid sourceId)
{
var res = _context.DiscordNotification
.Where(f => f.UserId != null && f.UserId.Equals(userId))
.Where(f => f.DiscordWebHookId.Equals(webhookId))
.FirstOrDefault(f => f.SourceId.Equals(sourceId));
return res ??= new DiscordNotificationEntity();
}
public int Delete(string userId, Guid id)
{
var res = _context.DiscordNotification
.Where(f => f.UserId != null && f.UserId.Equals(userId))
.FirstOrDefault(f => f.Id.Equals(id));
if (res is null)
{
return -1;
}
_context.DiscordNotification.Remove(res);
return _context.SaveChanges();
}
}

View File

@ -9,42 +9,49 @@ namespace Newsbot.Collector.Database.Repositories;
public class DiscordQueueTable : IDiscordQueueRepository
{
private string _connectionString;
//private string _connectionString;
private DatabaseContext _context;
public DiscordQueueTable(string connectionString)
{
_connectionString = connectionString;
//_connectionString = connectionString;
_context = new DatabaseContext(connectionString);
}
private IDbConnection OpenConnection(string connectionString)
public DiscordQueueTable(DatabaseContext context)
{
var conn = new NpgsqlConnection(_connectionString);
conn.Open();
return conn;
_context = context;
}
//private IDbConnection OpenConnection(string connectionString)
//{
// var conn = new NpgsqlConnection(_connectionString);
// conn.Open();
// return conn;
//}
public void New(DiscordQueueEntity model)
{
model.Id = new Guid();
using var context = new DatabaseContext(_connectionString);
var res = context.DiscordQueue.Add(model);
context.SaveChanges();
//using var context = new DatabaseContext(_connectionString);
var res = _context.DiscordQueue.Add(model);
_context.SaveChanges();
}
public void Delete(Guid id)
{
using var context = new DatabaseContext(_connectionString);
var res = context.DiscordQueue.FirstOrDefault(d => d.Id.Equals(id));
//using var context = new DatabaseContext(_connectionString);
var res = _context.DiscordQueue.FirstOrDefault(d => d.Id.Equals(id));
res ??= new DiscordQueueEntity();
context.DiscordQueue.Remove(res);
context.SaveChanges();
_context.DiscordQueue.Remove(res);
_context.SaveChanges();
}
public List<DiscordQueueEntity> List(int limit = 25)
{
using var context = new DatabaseContext(_connectionString);
var res = context.DiscordQueue.Take(limit).ToList();
//using var context = new DatabaseContext(_connectionString);
var res = _context.DiscordQueue.Take(limit).ToList();
return res;
}
}

View File

@ -1,5 +1,6 @@
using System.Data;
using Dapper;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Newsbot.Collector.Domain.Interfaces;
using Newsbot.Collector.Domain.Entities;
@ -9,49 +10,60 @@ namespace Newsbot.Collector.Database.Repositories;
public class DiscordWebhooksTable : IDiscordWebHooksRepository
{
private readonly string _connectionString;
//private readonly string _connectionString;
private DatabaseContext _context;
public DiscordWebhooksTable(string connectionString)
{
_connectionString = connectionString;
//_connectionString = connectionString;
_context = new DatabaseContext(connectionString);
}
public DiscordWebhooksTable(IConfiguration configuration)
public DiscordWebhooksTable(DatabaseContext context)
{
var connstr = configuration.GetConnectionString("database") ?? "";
_connectionString = connstr;
_context = context;
}
public DiscordWebhookEntity New(DiscordWebhookEntity model)
{
model.Id = new Guid();
using var context = new DatabaseContext(_connectionString);
context.DiscordWebhooks.Add(model);
context.SaveChanges();
//using var context = new DatabaseContext(_connectionString);
_context.DiscordWebhooks.Add(model);
_context.SaveChanges();
return model;
}
public DiscordWebhookEntity GetById(string userId, Guid id)
{
//using var context = new DatabaseContext(_connectionString);
var res = _context.DiscordWebhooks
.Where(i => i.UserId != null && i.UserId.Equals(userId))
.FirstOrDefault(d => d.Id.Equals(id));
res ??= new DiscordWebhookEntity();
return res;
}
public DiscordWebhookEntity GetById(Guid id)
{
using var context = new DatabaseContext(_connectionString);
var res = context.DiscordWebhooks.FirstOrDefault(d => d.Id.Equals(id));
var res = _context.DiscordWebhooks
.FirstOrDefault(d => d.Id.Equals(id));
res ??= new DiscordWebhookEntity();
return res;
}
public DiscordWebhookEntity GetByUrl(string url)
{
using var context = new DatabaseContext(_connectionString);
var res = context.DiscordWebhooks.FirstOrDefault(d => d.Url.Equals(url));
//using var context = new DatabaseContext(_connectionString);
var res = _context.DiscordWebhooks.FirstOrDefault(d => d.Url.Equals(url));
res ??= new DiscordWebhookEntity();
return res;
}
public List<DiscordWebhookEntity> List(int page, int count = 25)
{
using var context = new DatabaseContext(_connectionString);
var res = context.DiscordWebhooks
//using var context = new DatabaseContext(_connectionString);
var res = _context.DiscordWebhooks
.Skip(page * count)
.Take(count)
.ToList();
@ -59,10 +71,20 @@ public class DiscordWebhooksTable : IDiscordWebHooksRepository
return res;
}
public List<DiscordWebhookEntity> ListByUserId(string userId, int page)
{
var query = _context.DiscordWebhooks
.Where(p => p.UserId != null && p.UserId.Equals(userId))
.Skip(page * 25)
.Take(25)
.ToList();
return query;
}
public List<DiscordWebhookEntity> ListByServer(string server, int limit = 25)
{
using var context = new DatabaseContext(_connectionString);
var res = context.DiscordWebhooks
//using var context = new DatabaseContext(_connectionString);
var res = _context.DiscordWebhooks
.Where(d => d.Server.Equals(server))
.Take(limit)
.ToList();
@ -70,10 +92,11 @@ public class DiscordWebhooksTable : IDiscordWebHooksRepository
return res;
}
public List<DiscordWebhookEntity> ListByServerAndChannel(string server, string channel, int limit = 25)
public List<DiscordWebhookEntity> ListByServerAndChannel(string userId, string server, string channel, int limit = 25)
{
using var context = new DatabaseContext(_connectionString);
var res = context.DiscordWebhooks
//using var context = new DatabaseContext(_connectionString);
var res = _context.DiscordWebhooks
.Where(i => i.UserId != null && i.UserId.Equals(userId))
.Where(s => s.Server.Equals(server))
.Where(c => c.Channel.Equals(channel))
.Take(limit)
@ -82,17 +105,17 @@ public class DiscordWebhooksTable : IDiscordWebHooksRepository
return res;
}
public int Disable(Guid id)
public int Disable(string userId, Guid id)
{
var res = GetById(id);
using var context = new DatabaseContext(_connectionString);
var res = GetById(userId, id);
//using var context = new DatabaseContext(_connectionString);
res.Enabled = true;
context.DiscordWebhooks.Update(res);
_context.DiscordWebhooks.Update(res);
try
{
context.SaveChanges();
_context.SaveChanges();
return 1;
}
catch(Exception ex)
@ -102,17 +125,17 @@ public class DiscordWebhooksTable : IDiscordWebHooksRepository
}
}
public int Enable(Guid id)
public int Enable(string userId, Guid id)
{
var res = GetById(id);
using var context = new DatabaseContext(_connectionString);
var res = GetById(userId, id);
//using var context = new DatabaseContext(_connectionString);
res.Enabled = false;
context.DiscordWebhooks.Update(res);
_context.DiscordWebhooks.Update(res);
try
{
context.SaveChanges();
_context.SaveChanges();
return 1;
}
catch(Exception ex)
@ -122,10 +145,10 @@ public class DiscordWebhooksTable : IDiscordWebHooksRepository
}
}
private IDbConnection OpenConnection(string connectionString)
{
var conn = new NpgsqlConnection(_connectionString);
conn.Open();
return conn;
}
//private IDbConnection OpenConnection(string connectionString)
//{
// var conn = new NpgsqlConnection(_connectionString);
// conn.Open();
// return conn;
//}
}

View File

@ -10,49 +10,49 @@ namespace Newsbot.Collector.Database.Repositories;
public class IconsTable : IIconsRepository
{
private readonly string _connectionString;
//private readonly string _connectionString;
private DatabaseContext _context;
public IconsTable(string connectionString)
{
_connectionString = connectionString;
//_connectionString = connectionString;
_context = new DatabaseContext(connectionString);
}
public IconsTable(IConfiguration configuration)
public IconsTable(DatabaseContext context)
{
var connstr = configuration.GetConnectionString("database");
if (connstr is null) connstr = "";
_connectionString = connstr;
_context = context;
}
public void New(IconEntity model)
{
using var context = new DatabaseContext(_connectionString);
//using var context = new DatabaseContext(_connectionString);
model.Id = Guid.NewGuid();
context.Icons.Add(model);
context.SaveChanges();
_context.Icons.Add(model);
_context.SaveChanges();
}
public IconEntity GetById(Guid id)
{
using var context = new DatabaseContext(_connectionString);
var res = context.Icons.FirstOrDefault(f => f.Id.Equals(id));
//using var context = new DatabaseContext(_connectionString);
var res = _context.Icons.FirstOrDefault(f => f.Id.Equals(id));
res ??= new IconEntity();
return res;
}
public IconEntity GetBySourceId(Guid id)
{
using var context = new DatabaseContext(_connectionString);
var res = context.Icons.FirstOrDefault(f => f.SourceId.Equals(id));
//using var context = new DatabaseContext(_connectionString);
var res = _context.Icons.FirstOrDefault(f => f.SourceId.Equals(id));
res ??= new IconEntity();
return res;
}
private IDbConnection OpenConnection(string connectionString)
{
var conn = new NpgsqlConnection(_connectionString);
conn.Open();
return conn;
}
//private IDbConnection OpenConnection(string connectionString)
//{
// var conn = new NpgsqlConnection(_connectionString);
// conn.Open();
// return conn;
//}
}

View File

@ -0,0 +1,42 @@
using Newsbot.Collector.Domain.Entities;
using Newsbot.Collector.Domain.Interfaces;
namespace Newsbot.Collector.Database.Repositories;
public class RefreshTokenTable : IRefreshTokenRepository
{
private readonly DatabaseContext _context;
public RefreshTokenTable(string connectionString)
{
_context = new DatabaseContext(connectionString);
}
public RefreshTokenTable(DatabaseContext context)
{
_context = context;
}
public void Add(RefreshTokenEntity entity)
{
_context.RefreshTokens.Add(entity);
_context.SaveChanges();
}
public RefreshTokenEntity? Get(string token)
{
return _context.RefreshTokens.SingleOrDefault(x => x.Token == token);
}
public void UpdateTokenIsUsed(string token)
{
var entity = Get(token);
if (entity is null)
{
return;
}
entity.Used = true;
_context.RefreshTokens.Update(entity);
_context.SaveChanges();
}
}

View File

@ -1,37 +1,33 @@
using System.Data;
using Dapper;
using Microsoft.Extensions.Configuration;
using Microsoft.EntityFrameworkCore;
using Newsbot.Collector.Domain.Entities;
using Newsbot.Collector.Domain.Interfaces;
using Newsbot.Collector.Domain.Models;
using Npgsql;
namespace Newsbot.Collector.Database.Repositories;
public class SourcesTable : ISourcesRepository
{
private readonly string _connectionString;
//private readonly string _connectionString;
private readonly DatabaseContext _context;
public SourcesTable(string connectionString)
{
_connectionString = connectionString;
//_connectionString = connectionString;
_context = new DatabaseContext(connectionString);
}
public SourcesTable(IConfiguration configuration)
public SourcesTable(DatabaseContext context)
{
var connstr = configuration.GetConnectionString("database");
if (connstr is null) connstr = "";
_connectionString = connstr;
_context = context;
}
public SourceEntity New(SourceEntity model)
{
model.Id = Guid.NewGuid();
using var context = new DatabaseContext(_connectionString);
context.Sources.Add(model);
//using var context = new DatabaseContext(_connectionString);
_context.Sources.Add(model);
try
{
context.SaveChanges();
_context.SaveChanges();
}
catch (Exception ex)
{
@ -43,8 +39,8 @@ public class SourcesTable : ISourcesRepository
public SourceEntity GetById(Guid id)
{
using var context = new DatabaseContext(_connectionString);
var res = context.Sources.FirstOrDefault(f => f.Id.Equals(id));
//using var context = new DatabaseContext(_connectionString);
var res = _context.Sources.FirstOrDefault(f => f.Id.Equals(id));
res ??= new SourceEntity();
return res;
}
@ -57,16 +53,16 @@ public class SourcesTable : ISourcesRepository
public SourceEntity GetByName(string name)
{
using var context = new DatabaseContext(_connectionString);
var res = context.Sources.FirstOrDefault(f => f.Name.Equals(name));
//using var context = new DatabaseContext(_connectionString);
var res = _context.Sources.FirstOrDefault(f => f.Name.Equals(name));
res ??= new SourceEntity();
return res;
}
public SourceEntity GetByNameAndType(string name, string type)
{
using var context = new DatabaseContext(_connectionString);
var res = context.Sources
//using var context = new DatabaseContext(_connectionString);
var res = _context.Sources
.Where(f => f.Name.Equals(name))
.FirstOrDefault(f => f.Type.Equals(type));
res ??= new SourceEntity();
@ -75,8 +71,8 @@ public class SourcesTable : ISourcesRepository
public SourceEntity GetByUrl(string url)
{
using var context = new DatabaseContext(_connectionString);
var res = context.Sources
//using var context = new DatabaseContext(_connectionString);
var res = _context.Sources
.FirstOrDefault(f => f.Url.Equals(url));
res ??= new SourceEntity();
return res;
@ -84,8 +80,8 @@ public class SourcesTable : ISourcesRepository
public List<SourceEntity> List(int page = 0, int count = 100)
{
using var context = new DatabaseContext(_connectionString);
var res = context.Sources
//using var context = new DatabaseContext(_connectionString);
var res = _context.Sources
.Skip(page * count)
.Take(count)
.ToList();
@ -94,8 +90,8 @@ public class SourcesTable : ISourcesRepository
public List<SourceEntity> ListBySource(string source, int page = 0, int limit = 25)
{
using var context = new DatabaseContext(_connectionString);
var res = context.Sources
//using var context = new DatabaseContext(_connectionString);
var res = _context.Sources
.Where(f => f.Source.Equals(source))
.Skip(page * limit)
.Take(limit)
@ -105,8 +101,8 @@ public class SourcesTable : ISourcesRepository
public List<SourceEntity> ListByType(string type,int page = 0, int limit = 25)
{
using var context = new DatabaseContext(_connectionString);
var res = context.Sources
//using var context = new DatabaseContext(_connectionString);
var res = _context.Sources
.Where(f => f.Type.Equals(type))
.Skip(page * limit)
.Take(limit)
@ -114,15 +110,23 @@ public class SourcesTable : ISourcesRepository
return res;
}
public async Task<int> TotalByTypeAsync(string type)
{
var res = await _context.Sources
.Where(f => f.Type == type )
.CountAsync();
return res;
}
public int Disable(Guid id)
{
using var context = new DatabaseContext(_connectionString);
//using var context = new DatabaseContext(_connectionString);
var res = GetById(id);
res.Enabled = false;
context.Sources.Update(res);
_context.Sources.Update(res);
try
{
context.SaveChanges();
_context.SaveChanges();
return 1;
}
catch
@ -133,13 +137,13 @@ public class SourcesTable : ISourcesRepository
public int Enable(Guid id)
{
using var context = new DatabaseContext(_connectionString);
//using var context = new DatabaseContext(_connectionString);
var res = GetById(id);
res.Enabled = true;
context.Sources.Update(res);
_context.Sources.Update(res);
try
{
context.SaveChanges();
_context.SaveChanges();
return 1;
}
catch
@ -150,21 +154,21 @@ public class SourcesTable : ISourcesRepository
public void Delete(Guid id)
{
using var context = new DatabaseContext(_connectionString);
//using var context = new DatabaseContext(_connectionString);
var res = GetById(id);
context.Sources.Remove(res);
context.SaveChanges();
_context.Sources.Remove(res);
_context.SaveChanges();
}
public int UpdateYoutubeId(Guid id, string youtubeId)
{
using var context = new DatabaseContext(_connectionString);
//using var context = new DatabaseContext(_connectionString);
var res = GetById(id);
res.YoutubeId = youtubeId;
context.Sources.Update(res);
_context.Sources.Update(res);
try
{
context.SaveChanges();
_context.SaveChanges();
return 1;
}
catch
@ -173,10 +177,10 @@ public class SourcesTable : ISourcesRepository
}
}
private IDbConnection OpenConnection(string connectionString)
{
var conn = new NpgsqlConnection(_connectionString);
conn.Open();
return conn;
}
//private IDbConnection OpenConnection(string connectionString)
//{
// var conn = new NpgsqlConnection(_connectionString);
// conn.Open();
// return conn;
//}
}

View File

@ -1,93 +0,0 @@
using System.Data;
using Dapper;
using Microsoft.Extensions.Configuration;
using Newsbot.Collector.Domain.Entities;
using Newsbot.Collector.Domain.Interfaces;
using Newsbot.Collector.Domain.Models;
using Npgsql;
namespace Newsbot.Collector.Database.Repositories;
public class SubscriptionsTable : ISubscriptionRepository
{
private readonly string _connectionString;
public SubscriptionsTable(string connectionString)
{
_connectionString = connectionString;
}
public SubscriptionsTable(IConfiguration configuration)
{
var connstr = configuration.GetConnectionString("database");
if (connstr is null) connstr = "";
_connectionString = connstr;
}
public SubscriptionEntity New(SubscriptionEntity model)
{
model.Id = new Guid();
using var context = new DatabaseContext(_connectionString);
context.Subscriptions.Add(model);
context.SaveChanges();
return model;
}
public List<SubscriptionEntity> List(int page = 0, int count = 25)
{
using var context = new DatabaseContext(_connectionString);
return context.Subscriptions.Skip(page * count).Take(count).ToList();
}
public List<SubscriptionEntity> ListBySourceId(Guid id, int page = 0, int count = 25)
{
using var context = new DatabaseContext(_connectionString);
return context.Subscriptions.Where(f => f.SourceId.Equals(id))
.Skip(page * count)
.ToList();
}
public List<SubscriptionEntity> ListByWebhook(Guid id, int page = 0, int count = 25)
{
using var context = new DatabaseContext(_connectionString);
return context.Subscriptions.Where(f => f.DiscordWebHookId.Equals(id)).Skip(page * count).ToList();
}
public SubscriptionEntity GetById(Guid id)
{
using var context = new DatabaseContext(_connectionString);
var res = context.Subscriptions
.FirstOrDefault(f => f.Id.Equals(id));
return res ??= new SubscriptionEntity();
}
public SubscriptionEntity GetByWebhookAndSource(Guid webhookId, Guid sourceId)
{
using var context = new DatabaseContext(_connectionString);
var res = context.Subscriptions
.Where(f => f.DiscordWebHookId.Equals(webhookId))
.FirstOrDefault(f => f.SourceId.Equals(sourceId));
return res ??= new SubscriptionEntity();
}
public void Delete(Guid id)
{
using var context = new DatabaseContext(_connectionString);
var res = context.Subscriptions.FirstOrDefault(f => f.Id.Equals(id));
if (res is null)
{
return;
}
context.Subscriptions.Remove(res);
context.SaveChanges();
}
private IDbConnection OpenConnection(string connectionString)
{
var conn = new NpgsqlConnection(_connectionString);
conn.Open();
return conn;
}
}

View File

@ -0,0 +1,27 @@
using Newsbot.Collector.Domain.Entities;
using Newsbot.Collector.Domain.Interfaces;
namespace Newsbot.Collector.Database.Repositories;
public class UserSourceSubscriptionTable : IUserSourceSubscription
{
private DatabaseContext _context;
public UserSourceSubscriptionTable(string connectionString)
{
_context = new DatabaseContext(connectionString);
}
public UserSourceSubscriptionTable(DatabaseContext context)
{
_context = context;
}
public List<UserSourceSubscriptionEntity> ListUserSubscriptions(Guid userId)
{
var results =_context.UserSourceSubscription
.Where(i => i.UserId != null && i.UserId.Equals(userId)).ToList();
return results;
}
}

View File

@ -0,0 +1,13 @@
namespace Newsbot.Collector.Api.Domain.Consts;
public static class Authorization
{
public const string AdministratorPolicy = "Administrator";
public const string AdministratorsRole = AdministratorPolicy;
public const string AdministratorClaim = "administrator";
public const string UserPolicy = "User";
public const string UsersRole = UserPolicy;
public const string UserClaim = "user";
}

View File

@ -1,10 +0,0 @@
namespace Newsbot.Collector.Domain.Consts;
/// <summary>
/// This class contains const entries to access keys within IConfiguration.
/// </summary>
public static class ConfigConnectionStringConst
{
public const string Database = "ConnectionStrings:Database";
public const string OpenTelemetry = "ConnectionStrings:OpenTelemetry";
}

View File

@ -2,6 +2,9 @@ namespace Newsbot.Collector.Domain.Consts;
public class ConfigConst
{
public const string RunDatabaseMigrationsOnStartup = "RunDatabaseMigrationsOnStartup";
public const string ApiKeys = "ApiKeys";
public const string LoggingDefault = "Logging:LogLevel:Default";
public const string SectionConnectionStrings = "ConnectionStrings";

View File

@ -1,11 +0,0 @@
namespace Newsbot.Collector.Domain.Consts;
/// <summary>
/// This class contains const entries to access keys within IConfiguration.
/// </summary>
public class ConfigTwitchConst
{
public const string IsEnabled = "Twitch:IsEnabled";
public const string ClientID = "Twitch:ClientID";
public const string ClientSecret = "Twitch:ClientSecret";
}

View File

@ -1,10 +0,0 @@
namespace Newsbot.Collector.Domain.Consts;
/// <summary>
/// This class contains const entries to access keys within IConfiguration.
/// </summary>
public class ConfigYoutubeConst
{
public const string IsEnable = "Youtube:IsEnabled";
public const string Debug = "Youtube:Debug";
}

View File

@ -19,8 +19,9 @@ public class ArticleDetailsDto
public string? AuthorImage { get; set; }
public SourceDto? Source { get; set; }
public AuthorEntity? Author { get; set; }
public static ArticleDetailsDto Convert(ArticlesEntity article, SourceEntity source)
public static ArticleDetailsDto Convert(ArticlesEntity article, SourceEntity source, AuthorEntity? author)
{
return new ArticleDetailsDto
{
@ -34,9 +35,8 @@ public class ArticleDetailsDto
VideoWidth = article.VideoWidth,
Thumbnail = article.Thumbnail,
Description = article.Description,
AuthorName = article.AuthorName,
AuthorImage = article.AuthorImage,
Source = SourceDto.Convert(source)
Source = SourceDto.Convert(source),
};
}
}

View File

@ -7,6 +7,7 @@ public class ArticleDto
{
public Guid ID { get; set; }
public Guid SourceID { get; set; }
public Guid AuthorId { get; set; }
public string[]? Tags { get; set; }
public string? Title { get; set; }
public string? Url { get; set; }
@ -16,8 +17,6 @@ public class ArticleDto
public int VideoWidth { get; set; }
public string? Thumbnail { get; set; }
public string? Description { get; set; }
public string? AuthorName { get; set; }
public string? AuthorImage { get; set; }
public static ArticleDto Convert(ArticlesModel article)
{
return new ArticleDto
@ -33,8 +32,6 @@ public class ArticleDto
VideoWidth = article.VideoWidth,
Thumbnail = article.Thumbnail,
Description = article.Description,
AuthorName = article.AuthorName,
AuthorImage = article.AuthorImage,
};
}
@ -44,6 +41,7 @@ public class ArticleDto
{
ID = article.Id,
SourceID = article.SourceId,
AuthorId = article.AuthorId,
Tags = article.Tags.Split(','),
Title = article.Title,
Url = article.Url,
@ -52,9 +50,7 @@ public class ArticleDto
VideoHeight = article.VideoHeight,
VideoWidth = article.VideoWidth,
Thumbnail = article.Thumbnail,
Description = article.Description,
AuthorName = article.AuthorName,
AuthorImage = article.AuthorImage,
Description = article.Description
};
}
}

View File

@ -0,0 +1,9 @@
namespace Newsbot.Collector.Domain.Dto;
public class AuthorDto
{
public Guid Id { get; set; }
public Guid SourceId { get; set; }
public string? Name { get; set; }
public string? Image { get; set; }
}

View File

@ -3,7 +3,7 @@ using Newsbot.Collector.Domain.Models;
namespace Newsbot.Collector.Domain.Dto;
public class SubscriptionDetailsDto
public class DiscordNotificationDetailsDto
{
public Guid Id { get; set; }
public bool CodeAllowReleases { get; set; }
@ -11,14 +11,14 @@ public class SubscriptionDetailsDto
public SourceDto? Source { get; set; }
public DiscordWebHookDto? DiscordWebHook { get; set; }
public static SubscriptionDetailsDto Convert(SubscriptionEntity subscription, SourceEntity source,
public static DiscordNotificationDetailsDto Convert(DiscordNotificationEntity discordNotification, SourceEntity source,
DiscordWebhookEntity discord)
{
return new SubscriptionDetailsDto
return new DiscordNotificationDetailsDto
{
Id = subscription.Id,
CodeAllowCommits = subscription.CodeAllowCommits,
CodeAllowReleases = subscription.CodeAllowReleases,
Id = discordNotification.Id,
CodeAllowCommits = discordNotification.CodeAllowCommits,
CodeAllowReleases = discordNotification.CodeAllowReleases,
Source = SourceDto.Convert(source),
DiscordWebHook = DiscordWebHookDto.Convert(discord)
};

View File

@ -3,7 +3,7 @@ using Newsbot.Collector.Domain.Models;
namespace Newsbot.Collector.Domain.Dto;
public class SubscriptionDto
public class DiscordNotificationDto
{
public Guid Id { get; set; }
public Guid SourceId { get; set; }
@ -11,9 +11,9 @@ public class SubscriptionDto
public bool CodeAllowReleases { get; set; }
public bool CodeAllowCommits { get; set; }
public static SubscriptionDto Convert(SubscriptionEntity model)
public static DiscordNotificationDto Convert(DiscordNotificationEntity model)
{
return new SubscriptionDto
return new DiscordNotificationDto
{
Id = model.Id,
SourceId = model.SourceId,

View File

@ -0,0 +1,7 @@
namespace Newsbot.Collector.Domain.Dto;
public class UserNewDto
{
public string? Username { get; set; }
public string? Password { get; set; }
}

View File

@ -4,6 +4,7 @@ public class ArticlesEntity
{
public Guid Id { get; set; }
public Guid SourceId { get; set; }
public Guid AuthorId { get; set; }
public string Tags { get; set; } = "";
public string Title { get; set; } = "";
public string? Url { get; set; }
@ -13,8 +14,6 @@ public class ArticlesEntity
public int VideoWidth { get; set; } = 0;
public string Thumbnail { get; set; } = "";
public string Description { get; set; } = "";
public string AuthorName { get; set; } = "";
public string? AuthorImage { get; set; }
public bool CodeIsRelease { get; set; }
public bool CodeIsCommit { get; set; }
}

View File

@ -1,6 +1,9 @@
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.AspNetCore.Identity;
namespace Newsbot.Collector.Domain.Entities;
public class SubscriptionEntity
public class DiscordNotificationEntity
{
public Guid Id { get; set; }
public bool CodeAllowReleases { get; set; }
@ -8,4 +11,8 @@ public class SubscriptionEntity
public Guid SourceId { get; set; }
public Guid DiscordWebHookId { get; set; }
public string? UserId { get; set; }
//[ForeignKey(nameof(UserId))]
//public IdentityUser User { get; set; }
}

View File

@ -1,3 +1,6 @@
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.AspNetCore.Identity;
namespace Newsbot.Collector.Domain.Entities;
public class DiscordWebhookEntity
@ -7,4 +10,8 @@ public class DiscordWebhookEntity
public string Server { get; set; } = "";
public string Channel { get; set; } = "";
public bool Enabled { get; set; }
public string? UserId { get; set; }
[ForeignKey(nameof(UserId))]
public IdentityUser? User { get; set; }
}

View File

@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.AspNetCore.Identity;
namespace Newsbot.Collector.Domain.Entities;
public class RefreshTokenEntity
{
[Key]
public string? Token { get; set; }
public string? JwtId { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime ExpiryDate { get; set; }
public bool Used { get; set; }
public bool Invalidated { get; set; }
public string? UserId { get; set; }
[ForeignKey(nameof(UserId))]
public IdentityUser? User { get; set; }
}

View File

@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.AspNetCore.Identity;
namespace Newsbot.Collector.Domain.Entities;
/// <summary>
/// This defines the sources a user will want to see as a feed.
/// </summary>
public class UserSourceSubscriptionEntity
{
[Key]
public Guid Id { get; set; }
public Guid SourceId { get; set; }
public DateTimeOffset DateAdded { get; set; }
public string? UserId { get; set; }
[ForeignKey(nameof(UserId))]
public IdentityUser? User { get; set; }
}

View File

@ -5,7 +5,7 @@ namespace Newsbot.Collector.Domain.Interfaces;
public interface IArticlesRepository : ITableRepository
{
List<ArticlesEntity> List(int page, int count);
List<ArticlesEntity> List(int page, int count = 25);
List<ArticlesEntity> ListBySourceId(Guid id, int page = 0, int count = 25);
ArticlesEntity GetById(Guid id);
ArticlesEntity GetByUrl(string url);

View File

@ -0,0 +1,14 @@
using Newsbot.Collector.Domain.Entities;
namespace Newsbot.Collector.Domain.Interfaces;
public interface IAuthorTable
{
Task<AuthorEntity> NewAsync(AuthorEntity entity);
Task<AuthorEntity> CreateIfMissingAsync(AuthorEntity entity);
Task<List<AuthorEntity>> ListBySourceIdAsync(Guid sourceId);
Task<int> TotalPostsAsync(Guid id);
Task<AuthorEntity?> GetBySourceIdAndNameAsync(Guid sourceId, string name);
Task<AuthorEntity?> GetById(Guid id);
}

View File

@ -1,9 +0,0 @@
using Newsbot.Collector.Domain.Models;
namespace Newsbot.Collector.Domain.Interfaces;
///
public interface ICollector : IHangfireJob
{
List<ArticlesModel> Collect();
}

View File

@ -2,7 +2,7 @@ using Newsbot.Collector.Domain.Models;
namespace Newsbot.Collector.Domain.Interfaces;
public interface IDiscordNotificatioClient
public interface IDiscordClient
{
void SendMessage(DiscordMessage payload);
}

View File

@ -0,0 +1,28 @@
using Newsbot.Collector.Domain.Entities;
using Newsbot.Collector.Domain.Models;
namespace Newsbot.Collector.Domain.Interfaces;
public interface IDiscordNotificationRepository
{
DiscordNotificationEntity New(DiscordNotificationEntity model);
List<DiscordNotificationEntity> List(string userId, int page = 0, int count = 25);
List<DiscordNotificationEntity> ListBySourceId(string userId, Guid id, int page = 0, int count = 25);
/// <summary>
/// This will collect all the records based on the SourceId.
/// Background jobs can use this but user facing calls need to define UserId
/// </summary>
/// <param name="id"></param>
/// <param name="page"></param>
/// <param name="count"></param>
/// <returns></returns>
List<DiscordNotificationEntity> ListBySourceId(Guid id, int page = 0, int count = 25);
List<DiscordNotificationEntity> ListByWebhook(string userId, Guid id, int page = 0, int count = 25);
DiscordNotificationEntity GetById(string userId, Guid id);
DiscordNotificationEntity GetByWebhookAndSource(string userId, Guid webhookId, Guid sourceId);
int Delete(string userId, Guid id);
}

View File

@ -6,13 +6,16 @@ public interface IDiscordWebHooksRepository
{
DiscordWebhookEntity New(DiscordWebhookEntity model);
DiscordWebhookEntity GetById(string userId, Guid id);
DiscordWebhookEntity GetById(Guid id);
DiscordWebhookEntity GetByUrl(string url);
List<DiscordWebhookEntity> List(int page, int count = 25);
List<DiscordWebhookEntity> ListByUserId(string userId, int page);
List<DiscordWebhookEntity> ListByServer(string server, int limit);
List<DiscordWebhookEntity> ListByServerAndChannel(string server, string channel, int limit);
List<DiscordWebhookEntity> ListByServerAndChannel(string userId, string server, string channel, int limit);
int Disable(Guid id);
int Enable(Guid id);
int Disable(string userId, Guid id);
int Enable(string userId, Guid id);
}

View File

@ -0,0 +1,10 @@
using Newsbot.Collector.Domain.Entities;
namespace Newsbot.Collector.Domain.Interfaces;
public interface IRefreshTokenRepository
{
void Add(RefreshTokenEntity entity);
public RefreshTokenEntity? Get(string token);
void UpdateTokenIsUsed(string token);
}

View File

@ -14,6 +14,7 @@ public interface ISourcesRepository
public List<SourceEntity> List(int page, int count);
public List<SourceEntity> ListBySource(string source,int page, int limit);
public List<SourceEntity> ListByType(string type,int page, int limit = 25);
public Task<int> TotalByTypeAsync(string type);
public int Disable(Guid id);
public int Enable(Guid id);
public void Delete(Guid id);

View File

@ -1,18 +0,0 @@
using Newsbot.Collector.Domain.Entities;
using Newsbot.Collector.Domain.Models;
namespace Newsbot.Collector.Domain.Interfaces;
public interface ISubscriptionRepository
{
SubscriptionEntity New(SubscriptionEntity model);
List<SubscriptionEntity> List(int page = 0, int count = 25);
List<SubscriptionEntity> ListBySourceId(Guid id, int page = 0, int count = 25);
List<SubscriptionEntity> ListByWebhook(Guid id, int page = 0, int count = 25);
SubscriptionEntity GetById(Guid id);
SubscriptionEntity GetByWebhookAndSource(Guid webhookId, Guid sourceId);
void Delete(Guid id);
}

View File

@ -0,0 +1,8 @@
using Newsbot.Collector.Domain.Entities;
namespace Newsbot.Collector.Domain.Interfaces;
public interface IUserSourceSubscription
{
List<UserSourceSubscriptionEntity> ListUserSubscriptions(Guid userId);
}

View File

@ -1,13 +1,24 @@
using Newsbot.Collector.Domain.Models.Config;
namespace Newsbot.Collector.Domain.Models;
public class ConfigModel
{
public string? ServerAddress { get; set; }
public string? SqlConnectionString { get; set; }
public RedditConfigModel? Reddit { get; set; }
public ConnectionStrings? ConnectionStrings { get; set; }
public RedditConfig? Reddit { get; set; }
public YoutubeConfig? Youtube { get; set; }
public TwitchConfig? Twitch { get; set; }
public BasicSourceConfig? FinalFantasyXiv { get; set; }
public BasicSourceConfig? Rss { get; set; }
public BasicSourceConfig? CodeProjects { get; set; }
public NotificationsConfig? Notifications { get; set; }
public bool EnableSwagger { get; set; }
public bool RunDatabaseMigrationsOnStartup { get; set; }
public List<string>? ApiKeys { get; set; }
public JwtSettings? JwtSettings { get; set; }
}
public class RedditConfigModel
public class RedditConfig
{
public bool IsEnabled { get; set; }
public bool PullHot { get; set; }
@ -17,5 +28,29 @@ public class RedditConfigModel
public class ConnectionStrings
{
public string Database { get; set; } = "";
}
public string? Database { get; set; }
public string? OpenTelemetry { get; set; }
}
public class BasicSourceConfig
{
public bool IsEnabled { get; set; }
}
public class YoutubeConfig
{
public bool IsEnabled { get; set; }
public bool Debug { get; set; }
}
public class TwitchConfig
{
public bool IsEnabled { get; set; }
public string? ClientId { get; set; }
public string? ClientSecret { get; set; }
}
public class NotificationsConfig
{
public BasicSourceConfig? Discord { get; set; }
}

View File

@ -1,9 +0,0 @@
namespace Newsbot.Collector.Domain.Models.Config;
public class ConfigSectionRedditModel
{
public bool IsEnabled { get; set; }
public bool PullHot { get; set; }
public bool PullNsfw { get; set; }
public bool PullTop { get; set; }
}

View File

@ -0,0 +1,6 @@
namespace Newsbot.Collector.Domain.Models.Config;
public class JwtSettings
{
public string? Secret { get; set; }
}

View File

@ -1,4 +1,4 @@
namespace Newsbot.Collector.Domain.Models.Config;
namespace Newsbot.Collector.Domain.Models.Config.Sources;
public class ConfigSectionRssModel
{

View File

@ -1,4 +1,4 @@
namespace Newsbot.Collector.Domain.Models.Config;
namespace Newsbot.Collector.Domain.Models.Config.Sources;
public class ConfigSectionYoutubeModel
{

View File

@ -4,10 +4,14 @@
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="7.0.8" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="7.0.8" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
</ItemGroup>

View File

@ -0,0 +1,10 @@
namespace Newsbot.Collector.Domain.Requests;
public class NewDiscordNotificationRequest
{
public Guid SourceId { get; set; }
public Guid DiscordId { get; set; }
public bool AllowReleases { get; set; } = false;
public bool AllowCommits { get; set; } = false;
}

View File

@ -0,0 +1,8 @@
namespace Newsbot.Collector.Domain.Requests;
public class NewDiscordWebhookRequest
{
public string? Url { get; set; }
public string? Server { get; set; }
public string? Channel { get; set; }
}

View File

@ -0,0 +1,7 @@
namespace Newsbot.Collector.Domain.Requests;
public class AddRoleRequest
{
public string? RoleName { get; set; }
public string? UserId { get; set; }
}

View File

@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations;
namespace Newsbot.Collector.Domain.Requests;
public class RegisterUserRequest
{
//public string? Name { get; set; }
[EmailAddress]
public string? Email { get; set; }
public string? Password { get; set; }
}

View File

@ -0,0 +1,10 @@
using System.ComponentModel.DataAnnotations;
namespace Newsbot.Collector.Domain.Requests;
public class UserLoginRequest
{
[EmailAddress]
public string? Email { get; set; }
public string? Password { get; set; }
}

View File

@ -0,0 +1,7 @@
namespace Newsbot.Collector.Domain.Requests;
public class UserRefreshTokenRequest
{
public string? Token { get; set; }
public string? RefreshToken { get; set; }
}

View File

@ -0,0 +1,6 @@
namespace Newsbot.Collector.Domain.Response;
public class AuthFailedResponse
{
public IEnumerable<string?>? Errors { get; set; }
}

View File

@ -0,0 +1,9 @@
namespace Newsbot.Collector.Domain.Response;
public class AuthSuccessfulResponse
{
// might want to validate the user before we return the token
public string? Token { get; set; }
public string? RefreshToken { get; set; }
}

View File

@ -0,0 +1,8 @@
using Newsbot.Collector.Domain.Dto;
namespace Newsbot.Collector.Domain.Results;
public class ArticleDetailsResult : BaseResult
{
public ArticleDetailsDto? Item { get; set; }
}

View File

@ -0,0 +1,8 @@
using Newsbot.Collector.Domain.Dto;
namespace Newsbot.Collector.Domain.Results;
public class ArticleResult : BaseResult
{
public IEnumerable<ArticleDto>? Items { get; set; }
}

View File

@ -0,0 +1,7 @@
namespace Newsbot.Collector.Domain.Results;
public class AuthenticationResult : BaseResult
{
public string? Token { get; set; }
public string? RefreshToken { get; set; }
}

View File

@ -0,0 +1,7 @@
namespace Newsbot.Collector.Domain.Results;
public class BaseResult
{
public bool IsSuccessful { get; set; }
public IEnumerable<string>? ErrorMessage { get; set; }
}

View File

@ -0,0 +1,8 @@
using Newsbot.Collector.Domain.Dto;
namespace Newsbot.Collector.Domain.Results;
public class DiscordNotificationDetailsResult : BaseResult
{
public DiscordNotificationDetailsDto? Item { get; set; }
}

View File

@ -0,0 +1,8 @@
using Newsbot.Collector.Domain.Dto;
namespace Newsbot.Collector.Domain.Results;
public class DiscordNotificationResult : BaseResult
{
public IEnumerable<DiscordNotificationDto>? Items { get; set; }
}

View File

@ -0,0 +1,8 @@
using Newsbot.Collector.Domain.Dto;
namespace Newsbot.Collector.Domain.Results;
public class DiscordWebhookResult : BaseResult
{
public List<DiscordWebHookDto>? Items { get; set; }
}

View File

@ -36,11 +36,19 @@ public class HtmlPageReader
private string ReadSiteContent(string url)
{
using var client = new HttpClient();
var html = client.GetStringAsync(url);
html.Wait();
try
{
var html = client.GetStringAsync(url);
html.Wait();
var content = html.Result;
return content;
var content = html.Result;
return content;
}
catch (Exception ex)
{
Console.WriteLine($"Failed to connect to '{url}'. {ex.Message}");
return "";
}
}
public string GetSiteContent()

View File

@ -0,0 +1,282 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.AspNetCore.Identity;
using Microsoft.IdentityModel.Tokens;
using Newsbot.Collector.Api.Domain.Consts;
using Newsbot.Collector.Domain.Results;
using Newsbot.Collector.Domain.Entities;
using Newsbot.Collector.Domain.Interfaces;
using Newsbot.Collector.Domain.Models.Config;
using JwtRegisteredClaimNames = Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames;
namespace Newsbot.Collector.Services;
public interface IIdentityService
{
AuthenticationResult Register(string email, string password);
AuthenticationResult Login(string email, string password);
AuthenticationResult RefreshToken(string token, string refreshToken);
void AddRole(string name, string userId);
}
public class IdentityService : IIdentityService
{
private readonly UserManager<IdentityUser> _userManager;
private readonly RoleManager<IdentityRole> _roleManager;
private readonly JwtSettings _jwtSettings;
private readonly TokenValidationParameters _tokenValidationParameters;
private readonly IRefreshTokenRepository _refreshTokenRepository;
public IdentityService(UserManager<IdentityUser> userManager, JwtSettings jwtSettings, TokenValidationParameters tokenValidationParameters, IRefreshTokenRepository refreshTokenRepository, RoleManager<IdentityRole> roleManager)
{
_userManager = userManager;
_jwtSettings = jwtSettings;
_tokenValidationParameters = tokenValidationParameters;
_refreshTokenRepository = refreshTokenRepository;
_roleManager = roleManager;
}
public AuthenticationResult Register(string email, string password)
{
var userExists = _userManager.FindByEmailAsync(email);
userExists.Wait();
if (userExists.Result != null)
{
return new AuthenticationResult
{
ErrorMessage = new[] { "A user with this email address already exists" }
};
}
var newUser = new IdentityUser
{
UserName = email,
Email = email
};
var createdUser = _userManager.CreateAsync(newUser, password);
createdUser.Wait();
if (!createdUser.Result.Succeeded)
{
return new AuthenticationResult
{
ErrorMessage = new List<string>(createdUser.Result.Errors.Select(x => x.Description))
};
}
var addRole = _userManager.AddToRoleAsync(newUser, Authorization.UsersRole);
addRole.Wait();
return GenerateJwtToken(newUser);
}
public AuthenticationResult Login(string email, string password)
{
var user =_userManager.FindByEmailAsync(email);
user.Wait();
if (user.Result == null)
{
return new AuthenticationResult
{
ErrorMessage = new[] { "User does not exist" }
};
}
var hasValidPassword = _userManager.CheckPasswordAsync(user.Result ?? new IdentityUser(), password);
hasValidPassword.Wait();
if (!hasValidPassword.Result)
{
return new AuthenticationResult()
{
ErrorMessage = new[] { "Password is invalid" }
};
}
return GenerateJwtToken(user.Result ?? new IdentityUser());
}
public AuthenticationResult RefreshToken(string token, string refreshToken)
{
var validatedToken = CheckTokenSigner(token);
if (validatedToken is null)
{
return new AuthenticationResult
{
ErrorMessage = new List<string> { "Invalid Token" }
};
}
// Get the expire datetime of the token
var expiryDateUnix = long.Parse(validatedToken.Claims.Single(x => x.Type == JwtRegisteredClaimNames.Exp).Value);
// generate the unix epoc, add expiry time
var unixTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var expiryDateTimeUtc = unixTime.AddSeconds(expiryDateUnix);
// if it expires in the future
if (expiryDateTimeUtc > DateTime.Now)
{
return new AuthenticationResult
{
ErrorMessage = new List<string> { "The token has not expired yet" }
};
}
var jti = validatedToken.Claims.Single(x => x.Type == JwtRegisteredClaimNames.Jti).Value;
var storedToken = _refreshTokenRepository.Get(token);
if (storedToken is null)
{
return new AuthenticationResult
{
ErrorMessage = new List<string> { "The refresh token does not exist" }
};
}
if (DateTime.UtcNow > storedToken.ExpiryDate)
{
return new AuthenticationResult
{
ErrorMessage = new List<string> { "The refresh token has expired" }
};
}
if (storedToken.Invalidated)
{
return new AuthenticationResult
{
ErrorMessage = new List<string> { "The token is not valid" }
};
}
if (storedToken.Used)
{
return new AuthenticationResult
{
ErrorMessage = new List<string> { "The token has been used" }
};
}
if (storedToken.JwtId != jti)
{
return new AuthenticationResult
{
ErrorMessage = new List<string> { "The token does not match this JWT" }
};
}
_refreshTokenRepository.UpdateTokenIsUsed(token);
var user = _userManager.FindByIdAsync(validatedToken.Claims.Single(x => x.Type == "id").Value);
user.Wait();
if (user.Result is null)
{
return new AuthenticationResult
{
ErrorMessage = new List<string> { "Unable to find user" }
};
}
return GenerateJwtToken(user.Result);
}
public void AddRole(string name, string userId)
{
var user = _userManager.FindByIdAsync(userId);
user.Wait();
if (user.Result is null)
{
throw new Exception("User was not found");
}
if (!name.Equals(Authorization.AdministratorClaim)
|| !name.Equals(Authorization.UserClaim))
{
throw new Exception("Invalid role");
}
var addRole = _userManager.AddToRoleAsync(user.Result, name);
addRole.Wait();
}
private ClaimsPrincipal? CheckTokenSigner(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
try
{
var principal = tokenHandler.ValidateToken(token, _tokenValidationParameters, out var validatedToken);
if (IsSecurityTokenValidSecurity(validatedToken))
{
return null;
}
return principal;
}
catch
{
return null;
}
}
private bool IsSecurityTokenValidSecurity(SecurityToken token)
{
return (token is JwtSecurityToken jwtSecurityToken) && jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha512, StringComparison.InvariantCultureIgnoreCase);
}
private AuthenticationResult GenerateJwtToken(IdentityUser user)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_jwtSettings.Secret ?? "");
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, user.Email ?? ""),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Email, user.Email ?? ""),
new Claim("id", user.Id)
};
var userRoles = _userManager.GetRolesAsync(user);
userRoles.Wait();
foreach (var role in userRoles.Result)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddHours(3),
SigningCredentials =
new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var refreshToken = new RefreshTokenEntity
{
Token = token.Id,
JwtId = token.Id,
UserId = user.Id,
CreatedDate = DateTime.UtcNow,
ExpiryDate = DateTime.UtcNow.AddMonths(6)
};
_refreshTokenRepository.Add(refreshToken);
return new AuthenticationResult
{
IsSuccessful = true,
Token = tokenHandler.WriteToken(token),
RefreshToken = refreshToken.Token
};
}
}

View File

@ -30,6 +30,7 @@ public class CodeProjectWatcherJob
private ILogger _logger;
private IDiscordQueueRepository _queue;
private ISourcesRepository _source;
private IAuthorTable _author;
public CodeProjectWatcherJob()
{
@ -37,12 +38,14 @@ public class CodeProjectWatcherJob
_queue = new DiscordQueueTable("");
_source = new SourcesTable("");
_logger = JobLogger.GetLogger("", JobName);
_author = new AuthorsTable("");
}
public CodeProjectWatcherJob(CodeProjectWatcherJobOptions options)
{
options.ConnectionStrings ??= new ConfigSectionConnectionStrings();
_articles = new ArticlesTable(options.ConnectionStrings.Database ?? "");
_author = new AuthorsTable(options.ConnectionStrings.Database ?? "");
_queue = new DiscordQueueTable(options.ConnectionStrings.Database ?? "");
_source = new SourcesTable(options.ConnectionStrings.Database ?? "");
_logger = JobLogger.GetLogger(options.ConnectionStrings.OpenTelemetry ?? "", JobName);
@ -52,6 +55,7 @@ public class CodeProjectWatcherJob
{
options.ConnectionStrings ??= new ConfigSectionConnectionStrings();
_articles = new ArticlesTable(options.ConnectionStrings.Database ?? "");
_author = new AuthorsTable(options.ConnectionStrings.Database ?? "");
_queue = new DiscordQueueTable(options.ConnectionStrings.Database ?? "");
_source = new SourcesTable(options.ConnectionStrings.Database ?? "");
_logger = JobLogger.GetLogger(options.ConnectionStrings.OpenTelemetry ?? "", JobName);
@ -157,17 +161,29 @@ public class CodeProjectWatcherJob
});
parser.Parse();
if (item.Authors[0].Name is null)
{
_logger.Warning("Author was missing from the record and will continue with a missing author");
}
var authorExists = _author.CreateIfMissingAsync(new AuthorEntity
{
Name = item.Authors[0].Name,
SourceId = source.Id,
Image = "",
});
authorExists.Wait();
var a = new ArticlesEntity
{
SourceId = source.Id,
AuthorId = authorExists.Result.Id,
Tags = source.Tags,
Title = item.Title.Text,
Url = itemUrl,
PubDate = item.LastUpdatedTime.DateTime,
PubDate = item.LastUpdatedTime.DateTime.ToUniversalTime(),
Thumbnail = parser.Data.Header.Image,
Description = item.Title.Text,
AuthorName = item.Authors[0].Name ?? "",
AuthorImage = item.Authors[0].Uri ?? "",
CodeIsRelease = isRelease,
CodeIsCommit = isCommit,
};

View File

@ -1,3 +1,4 @@
using Newsbot.Collector.Database;
using Newsbot.Collector.Database.Repositories;
using Newsbot.Collector.Domain.Entities;
using Newsbot.Collector.Domain.Interfaces;
@ -32,33 +33,36 @@ public class DiscordNotificationJobOptions
public class DiscordNotificationJob
{
private const string JobName = "DiscordNotifications";
private IArticlesRepository _article;
private IIconsRepository _icons;
private ILogger _logger;
//private DatabaseContext _databaseContext;
private IArticlesRepository _article;
private IAuthorTable _author;
private IIconsRepository _icons;
private IDiscordQueueRepository _queue;
private ISourcesRepository _sources;
private ISubscriptionRepository _subs;
private IDiscordNotificationRepository _subs;
private IDiscordWebHooksRepository _webhook;
public DiscordNotificationJob()
{
_queue = new DiscordQueueTable("");
_article = new ArticlesTable("");
_author = new AuthorsTable("");
_webhook = new DiscordWebhooksTable("");
_sources = new SourcesTable("");
_subs = new SubscriptionsTable("");
_subs = new DiscordNotificationTable("");
_icons = new IconsTable("");
_logger = JobLogger.GetLogger("", JobName);
}
public void InitAndExecute(DiscordNotificationJobOptions options)
{
//_databaseContext = new DatabaseContext(options.ConnectionString ?? "");
_queue = new DiscordQueueTable(options.ConnectionString ?? "");
_article = new ArticlesTable(options.ConnectionString ?? "");
_webhook = new DiscordWebhooksTable(options.ConnectionString ?? "");
_sources = new SourcesTable(options.ConnectionString ?? "");
_subs = new SubscriptionsTable(options.ConnectionString ?? "");
_subs = new DiscordNotificationTable(options.ConnectionString ?? "");
_icons = new IconsTable(options.ConnectionString ?? "");
_logger = JobLogger.GetLogger(options.OpenTelemetry ?? "", JobName);
@ -76,6 +80,7 @@ public class DiscordNotificationJob
private void Execute()
{
//collect all the new requests
var requests = _queue.List(100);
_logger.Debug($"{JobName} - Collected {requests.Count} items to send");
@ -89,7 +94,6 @@ public class DiscordNotificationJob
_logger.Debug($"{JobName} - Processing {request.Id}");
// Get all details on the article in the queue
var articleDetails = _article.GetById(request.ArticleId);
// Get the details of the source
var sourceDetails = _sources.GetById(articleDetails.SourceId);
if (sourceDetails.Id == Guid.Empty)
@ -100,6 +104,9 @@ public class DiscordNotificationJob
return;
}
var author = _author.GetBySourceIdAndNameAsync(sourceDetails.Id, sourceDetails.Name);
author.Wait();
var sourceIcon = new IconEntity();
try
{
@ -115,13 +122,13 @@ public class DiscordNotificationJob
var allSubscriptions = _subs.ListBySourceId(sourceDetails.Id);
foreach (var sub in allSubscriptions)
SendSubscriptionNotification(request.Id, articleDetails, sourceDetails, sourceIcon, sub);
SendSubscriptionNotification(request.Id, articleDetails, sourceDetails, sourceIcon, sub, author.Result ?? new AuthorEntity());
_logger.Debug("{JobName} - Removing {RequestId} from the queue", JobName, request.Id);
_queue.Delete(request.Id);
}
public void SendSubscriptionNotification(Guid requestId, ArticlesEntity articleDetails, SourceEntity sourceDetails, IconEntity sourceIcon, SubscriptionEntity sub)
public void SendSubscriptionNotification(Guid requestId, ArticlesEntity articleDetails, SourceEntity sourceDetails, IconEntity sourceIcon, DiscordNotificationEntity sub, AuthorEntity authorEntity)
{
// Check if the subscription code flags
// If the article is a code commit and the subscription does not want them, skip.
@ -134,10 +141,10 @@ public class DiscordNotificationJob
var discordDetails = _webhook.GetById(sub.DiscordWebHookId);
if (discordDetails.Enabled == false) return;
var client = new DiscordWebhookClient(discordDetails.Url);
var client = new DiscordClient(discordDetails.Url);
try
{
client.SendMessage(GenerateDiscordMessage(sourceDetails, articleDetails, sourceIcon));
client.SendMessage(GenerateDiscordMessage(sourceDetails, articleDetails, sourceIcon, authorEntity));
}
catch (Exception e)
{
@ -151,7 +158,7 @@ public class DiscordNotificationJob
Thread.Sleep(3000);
}
public DiscordMessage GenerateDiscordMessage(SourceEntity source, ArticlesEntity article, IconEntity icon)
public DiscordMessage GenerateDiscordMessage(SourceEntity source, ArticlesEntity article, IconEntity icon, AuthorEntity author)
{
var embed = new DiscordMessageEmbed
{
@ -160,7 +167,7 @@ public class DiscordNotificationJob
Description = MessageValidation.ConvertHtmlCodes(article.Description),
Author = new DiscordMessageEmbedAuthor
{
Name = article.AuthorName,
Name = author.Name,
IconUrl = icon.FileName
},
Footer = new DiscordMessageEmbedFooter
@ -186,7 +193,7 @@ public class DiscordNotificationJob
Url = article.Thumbnail
};
if (article.AuthorImage is not null && article.AuthorImage != "") embed.Author.IconUrl = article.AuthorImage;
embed.Author.IconUrl = author.Image ?? "";
return new DiscordMessage
{

View File

@ -105,7 +105,7 @@ public class RssWatcherJob
Title = post.Title.Text,
Tags = FetchTags(post),
Url = articleUrl,
PubDate = post.PublishDate.DateTime,
PubDate = post.PublishDate.DateTime.ToUniversalTime(),
Thumbnail = meta.Data.Header.Image,
Description = meta.Data.Header.Description,
SourceId = sourceId

View File

@ -25,6 +25,7 @@ public class YoutubeWatcherJob
private readonly YoutubeWatcherJobOptions _options;
private IArticlesRepository _articles;
private IAuthorTable _author;
private IIconsRepository _icons;
private ILogger _logger;
private IDiscordQueueRepository _queue;
@ -34,6 +35,7 @@ public class YoutubeWatcherJob
{
_options = new YoutubeWatcherJobOptions();
_articles = new ArticlesTable("");
_author = new AuthorsTable("");
_queue = new DiscordQueueTable("");
_source = new SourcesTable("");
_icons = new IconsTable("");
@ -43,6 +45,7 @@ public class YoutubeWatcherJob
public void InitAndExecute(YoutubeWatcherJobOptions options)
{
_articles = new ArticlesTable(options.DatabaseConnectionString ?? "");
_author = new AuthorsTable(options.DatabaseConnectionString ?? "");
_queue = new DiscordQueueTable(options.DatabaseConnectionString ?? "");
_source = new SourcesTable(options.DatabaseConnectionString ?? "");
_icons = new IconsTable(options.DatabaseConnectionString ?? "");
@ -53,7 +56,9 @@ public class YoutubeWatcherJob
private void Execute()
{
var sources = _source.ListByType(SourceTypes.YouTube, 100);
var totalSources = _source.TotalByTypeAsync(SourceTypes.YouTube);
var sources = _source.ListByType(SourceTypes.YouTube, 0);
foreach (var source in sources)
{
@ -77,11 +82,23 @@ public class YoutubeWatcherJob
_logger.Information($"{JobName} - Checking '{source.Name}'");
var url = $"https://www.youtube.com/feeds/videos.xml?channel_id={channelId}";
var newVideos = CheckFeed(url, source);
var newVideos = FindMissingPosts(url, source);
_logger.Debug($"{JobName} - Collected {newVideos.Count} new videos");
foreach (var video in newVideos)
{
_logger.Debug($"{JobName} - {video.AuthorName} '{video.Title}' was found");
var author = _author.GetById(video.AuthorId);
author.Wait();
if (author.Result is null)
{
_logger.Warning("Missing author record for article id {VideoId}", video.Id);
}
else
{
_logger.Debug("{JobName} - {ResultName} \'{VideoTitle}\' was found", JobName, author.Result.Name, video.Title);
}
_articles.New(video);
_queue.New(new DiscordQueueEntity
{
@ -104,13 +121,12 @@ public class YoutubeWatcherJob
var id = pageReader.Data.Header.YoutubeChannelID ?? "";
if (id == "")
_logger.Error(new Exception($"{JobName} - Unable to find the Youtube Channel ID for the requested url."),
url);
_logger.Error(new Exception($"{JobName} - Unable to find the Youtube Channel ID for the requested url"), "");
return id;
}
private List<ArticlesEntity> CheckFeed(string url, SourceEntity source)
private List<ArticlesEntity> FindMissingPosts(string url, SourceEntity source)
{
var videos = new List<ArticlesEntity>();
@ -118,37 +134,49 @@ public class YoutubeWatcherJob
var feed = SyndicationFeed.Load(reader);
foreach (var post in feed.Items.ToList())
{
var articleUrl = post.Links[0].Uri.AbsoluteUri;
if (IsThisUrlKnown(articleUrl)) continue;
var videoDetails = new HtmlPageReader(new HtmlPageReaderOptions
{
Url = articleUrl
});
videoDetails.Parse();
var article = new ArticlesEntity
{
//Todo add the icon
AuthorName = post.Authors[0].Name,
Title = post.Title.Text,
Tags = FetchTags(post),
Url = articleUrl,
PubDate = post.PublishDate.DateTime,
Thumbnail = videoDetails.Data.Header.Image,
Description = videoDetails.Data.Header.Description,
SourceId = source.Id,
Video = "true"
};
var article = CheckFeedItem(post, source.Id);
if (article is null) continue;
videos.Add(article);
Thread.Sleep(_options.SleepTimer);
}
return videos;
}
private ArticlesEntity? CheckFeedItem(SyndicationItem post, Guid sourceId)
{
var articleUrl = post.Links[0].Uri.AbsoluteUri;
if (IsThisUrlKnown(articleUrl)) return null;
var videoDetails = new HtmlPageReader(new HtmlPageReaderOptions
{
Url = articleUrl
});
videoDetails.Parse();
var author = _author.CreateIfMissingAsync(new AuthorEntity
{
Image = post.Authors[0].Uri,
Name = post.Authors[0].Name
});
author.Wait();
var article = new ArticlesEntity
{
//Todo add the icon
AuthorId = author.Result.Id,
Title = post.Title.Text,
Tags = FetchTags(post),
Url = articleUrl,
PubDate = post.PublishDate.DateTime.ToUniversalTime(),
Thumbnail = videoDetails.Data.Header.Image,
Description = videoDetails.Data.Header.Description,
SourceId = sourceId,
Video = "true"
};
return article;
}
private bool IsThisUrlKnown(string url)
{
var isKnown = _articles.GetByUrl(url);

View File

@ -11,11 +11,14 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="6.15.1" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.15.1" />
<PackageReference Include="Selenium.WebDriver" Version="4.8.1" />
<PackageReference Include="Selenium.WebDriver.GeckoDriver" Version="0.32.2" />
<PackageReference Include="Serilog" Version="2.12.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.OpenTelemetry" Version="1.0.0-dev-00113" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.15.1" />
<PackageReference Include="System.ServiceModel.Syndication" Version="7.0.0" />
</ItemGroup>
@ -23,6 +26,8 @@
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>

Some files were not shown because too many files have changed in this diff Show More