Add project files.
This commit is contained in:
parent
70d055282c
commit
ae782025f6
25
YTManager.sln
Normal file
25
YTManager.sln
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio 15
|
||||||
|
VisualStudioVersion = 15.0.26730.12
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YTManager", "YTManager\YTManager.csproj", "{14F56CC1-67A1-477F-817C-A0A7B55AE954}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{14F56CC1-67A1-477F-817C-A0A7B55AE954}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{14F56CC1-67A1-477F-817C-A0A7B55AE954}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{14F56CC1-67A1-477F-817C-A0A7B55AE954}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{14F56CC1-67A1-477F-817C-A0A7B55AE954}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {DB293C09-A2E4-4179-ADA9-BAE2093F425A}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
95
YTManager/Controllers/ValuesController.cs
Normal file
95
YTManager/Controllers/ValuesController.cs
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace YTManager.Controllers {
|
||||||
|
[Produces("application/json")]
|
||||||
|
[Route("api/Admin")]
|
||||||
|
public class AdminController : Controller {
|
||||||
|
// POST api/Admin/refreshytvids
|
||||||
|
[HttpPost("refreshytvids")]
|
||||||
|
public void Post() {
|
||||||
|
Hangfire.BackgroundJob.Enqueue(() => YTManager.Tasks.FetchVideos.run());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Produces("application/json")]
|
||||||
|
[Route("api/Channels")]
|
||||||
|
public class ChannelsController : Controller {
|
||||||
|
private readonly MediaDB _context;
|
||||||
|
|
||||||
|
public ChannelsController(MediaDB context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET api/Channels
|
||||||
|
[HttpGet]
|
||||||
|
public IEnumerable<Models.Channel> Get() {
|
||||||
|
return _context.Channels.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET api/Channels/5
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
public Models.Channel Get(int id) {
|
||||||
|
return _context.Channels.Single(m => m.ChannelId == id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/Channels/sdfs6DFS65f/Videos (using YouTube channel ID)
|
||||||
|
[HttpGet("{id}/Videos")]
|
||||||
|
public async Task<IActionResult> GetVideos([FromRoute] string YTchannelID) {
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
return BadRequest(ModelState);
|
||||||
|
|
||||||
|
// Verify the channel exists.
|
||||||
|
var Chan = await _context.Channels.SingleOrDefaultAsync(c => c.YTChannelID == YTchannelID);
|
||||||
|
if (Chan == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
// Send back the found stuff.
|
||||||
|
return Ok(_context.Entry(Chan).Collection(c => c.Videos).LoadAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST api/values
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult Post([FromBody]JObject value) {
|
||||||
|
// Get the channel out of our json body.
|
||||||
|
Models.Channel posted = value.ToObject<Models.Channel>();
|
||||||
|
|
||||||
|
// Verify items aren't empty.
|
||||||
|
if ((posted.YTChannelID.Length == 0) || (posted.Description.Length == 0) || (posted.Title.Length == 0))
|
||||||
|
return BadRequest();
|
||||||
|
|
||||||
|
// Verify the channel doesn't already exist.
|
||||||
|
if (_context.Channels.Any(c => c.YTChannelID == posted.YTChannelID))
|
||||||
|
return BadRequest();
|
||||||
|
|
||||||
|
// Seems good, so add it.
|
||||||
|
_context.Channels.Add(posted);
|
||||||
|
_context.SaveChanges();
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT api/values/5
|
||||||
|
[HttpPut("{id}")]
|
||||||
|
public void Put(int id, [FromBody]JObject value)
|
||||||
|
{
|
||||||
|
Models.Channel posted = value.ToObject<Models.Channel>();
|
||||||
|
posted.ChannelId = id; // Ensure an id is attached
|
||||||
|
_context.Channels.Update(posted);
|
||||||
|
_context.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE api/values/5
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
public void Delete(int id)
|
||||||
|
{
|
||||||
|
_context.Remove(_context.Channels.Single(m => m.ChannelId == id));
|
||||||
|
_context.SaveChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
126
YTManager/Controllers/VideosController.cs
Normal file
126
YTManager/Controllers/VideosController.cs
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using YTManager;
|
||||||
|
using YTManager.Models;
|
||||||
|
|
||||||
|
namespace YTManager.Controllers
|
||||||
|
{
|
||||||
|
[Produces("application/json")]
|
||||||
|
[Route("api/Videos")]
|
||||||
|
public class VideosController : Controller
|
||||||
|
{
|
||||||
|
private readonly MediaDB _context;
|
||||||
|
|
||||||
|
public VideosController(MediaDB context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/Videos
|
||||||
|
[HttpGet]
|
||||||
|
public IEnumerable<Video> GetVideos()
|
||||||
|
{
|
||||||
|
return _context.Videos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/Videos/5
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
public async Task<IActionResult> GetVideo([FromRoute] long id)
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return BadRequest(ModelState);
|
||||||
|
}
|
||||||
|
|
||||||
|
var video = await _context.Videos.SingleOrDefaultAsync(m => m.VideoId == id);
|
||||||
|
|
||||||
|
if (video == null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(video);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT: api/Videos/5
|
||||||
|
[HttpPut("{id}")]
|
||||||
|
public async Task<IActionResult> PutVideo([FromRoute] long id, [FromBody] Video video)
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return BadRequest(ModelState);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id != video.VideoId)
|
||||||
|
{
|
||||||
|
return BadRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.Entry(video).State = EntityState.Modified;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
catch (DbUpdateConcurrencyException)
|
||||||
|
{
|
||||||
|
if (!VideoExists(id))
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST: api/Videos
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> PostVideo([FromBody] Video video)
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return BadRequest(ModelState);
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.Videos.Add(video);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return CreatedAtAction("GetVideo", new { id = video.VideoId }, video);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE: api/Videos/5
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
public async Task<IActionResult> DeleteVideo([FromRoute] long id)
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return BadRequest(ModelState);
|
||||||
|
}
|
||||||
|
|
||||||
|
var video = await _context.Videos.SingleOrDefaultAsync(m => m.VideoId == id);
|
||||||
|
if (video == null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.Videos.Remove(video);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return Ok(video);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool VideoExists(long id)
|
||||||
|
{
|
||||||
|
return _context.Videos.Any(e => e.VideoId == id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
YTManager/MediaDB.cs
Normal file
21
YTManager/MediaDB.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace YTManager
|
||||||
|
{
|
||||||
|
public class MediaDB : DbContext
|
||||||
|
{
|
||||||
|
public DbSet<Models.Channel> Channels { get; set; }
|
||||||
|
public DbSet<Models.Video> Videos { get; set; }
|
||||||
|
|
||||||
|
public MediaDB(DbContextOptions<MediaDB> options) :base(options){ }
|
||||||
|
|
||||||
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
|
{
|
||||||
|
base.OnConfiguring(optionsBuilder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
86
YTManager/Migrations/20170901083156_initial.Designer.cs
generated
Normal file
86
YTManager/Migrations/20170901083156_initial.Designer.cs
generated
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// <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("20170901083156_initial")]
|
||||||
|
partial class initial
|
||||||
|
{
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)
|
||||||
|
.HasAnnotation("ProductVersion", "2.0.0-rtm-26452");
|
||||||
|
|
||||||
|
modelBuilder.Entity("YTManager.Models.Channel", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("ChannelId")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<string>("ThumbnailURL")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<string>("YTChannelID")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasKey("ChannelId");
|
||||||
|
|
||||||
|
b.ToTable("Channels");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("YTManager.Models.Video", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("VideoId")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<DateTime>("AddedtoDB");
|
||||||
|
|
||||||
|
b.Property<long>("ChannelId");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<string>("ThumbnailURL")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<DateTime>("Uploaded");
|
||||||
|
|
||||||
|
b.Property<string>("YTVideoID")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasKey("VideoId");
|
||||||
|
|
||||||
|
b.HasIndex("ChannelId");
|
||||||
|
|
||||||
|
b.ToTable("Videos");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("YTManager.Models.Video", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("YTManager.Models.Channel", "Channel")
|
||||||
|
.WithMany("Videos")
|
||||||
|
.HasForeignKey("ChannelId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
68
YTManager/Migrations/20170901083156_initial.cs
Normal file
68
YTManager/Migrations/20170901083156_initial.cs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace YTManager.Migrations
|
||||||
|
{
|
||||||
|
public partial class initial : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Channels",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
ChannelId = table.Column<long>(type: "int8", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn),
|
||||||
|
Description = table.Column<string>(type: "text", nullable: false),
|
||||||
|
ThumbnailURL = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Title = table.Column<string>(type: "text", nullable: false),
|
||||||
|
YTChannelID = table.Column<string>(type: "text", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Channels", x => x.ChannelId);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Videos",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
VideoId = table.Column<long>(type: "int8", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn),
|
||||||
|
AddedtoDB = table.Column<DateTime>(type: "timestamp", nullable: false),
|
||||||
|
ChannelId = table.Column<long>(type: "int8", nullable: false),
|
||||||
|
Description = table.Column<string>(type: "text", nullable: false),
|
||||||
|
ThumbnailURL = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Title = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Uploaded = table.Column<DateTime>(type: "timestamp", nullable: false),
|
||||||
|
YTVideoID = table.Column<string>(type: "text", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Videos", x => x.VideoId);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Videos_Channels_ChannelId",
|
||||||
|
column: x => x.ChannelId,
|
||||||
|
principalTable: "Channels",
|
||||||
|
principalColumn: "ChannelId",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Videos_ChannelId",
|
||||||
|
table: "Videos",
|
||||||
|
column: "ChannelId");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Videos");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Channels");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
85
YTManager/Migrations/MediaDBModelSnapshot.cs
Normal file
85
YTManager/Migrations/MediaDBModelSnapshot.cs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
// <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.0-rtm-26452");
|
||||||
|
|
||||||
|
modelBuilder.Entity("YTManager.Models.Channel", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("ChannelId")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<string>("ThumbnailURL")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<string>("YTChannelID")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasKey("ChannelId");
|
||||||
|
|
||||||
|
b.ToTable("Channels");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("YTManager.Models.Video", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("VideoId")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<DateTime>("AddedtoDB");
|
||||||
|
|
||||||
|
b.Property<long>("ChannelId");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<string>("ThumbnailURL")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<DateTime>("Uploaded");
|
||||||
|
|
||||||
|
b.Property<string>("YTVideoID")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasKey("VideoId");
|
||||||
|
|
||||||
|
b.HasIndex("ChannelId");
|
||||||
|
|
||||||
|
b.ToTable("Videos");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("YTManager.Models.Video", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("YTManager.Models.Channel", "Channel")
|
||||||
|
.WithMany("Videos")
|
||||||
|
.HasForeignKey("ChannelId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
YTManager/Models/Channel.cs
Normal file
32
YTManager/Models/Channel.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace YTManager.Models {
|
||||||
|
public class Channel {
|
||||||
|
// Uniquie ID for this media type
|
||||||
|
[Key]
|
||||||
|
public long ChannelId { get; set; }
|
||||||
|
|
||||||
|
// Title of the media
|
||||||
|
[Required]
|
||||||
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
// Short description of the media
|
||||||
|
[Required]
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
// Thumbnail link
|
||||||
|
[Required]
|
||||||
|
public string ThumbnailURL { get; set; }
|
||||||
|
|
||||||
|
// Youtube Channel ID
|
||||||
|
[Required]
|
||||||
|
public string YTChannelID { get; set; }
|
||||||
|
|
||||||
|
// Videos this channel has.
|
||||||
|
public List<Video> Videos { get; set; }
|
||||||
|
}
|
||||||
|
}
|
41
YTManager/Models/Video.cs
Normal file
41
YTManager/Models/Video.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace YTManager.Models {
|
||||||
|
public class Video {
|
||||||
|
// Uniquie ID for this media type
|
||||||
|
[Key]
|
||||||
|
public long VideoId { get; set; }
|
||||||
|
|
||||||
|
// Title of the media
|
||||||
|
[Required]
|
||||||
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
// Short description of the media
|
||||||
|
[Required]
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
// Youtube Video ID
|
||||||
|
[Required]
|
||||||
|
public string YTVideoID { get; set; }
|
||||||
|
|
||||||
|
// Thumbnail link
|
||||||
|
[Required]
|
||||||
|
public string ThumbnailURL { get; set; }
|
||||||
|
|
||||||
|
// Date video was uploaded to YT
|
||||||
|
[Required]
|
||||||
|
public DateTime Uploaded { get; set; }
|
||||||
|
|
||||||
|
// Date added to database
|
||||||
|
[Required]
|
||||||
|
public DateTime AddedtoDB { get; set; }
|
||||||
|
|
||||||
|
// Refer back to what channel this video belongs to.
|
||||||
|
public long ChannelId { get; set; }
|
||||||
|
public Channel Channel { get; set; }
|
||||||
|
}
|
||||||
|
}
|
26
YTManager/Program.cs
Normal file
26
YTManager/Program.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace YTManager
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
BuildWebHost(args).Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IWebHost BuildWebHost(string[] args) =>
|
||||||
|
WebHost.CreateDefaultBuilder(args)
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseStartup<Startup>()
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
}
|
28
YTManager/Properties/launchSettings.json
Normal file
28
YTManager/Properties/launchSettings.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:62213/",
|
||||||
|
"sslPort": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "api/values",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"YTManager": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchUrl": "api/values",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"applicationUrl": "http://localhost:62214/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
39
YTManager/ScaffoldingReadMe.txt
Normal file
39
YTManager/ScaffoldingReadMe.txt
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
ASP.NET MVC core dependencies have been added to the project.
|
||||||
|
(These dependencies include packages required to enable scaffolding)
|
||||||
|
|
||||||
|
However you may still need to do make changes to your project.
|
||||||
|
|
||||||
|
1. Suggested changes to Startup class:
|
||||||
|
1.1 Add a constructor:
|
||||||
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
|
public Startup(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
Configuration = configuration;
|
||||||
|
}
|
||||||
|
1.2 Add MVC services:
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
// Add framework services.
|
||||||
|
services.AddMvc();
|
||||||
|
}
|
||||||
|
|
||||||
|
1.3 Configure web app to use use Configuration and use MVC routing:
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||||
|
{
|
||||||
|
if (env.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseDeveloperExceptionPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseStaticFiles();
|
||||||
|
|
||||||
|
app.UseMvc(routes =>
|
||||||
|
{
|
||||||
|
routes.MapRoute(
|
||||||
|
name: "default",
|
||||||
|
template: "{controller=Home}/{action=Index}/{id?}");
|
||||||
|
});
|
||||||
|
}
|
49
YTManager/Startup.cs
Normal file
49
YTManager/Startup.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Hangfire;
|
||||||
|
using Hangfire.PostgreSql;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace YTManager {
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
public Startup(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
Configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to add services to the container.
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddMvc();
|
||||||
|
string constr = "Host=home.hak8or.com;Database=postgres;Username=postgres;Password=mysecretpassword";
|
||||||
|
services.AddHangfire(x => x.UsePostgreSqlStorage(constr));
|
||||||
|
services.AddDbContext<MediaDB>(options => options.UseNpgsql(constr));
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
|
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||||
|
{
|
||||||
|
if (env.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseDeveloperExceptionPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseDefaultFiles();
|
||||||
|
app.UseStaticFiles();
|
||||||
|
app.UseMvc();
|
||||||
|
app.UseHangfireServer();
|
||||||
|
app.UseHangfireDashboard();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
60
YTManager/Tasks/FetchVideos.cs
Normal file
60
YTManager/Tasks/FetchVideos.cs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Google.Apis.YouTube.v3;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace YTManager.Tasks
|
||||||
|
{
|
||||||
|
public class FetchVideos
|
||||||
|
{
|
||||||
|
public static void run()
|
||||||
|
{
|
||||||
|
Console.WriteLine("Startnig job ...");
|
||||||
|
|
||||||
|
// YT API access key
|
||||||
|
var youtubeService = new YouTubeService(new Google.Apis.Services.BaseClientService.Initializer()
|
||||||
|
{
|
||||||
|
ApiKey = "AIzaSyCuIYkMc5SktlnXRXNaDf2ObX-fQvtWCnQ",
|
||||||
|
ApplicationName = "testingapppp"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get all the channels to update.
|
||||||
|
var ops = new DbContextOptionsBuilder<MediaDB>();
|
||||||
|
string constr = "Host=home.hak8or.com;Database=postgres;Username=postgres;Password=mysecretpassword";
|
||||||
|
ops.UseNpgsql(constr);
|
||||||
|
using (var dbcontext = new MediaDB(ops.Options)) {
|
||||||
|
var channels = dbcontext.Channels.ToList();
|
||||||
|
|
||||||
|
// Get all the most recent videos for each channel.
|
||||||
|
channels.ForEach(ch => {
|
||||||
|
// Get channel videos from youtube.
|
||||||
|
var query = youtubeService.Activities.List("snippet");
|
||||||
|
query.ChannelId = ch.YTChannelID;
|
||||||
|
var response = query.Execute();
|
||||||
|
|
||||||
|
// Get all videos which aren't already in the DB.
|
||||||
|
var notindb = response.Items
|
||||||
|
.Where(i => !dbcontext.Videos.Any(dbvid => dbvid.YTVideoID != i.Id))
|
||||||
|
.Select(newvid =>
|
||||||
|
new Models.Video {
|
||||||
|
Title = newvid.Snippet.Title,
|
||||||
|
Description = newvid.Snippet.Description,
|
||||||
|
YTVideoID = newvid.Id,
|
||||||
|
Uploaded = 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.
|
||||||
|
notindb.ForEach(newvid => dbcontext.Videos.Add(newvid));
|
||||||
|
|
||||||
|
// And save since we are done.
|
||||||
|
dbcontext.SaveChanges();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
YTManager/YTManager.csproj
Normal file
31
YTManager/YTManager.csproj
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Google.Apis.YouTube.v3" Version="1.29.0.760" />
|
||||||
|
<PackageReference Include="Hangfire" Version="1.6.15" />
|
||||||
|
<PackageReference Include="Hangfire.AspNetCore" Version="1.6.15" />
|
||||||
|
<PackageReference Include="Hangfire.PostgreSql" Version="1.4.8.1" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.0" />
|
||||||
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="2.0.0" />
|
||||||
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
|
||||||
|
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Migrations\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
10
YTManager/appsettings.Development.json
Normal file
10
YTManager/appsettings.Development.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"IncludeScopes": false,
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Debug",
|
||||||
|
"System": "Information",
|
||||||
|
"Microsoft": "Information"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
YTManager/appsettings.json
Normal file
15
YTManager/appsettings.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"IncludeScopes": false,
|
||||||
|
"Debug": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Console": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
109
YTManager/wwwroot/admin.html
Normal file
109
YTManager/wwwroot/admin.html
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>YT Manager Admin</title>
|
||||||
|
|
||||||
|
<!-- Compressed CSS -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/foundation/6.4.3/css/foundation.min.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="index.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Dumb YT Manager</h1>
|
||||||
|
<p>Youtube banned my account and refuses to say why, taking all my subscribed channels with it. This is a simple scrubscribed channel manager, showing recent releases from each channel and finally proper sub-catagory functionality!</p>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
<h2>Subscribed Channels</h2>
|
||||||
|
<table id="tbody-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>YT Channel ID</th>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-cloak v-for="entry in entries">
|
||||||
|
<td class="tinytext12px">{{entry.ytChannelID}}</td>
|
||||||
|
<td class="tinytext12px">{{entry.title}}</td>
|
||||||
|
<td class="tinytext12px">{{entry.description}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div id="addchanel-0">
|
||||||
|
<div class="grid-x">
|
||||||
|
<div class="medium-11 cell"></div>
|
||||||
|
<div class="medium-1 cell">
|
||||||
|
<button type="button" class="button input-group" v-on:click='expanded = !expanded'>
|
||||||
|
<i class="fa fa-plus" aria-hidden="true"></i> Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<transition name="fade">
|
||||||
|
<form v-cloak v-if="expanded">
|
||||||
|
<h2>Add new Channel</h2>
|
||||||
|
<div class="grid-x">
|
||||||
|
<div class="input-group medium-6 cell">
|
||||||
|
<span class="input-group-label">Title</span>
|
||||||
|
<input class="input-group-field" type="text" placeholder="Title of Entry" v-model="entry.title">
|
||||||
|
</div>
|
||||||
|
<div class="input-group medium-1 cell"></div>
|
||||||
|
<div class="input-group medium-5 cell">
|
||||||
|
<span class="input-group-label">Channel</span>
|
||||||
|
<input class="input-group-field" v-bind:class="{ error: fail }" type="text"
|
||||||
|
@blur="newurl" placeholder="URL of Entry" v-model="entry.yTChannelID">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-label">Description</span>
|
||||||
|
<input class="input-group-field" type="text" placeholder="Description of Entry" v-model="entry.description">
|
||||||
|
</div>
|
||||||
|
<div class="grid-x">
|
||||||
|
<img class="small-4 cell" :src="entry.thumbnailURL" />
|
||||||
|
<div class="small-7"></div>
|
||||||
|
<button type="button" style="max-height:50px" class="button input-group small-1 cell" @click="submit">Submit</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
<h2>Videos in DB</h2>
|
||||||
|
<table id="videostb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>Youtube Video ID</th>
|
||||||
|
<th>ID</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-cloak v-for="video in Videos">
|
||||||
|
<td class="tinytext12px">{{video.title}}</td>
|
||||||
|
<td class="tinytext12px">{{video.description}}</td>
|
||||||
|
<td class="tinytext12px">{{video.ytVideoID}}</td>
|
||||||
|
<td class="tinytext12px">{{video.videoId}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- Compressed JavaScript -->
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/foundation/6.4.3/js/foundation.min.js"></script>
|
||||||
|
|
||||||
|
<!-- For doing REST based API stuff. -->
|
||||||
|
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
||||||
|
|
||||||
|
<!-- Some icons -->
|
||||||
|
<script src="https://use.fontawesome.com/91af8ab4ba.js"></script>
|
||||||
|
|
||||||
|
<!-- Good ole Vue :D -->
|
||||||
|
<script src="https://unpkg.com/vue"></script>
|
||||||
|
|
||||||
|
<!-- All of my custom JS. Put here so body loads before Vue based stuff loads/attempts to bind. -->
|
||||||
|
<script src="admin.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
139
YTManager/wwwroot/admin.js
Normal file
139
YTManager/wwwroot/admin.js
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
$(document).foundation();
|
||||||
|
|
||||||
|
// How many chars at most for the description.
|
||||||
|
var maxcharstablefield = 200;
|
||||||
|
|
||||||
|
// Bind to the form for submitting a new channel
|
||||||
|
var addchanel0 = new Vue({
|
||||||
|
el: '#addchanel-0',
|
||||||
|
data: {
|
||||||
|
entry: {
|
||||||
|
title: "",
|
||||||
|
description: "",
|
||||||
|
yTChannelID: "",
|
||||||
|
thumbnailURL: "",
|
||||||
|
},
|
||||||
|
fail: false,
|
||||||
|
expanded: false
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
submit: function (event) {
|
||||||
|
// Send the channel to our API and request tables be updated.
|
||||||
|
var d = this.entry;
|
||||||
|
axios.post('/api/Channels', d)
|
||||||
|
.then(function (response) {
|
||||||
|
this.entry.title = "";
|
||||||
|
this.entry.description = "";
|
||||||
|
this.entry.yTChannelID = "";
|
||||||
|
this.entry.thumbnailURL = "";
|
||||||
|
this.expanded = false;
|
||||||
|
tbody0.retrieve();
|
||||||
|
|
||||||
|
axios.post('/api/admin/refreshytvids');
|
||||||
|
setTimeout(() => videostb.retrieve(), 1000);
|
||||||
|
}.bind(this))
|
||||||
|
.catch(function (error) {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Handle when user put in a new URL (verification and thumbnail fetch)
|
||||||
|
newurl: function (event) {
|
||||||
|
// Remove any potential youtube URL from the field.
|
||||||
|
var ytchurl = "https://www.youtube.com/channel/";
|
||||||
|
if (this.entry.yTChannelID.startsWith(ytchurl)) {
|
||||||
|
this.entry.yTChannelID = this.entry.yTChannelID.replace(ytchurl, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if what remains looks like a youtube channel ID.
|
||||||
|
if (this.entry.yTChannelID.length != "UCyS4xQE6DK4_p3qXQwJQAyA".length) {
|
||||||
|
this.fail = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.fail = false;
|
||||||
|
|
||||||
|
// Get the Channel URL
|
||||||
|
var basestr = 'https://www.googleapis.com/youtube/v3/channels?part=snippet%2CcontentDetails%2Cstatistics';
|
||||||
|
var apikey = '&key=AIzaSyCuIYkMc5SktlnXRXNaDf2ObX-fQvtWCnQ '
|
||||||
|
var channel = '&id=' + addchanel0.entry.yTChannelID;
|
||||||
|
axios.get(basestr + channel + apikey)
|
||||||
|
.then(function (response) {
|
||||||
|
// Only attempt to fill the UI text boxes if they are empty.
|
||||||
|
if (this.entry.description.length == 0) {
|
||||||
|
this.entry.description = response.data.items[0].snippet.description;
|
||||||
|
}
|
||||||
|
if (this.entry.title.length == 0) {
|
||||||
|
this.entry.title = response.data.items[0].snippet.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get client to load the thumbnail
|
||||||
|
this.entry.thumbnailURL = response.data.items[0].snippet.thumbnails.medium.url;
|
||||||
|
}.bind(this))
|
||||||
|
.catch(function (error) {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Bind to our table of entries.
|
||||||
|
var tbody0 = new Vue({
|
||||||
|
el: '#tbody-0',
|
||||||
|
data: {
|
||||||
|
entries: [""]
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
retrieve: function (event) {
|
||||||
|
axios.get('/api/Channels')
|
||||||
|
.then(function (response) {
|
||||||
|
// Wipe out all old entries.
|
||||||
|
this.entries = [];
|
||||||
|
|
||||||
|
// And fill it with all the retrieved entries.
|
||||||
|
response.data.forEach(function (x) {
|
||||||
|
if (x.description.length > maxcharstablefield) {
|
||||||
|
x.description = x.description.substring(0, maxcharstablefield) + " ...";
|
||||||
|
}
|
||||||
|
this.entries.push(x);
|
||||||
|
}.bind(this));
|
||||||
|
}.bind(this))
|
||||||
|
.catch(function (error) {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Grid if images.
|
||||||
|
var videostb = new Vue({
|
||||||
|
el: '#videostb-0',
|
||||||
|
data: {
|
||||||
|
Videos: []
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// Get new videos from the web api.
|
||||||
|
retrieve: function (event) {
|
||||||
|
axios.get('/api/Videos')
|
||||||
|
.then(function (response) {
|
||||||
|
// And fill it with all the retrieved entries.
|
||||||
|
response.data.forEach(function (x) {
|
||||||
|
// Trim description if needed.
|
||||||
|
if (x.description.length > maxcharstablefield) {
|
||||||
|
x.description = x.description.substring(0, maxcharstablefield) + " ...";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add it to our array
|
||||||
|
this.Videos.push(x);
|
||||||
|
}.bind(this));
|
||||||
|
}.bind(this))
|
||||||
|
.catch(function (error) {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('load', function () {
|
||||||
|
tbody0.retrieve();
|
||||||
|
videostb.retrieve();
|
||||||
|
});
|
39
YTManager/wwwroot/index.css
Normal file
39
YTManager/wwwroot/index.css
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
body {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: auto;
|
||||||
|
line-height: 1.6;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #444;
|
||||||
|
background-color: #F8F8F8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
#addnewchannelform {
|
||||||
|
width: inherit;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#vidholder-0 {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.tinytext10px{ font-size: 10px; }
|
||||||
|
.tinytext12px{ font-size: 12px; }
|
||||||
|
.tinytext14px{ font-size: 14px; }
|
||||||
|
.tinytext16px{ font-size: 16px; }
|
||||||
|
.tinytext18px{ font-size: 18px; }
|
||||||
|
|
||||||
|
[v-cloak] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-active, .fade-leave-active {
|
||||||
|
transition: opacity 0.75s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
47
YTManager/wwwroot/index.html
Normal file
47
YTManager/wwwroot/index.html
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
<title>YT Manager</title>
|
||||||
|
|
||||||
|
<!-- Compressed CSS -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/foundation/6.4.3/css/foundation.min.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="index.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Dumb YT Manager</h1>
|
||||||
|
<p>Youtube banned my account and refuses to say why, taking all my subscribed channels with it. This is a simple scrubscribed channel manager, showing recent releases from each channel and finally proper sub-catagory functionality!</p>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
<h2>Most Recent Videos</h2>
|
||||||
|
<div v-cloak id="vidholder-0">
|
||||||
|
<div class="grid-x grid-margin-x imgrow" v-for="row in Videos.Rows">
|
||||||
|
<div class="card medium-2 large-1 cell" v-for="video in row.Columns">
|
||||||
|
<div class="card-divider">
|
||||||
|
<h6>{{video.title}}</h6>
|
||||||
|
</div>
|
||||||
|
<img :src="video.thumbnailURL">
|
||||||
|
<div class="card-section">
|
||||||
|
<p class="tinytext14px">{{video.description}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Compressed JavaScript -->
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/foundation/6.4.3/js/foundation.min.js"></script>
|
||||||
|
|
||||||
|
<!-- For doing REST based API stuff. -->
|
||||||
|
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
||||||
|
|
||||||
|
<!-- Some icons -->
|
||||||
|
<script src="https://use.fontawesome.com/91af8ab4ba.js"></script>
|
||||||
|
|
||||||
|
<!-- Good ole Vue :D -->
|
||||||
|
<script src="https://unpkg.com/vue"></script>
|
||||||
|
|
||||||
|
<!-- All of my custom JS. Put here so body loads before Vue based stuff loads/attempts to bind. -->
|
||||||
|
<script src="index.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
73
YTManager/wwwroot/index.js
Normal file
73
YTManager/wwwroot/index.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
$(document).foundation();
|
||||||
|
|
||||||
|
// How many chars at most for the description.
|
||||||
|
var maxcharsdescriptionfield = 100;
|
||||||
|
|
||||||
|
// Grid if images.
|
||||||
|
var vidholder = new Vue({
|
||||||
|
el: '#vidholder-0',
|
||||||
|
data: {
|
||||||
|
Videos: {
|
||||||
|
Rows: [
|
||||||
|
/*{
|
||||||
|
Columns: [
|
||||||
|
{
|
||||||
|
"title": "0",
|
||||||
|
"description": "",
|
||||||
|
"yTVideoID": "",
|
||||||
|
"thumbnailURL": "",
|
||||||
|
"uploaded": "",
|
||||||
|
"yTChannelID": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}*/
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// Get new videos from the web api.
|
||||||
|
retrieve: function (event) {
|
||||||
|
// Wipe out all old entries.
|
||||||
|
this.Videos.Rows = [];
|
||||||
|
|
||||||
|
axios.get('/api/Videos')
|
||||||
|
.then(function (response) {
|
||||||
|
// Dimension counters and limits.
|
||||||
|
var maxcols = 4;
|
||||||
|
var colcounter = 0;
|
||||||
|
var rowcounter = 0;
|
||||||
|
|
||||||
|
// And fill it with all the retrieved entries.
|
||||||
|
response.data.forEach(function (x) {
|
||||||
|
// Trim description if needed.
|
||||||
|
if (x.description.length > maxcharsdescriptionfield) {
|
||||||
|
x.description = x.description.substring(0, maxcharsdescriptionfield) + " ...";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle creation of a new row.
|
||||||
|
if (colcounter == 0) {
|
||||||
|
Vue.set(this.Videos.Rows, rowcounter, []);
|
||||||
|
Vue.set(this.Videos.Rows[rowcounter], 'Columns', []);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add it to our array
|
||||||
|
this.Videos.Rows[rowcounter].Columns.push(x);
|
||||||
|
|
||||||
|
// So we get it all in groups of maxcols.
|
||||||
|
colcounter = colcounter + 1;
|
||||||
|
if (colcounter == maxcols) {
|
||||||
|
colcounter = 0;
|
||||||
|
rowcounter = rowcounter + 1;
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
}.bind(this))
|
||||||
|
.catch(function (error) {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('load', function () {
|
||||||
|
vidholder.retrieve();
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user