Huge cleanup and rework pass, postgres now usable too

This commit is contained in:
hak8or 2018-02-19 23:57:14 -05:00
parent d996f7a2cb
commit a3a3c3a780
18 changed files with 472 additions and 110 deletions

View File

@ -13,35 +13,40 @@ namespace YTManager.Controllers {
[Produces("application/json")] [Produces("application/json")]
[Route("api/Admin")] [Route("api/Admin")]
public class AdminController : Controller { public class AdminController : Controller {
// POST api/Admin/refreshytvids // ID for mass update job, used to tell if the job is running or not.
// Force a general youtube channel update. public static string Mass_Updater_ID { get; } = "2013066213";
[HttpPost("job_update")]
public void Post_forcejobupdate() { // POST api/Admin/Trigger_Mass_Update
Hangfire.RecurringJob.Trigger(Startup.periodicupdatejobID); // Trigger a mass channel update.
[HttpPost("Trigger_Mass_Update")]
public IActionResult Trigger_Mass_Update() {
Hangfire.RecurringJob.Trigger(Mass_Updater_ID);
return Ok();
} }
// POST api/Admin/job_periodicupdate/YTChannelID // POST api/Admin/Update/YTChannelID
// Force a YT Channel update. // Update the videos for a specific channel.
[HttpPost("job_update/{id}")] [HttpPost("Update/{YoutubeID}")]
public void Post_forcejobupdateID([FromRoute] string id) { public IActionResult Update([FromRoute] string YoutubeID) {
Hangfire.BackgroundJob.Enqueue(() => YTManager.Tasks.FetchVideos.run(id)); Hangfire.BackgroundJob.Enqueue(() => Tasks.FetchVideos.ChannelUpdate(Startup.DBStr, YoutubeID));
return Ok();
} }
// POST api/Admin/job_periodicupdate // POST api/Admin/Start_Updater
// Ensures that the background YT Channel update API is running. // Ensures that the background YT Channel update API is running.
[HttpPost("job_periodicupdate")] [HttpPost("Start_Updater")]
public void Post_periodicupdatejob() { public IActionResult Start_Updater() {
Hangfire.RecurringJob.AddOrUpdate( Hangfire.RecurringJob.AddOrUpdate(
Startup.periodicupdatejobID, Mass_Updater_ID,
() => YTManager.Tasks.FetchVideos.run(""), () => Tasks.FetchVideos.MassUpdate(Startup.DBStr), Hangfire.Cron.Hourly);
Hangfire.Cron.Hourly); return Ok();
} }
// GET api/Admin/job_periodicupdate // GET api/Admin/Updater
// Check if the periodic update job is enqued. // Check if the periodic update job is enqued.
[HttpGet("job_periodicupdate")] [HttpGet("Update")]
public IActionResult Get_periodicupdatejob() { public IActionResult Get_Update_Status() {
bool exists = Hangfire.JobStorage.Current.GetConnection().GetRecurringJobs().Count(j => j.Id == Startup.periodicupdatejobID) > 0; bool exists = Hangfire.JobStorage.Current.GetConnection().GetRecurringJobs().Any(j => j.Id == Mass_Updater_ID);
return Ok(exists ? "true" : "false"); return Ok(exists ? "true" : "false");
} }
} }

View File

@ -28,7 +28,7 @@ namespace YTManager.Controllers {
// GET api/Channels/5 // GET api/Channels/5
[HttpGet("{YTchannelID}")] [HttpGet("{YTchannelID}")]
public Task<Models.Channel> Get([FromQuery] string YTchannelID) { public Task<Models.Channel> Get([FromQuery] string YTchannelID) {
return db.Channels.SingleOrDefaultAsync(c => c.YTChannelID == YTchannelID); return db.Channels.SingleOrDefaultAsync(c => c.YoutubeID == YTchannelID);
} }
// GET: api/Channels/sdfs6DFS65f/Videos (using YouTube channel ID) // GET: api/Channels/sdfs6DFS65f/Videos (using YouTube channel ID)
@ -38,7 +38,7 @@ namespace YTManager.Controllers {
return BadRequest(ModelState); return BadRequest(ModelState);
// Attempt to get the video from the database. // Attempt to get the video from the database.
var channel = await db.Channels.SingleOrDefaultAsync(m => m.YTChannelID == YTchannelID); var channel = await db.Channels.SingleOrDefaultAsync(m => m.YoutubeID == YTchannelID);
// If the channel wasn't found then send back not found. // If the channel wasn't found then send back not found.
if (channel == null) if (channel == null)
@ -48,6 +48,27 @@ namespace YTManager.Controllers {
return Ok(db.Entry(channel).Collection(c => c.Videos).LoadAsync()); return Ok(db.Entry(channel).Collection(c => c.Videos).LoadAsync());
} }
// POST api/Channels/sdfs6DFS65f
[HttpPost("{YTchannelID}")]
public async Task<IActionResult> Post([FromRoute] string YTchannelID) {
// Check if we were able to parse.
if (!ModelState.IsValid) return BadRequest(ModelState);
// Verify the channel doesn't already exist.
if (db.Channels.Any(c => c.YoutubeID == YTchannelID))
return BadRequest();
// Get the channel info.
var ch = await Tasks.FetchVideos.Get_YTChannel(YTchannelID);
// Seems good, so add it.
db.Channels.Add(ch);
await db.SaveChangesAsync();
// And all is well.
return Ok();
}
// POST api/Channels // POST api/Channels
[HttpPost] [HttpPost]
public async Task<IActionResult> Post([FromBody] Channel channel) { public async Task<IActionResult> Post([FromBody] Channel channel) {
@ -55,28 +76,25 @@ namespace YTManager.Controllers {
if (!ModelState.IsValid) return BadRequest(ModelState); if (!ModelState.IsValid) return BadRequest(ModelState);
// Verify the channel doesn't already exist. // Verify the channel doesn't already exist.
if (db.Channels.Any(c => c.YTChannelID == channel.YTChannelID)) if (db.Channels.Any(c => c.YoutubeID == channel.YoutubeID))
return BadRequest(); return BadRequest();
// Seems good, so add it. // Seems good, so add it.
db.Channels.Add(channel); db.Channels.Add(channel);
await db.SaveChangesAsync(); await db.SaveChangesAsync();
// Get all new videos for the channel.
Hangfire.RecurringJob.Trigger(Startup.periodicupdatejobID);
// And all is well. // And all is well.
return Ok(); return Ok();
} }
// DELETE api/Channels/5 // DELETE api/Channels/5
[HttpDelete("{YTChannelID}")] [HttpDelete("{YTChannelID}")]
public async Task<IActionResult> Delete([FromQuery] string YTChannelID) { public async Task<IActionResult> Delete([FromRoute] string YTChannelID) {
// Check if we were able to parse. // Check if we were able to parse.
if (!ModelState.IsValid) return BadRequest(ModelState); if (!ModelState.IsValid) return BadRequest(ModelState);
// Attempt to find the channel. // Attempt to find the channel.
var channel = await db.Channels.SingleOrDefaultAsync(c => c.YTChannelID == YTChannelID); var channel = await db.Channels.SingleOrDefaultAsync(c => c.YoutubeID == YTChannelID);
// Check if such an entry exists already. // Check if such an entry exists already.
if (channel == null) if (channel == null)

View File

@ -31,7 +31,7 @@ namespace YTManager.Controllers {
if (!ModelState.IsValid) return BadRequest(ModelState); if (!ModelState.IsValid) return BadRequest(ModelState);
// Attempt to get the video from the database. // Attempt to get the video from the database.
var video = await db.Videos.SingleOrDefaultAsync(m => m.key == id); var video = await db.Videos.SingleOrDefaultAsync(m => m.PrimaryKey == id);
// If the video wasn't found then send back not foud. // If the video wasn't found then send back not foud.
if (video == null) if (video == null)
@ -47,7 +47,7 @@ namespace YTManager.Controllers {
if (!ModelState.IsValid) return BadRequest(ModelState); if (!ModelState.IsValid) return BadRequest(ModelState);
// Check if such a database exists already. // Check if such a database exists already.
if (await db.Videos.AnyAsync(d => d.YTVideoID == video.YTVideoID)) if (await db.Videos.AnyAsync(d => d.YoutubeID == video.YoutubeID))
return BadRequest(); return BadRequest();
// Add our video to the database and tell db to update. // Add our video to the database and tell db to update.
@ -65,7 +65,7 @@ namespace YTManager.Controllers {
if (!ModelState.IsValid) return BadRequest(ModelState); if (!ModelState.IsValid) return BadRequest(ModelState);
// Attempt to find the video. // Attempt to find the video.
var video = await db.Videos.SingleOrDefaultAsync(m => m.YTVideoID == YTVideoID); var video = await db.Videos.SingleOrDefaultAsync(m => m.YoutubeID == YTVideoID);
// Check if such a database exists already. // Check if such a database exists already.
if (video == null) if (video == null)

View File

@ -12,10 +12,9 @@ namespace YTManager
public DbSet<Models.Video> Videos { get; set; } public DbSet<Models.Video> Videos { get; set; }
public DbSet<Models.Tag> Tags { get; set; } public DbSet<Models.Tag> Tags { get; set; }
public MediaDB(DbContextOptions<MediaDB> options) :base(options){ } public MediaDB(DbContextOptions<MediaDB> options) : base(options){ }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
{
base.OnConfiguring(optionsBuilder); base.OnConfiguring(optionsBuilder);
} }
} }

View File

@ -0,0 +1,100 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using System;
using YTManager;
namespace YTManager.Migrations
{
[DbContext(typeof(MediaDB))]
[Migration("20180220032847_initiailmigration")]
partial class initiailmigration
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
.HasAnnotation("ProductVersion", "2.0.1-rtm-125");
modelBuilder.Entity("YTManager.Models.Channel", b =>
{
b.Property<long>("PrimaryKey")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedtoDB");
b.Property<string>("Description")
.IsRequired();
b.Property<string>("ThumbnailURL")
.IsRequired();
b.Property<string>("Title")
.IsRequired();
b.Property<string>("YoutubeID")
.IsRequired();
b.HasKey("PrimaryKey");
b.ToTable("Channels");
});
modelBuilder.Entity("YTManager.Models.Tag", b =>
{
b.Property<long>("PrimaryKey")
.ValueGeneratedOnAdd();
b.Property<string>("Name")
.IsRequired();
b.HasKey("PrimaryKey");
b.ToTable("Tags");
});
modelBuilder.Entity("YTManager.Models.Video", b =>
{
b.Property<long>("PrimaryKey")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedToYT");
b.Property<DateTime>("AddedtoDB");
b.Property<long?>("ChannelPrimaryKey");
b.Property<string>("Description")
.IsRequired();
b.Property<string>("ThumbnailURL")
.IsRequired();
b.Property<string>("Title")
.IsRequired();
b.Property<string>("YoutubeID")
.IsRequired();
b.HasKey("PrimaryKey");
b.HasIndex("ChannelPrimaryKey");
b.ToTable("Videos");
});
modelBuilder.Entity("YTManager.Models.Video", b =>
{
b.HasOne("YTManager.Models.Channel")
.WithMany("Videos")
.HasForeignKey("ChannelPrimaryKey");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,85 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace YTManager.Migrations
{
public partial class initiailmigration : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Channels",
columns: table => new
{
PrimaryKey = table.Column<long>(nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn),
AddedtoDB = table.Column<DateTime>(nullable: false),
Description = table.Column<string>(nullable: false),
ThumbnailURL = table.Column<string>(nullable: false),
Title = table.Column<string>(nullable: false),
YoutubeID = table.Column<string>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Channels", x => x.PrimaryKey);
});
migrationBuilder.CreateTable(
name: "Tags",
columns: table => new
{
PrimaryKey = table.Column<long>(nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn),
Name = table.Column<string>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Tags", x => x.PrimaryKey);
});
migrationBuilder.CreateTable(
name: "Videos",
columns: table => new
{
PrimaryKey = table.Column<long>(nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn),
AddedToYT = table.Column<DateTime>(nullable: false),
AddedtoDB = table.Column<DateTime>(nullable: false),
ChannelPrimaryKey = table.Column<long>(nullable: true),
Description = table.Column<string>(nullable: false),
ThumbnailURL = table.Column<string>(nullable: false),
Title = table.Column<string>(nullable: false),
YoutubeID = table.Column<string>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Videos", x => x.PrimaryKey);
table.ForeignKey(
name: "FK_Videos_Channels_ChannelPrimaryKey",
column: x => x.ChannelPrimaryKey,
principalTable: "Channels",
principalColumn: "PrimaryKey",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_Videos_ChannelPrimaryKey",
table: "Videos",
column: "ChannelPrimaryKey");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Tags");
migrationBuilder.DropTable(
name: "Videos");
migrationBuilder.DropTable(
name: "Channels");
}
}
}

View File

@ -0,0 +1,99 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using System;
using YTManager;
namespace YTManager.Migrations
{
[DbContext(typeof(MediaDB))]
partial class MediaDBModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
.HasAnnotation("ProductVersion", "2.0.1-rtm-125");
modelBuilder.Entity("YTManager.Models.Channel", b =>
{
b.Property<long>("PrimaryKey")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedtoDB");
b.Property<string>("Description")
.IsRequired();
b.Property<string>("ThumbnailURL")
.IsRequired();
b.Property<string>("Title")
.IsRequired();
b.Property<string>("YoutubeID")
.IsRequired();
b.HasKey("PrimaryKey");
b.ToTable("Channels");
});
modelBuilder.Entity("YTManager.Models.Tag", b =>
{
b.Property<long>("PrimaryKey")
.ValueGeneratedOnAdd();
b.Property<string>("Name")
.IsRequired();
b.HasKey("PrimaryKey");
b.ToTable("Tags");
});
modelBuilder.Entity("YTManager.Models.Video", b =>
{
b.Property<long>("PrimaryKey")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedToYT");
b.Property<DateTime>("AddedtoDB");
b.Property<long?>("ChannelPrimaryKey");
b.Property<string>("Description")
.IsRequired();
b.Property<string>("ThumbnailURL")
.IsRequired();
b.Property<string>("Title")
.IsRequired();
b.Property<string>("YoutubeID")
.IsRequired();
b.HasKey("PrimaryKey");
b.HasIndex("ChannelPrimaryKey");
b.ToTable("Videos");
});
modelBuilder.Entity("YTManager.Models.Video", b =>
{
b.HasOne("YTManager.Models.Channel")
.WithMany("Videos")
.HasForeignKey("ChannelPrimaryKey");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -8,7 +8,7 @@ namespace YTManager.Models {
public class Channel { public class Channel {
// Uniquie ID for this media type // Uniquie ID for this media type
[Key] [Key]
public long key { get; set; } public long PrimaryKey { get; set; }
// Title of the media // Title of the media
[Required] [Required]
@ -24,7 +24,7 @@ namespace YTManager.Models {
// Youtube Channel ID // Youtube Channel ID
[Required] [Required]
public string YTChannelID { get; set; } public string YoutubeID { get; set; }
// Added to this manager. // Added to this manager.
[Required] [Required]

View File

@ -10,16 +10,10 @@ namespace YTManager.Models
{ {
// Uniquie ID for this media type // Uniquie ID for this media type
[Key] [Key]
public long key { get; set; } public long PrimaryKey { get; set; }
// Tag Name. // Tag Name.
[Required] [Required]
public string Name { get; set; } public string Name { get; set; }
// Videos this tag applies to.
public List<Video> Videos { get; set; }
// Channels this tag applies to.
public List<Channel> Channels { get; set; }
} }
} }

View File

@ -8,7 +8,7 @@ namespace YTManager.Models {
public class Video { public class Video {
// Uniquie ID for this media type // Uniquie ID for this media type
[Key] [Key]
public long key { get; set; } public long PrimaryKey { get; set; }
// Title of the media // Title of the media
[Required] [Required]
@ -20,7 +20,7 @@ namespace YTManager.Models {
// Youtube Video ID // Youtube Video ID
[Required] [Required]
public string YTVideoID { get; set; } public string YoutubeID { get; set; }
// Thumbnail link // Thumbnail link
[Required] [Required]
@ -34,8 +34,8 @@ namespace YTManager.Models {
[Required] [Required]
public DateTime AddedtoDB { get; set; } public DateTime AddedtoDB { get; set; }
// Channel this video comes from. // Tag this video applies to.
[Required] [Required]
public Channel channel; public List<Tag> Tags;
} }
} }

View File

@ -20,6 +20,7 @@
"commandName": "Project", "commandName": "Project",
"launchUrl": "api/values", "launchUrl": "api/values",
"environmentVariables": { "environmentVariables": {
"POSTGRESQL_DBSTR": "Server=192.168.1.211;Port=32768;Database=postgres;User Id=postgres;",
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
}, },
"applicationUrl": "http://localhost:62214/" "applicationUrl": "http://localhost:62214/"

View File

@ -9,7 +9,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Hangfire; using Hangfire;
using Hangfire.MemoryStorage; using Hangfire.PostgreSql;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace YTManager { namespace YTManager {
@ -20,14 +20,20 @@ namespace YTManager {
public IConfiguration Configuration { get; } public IConfiguration Configuration { get; }
// ID for periodic job to update all channels. public static string DBStr {
public static string periodicupdatejobID { get; } = "2013066213"; get {
string s = Environment.GetEnvironmentVariable("POSTGRESQL_DBSTR");
s = s ?? "Server=localhost;Port=32768;Database=postgres;User Id=postgres;";
return s;
}
}
// This method gets called by the runtime. Use this method to add services to the container. // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) { public void ConfigureServices(IServiceCollection services) {
Console.WriteLine("Using db string of: " + DBStr);
services.AddMvc(); services.AddMvc();
services.AddHangfire(x => x.UseMemoryStorage()); services.AddDbContext<MediaDB>(x => x.UseNpgsql(DBStr));
services.AddDbContext<MediaDB>(options => options.UseInMemoryDatabase(databaseName: "testdb")); services.AddHangfire(x => x.UsePostgreSqlStorage(DBStr));
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

View File

@ -5,12 +5,10 @@ using System.Threading.Tasks;
using Google.Apis.YouTube.v3; using Google.Apis.YouTube.v3;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace YTManager.Tasks namespace YTManager.Tasks {
{ public class FetchVideos {
public class FetchVideos // Get a bunch of videos from youtube that the channel generated.
{ private static async Task<List<Models.Video>> Get_YTVideos(string channelID) {
public static void run(string youtubechannelIDstr = "")
{
// YT API access key // YT API access key
var youtubeService = new YouTubeService(new Google.Apis.Services.BaseClientService.Initializer() var youtubeService = new YouTubeService(new Google.Apis.Services.BaseClientService.Initializer()
{ {
@ -18,49 +16,92 @@ namespace YTManager.Tasks
ApplicationName = "testingapppp" ApplicationName = "testingapppp"
}); });
// Search youtube for all the relevant data of the channel.
var query = youtubeService.Search.List("snippet");
query.ChannelId = channelID;
query.Order = SearchResource.ListRequest.OrderEnum.Date;
query.MaxResults = 50;
var response = await query.ExecuteAsync();
// Convert the response into models.
return response.Items?
.Where(i => i.Id.Kind == "youtube#video")
.Select(newvid => new Models.Video
{
Title = newvid.Snippet.Title,
Description = newvid.Snippet.Description,
YoutubeID = newvid.Id.VideoId,
AddedToYT = newvid.Snippet.PublishedAt.GetValueOrDefault(),
AddedtoDB = DateTime.Now,
ThumbnailURL = newvid.Snippet.Thumbnails.Medium.Url
}).ToList();
}
public static async Task<Models.Channel> Get_YTChannel(string channelID) {
// YT API access key
var youtubeService = new YouTubeService(new Google.Apis.Services.BaseClientService.Initializer()
{
ApiKey = "AIzaSyCuIYkMc5SktlnXRXNaDf2ObX-fQvtWCnQ",
ApplicationName = "testingapppp"
});
// Search youtube for all the relevant data of the channel.
var query = youtubeService.Channels.List("snippet");
query.Id = channelID;
query.MaxResults = 1;
var response = await query.ExecuteAsync();
// Parse the response into a channel.
return new Models.Channel {
Description = response.Items.First().Snippet.Description,
Title = response.Items.First().Snippet.Title,
ThumbnailURL = response.Items.First().Snippet.Thumbnails.Medium.Url,
YoutubeID = channelID,
AddedtoDB = DateTime.Now
};
}
// Update videos for all our channels.
public static async Task MassUpdate(string dbstr) {
// Get the interface to the database. // Get the interface to the database.
var ops = new DbContextOptionsBuilder<MediaDB>(); var ops = new DbContextOptionsBuilder<MediaDB>();
ops.UseInMemoryDatabase(databaseName: "testdb"); ops.UseNpgsql(dbstr);
// Get all the channels to update. // Get all the channels from the db.
using (var dbcontext = new MediaDB(ops.Options)) { var channels = await (new MediaDB(ops.Options)).Channels.ToListAsync();
// Get all the potential relevant channels.
List<Models.Channel> channels;
if (youtubechannelIDstr == "")
channels = dbcontext.Channels.ToList();
else
channels = dbcontext.Channels.Where(c => c.YTChannelID == youtubechannelIDstr).ToList();
// Get all the most recent videos for each channel. // For each channel, do an update.
channels.ForEach(ch => { channels.ForEach(async ch => await ChannelUpdate(dbstr, ch.YoutubeID));
// Get channel videos from youtube. }
var query = youtubeService.Search.List("snippet");
query.ChannelId = ch.YTChannelID;
query.Order = SearchResource.ListRequest.OrderEnum.Date;
query.MaxResults = 50;
var response = query.Execute();
// Get all videos which aren't already in the DB based on the ytid. // Update videos for just one channel.
var notindb = response.Items public static async Task ChannelUpdate(string dbstr, string youtubechannelIDstr) {
.Where(i => i.Id.Kind == "youtube#video") // Get the interface to the database.
.Where(i => !dbcontext.Videos.Any(dbvid => dbvid.YTVideoID == i.Id.VideoId)) var ops = new DbContextOptionsBuilder<MediaDB>();
.Select(newvid => ops.UseNpgsql(dbstr);
new Models.Video { var db = new MediaDB(ops.Options);
Title = newvid.Snippet.Title,
Description = newvid.Snippet.Description,
YTVideoID = newvid.Id.VideoId,
AddedToYT = newvid.Snippet.PublishedAt.GetValueOrDefault(),
AddedtoDB = DateTime.Now,
channel = ch,
ThumbnailURL = newvid.Snippet.Thumbnails.Medium.Url
}).ToList();
// Add all videos not already in the database over. // Get the channel from the db when including it's videos.
notindb.ForEach(newvid => dbcontext.Videos.Add(newvid)); var channel = await db.Channels
.Include(c => c.Videos)
.SingleOrDefaultAsync(ch => ch.YoutubeID == youtubechannelIDstr);
// And save since we are done. // Update the channel if it was found.
dbcontext.SaveChanges(); if (channel != null) {
}); // Get all the new videos for the channel.
var Videos = await Get_YTVideos(channel.YoutubeID);
// Get all the videos which haven't been put into this channels videos.
var newvids = Videos.Where(nv => !channel.Videos.Any(cv => cv.YoutubeID == nv.YoutubeID));
// Add all the videos to the databse.
await db.Videos.AddRangeAsync(newvids);
// Update the videos this channel refers to.
channel.Videos.AddRange(newvids);
// And say the database should be changed.
await db.SaveChangesAsync();
} }
} }
} }

View File

@ -4,11 +4,22 @@
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<!-- Job System -->
<ItemGroup>
<PackageReference Include="Hangfire" Version="1.6.17" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.6.17" />
<PackageReference Include="Hangfire.PostgreSql" Version="1.4.8.1" />
</ItemGroup>
<!-- Database -->
<ItemGroup>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="2.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.1" />
</ItemGroup>
<!-- Backend Website -->
<ItemGroup> <ItemGroup>
<PackageReference Include="Google.Apis.YouTube.v3" Version="1.32.2.1131" /> <PackageReference Include="Google.Apis.YouTube.v3" Version="1.32.2.1131" />
<PackageReference Include="Hangfire" Version="1.6.17"/>
<PackageReference Include="Hangfire.AspNetCore" Version="1.6.17" />
<PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0"/>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.5" /> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.1" />
@ -17,6 +28,7 @@
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.2" /> <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.2" />
</ItemGroup> </ItemGroup>
<!-- So we can run dotnet migration ... -->
<ItemGroup> <ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.2" /> <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.2" />
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.1" /> <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.1" />

View File

@ -59,7 +59,7 @@ var addchanel0 = new Vue({
submit: function (event) { submit: function (event) {
// Send the channel to our API and request tables be updated. // Send the channel to our API and request tables be updated.
var d = this.entry; var d = this.entry;
axios.post('/api/Channels', d) axios.post('/api/Channels/' + this.entry.yTChannelID)
.then(function (response) { .then(function (response) {
this.entry.title = ""; this.entry.title = "";
this.entry.description = ""; this.entry.description = "";
@ -132,13 +132,13 @@ var tbody0 = new Vue({
</thead> </thead>
<tbody> <tbody>
<tr v-cloak v-for="entry in entries"> <tr v-cloak v-for="entry in entries">
<td class="tinytext12px">{{entry.ytChannelID}}</td> <td class="tinytext12px">{{entry.youtubeID}}</td>
<td class="tinytext12px">{{entry.title}}</td> <td class="tinytext12px">{{entry.title}}</td>
<td class="tinytext12px">{{entry.description}}</td> <td class="tinytext12px">{{entry.description}}</td>
<td class="tinytext12px">{{entry.channelId}}</td> <td class="tinytext12px">{{entry.primaryKey}}</td>
<td> <td>
<a href="#"><i class="fa fa-refresh" aria-hidden="true" :id="entry.ytChannelID" @click="forcerefresh"/></a> <a href="#"><i class="fa fa-refresh" aria-hidden="true" :id="entry.youtubeID" @click="forcerefresh"/></a>
<a href="#"><i class="fa fa-trash-o" aria-hidden="true" :id="entry.ytChannelID" @click="deletechannel"/></a> <a href="#"><i class="fa fa-trash-o" aria-hidden="true" :id="entry.youtubeID" @click="deletechannel"/></a>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@ -165,7 +165,7 @@ var tbody0 = new Vue({
}, },
forcerefresh: function (event) { forcerefresh: function (event) {
axios axios
.post('/api/Admin/job_update/' + event.target.id) .post('/api/Admin/Update/' + event.target.id)
.catch(function (error) { console.log(error); }); .catch(function (error) { console.log(error); });
}, },
deletechannel: function (event) { deletechannel: function (event) {
@ -200,7 +200,7 @@ var apistatus = new Vue({
`, `,
methods: { methods: {
update: function (event) { update: function (event) {
axios.get('/api/Admin/job_periodicupdate') axios.get('/api/Admin/Update')
.then(function (response) { .then(function (response) {
this.apiaccessible = (response.status == 200); this.apiaccessible = (response.status == 200);
this.updatingjobactive = (response.data != "false") this.updatingjobactive = (response.data != "false")
@ -213,7 +213,7 @@ var apistatus = new Vue({
} }
}); });
// Grid if images. // Grid of images.
var videostb = new Vue({ var videostb = new Vue({
el: '#videosindbtable-0', el: '#videosindbtable-0',
data: { data: {
@ -234,9 +234,9 @@ var videostb = new Vue({
<tr v-cloak v-for="video in Videos"> <tr v-cloak v-for="video in Videos">
<td class="tinytext12px">{{video.title}}</td> <td class="tinytext12px">{{video.title}}</td>
<td class="tinytext12px">{{video.description}}</td> <td class="tinytext12px">{{video.description}}</td>
<td class="tinytext12px">{{video.ytVideoID}}</td> <td class="tinytext12px">{{video.youtubeID}}</td>
<td class="tinytext12px">{{video.uploaded}}</td> <td class="tinytext12px">{{video.uploaded}}</td>
<td class="tinytext12px">{{video.videoId}}</td> <td class="tinytext12px">{{video.primaryKey}}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@ -5,10 +5,12 @@
font-size: 18px; font-size: 18px;
color: #003636; color: #003636;
background-color: #F8F8F8; background-color: #F8F8F8;
max-width: 1024px;
margin: auto;
} }
.pageheader { .pageheader {
max-width: 1280px; max-width: 960px;
margin: auto; margin: auto;
} }

View File

@ -15,7 +15,7 @@
</div> </div>
<hr /> <hr />
<h2>Most Recent Videos</h2>
<div v-cloak id="vidholder-0"></div> <div v-cloak id="vidholder-0"></div>
<!-- Compressed JavaScript --> <!-- Compressed JavaScript -->

View File

@ -40,7 +40,7 @@ var vidholder = new Vue({
} }
// Generate a new URL by adding the YT ID. // Generate a new URL by adding the YT ID.
x.url = "https://www.youtube.com/watch?v=" + x.ytVideoID x.url = "https://www.youtube.com/watch?v=" + x.youtubeID;
// Add it to our array // Add it to our array
this.Videos.push(x); this.Videos.push(x);