Removed frontend and moved out of sln into solely csproj style

This commit is contained in:
2018-03-06 16:22:21 -05:00
parent dc1937d2f8
commit a3e7b4f8f7
42 changed files with 1 additions and 11792 deletions

57
Controllers/Admin.cs Normal file
View File

@ -0,0 +1,57 @@
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Hangfire.Storage;
namespace YTManager.Controllers {
[Produces("application/json")]
[Route("api/admin")]
public class AdminController : Controller {
// Get the mass update daemon job.
private static RecurringJobDto get_massupdatedaemon() {
return Hangfire.JobStorage
.Current
.GetConnection()
.GetRecurringJobs()
.SingleOrDefault(j => j.Id == Mass_Updater_ID);
}
// ID for mass update job, used to tell if the job is running or not.
public static string Mass_Updater_ID { get; } = "2013066213";
// POST api/Admin/Trigger_Mass_Update
// Trigger a mass channel update.
[HttpPost("Trigger_Mass_Update")]
public IActionResult Trigger_Mass_Update() {
Hangfire.RecurringJob.Trigger(Mass_Updater_ID);
return Ok();
}
// POST api/Admin/Update/YTChannelID
// Update the videos for a specific channel.
[HttpPost("Update/{YoutubeID}")]
public IActionResult Update([FromRoute] string YoutubeID) {
Hangfire.BackgroundJob.Enqueue(() => Tasks.FetchVideos.ChannelUpdate(Startup.DBStr, YoutubeID));
return Ok();
}
// POST api/Admin/Start_Updater
// Ensures that the background YT Channel update API is running.
[HttpPost("Start_Updater")]
public IActionResult Start_Updater() {
if (get_massupdatedaemon() == null) {
Hangfire.RecurringJob.AddOrUpdate(
Mass_Updater_ID,
() => Tasks.FetchVideos.MassUpdate(Startup.DBStr), Hangfire.Cron.Minutely);
}
return Ok();
}
// GET api/Admin/Updater
// Check if the periodic update job is enqued.
[HttpGet("Update")]
public IActionResult Get_Update_Status() {
return Ok(get_massupdatedaemon() == null ? "false" : "true");
}
}
}

85
Controllers/Channels.cs Normal file
View File

@ -0,0 +1,85 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System;
namespace YTManager.Controllers {
[Produces("application/json")]
[Route("api/Channels")]
public class ChannelsController : Controller {
// Custom return type for API accesses. Done this way to ensure we
// always return the expected data regardless of the underlying model.
struct Channel_ForAPI {
public string Title;
public string Description;
public string ID;
public List<string> User_Tags;
public List<string> Video_IDs;
public Channel_ForAPI(Models.Channel c) {
Title = c.Title;
Description = c.Description;
ID = c.YoutubeID;
Video_IDs = c.Videos.Select(v => v.YoutubeID).ToList();
User_Tags = c.UserTags == null ? new List<string>() : c.UserTags.ToList();
}
}
// DB context used for all these calls.
private readonly MediaDB db;
// Maximum number of channels to return per query.
private readonly int max_per_query = 10;
// Constructor to fetch the db context.
public ChannelsController(MediaDB context) => db = context;
// Returns the most recently added channels.
[HttpGet]
public async Task<IActionResult> Get() {
// Log this to the terminal.
Console.WriteLine($"{DateTime.Now} == Channels GET");
// Get all the relevant channels.
var chanels = await db.Channels
.Include(c => c.Videos)
.OrderByDescending(i => i.AddedtoDB)
.Take(max_per_query)
.ToListAsync();
// Convert them to what we will send out.
var converted = chanels
.Select(ch => new Channel_ForAPI(ch))
.ToList();
// Convert all the videos to what we will send back.
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();
}
}
}

View File

@ -0,0 +1,124 @@
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.Private
{
[Produces("application/json")]
[Route("api_raw/Channels")]
public class Private_Channel : Controller
{
private readonly MediaDB db;
public Private_Channel(MediaDB context)
{
db = context;
}
[HttpGet]
public IEnumerable<Channel> GetChannels()
{
return db.Channels;
}
[HttpGet("{id}")]
public async Task<IActionResult> GetChannel([FromRoute] long id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var channel = await db
.Channels
.Include(c => c.Videos)
.SingleOrDefaultAsync(m => m.PrimaryKey == id);
if (channel == null)
{
return NotFound();
}
return Ok(channel);
}
[HttpPut("{id}")]
public async Task<IActionResult> PutChannel([FromRoute] long id, [FromBody] Channel channel)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != channel.PrimaryKey)
{
return BadRequest();
}
db.Entry(channel).State = EntityState.Modified;
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ChannelExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
[HttpPost]
public async Task<IActionResult> PostChannel([FromBody] Channel channel)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Channels.Add(channel);
await db.SaveChangesAsync();
return CreatedAtAction("GetChannel", new { id = channel.PrimaryKey }, channel);
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteChannel([FromRoute] long id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var channel = await db.Channels.SingleOrDefaultAsync(m => m.PrimaryKey == id);
if (channel == null)
{
return NotFound();
}
db.Channels.Remove(channel);
await db.SaveChangesAsync();
return Ok(channel);
}
private bool ChannelExists(long id)
{
return db.Channels.Any(e => e.PrimaryKey == id);
}
}
}

View File

@ -0,0 +1,129 @@
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_raw/Videos")]
public class Private_Videos : Controller
{
private readonly MediaDB _context;
public Private_Videos(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
.Include(v => v.Channel)
.SingleOrDefaultAsync(m => m.PrimaryKey == 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.PrimaryKey)
{
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.PrimaryKey }, 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.PrimaryKey == 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.PrimaryKey == id);
}
}
}

160
Controllers/Videos.cs Normal file
View File

@ -0,0 +1,160 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace YTManager.Controllers {
[Produces("application/json")]
[Route("api/Videos")]
public class VideosController : Controller {
// Custom return type for API accesses. Done this way to ensure we
// always return the expected data regardless of the underlying model.
struct Video_ForAPI {
// Title of the video.
public string Title;
// Description of the video.
public string Description;
// Youtube ID of the video.
public string ID;
// Thumbnail URL.
public string Thumbnail;
// Channel on youtube that owns this video
public string Channel;
// Duration of the video in seconds.
public int Seconds;
// What tags are relevant with this video.
public List<string> Tags;
// Populate this struct using a model video.
public Video_ForAPI(Models.Video video) {
Title = video.Title;
Description = video.Description;
ID = video.YoutubeID;
Thumbnail = video.ThumbnailURL;
Channel = video.Channel.Title;
Seconds = (int)video.Duration.TotalSeconds;
Tags = video.Tags == null ? new List<string>() : video.Tags.ToList();
}
}
// Maximum number of entries to return per query.
private readonly int max_per_query = 18;
// DB context used for all these calls.
private readonly MediaDB db;
// Constructor to fetch the db context.
public VideosController(MediaDB context) => db = context;
struct Search_Query {
// What the duration type is.
public enum _Duration_Type { LessThan, GreaterThan, Unset };
public _Duration_Type Duration_Type;
// What the expected duration of the videos are.
public TimeSpan Duration;
// 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).
[HttpGet("{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.
var parsed_query = new Search_Query();
// Assume the search string is comma seperated.
parsed_query.Remaining_Tokens = searchstr.Split(',').Select(t => t.Trim().ToLower()).ToList();
// Find a potential single video duration.
List<string> greaterthan = parsed_query.Remaining_Tokens.Where(token => token.StartsWith('>')).ToList();
List<string> lessthan = parsed_query.Remaining_Tokens.Where(token => token.StartsWith('<')).ToList();
if (greaterthan.Count() + lessthan.Count() > 1)
return BadRequest();
if (greaterthan.Count() == 1) parsed_query.Duration_Type = Search_Query._Duration_Type.GreaterThan;
else if (lessthan.Count() == 1) parsed_query.Duration_Type = Search_Query._Duration_Type.LessThan;
else parsed_query.Duration_Type = Search_Query._Duration_Type.Unset;
greaterthan.ForEach(g => parsed_query.Remaining_Tokens.Remove(g));
lessthan.ForEach(l => parsed_query.Remaining_Tokens.Remove(l));
// Attempt to parse it using ISO8601 duration.
if (parsed_query.Duration_Type != Search_Query._Duration_Type.Unset) {
string timingstr = "PT" + greaterthan
.Concat(lessthan)
.Select(s => s.TrimStart(new char[] { '<', '>' }))
.First();
try {
parsed_query.Duration = XmlConvert.ToTimeSpan(timingstr.ToUpper());
} catch (Exception) {
return BadRequest("Failed to parse duration.");
}
}
// Get from the database all videos which satisfy the query via
// AND'ing all the queries.
var dbquery = db.Videos.Select(v => v);
// Get all videos that satisfy the time duration.
if (parsed_query.Duration_Type == Search_Query._Duration_Type.GreaterThan)
dbquery = dbquery.Where(v => v.Duration >= parsed_query.Duration);
else if (parsed_query.Duration_Type == Search_Query._Duration_Type.LessThan)
dbquery = dbquery.Where(v => v.Duration <= parsed_query.Duration);
// Get all videos that match their title, description, or channel name
// with the remaining tokens.
parsed_query.Remaining_Tokens.ForEach(token => {
dbquery = dbquery.Where(v =>
v.Channel.Title.ToLower().Contains(token) ||
v.Title.ToLower().Contains(token) ||
v.Description.ToLower().Contains(token) ||
v.YoutubeID.ToLower().Contains(token));
});
// Get all the relevant videos.
var vids = await dbquery
.OrderByDescending(i => i.AddedToYT)
.Take(max_per_query)
.Include(v => v.Channel)
.ToListAsync();
// Convert them to what we will send out.
var converted = vids
.Select(v => new Video_ForAPI(v))
.ToList();
// Log this to the terminal.
Console.WriteLine($"{DateTime.Now} == Search request for -> {searchstr} found {converted.Count()} videos.");
// Convert all the videos to what we will send back.
return Ok(converted);
}
}
}