HUGE arch changes to make searching work right
This commit is contained in:
parent
96a26ce30d
commit
9f5fb78835
@ -53,14 +53,5 @@ namespace YTManager.Controllers {
|
|||||||
public IActionResult Get_Update_Status() {
|
public IActionResult Get_Update_Status() {
|
||||||
return Ok(get_massupdatedaemon() == null ? "false" : "true");
|
return Ok(get_massupdatedaemon() == null ? "false" : "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Testing
|
|
||||||
[HttpGet("Test")]
|
|
||||||
public async System.Threading.Tasks.Task<IActionResult> Test() {
|
|
||||||
// await Tasks.FetchVideos.MassUpdate(Startup.DBStr);
|
|
||||||
// var vids = Tasks.FetchVideos.Get_YTVideos("UCsXVk37bltHxD1rDPwtNM8Q", 1).Result;
|
|
||||||
// return Ok(vids);
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Cors;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace YTManager.Controllers {
|
namespace YTManager.Controllers {
|
||||||
[Produces("application/json")]
|
[Produces("application/json")]
|
||||||
[Route("api/Channels")]
|
[Route("api/Channels")]
|
||||||
[EnableCors("AllowAllOrigins")]
|
|
||||||
public class ChannelsController : Controller {
|
public class ChannelsController : Controller {
|
||||||
// Custom return type for API accesses. Done this way to ensure we
|
// Custom return type for API accesses. Done this way to ensure we
|
||||||
// always return the expected data regardless of the underlying model.
|
// always return the expected data regardless of the underlying model.
|
||||||
@ -16,6 +15,7 @@ namespace YTManager.Controllers {
|
|||||||
public string Title;
|
public string Title;
|
||||||
public string Description;
|
public string Description;
|
||||||
public string ID;
|
public string ID;
|
||||||
|
public List<string> User_Tags;
|
||||||
public List<string> Video_IDs;
|
public List<string> Video_IDs;
|
||||||
|
|
||||||
public Channel_ForAPI(Models.Channel c) {
|
public Channel_ForAPI(Models.Channel c) {
|
||||||
@ -23,6 +23,7 @@ namespace YTManager.Controllers {
|
|||||||
Description = c.Description;
|
Description = c.Description;
|
||||||
ID = c.YoutubeID;
|
ID = c.YoutubeID;
|
||||||
Video_IDs = c.Videos.Select(v => v.YoutubeID).ToList();
|
Video_IDs = c.Videos.Select(v => v.YoutubeID).ToList();
|
||||||
|
User_Tags = c.UserTags == null ? new List<string>() : c.UserTags.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,6 +39,9 @@ namespace YTManager.Controllers {
|
|||||||
// Returns the most recently added channels.
|
// Returns the most recently added channels.
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> Get() {
|
public async Task<IActionResult> Get() {
|
||||||
|
// Log this to the terminal.
|
||||||
|
Console.WriteLine($"{DateTime.Now} == Channels GET");
|
||||||
|
|
||||||
// Get all the relevant channels.
|
// Get all the relevant channels.
|
||||||
var chanels = await db.Channels
|
var chanels = await db.Channels
|
||||||
.Include(c => c.Videos)
|
.Include(c => c.Videos)
|
||||||
@ -53,5 +57,29 @@ namespace YTManager.Controllers {
|
|||||||
// Convert all the videos to what we will send back.
|
// Convert all the videos to what we will send back.
|
||||||
return Ok(converted);
|
return Ok(converted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("{channelid}")]
|
||||||
|
public async Task<IActionResult> PostChannel([FromRoute] string channelid) {
|
||||||
|
Console.WriteLine($"{DateTime.Now} == Channels POST -> {channelid}");
|
||||||
|
|
||||||
|
// Verify the channel looks resonable.
|
||||||
|
var expected_len = "UCyS4xQE6DK4_p3qXQwJQAyA".Length;
|
||||||
|
if (channelid.Length != expected_len)
|
||||||
|
return BadRequest($"Length should be {expected_len} but is {channelid.Length}");
|
||||||
|
|
||||||
|
// Only add it to the databse if it's not in there already.
|
||||||
|
if (db.Channels.Any(c => c.YoutubeID == channelid))
|
||||||
|
return BadRequest($"Channel {channelid} is already in DB!");
|
||||||
|
|
||||||
|
// Get the channel contents from youtube.
|
||||||
|
var channel = await Tasks.FetchVideos.Get_YTChannel(channelid);
|
||||||
|
|
||||||
|
// Add it to the databse.
|
||||||
|
await db.Channels.AddAsync(channel);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Say all went ok.
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,23 +82,6 @@ namespace YTManager.Controllers.Private
|
|||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{channelid}")]
|
|
||||||
public async Task<IActionResult> PostChannel([FromRoute] string channelid) {
|
|
||||||
// Only add it to the databse if it's not in there already.
|
|
||||||
if (db.Channels.Any(c => c.YoutubeID == channelid))
|
|
||||||
return BadRequest();
|
|
||||||
|
|
||||||
// Get the channel contents from youtube.
|
|
||||||
var channel = await Tasks.FetchVideos.Get_YTChannel(channelid);
|
|
||||||
|
|
||||||
// Add it to the databse.
|
|
||||||
await db.Channels.AddAsync(channel);
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
|
|
||||||
// Say all went ok.
|
|
||||||
return Ok(channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> PostChannel([FromBody] Channel channel)
|
public async Task<IActionResult> PostChannel([FromBody] Channel channel)
|
||||||
{
|
{
|
||||||
|
@ -3,14 +3,12 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using Microsoft.AspNetCore.Cors;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace YTManager.Controllers {
|
namespace YTManager.Controllers {
|
||||||
[Produces("application/json")]
|
[Produces("application/json")]
|
||||||
[Route("api/Videos")]
|
[Route("api/Videos")]
|
||||||
[EnableCors("AllowAllOrigins")]
|
|
||||||
public class VideosController : Controller {
|
public class VideosController : Controller {
|
||||||
// Custom return type for API accesses. Done this way to ensure we
|
// Custom return type for API accesses. Done this way to ensure we
|
||||||
// always return the expected data regardless of the underlying model.
|
// always return the expected data regardless of the underlying model.
|
||||||
@ -44,7 +42,7 @@ namespace YTManager.Controllers {
|
|||||||
Thumbnail = video.ThumbnailURL;
|
Thumbnail = video.ThumbnailURL;
|
||||||
Channel = video.Channel.Title;
|
Channel = video.Channel.Title;
|
||||||
Seconds = (int)video.Duration.TotalSeconds;
|
Seconds = (int)video.Duration.TotalSeconds;
|
||||||
Tags = video.Tags?.Select(t => t.Name).ToList();
|
Tags = video.Tags == null ? new List<string>() : video.Tags.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,31 +55,7 @@ namespace YTManager.Controllers {
|
|||||||
// Constructor to fetch the db context.
|
// Constructor to fetch the db context.
|
||||||
public VideosController(MediaDB context) => db = context;
|
public VideosController(MediaDB context) => db = context;
|
||||||
|
|
||||||
// Returns the most recent videos.
|
struct Search_Query {
|
||||||
[HttpGet]
|
|
||||||
public async Task<IActionResult> GetVideos() {
|
|
||||||
// Get all the relevant videos.
|
|
||||||
var vids = await db.Videos
|
|
||||||
.OrderByDescending(i => i.AddedToYT)
|
|
||||||
.Take(max_per_query)
|
|
||||||
.Include(v => v.Channel)
|
|
||||||
.Include(v => v.Tags)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
// Convert them to what we will send out.
|
|
||||||
var converted = vids
|
|
||||||
.Select(v => new Video_ForAPI(v))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// Convert all the videos to what we will send back.
|
|
||||||
return Ok(converted);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Search_Query
|
|
||||||
{
|
|
||||||
// Tags.
|
|
||||||
public List<Models.Tag> Tags;
|
|
||||||
|
|
||||||
// What the duration type is.
|
// What the duration type is.
|
||||||
public enum _Duration_Type { LessThan, GreaterThan, Unset };
|
public enum _Duration_Type { LessThan, GreaterThan, Unset };
|
||||||
public _Duration_Type Duration_Type;
|
public _Duration_Type Duration_Type;
|
||||||
@ -93,10 +67,26 @@ namespace YTManager.Controllers {
|
|||||||
public List<String> Remaining_Tokens;
|
public List<String> Remaining_Tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Searches for videos using the specified tag(s).
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> Search() {
|
||||||
|
var converted = await db.Videos
|
||||||
|
.OrderByDescending(i => i.AddedToYT)
|
||||||
|
.Take(max_per_query)
|
||||||
|
.Include(v => v.Channel)
|
||||||
|
.Select(v => new Video_ForAPI(v))
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
// Convert all the videos to what we will send back.
|
||||||
|
return Ok(converted);
|
||||||
|
}
|
||||||
|
|
||||||
// Searches for videos using the specified tag(s).
|
// Searches for videos using the specified tag(s).
|
||||||
[HttpGet("search/{searchstr}")]
|
[HttpGet("{searchstr}")]
|
||||||
public async Task<IActionResult> Search([FromRoute] string searchstr) {
|
public async Task<IActionResult> Search([FromRoute] string searchstr) {
|
||||||
|
// Log this to the terminal.
|
||||||
|
Console.WriteLine($"{DateTime.Now} == Search request for -> {searchstr}.");
|
||||||
|
|
||||||
// What to use for searching videos with.
|
// What to use for searching videos with.
|
||||||
var parsed_query = new Search_Query();
|
var parsed_query = new Search_Query();
|
||||||
|
|
||||||
@ -128,12 +118,6 @@ namespace YTManager.Controllers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find which tokens are explicit tags
|
|
||||||
parsed_query.Tags = await db.Tags
|
|
||||||
.Where(t => parsed_query.Remaining_Tokens.Any(token => token == t.Name.ToLower()))
|
|
||||||
.ToListAsync();
|
|
||||||
parsed_query.Tags.ForEach(tag => parsed_query.Remaining_Tokens.Remove(tag.Name.ToLower()));
|
|
||||||
|
|
||||||
// Get from the database all videos which satisfy the query via
|
// Get from the database all videos which satisfy the query via
|
||||||
// AND'ing all the queries.
|
// AND'ing all the queries.
|
||||||
var dbquery = db.Videos.Select(v => v);
|
var dbquery = db.Videos.Select(v => v);
|
||||||
@ -144,16 +128,14 @@ namespace YTManager.Controllers {
|
|||||||
else if (parsed_query.Duration_Type == Search_Query._Duration_Type.LessThan)
|
else if (parsed_query.Duration_Type == Search_Query._Duration_Type.LessThan)
|
||||||
dbquery = dbquery.Where(v => v.Duration <= parsed_query.Duration);
|
dbquery = dbquery.Where(v => v.Duration <= parsed_query.Duration);
|
||||||
|
|
||||||
// Match videos where the tag matches.
|
|
||||||
parsed_query.Tags.ForEach(tag => dbquery = dbquery.Where(V => V.Tags.Any(vt => vt.Name == tag.Name)));
|
|
||||||
|
|
||||||
// Get all videos that match their title, description, or channel name
|
// Get all videos that match their title, description, or channel name
|
||||||
// with the remaining tokens.
|
// with the remaining tokens.
|
||||||
parsed_query.Remaining_Tokens.ForEach(token => {
|
parsed_query.Remaining_Tokens.ForEach(token => {
|
||||||
dbquery = dbquery.Where(v =>
|
dbquery = dbquery.Where(v =>
|
||||||
v.Channel.Title.ToLower().Contains(token) ||
|
v.Channel.Title.ToLower().Contains(token) ||
|
||||||
v.Title.ToLower().Contains(token) ||
|
v.Title.ToLower().Contains(token) ||
|
||||||
v.Description.ToLower().Contains(token));
|
v.Description.ToLower().Contains(token) ||
|
||||||
|
v.YoutubeID.ToLower().Contains(token));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get all the relevant videos.
|
// Get all the relevant videos.
|
||||||
@ -161,7 +143,6 @@ namespace YTManager.Controllers {
|
|||||||
.OrderByDescending(i => i.AddedToYT)
|
.OrderByDescending(i => i.AddedToYT)
|
||||||
.Take(max_per_query)
|
.Take(max_per_query)
|
||||||
.Include(v => v.Channel)
|
.Include(v => v.Channel)
|
||||||
.Include(v => v.Tags)
|
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
// Convert them to what we will send out.
|
// Convert them to what we will send out.
|
||||||
@ -169,26 +150,8 @@ namespace YTManager.Controllers {
|
|||||||
.Select(v => new Video_ForAPI(v))
|
.Select(v => new Video_ForAPI(v))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// Convert all the videos to what we will send back.
|
// Log this to the terminal.
|
||||||
return Ok(converted);
|
Console.WriteLine($"{DateTime.Now} == Search request for -> {searchstr} found {converted.Count()} videos.");
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the most recent videos of a channel.
|
|
||||||
[HttpGet("fromchannel/{channelName}")]
|
|
||||||
public async Task<IActionResult> Get_Channel_Videos([FromRoute] string channelName) {
|
|
||||||
// Get all the relevant videos.
|
|
||||||
var vids = await db.Videos
|
|
||||||
.Where(v => v.Channel.Title == channelName)
|
|
||||||
.OrderByDescending(i => i.AddedToYT)
|
|
||||||
.Take(max_per_query)
|
|
||||||
.Include(v => v.Channel)
|
|
||||||
.Include(v => v.Tags)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
// Convert them to what we will send out.
|
|
||||||
var converted = vids
|
|
||||||
.Select(v => new Video_ForAPI(v))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// Convert all the videos to what we will send back.
|
// Convert all the videos to what we will send back.
|
||||||
return Ok(converted);
|
return Ok(converted);
|
||||||
|
@ -6,7 +6,6 @@ namespace YTManager
|
|||||||
{
|
{
|
||||||
public DbSet<Models.Channel> Channels { get; set; }
|
public DbSet<Models.Channel> Channels { get; set; }
|
||||||
public DbSet<Models.Video> Videos { get; set; }
|
public DbSet<Models.Video> Videos { get; set; }
|
||||||
public DbSet<Models.Tag> Tags { get; set; }
|
|
||||||
|
|
||||||
public MediaDB(DbContextOptions<MediaDB> options) : base(options){ }
|
public MediaDB(DbContextOptions<MediaDB> options) : base(options){ }
|
||||||
|
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace YTManager.Migrations
|
|
||||||
{
|
|
||||||
public partial class fix_channel_relationship : Migration
|
|
||||||
{
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropForeignKey(
|
|
||||||
name: "FK_Videos_Channels_ChannelPrimaryKey",
|
|
||||||
table: "Videos");
|
|
||||||
|
|
||||||
migrationBuilder.AlterColumn<long>(
|
|
||||||
name: "ChannelPrimaryKey",
|
|
||||||
table: "Videos",
|
|
||||||
nullable: false,
|
|
||||||
oldClrType: typeof(long),
|
|
||||||
oldNullable: true);
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<long>(
|
|
||||||
name: "VideoPrimaryKey",
|
|
||||||
table: "Tags",
|
|
||||||
nullable: true);
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_Tags_VideoPrimaryKey",
|
|
||||||
table: "Tags",
|
|
||||||
column: "VideoPrimaryKey");
|
|
||||||
|
|
||||||
migrationBuilder.AddForeignKey(
|
|
||||||
name: "FK_Tags_Videos_VideoPrimaryKey",
|
|
||||||
table: "Tags",
|
|
||||||
column: "VideoPrimaryKey",
|
|
||||||
principalTable: "Videos",
|
|
||||||
principalColumn: "PrimaryKey",
|
|
||||||
onDelete: ReferentialAction.Restrict);
|
|
||||||
|
|
||||||
migrationBuilder.AddForeignKey(
|
|
||||||
name: "FK_Videos_Channels_ChannelPrimaryKey",
|
|
||||||
table: "Videos",
|
|
||||||
column: "ChannelPrimaryKey",
|
|
||||||
principalTable: "Channels",
|
|
||||||
principalColumn: "PrimaryKey",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropForeignKey(
|
|
||||||
name: "FK_Tags_Videos_VideoPrimaryKey",
|
|
||||||
table: "Tags");
|
|
||||||
|
|
||||||
migrationBuilder.DropForeignKey(
|
|
||||||
name: "FK_Videos_Channels_ChannelPrimaryKey",
|
|
||||||
table: "Videos");
|
|
||||||
|
|
||||||
migrationBuilder.DropIndex(
|
|
||||||
name: "IX_Tags_VideoPrimaryKey",
|
|
||||||
table: "Tags");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "VideoPrimaryKey",
|
|
||||||
table: "Tags");
|
|
||||||
|
|
||||||
migrationBuilder.AlterColumn<long>(
|
|
||||||
name: "ChannelPrimaryKey",
|
|
||||||
table: "Videos",
|
|
||||||
nullable: true,
|
|
||||||
oldClrType: typeof(long));
|
|
||||||
|
|
||||||
migrationBuilder.AddForeignKey(
|
|
||||||
name: "FK_Videos_Channels_ChannelPrimaryKey",
|
|
||||||
table: "Videos",
|
|
||||||
column: "ChannelPrimaryKey",
|
|
||||||
principalTable: "Channels",
|
|
||||||
principalColumn: "PrimaryKey",
|
|
||||||
onDelete: ReferentialAction.Restrict);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace YTManager.Migrations
|
|
||||||
{
|
|
||||||
public partial class added_vid_duration : Migration
|
|
||||||
{
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.AddColumn<TimeSpan>(
|
|
||||||
name: "Duration",
|
|
||||||
table: "Videos",
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: "00:00:00");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "Duration",
|
|
||||||
table: "Videos");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,12 +6,13 @@ using Microsoft.EntityFrameworkCore.Migrations;
|
|||||||
using Microsoft.EntityFrameworkCore.Storage;
|
using Microsoft.EntityFrameworkCore.Storage;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.Internal;
|
using Microsoft.EntityFrameworkCore.Storage.Internal;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using YTManager;
|
using YTManager;
|
||||||
|
|
||||||
namespace YTManager.Migrations
|
namespace YTManager.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(MediaDB))]
|
[DbContext(typeof(MediaDB))]
|
||||||
[Migration("20180224051602_initial")]
|
[Migration("20180304034317_initial")]
|
||||||
partial class initial
|
partial class initial
|
||||||
{
|
{
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
@ -39,6 +40,8 @@ namespace YTManager.Migrations
|
|||||||
b.Property<string>("Title")
|
b.Property<string>("Title")
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<List<string>>("UserTags");
|
||||||
|
|
||||||
b.Property<string>("YoutubeID")
|
b.Property<string>("YoutubeID")
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@ -47,19 +50,6 @@ namespace YTManager.Migrations
|
|||||||
b.ToTable("Channels");
|
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 =>
|
modelBuilder.Entity("YTManager.Models.Video", b =>
|
||||||
{
|
{
|
||||||
b.Property<long>("PrimaryKey")
|
b.Property<long>("PrimaryKey")
|
||||||
@ -69,11 +59,16 @@ namespace YTManager.Migrations
|
|||||||
|
|
||||||
b.Property<DateTime>("AddedtoDB");
|
b.Property<DateTime>("AddedtoDB");
|
||||||
|
|
||||||
b.Property<long?>("ChannelPrimaryKey");
|
b.Property<long>("ChannelPrimaryKey");
|
||||||
|
|
||||||
b.Property<string>("Description")
|
b.Property<string>("Description")
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<TimeSpan>("Duration");
|
||||||
|
|
||||||
|
b.Property<List<string>>("Tags")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
b.Property<string>("ThumbnailURL")
|
b.Property<string>("ThumbnailURL")
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@ -92,9 +87,10 @@ namespace YTManager.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("YTManager.Models.Video", b =>
|
modelBuilder.Entity("YTManager.Models.Video", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("YTManager.Models.Channel")
|
b.HasOne("YTManager.Models.Channel", "Channel")
|
||||||
.WithMany("Videos")
|
.WithMany("Videos")
|
||||||
.HasForeignKey("ChannelPrimaryKey");
|
.HasForeignKey("ChannelPrimaryKey")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
});
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
@ -20,6 +20,7 @@ namespace YTManager.Migrations
|
|||||||
Refreshed = table.Column<DateTime>(nullable: false),
|
Refreshed = table.Column<DateTime>(nullable: false),
|
||||||
ThumbnailURL = table.Column<string>(nullable: false),
|
ThumbnailURL = table.Column<string>(nullable: false),
|
||||||
Title = table.Column<string>(nullable: false),
|
Title = table.Column<string>(nullable: false),
|
||||||
|
UserTags = table.Column<List<string>>(nullable: true),
|
||||||
YoutubeID = table.Column<string>(nullable: false)
|
YoutubeID = table.Column<string>(nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
@ -27,19 +28,6 @@ namespace YTManager.Migrations
|
|||||||
table.PrimaryKey("PK_Channels", x => x.PrimaryKey);
|
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(
|
migrationBuilder.CreateTable(
|
||||||
name: "Videos",
|
name: "Videos",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
@ -48,8 +36,10 @@ namespace YTManager.Migrations
|
|||||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn),
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn),
|
||||||
AddedToYT = table.Column<DateTime>(nullable: false),
|
AddedToYT = table.Column<DateTime>(nullable: false),
|
||||||
AddedtoDB = table.Column<DateTime>(nullable: false),
|
AddedtoDB = table.Column<DateTime>(nullable: false),
|
||||||
ChannelPrimaryKey = table.Column<long>(nullable: true),
|
ChannelPrimaryKey = table.Column<long>(nullable: false),
|
||||||
Description = table.Column<string>(nullable: false),
|
Description = table.Column<string>(nullable: false),
|
||||||
|
Duration = table.Column<TimeSpan>(nullable: false),
|
||||||
|
Tags = table.Column<List<string>>(nullable: false),
|
||||||
ThumbnailURL = table.Column<string>(nullable: false),
|
ThumbnailURL = table.Column<string>(nullable: false),
|
||||||
Title = table.Column<string>(nullable: false),
|
Title = table.Column<string>(nullable: false),
|
||||||
YoutubeID = table.Column<string>(nullable: false)
|
YoutubeID = table.Column<string>(nullable: false)
|
||||||
@ -62,7 +52,7 @@ namespace YTManager.Migrations
|
|||||||
column: x => x.ChannelPrimaryKey,
|
column: x => x.ChannelPrimaryKey,
|
||||||
principalTable: "Channels",
|
principalTable: "Channels",
|
||||||
principalColumn: "PrimaryKey",
|
principalColumn: "PrimaryKey",
|
||||||
onDelete: ReferentialAction.Restrict);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
@ -73,9 +63,6 @@ namespace YTManager.Migrations
|
|||||||
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "Tags");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Videos");
|
name: "Videos");
|
||||||
|
|
@ -6,13 +6,14 @@ using Microsoft.EntityFrameworkCore.Migrations;
|
|||||||
using Microsoft.EntityFrameworkCore.Storage;
|
using Microsoft.EntityFrameworkCore.Storage;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.Internal;
|
using Microsoft.EntityFrameworkCore.Storage.Internal;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using YTManager;
|
using YTManager;
|
||||||
|
|
||||||
namespace YTManager.Migrations
|
namespace YTManager.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(MediaDB))]
|
[DbContext(typeof(MediaDB))]
|
||||||
[Migration("20180228202611_added_vid_duration")]
|
[Migration("20180305045634_Tag change")]
|
||||||
partial class added_vid_duration
|
partial class Tagchange
|
||||||
{
|
{
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
@ -39,6 +40,8 @@ namespace YTManager.Migrations
|
|||||||
b.Property<string>("Title")
|
b.Property<string>("Title")
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<List<string>>("UserTags");
|
||||||
|
|
||||||
b.Property<string>("YoutubeID")
|
b.Property<string>("YoutubeID")
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@ -47,23 +50,6 @@ namespace YTManager.Migrations
|
|||||||
b.ToTable("Channels");
|
b.ToTable("Channels");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("YTManager.Models.Tag", b =>
|
|
||||||
{
|
|
||||||
b.Property<long>("PrimaryKey")
|
|
||||||
.ValueGeneratedOnAdd();
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Property<long?>("VideoPrimaryKey");
|
|
||||||
|
|
||||||
b.HasKey("PrimaryKey");
|
|
||||||
|
|
||||||
b.HasIndex("VideoPrimaryKey");
|
|
||||||
|
|
||||||
b.ToTable("Tags");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("YTManager.Models.Video", b =>
|
modelBuilder.Entity("YTManager.Models.Video", b =>
|
||||||
{
|
{
|
||||||
b.Property<long>("PrimaryKey")
|
b.Property<long>("PrimaryKey")
|
||||||
@ -80,6 +66,9 @@ namespace YTManager.Migrations
|
|||||||
|
|
||||||
b.Property<TimeSpan>("Duration");
|
b.Property<TimeSpan>("Duration");
|
||||||
|
|
||||||
|
b.Property<List<string>>("Tags")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
b.Property<string>("ThumbnailURL")
|
b.Property<string>("ThumbnailURL")
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@ -96,13 +85,6 @@ namespace YTManager.Migrations
|
|||||||
b.ToTable("Videos");
|
b.ToTable("Videos");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("YTManager.Models.Tag", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("YTManager.Models.Video")
|
|
||||||
.WithMany("Tags")
|
|
||||||
.HasForeignKey("VideoPrimaryKey");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("YTManager.Models.Video", b =>
|
modelBuilder.Entity("YTManager.Models.Video", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("YTManager.Models.Channel", "Channel")
|
b.HasOne("YTManager.Models.Channel", "Channel")
|
19
YTManager/Migrations/20180305045634_Tag change.cs
Normal file
19
YTManager/Migrations/20180305045634_Tag change.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace YTManager.Migrations
|
||||||
|
{
|
||||||
|
public partial class Tagchange : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,13 +6,14 @@ using Microsoft.EntityFrameworkCore.Migrations;
|
|||||||
using Microsoft.EntityFrameworkCore.Storage;
|
using Microsoft.EntityFrameworkCore.Storage;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.Internal;
|
using Microsoft.EntityFrameworkCore.Storage.Internal;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using YTManager;
|
using YTManager;
|
||||||
|
|
||||||
namespace YTManager.Migrations
|
namespace YTManager.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(MediaDB))]
|
[DbContext(typeof(MediaDB))]
|
||||||
[Migration("20180224055707_fix_channel_relationship")]
|
[Migration("20180305052950_Made tags required")]
|
||||||
partial class fix_channel_relationship
|
partial class Madetagsrequired
|
||||||
{
|
{
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
@ -39,6 +40,9 @@ namespace YTManager.Migrations
|
|||||||
b.Property<string>("Title")
|
b.Property<string>("Title")
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<string[]>("UserTags")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
b.Property<string>("YoutubeID")
|
b.Property<string>("YoutubeID")
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@ -47,23 +51,6 @@ namespace YTManager.Migrations
|
|||||||
b.ToTable("Channels");
|
b.ToTable("Channels");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("YTManager.Models.Tag", b =>
|
|
||||||
{
|
|
||||||
b.Property<long>("PrimaryKey")
|
|
||||||
.ValueGeneratedOnAdd();
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Property<long?>("VideoPrimaryKey");
|
|
||||||
|
|
||||||
b.HasKey("PrimaryKey");
|
|
||||||
|
|
||||||
b.HasIndex("VideoPrimaryKey");
|
|
||||||
|
|
||||||
b.ToTable("Tags");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("YTManager.Models.Video", b =>
|
modelBuilder.Entity("YTManager.Models.Video", b =>
|
||||||
{
|
{
|
||||||
b.Property<long>("PrimaryKey")
|
b.Property<long>("PrimaryKey")
|
||||||
@ -78,6 +65,11 @@ namespace YTManager.Migrations
|
|||||||
b.Property<string>("Description")
|
b.Property<string>("Description")
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<TimeSpan>("Duration");
|
||||||
|
|
||||||
|
b.Property<List<string>>("Tags")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
b.Property<string>("ThumbnailURL")
|
b.Property<string>("ThumbnailURL")
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@ -94,13 +86,6 @@ namespace YTManager.Migrations
|
|||||||
b.ToTable("Videos");
|
b.ToTable("Videos");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("YTManager.Models.Tag", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("YTManager.Models.Video")
|
|
||||||
.WithMany("Tags")
|
|
||||||
.HasForeignKey("VideoPrimaryKey");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("YTManager.Models.Video", b =>
|
modelBuilder.Entity("YTManager.Models.Video", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("YTManager.Models.Channel", "Channel")
|
b.HasOne("YTManager.Models.Channel", "Channel")
|
28
YTManager/Migrations/20180305052950_Made tags required.cs
Normal file
28
YTManager/Migrations/20180305052950_Made tags required.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace YTManager.Migrations
|
||||||
|
{
|
||||||
|
public partial class Madetagsrequired : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<string[]>(
|
||||||
|
name: "UserTags",
|
||||||
|
table: "Channels",
|
||||||
|
nullable: false,
|
||||||
|
oldClrType: typeof(List<string>),
|
||||||
|
oldNullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<List<string>>(
|
||||||
|
name: "UserTags",
|
||||||
|
table: "Channels",
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(string[]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
98
YTManager/Migrations/20180305055427_Fixed videos tags.Designer.cs
generated
Normal file
98
YTManager/Migrations/20180305055427_Fixed videos tags.Designer.cs
generated
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
// <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("20180305055427_Fixed videos tags")]
|
||||||
|
partial class Fixedvideostags
|
||||||
|
{
|
||||||
|
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<DateTime>("Refreshed");
|
||||||
|
|
||||||
|
b.Property<string>("ThumbnailURL")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<string[]>("UserTags")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<string>("YoutubeID")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasKey("PrimaryKey");
|
||||||
|
|
||||||
|
b.ToTable("Channels");
|
||||||
|
});
|
||||||
|
|
||||||
|
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<TimeSpan>("Duration");
|
||||||
|
|
||||||
|
b.Property<string[]>("Tags")
|
||||||
|
.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", "Channel")
|
||||||
|
.WithMany("Videos")
|
||||||
|
.HasForeignKey("ChannelPrimaryKey")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
YTManager/Migrations/20180305055427_Fixed videos tags.cs
Normal file
19
YTManager/Migrations/20180305055427_Fixed videos tags.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace YTManager.Migrations
|
||||||
|
{
|
||||||
|
public partial class Fixedvideostags : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -38,6 +38,9 @@ namespace YTManager.Migrations
|
|||||||
b.Property<string>("Title")
|
b.Property<string>("Title")
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<string[]>("UserTags")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
b.Property<string>("YoutubeID")
|
b.Property<string>("YoutubeID")
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@ -46,23 +49,6 @@ namespace YTManager.Migrations
|
|||||||
b.ToTable("Channels");
|
b.ToTable("Channels");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("YTManager.Models.Tag", b =>
|
|
||||||
{
|
|
||||||
b.Property<long>("PrimaryKey")
|
|
||||||
.ValueGeneratedOnAdd();
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Property<long?>("VideoPrimaryKey");
|
|
||||||
|
|
||||||
b.HasKey("PrimaryKey");
|
|
||||||
|
|
||||||
b.HasIndex("VideoPrimaryKey");
|
|
||||||
|
|
||||||
b.ToTable("Tags");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("YTManager.Models.Video", b =>
|
modelBuilder.Entity("YTManager.Models.Video", b =>
|
||||||
{
|
{
|
||||||
b.Property<long>("PrimaryKey")
|
b.Property<long>("PrimaryKey")
|
||||||
@ -79,6 +65,9 @@ namespace YTManager.Migrations
|
|||||||
|
|
||||||
b.Property<TimeSpan>("Duration");
|
b.Property<TimeSpan>("Duration");
|
||||||
|
|
||||||
|
b.Property<string[]>("Tags")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
b.Property<string>("ThumbnailURL")
|
b.Property<string>("ThumbnailURL")
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@ -95,13 +84,6 @@ namespace YTManager.Migrations
|
|||||||
b.ToTable("Videos");
|
b.ToTable("Videos");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("YTManager.Models.Tag", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("YTManager.Models.Video")
|
|
||||||
.WithMany("Tags")
|
|
||||||
.HasForeignKey("VideoPrimaryKey");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("YTManager.Models.Video", b =>
|
modelBuilder.Entity("YTManager.Models.Video", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("YTManager.Models.Channel", "Channel")
|
b.HasOne("YTManager.Models.Channel", "Channel")
|
||||||
|
@ -33,6 +33,11 @@ namespace YTManager.Models {
|
|||||||
public DateTime Refreshed { get; set; }
|
public DateTime Refreshed { get; set; }
|
||||||
|
|
||||||
// Videos this channel has.
|
// Videos this channel has.
|
||||||
|
[Required]
|
||||||
public List<Video> Videos { get; set; }
|
public List<Video> Videos { get; set; }
|
||||||
|
|
||||||
|
// Tags attached to this channel by user.
|
||||||
|
[Required]
|
||||||
|
public string[] UserTags { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
|
|
||||||
namespace YTManager.Models
|
|
||||||
{
|
|
||||||
public class Tag
|
|
||||||
{
|
|
||||||
// Uniquie ID for this media type
|
|
||||||
[Key]
|
|
||||||
public long PrimaryKey { get; set; }
|
|
||||||
|
|
||||||
// Tag Name.
|
|
||||||
[Required]
|
|
||||||
public string Name { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -42,6 +42,6 @@ namespace YTManager.Models {
|
|||||||
|
|
||||||
// Tag this video applies to.
|
// Tag this video applies to.
|
||||||
[Required]
|
[Required]
|
||||||
public List<Tag> Tags { get; set; }
|
public string[] Tags { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,6 @@ namespace YTManager {
|
|||||||
services.AddMvc();
|
services.AddMvc();
|
||||||
services.AddDbContext<MediaDB>(x => x.UseNpgsql(DBStr));
|
services.AddDbContext<MediaDB>(x => x.UseNpgsql(DBStr));
|
||||||
services.AddHangfire(x => x.UsePostgreSqlStorage(DBStr));
|
services.AddHangfire(x => x.UsePostgreSqlStorage(DBStr));
|
||||||
services.AddCors(op => op.AddPolicy("AllowAllOrigins", builder => builder.AllowAnyOrigin()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
@ -39,6 +38,7 @@ namespace YTManager {
|
|||||||
|
|
||||||
app.UseDefaultFiles();
|
app.UseDefaultFiles();
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
|
app.UseCors(b => { b.AllowAnyOrigin(); b.AllowAnyMethod(); });
|
||||||
app.UseMvc();
|
app.UseMvc();
|
||||||
app.UseHangfireServer();
|
app.UseHangfireServer();
|
||||||
app.UseHangfireDashboard();
|
app.UseHangfireDashboard();
|
||||||
|
@ -36,7 +36,8 @@ namespace YTManager.Tasks {
|
|||||||
YoutubeID = newvid.Id.VideoId,
|
YoutubeID = newvid.Id.VideoId,
|
||||||
AddedToYT = newvid.Snippet.PublishedAt.GetValueOrDefault(),
|
AddedToYT = newvid.Snippet.PublishedAt.GetValueOrDefault(),
|
||||||
AddedtoDB = DateTime.Now,
|
AddedtoDB = DateTime.Now,
|
||||||
ThumbnailURL = newvid.Snippet.Thumbnails.Medium.Url
|
ThumbnailURL = newvid.Snippet.Thumbnails.Medium.Url,
|
||||||
|
Tags = new string[] {}
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
// Search youtube to get the length and tags for each video.
|
// Search youtube to get the length and tags for each video.
|
||||||
@ -52,10 +53,8 @@ namespace YTManager.Tasks {
|
|||||||
vid.Duration = System.Xml.XmlConvert.ToTimeSpan(contentdetail.ContentDetails.Duration);
|
vid.Duration = System.Xml.XmlConvert.ToTimeSpan(contentdetail.ContentDetails.Duration);
|
||||||
|
|
||||||
// Copy over all the created tags if any tags were provided.
|
// Copy over all the created tags if any tags were provided.
|
||||||
if (contentdetail.Snippet.Tags == null)
|
if (contentdetail.Snippet.Tags != null)
|
||||||
vid.Tags = new List<Models.Tag>();
|
vid.Tags = contentdetail.Snippet.Tags.Select(t => t.ToLower()).ToArray();
|
||||||
else
|
|
||||||
vid.Tags = contentdetail.Snippet.Tags.Select(t => new Models.Tag { Name = t.ToLower() }).ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send back the parsed vids.
|
// Send back the parsed vids.
|
||||||
@ -85,7 +84,8 @@ namespace YTManager.Tasks {
|
|||||||
YoutubeID = channelID,
|
YoutubeID = channelID,
|
||||||
AddedtoDB = DateTime.Now,
|
AddedtoDB = DateTime.Now,
|
||||||
Refreshed = DateTime.MinValue,
|
Refreshed = DateTime.MinValue,
|
||||||
Videos = null
|
UserTags = new string[]{},
|
||||||
|
Videos = { }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,8 +35,4 @@
|
|||||||
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.1" />
|
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Migrations\" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
15
YTManager/frontend/admin/index.html
Normal file
15
YTManager/frontend/admin/index.html
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<!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" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>Admin Page</h2>
|
||||||
|
<div id="admin_app"></div>
|
||||||
|
<script src="./../dist/build.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
6
YTManager/frontend/package-lock.json
generated
6
YTManager/frontend/package-lock.json
generated
@ -9320,6 +9320,12 @@
|
|||||||
"whet.extend": "0.9.9"
|
"whet.extend": "0.9.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"sweetalert2": {
|
||||||
|
"version": "7.13.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-7.13.3.tgz",
|
||||||
|
"integrity": "sha512-cClC9uxXg2oTygUbRhVG0ELVouDbKxzZmjM/pjmPsxbdvDWPmijOq350kvcOIClKezqaG7GsyUSxUbZQWQGZDg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"symbol-observable": {
|
"symbol-observable": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz",
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
"axios": "^0.18.0",
|
"axios": "^0.18.0",
|
||||||
"css-loader": "^0.28.10",
|
"css-loader": "^0.28.10",
|
||||||
"style-loader": "^0.20.2",
|
"style-loader": "^0.20.2",
|
||||||
|
"sweetalert2": "^7.13.3",
|
||||||
"ts-loader": "^4.0.0",
|
"ts-loader": "^4.0.0",
|
||||||
"typescript": "^2.7.2",
|
"typescript": "^2.7.2",
|
||||||
"uglifyjs-webpack-plugin": "^1.2.2",
|
"uglifyjs-webpack-plugin": "^1.2.2",
|
||||||
|
126
YTManager/frontend/src/components/Channel_Add.vue
Normal file
126
YTManager/frontend/src/components/Channel_Add.vue
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<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" v-model="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: !Valid }" type="text"
|
||||||
|
placeholder="URL" v-model="Channel_Identification_Box">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-label">Description</span>
|
||||||
|
<input class="input-group-field" type="text" placeholder="Description" v-model="Description">
|
||||||
|
</div>
|
||||||
|
<div class="grid-x">
|
||||||
|
<img class="small-4 cell" :src="Thumbnail" />
|
||||||
|
<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>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from "vue";
|
||||||
|
import * as Dyt from "../dumbyt";
|
||||||
|
import Axios from "axios";
|
||||||
|
import SA2 from "sweetalert2";
|
||||||
|
|
||||||
|
// Vue class for keeping state of the videos.
|
||||||
|
export default Vue.extend({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
Title: "",
|
||||||
|
Description: "",
|
||||||
|
ID: "",
|
||||||
|
Thumbnail: "",
|
||||||
|
Valid: false,
|
||||||
|
Expanded: false,
|
||||||
|
Channel_Identification_Box : ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
Channel_Identification_Box(newcontents: string) {
|
||||||
|
this.GetChannelFromYT(newcontents);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
Submit() : void {
|
||||||
|
Dyt.channel_modify(this.ID, Dyt.Modification.Add).then((resp) => {
|
||||||
|
if (resp == null)
|
||||||
|
SA2(
|
||||||
|
"Channel Add Success!",
|
||||||
|
"The channel has been added succesfully, video contents should be updated shortly.",
|
||||||
|
"success"
|
||||||
|
);
|
||||||
|
else
|
||||||
|
SA2(
|
||||||
|
"Channel Add Fail!",
|
||||||
|
"The channel has not been added due to the following: \n" + resp,
|
||||||
|
"error"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hide the bars and erase internal state.
|
||||||
|
this.Expanded = false;
|
||||||
|
this.Title = "";
|
||||||
|
this.Description = "";
|
||||||
|
this.ID = "";
|
||||||
|
this.Thumbnail = "";
|
||||||
|
this.Valid = false;
|
||||||
|
this.Channel_Identification_Box = "";
|
||||||
|
},
|
||||||
|
|
||||||
|
GetChannelFromYT(Channel: string) : void {
|
||||||
|
// Say it failed first so if we exit early then correctly marked fail.
|
||||||
|
this.Valid = false;
|
||||||
|
|
||||||
|
// Copy over to internal ID box.
|
||||||
|
this.ID = Channel;
|
||||||
|
|
||||||
|
// Remove any potential youtube URL from the field.
|
||||||
|
const ytchurl = "https://www.youtube.com/channel/";
|
||||||
|
if (this.ID.startsWith(ytchurl))
|
||||||
|
this.ID = this.ID.replace(ytchurl, "");
|
||||||
|
|
||||||
|
// Check if what remains looks like a youtube channel ID.
|
||||||
|
if (this.ID.length != "UCyS4xQE6DK4_p3qXQwJQAyA".length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Get channel metadata.
|
||||||
|
const API = 'https://www.googleapis.com/youtube/v3/channels?';
|
||||||
|
const API_Parts = 'part=snippet%2CcontentDetails%2Cstatistics';
|
||||||
|
const API_Key = '&key=AIzaSyCuIYkMc5SktlnXRXNaDf2ObX-fQvtWCnQ'
|
||||||
|
const API_Search_ID = '&id=' + this.ID;
|
||||||
|
Axios.get(API + API_Parts + API_Search_ID + API_Key).then((resp) => {
|
||||||
|
this.Description = resp.data.items[0].snippet.description;
|
||||||
|
this.Title = resp.data.items[0].snippet.title;
|
||||||
|
this.Thumbnail = resp.data.items[0].snippet.thumbnails.medium.url;
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
44
YTManager/frontend/src/components/Channel_Table.vue
Normal file
44
YTManager/frontend/src/components/Channel_Table.vue
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>User Tags</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-cloak v-for="channel in Channels" :key=channel.ID class="Grid_Small_Text">
|
||||||
|
<td>{{channel.ID}}</td>
|
||||||
|
<td>{{channel.Title}}</td>
|
||||||
|
<td>{{channel.Description}}</td>
|
||||||
|
<td>{{channel.User_Tags}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from "vue";
|
||||||
|
import * as Dyt from "../dumbyt";
|
||||||
|
|
||||||
|
// Vue class for keeping state of the videos.
|
||||||
|
export default Vue.extend({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
Channels: Array<Dyt.Channel>()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted(){
|
||||||
|
Dyt.search_channels("").then(v => this.Channels = v);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.Grid_Small_Text {
|
||||||
|
font-size: 0.7em;
|
||||||
|
}
|
||||||
|
</style>
|
44
YTManager/frontend/src/components/Search.vue
Normal file
44
YTManager/frontend/src/components/Search.vue
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<input id="searchbox0" class="searchbox" type="text" v-model="searchbox_str"
|
||||||
|
placeholder="Search String goes here, for example: >5m,c++"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from "vue";
|
||||||
|
import * as _ from "lodash";
|
||||||
|
import * as Dyt from "../dumbyt";
|
||||||
|
|
||||||
|
// Vue class for keeping state of the videos.
|
||||||
|
export default Vue.extend({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
searchbox_str: "",
|
||||||
|
Videos: Array<Dyt.Video>()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Manually attaching functions to watchers of data.
|
||||||
|
watch: {
|
||||||
|
// Searchbox updater.
|
||||||
|
searchbox_str (s: string) {
|
||||||
|
var v = _.debounce((s) => {
|
||||||
|
Dyt.search_videos(s).then(e => this.Videos = e);
|
||||||
|
}, 400)
|
||||||
|
v(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.searchbox {
|
||||||
|
font-size: 1.2em;
|
||||||
|
max-width: 70em;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-top: 2em;
|
||||||
|
}
|
||||||
|
</style>
|
@ -17,114 +17,15 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
import Axios from "axios";
|
|
||||||
import * as _ from "lodash";
|
import * as _ from "lodash";
|
||||||
|
import * as Dyt from "../dumbyt";
|
||||||
// Wrapper for videos returned from our server.
|
|
||||||
class Video {
|
|
||||||
// Title of the video according to youtube.
|
|
||||||
public Title: string;
|
|
||||||
|
|
||||||
// Description of video according to youtube.
|
|
||||||
public Description: string;
|
|
||||||
|
|
||||||
// Youtube ID
|
|
||||||
public ID: string;
|
|
||||||
|
|
||||||
// Thumbnail
|
|
||||||
public Thumbnail: string;
|
|
||||||
|
|
||||||
// What channel made this video.
|
|
||||||
public Channel: string;
|
|
||||||
|
|
||||||
// Duration of the video in seconds.
|
|
||||||
public Seconds: number;
|
|
||||||
|
|
||||||
// Tags relevant to the video.
|
|
||||||
public Tags: Array<string>;
|
|
||||||
|
|
||||||
// Youtube URL of the video.
|
|
||||||
public URL: string;
|
|
||||||
|
|
||||||
// Popuplate this using data from our server.
|
|
||||||
constructor(v: any){
|
|
||||||
this.Title = v.title;
|
|
||||||
this.Description = v.description;
|
|
||||||
this.ID = v.id;
|
|
||||||
this.Thumbnail = v.thumbnail;
|
|
||||||
this.Channel = v.channel;
|
|
||||||
this.Seconds = v.seconds;
|
|
||||||
this.Tags = v.tags;
|
|
||||||
this.URL = "https://www.youtube.com/watch?v=" + v.id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function search_videos(searchstr : string): Promise<Array<Video>> {
|
|
||||||
// How many chars at most for the description.
|
|
||||||
const maxcharsdescriptionfield = 100;
|
|
||||||
|
|
||||||
// Temporary holder for videos.
|
|
||||||
let Videos : Array<Video> = [];
|
|
||||||
|
|
||||||
// Ask server for videos.
|
|
||||||
let resp = await Axios.get('http://localhost:62214/api/Videos/search/' + searchstr).catch(e => console.log(e));
|
|
||||||
|
|
||||||
// Handle all our nulls.
|
|
||||||
if (resp == null || resp.data == null)
|
|
||||||
console.log("server /api/videos/search return is null");
|
|
||||||
else {
|
|
||||||
// Parse our videos.
|
|
||||||
resp.data.forEach((v: any) => {
|
|
||||||
// Trim description if needed.
|
|
||||||
if (v.description.length > maxcharsdescriptionfield) {
|
|
||||||
v.description = v.description.substring(0, maxcharsdescriptionfield) + " ...";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add it to our array
|
|
||||||
Videos.push(new Video(v));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send back the resulting videos.
|
|
||||||
return Promise.resolve(Videos);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function get_videos(): Promise<Array<Video>> {
|
|
||||||
// How many chars at most for the description.
|
|
||||||
const maxcharsdescriptionfield = 100;
|
|
||||||
|
|
||||||
// Temporary holder for videos.
|
|
||||||
let Videos : Array<Video> = [];
|
|
||||||
|
|
||||||
// Ask server for videos.
|
|
||||||
let resp = await Axios.get('http://localhost:62214/api/Videos').catch(e => console.log(e));
|
|
||||||
|
|
||||||
// Handle all our nulls.
|
|
||||||
if (resp == null || resp.data == null)
|
|
||||||
console.log("server /api/videos return is null");
|
|
||||||
else {
|
|
||||||
// Parse our videos.
|
|
||||||
resp.data.forEach((v: any) => {
|
|
||||||
// Trim description if needed.
|
|
||||||
if (v.description.length > maxcharsdescriptionfield) {
|
|
||||||
v.description = v.description.substring(0, maxcharsdescriptionfield) + " ...";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add it to our array
|
|
||||||
Videos.push(new Video(v));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send back the resulting videos.
|
|
||||||
return Promise.resolve(Videos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vue class for keeping state of the videos.
|
// Vue class for keeping state of the videos.
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
searchbox_str: "",
|
searchbox_str: "",
|
||||||
Videos: Array<Video>()
|
Videos: Array<Dyt.Video>()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -132,19 +33,15 @@ export default Vue.extend({
|
|||||||
watch: {
|
watch: {
|
||||||
// Searchbox updater.
|
// Searchbox updater.
|
||||||
searchbox_str (s: string) {
|
searchbox_str (s: string) {
|
||||||
if (s.trim().length > 0){
|
var v = _.debounce((s) => {
|
||||||
var v = _.debounce((s) => {
|
Dyt.search_videos(s).then(e => this.Videos = e);
|
||||||
search_videos(s).then(e => this.Videos = e);
|
})
|
||||||
})
|
v(s);
|
||||||
v(s);
|
|
||||||
} else {
|
|
||||||
get_videos().then(e => this.Videos = e );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Ugh, vue doesn't have async support for computed, wow ...
|
// Ugh, vue doesn't have async support for computed, wow ...
|
||||||
mounted() { get_videos().then(e => this.Videos = e ); }
|
mounted() { Dyt.search_videos("").then(e => this.Videos = e ); }
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
47
YTManager/frontend/src/components/Video_Table.vue
Normal file
47
YTManager/frontend/src/components/Video_Table.vue
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<template>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Channel</th>
|
||||||
|
<th>Seconds</th>
|
||||||
|
<th>Tags</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-cloak v-for="video in Videos" :key=video.ID class="Grid_Small_Text">
|
||||||
|
<td>{{video.ID}}</td>
|
||||||
|
<td>{{video.Title}}</td>
|
||||||
|
<td>{{video.Channel}}</td>
|
||||||
|
<td>{{video.Seconds}}</td>
|
||||||
|
<td>{{video.Tags}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from "vue";
|
||||||
|
import * as Dyt from "../dumbyt";
|
||||||
|
|
||||||
|
// Vue class for keeping state of the videos.
|
||||||
|
export default Vue.extend({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
Videos: Array<Dyt.Video>()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted (){
|
||||||
|
Dyt.search_videos("").then(v => this.Videos = v);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.Grid_Small_Text {
|
||||||
|
font-size: 0.7em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
210
YTManager/frontend/src/dumbyt.ts
Normal file
210
YTManager/frontend/src/dumbyt.ts
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
import Axios from "axios";
|
||||||
|
|
||||||
|
// Base URL for API queries.
|
||||||
|
const API_BASE_URL = "http://localhost:62214/api";
|
||||||
|
|
||||||
|
// How many chars at most for the description.
|
||||||
|
const max_description_length = 100;
|
||||||
|
|
||||||
|
// Wrapper for channels returned from our server.
|
||||||
|
export class Channel {
|
||||||
|
// Title of the channel according to youtube.
|
||||||
|
public Title: string;
|
||||||
|
|
||||||
|
// Description of channel according to youtube.
|
||||||
|
public Description: string;
|
||||||
|
|
||||||
|
// Youtube ID
|
||||||
|
public ID: string;
|
||||||
|
|
||||||
|
// Tags given to the video.
|
||||||
|
public User_Tags: Array<string>;
|
||||||
|
|
||||||
|
// ID's belonging to the channel.
|
||||||
|
public Video_IDs: Array<string>;
|
||||||
|
|
||||||
|
// Popuplate this using data from our server.
|
||||||
|
constructor(c : any){
|
||||||
|
if (c == null) {
|
||||||
|
this.Title = "NULL"
|
||||||
|
this.Description = "NULL"
|
||||||
|
this.ID = "NULL"
|
||||||
|
this.User_Tags = ["NULL", "NULL", "NULL"]
|
||||||
|
this.Video_IDs = ["NULL", "NULL"]
|
||||||
|
} else {
|
||||||
|
this.Title = c.title;
|
||||||
|
this.Description = c.description;
|
||||||
|
this.ID = c.id;
|
||||||
|
this.User_Tags = c.user_Tags;
|
||||||
|
this.Video_IDs = c.video_IDs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapper for videos returned from our server.
|
||||||
|
export class Video {
|
||||||
|
// Title of the video according to youtube.
|
||||||
|
public Title: string;
|
||||||
|
|
||||||
|
// Description of video according to youtube.
|
||||||
|
public Description: string;
|
||||||
|
|
||||||
|
// Youtube ID
|
||||||
|
public ID: string;
|
||||||
|
|
||||||
|
// Thumbnail
|
||||||
|
public Thumbnail: string;
|
||||||
|
|
||||||
|
// What channel made this video.
|
||||||
|
public Channel: string;
|
||||||
|
|
||||||
|
// Duration of the video in seconds.
|
||||||
|
public Seconds: number;
|
||||||
|
|
||||||
|
// Tags relevant to the video.
|
||||||
|
public Tags: Array<string>;
|
||||||
|
|
||||||
|
// Youtube URL of the video.
|
||||||
|
public URL: string;
|
||||||
|
|
||||||
|
// Popuplate this using data from our server.
|
||||||
|
constructor(v: any){
|
||||||
|
if (v == null){
|
||||||
|
this.Title = "NULL";
|
||||||
|
this.Description = "NULL";
|
||||||
|
this.ID = "NULL";
|
||||||
|
this.Thumbnail = "NULL";
|
||||||
|
this.Channel = "NULL";
|
||||||
|
this.Seconds = 9999999;
|
||||||
|
this.Tags = ["NULL", "NULL"];
|
||||||
|
this.URL = "NULL";
|
||||||
|
} else {
|
||||||
|
this.Title = v.title;
|
||||||
|
this.Description = v.description;
|
||||||
|
this.ID = v.id;
|
||||||
|
this.Thumbnail = v.thumbnail;
|
||||||
|
this.Channel = v.channel;
|
||||||
|
this.Seconds = v.seconds;
|
||||||
|
this.Tags = v.tags;
|
||||||
|
this.URL = "https://www.youtube.com/watch?v=" + v.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types of modifications which can be applied to various models.
|
||||||
|
export enum Modification {
|
||||||
|
Add, Delete, Refresh
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change a channel state.
|
||||||
|
export async function channel_modify(youtubeID : string, modify : Modification): Promise<void | string> {
|
||||||
|
switch (modify){
|
||||||
|
case Modification.Add: {
|
||||||
|
let URL = API_BASE_URL + '/Channels/' + youtubeID;
|
||||||
|
let resp = await Axios.post(URL).catch((error) => {
|
||||||
|
if (error.response)
|
||||||
|
return error.response.data;
|
||||||
|
else if (error.request)
|
||||||
|
return error.request;
|
||||||
|
else
|
||||||
|
return "Axios request failed for unkown reason.";
|
||||||
|
});
|
||||||
|
if (typeof resp == "string")
|
||||||
|
return resp;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Modification.Delete: {
|
||||||
|
let URL = API_BASE_URL + '/Channels/' + youtubeID;
|
||||||
|
let resp = await Axios.delete(URL).catch(e => console.log(e));
|
||||||
|
if (resp != null){
|
||||||
|
if (resp.status != 200)
|
||||||
|
return resp.data();
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
return "Response is null";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case Modification.Refresh: {
|
||||||
|
let URL = API_BASE_URL + '/Channels/Update/' + youtubeID;
|
||||||
|
let resp = await Axios.post(URL).catch(e => console.log(e));
|
||||||
|
if (resp != null){
|
||||||
|
if (resp.status != 200)
|
||||||
|
return resp.data();
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
return "Response is null";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
console.log("Unknown request type, error ...");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete a video.
|
||||||
|
export async function video_delete(youtubeID : string) : Promise<void> {
|
||||||
|
let URL = API_BASE_URL + '/Videos/' + youtubeID;
|
||||||
|
let resp = await Axios.delete(URL).catch(e => console.log(e));
|
||||||
|
if ((resp == null) || (resp.data == null)){
|
||||||
|
console.log("Video delete via " + URL + " FAIL");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for channels based on a search string.
|
||||||
|
export async function search_channels(searchstr : string): Promise<Array<Channel>> {
|
||||||
|
// Temporary holder for data.
|
||||||
|
let Channels : Array<Channel> = [];
|
||||||
|
|
||||||
|
// Ask server for data.
|
||||||
|
let resp = await Axios.get(API_BASE_URL + '/Channels/' + searchstr).catch(e => console.log(e));
|
||||||
|
|
||||||
|
// Handle all our nulls.
|
||||||
|
if (resp == null || resp.data == null)
|
||||||
|
console.log("server /api/Channels/" + searchstr + " return is null");
|
||||||
|
else {
|
||||||
|
// Parse our videos.
|
||||||
|
resp.data.forEach((c: any) => {
|
||||||
|
// Trim description if needed.
|
||||||
|
if (c.description.length > max_description_length) {
|
||||||
|
c.description = c.description.substring(0, max_description_length) + " ...";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add it to our array
|
||||||
|
Channels.push(new Channel(c));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send back the resulting videos.
|
||||||
|
return Promise.resolve(Channels);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for videos based on a search string.
|
||||||
|
export async function search_videos(searchstr : string): Promise<Array<Video>> {
|
||||||
|
// Temporary holder for videos.
|
||||||
|
let Videos : Array<Video> = [];
|
||||||
|
|
||||||
|
// Ask server for videos.
|
||||||
|
let resp = await Axios.get(API_BASE_URL + '/Videos/' + searchstr).catch(e => console.log(e));
|
||||||
|
|
||||||
|
// Handle all our nulls.
|
||||||
|
if (resp == null || resp.data == null)
|
||||||
|
console.log("server /videos/ return is null");
|
||||||
|
else {
|
||||||
|
// Parse our videos.
|
||||||
|
resp.data.forEach((v: any) => {
|
||||||
|
// Trim description if needed.
|
||||||
|
if (v.description.length > max_description_length) {
|
||||||
|
v.description = v.description.substring(0, max_description_length) + " ...";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add it to our array
|
||||||
|
Videos.push(new Video(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send back the resulting videos.
|
||||||
|
return Promise.resolve(Videos);
|
||||||
|
}
|
@ -1,16 +1,40 @@
|
|||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
import VGC from "./components/Video_Grid.vue";
|
import VGC from "./components/Video_Grid.vue";
|
||||||
|
import SCH from "./components/Search.vue";
|
||||||
|
import VTBL from "./components/Video_Table.vue";
|
||||||
|
import CTBL from "./components/Channel_Table.vue";
|
||||||
|
import CADD from "./components/Channel_Add.vue";
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
let v = new Vue({
|
let MainApp = new Vue({
|
||||||
el: "#app",
|
el: "#app",
|
||||||
template: `
|
template: `
|
||||||
<div>
|
<div>
|
||||||
<VGC/>
|
<VGC/>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
data: { name: "something" },
|
|
||||||
components: {
|
components: {
|
||||||
VGC
|
VGC
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let AdminApp = new Vue({
|
||||||
|
el: "#admin_app",
|
||||||
|
template: `
|
||||||
|
<div>
|
||||||
|
<SCH style="max-width:800px; padding-left:100px;" />
|
||||||
|
<CADD/>
|
||||||
|
<CTBL/>
|
||||||
|
<VTBL/>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
components: {
|
||||||
|
SCH,
|
||||||
|
CADD,
|
||||||
|
CTBL,
|
||||||
|
VTBL
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user