Huge API rewrite

This commit is contained in:
hak8or 2018-02-24 01:10:12 -05:00
parent a37fe30b6f
commit 038d363b00
16 changed files with 472 additions and 77 deletions

View File

@ -20,7 +20,7 @@ namespace YTManager.Controllers {
Title = c.Title;
Description = c.Description;
ID = c.YoutubeID;
Video_IDs = c.Videos?.Select(v => v.YoutubeID).ToList();
Video_IDs = c.Videos.Select(v => v.YoutubeID).ToList();
}
}
@ -38,6 +38,7 @@ namespace YTManager.Controllers {
public async Task<IActionResult> Get() {
// Get all the relevant channels.
var chanels = await db.Channels
.Include(c => c.Videos)
.OrderByDescending(i => i.AddedtoDB)
.Take(max_per_query)
.ToListAsync();

View File

@ -0,0 +1,141 @@
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("{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]
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);
}
}
}

View File

@ -22,7 +22,7 @@ namespace YTManager.Controllers {
// Thumbnail URL.
public string Thumbnail;
// Channel youtube ID.
// Channel on youtube that owns this video
public string Channel;
// Populate this struct using a model video.
@ -31,7 +31,7 @@ namespace YTManager.Controllers {
Description = video.Description;
ID = video.YoutubeID;
Thumbnail = video.ThumbnailURL;
Channel = video.channel?.YoutubeID;
Channel = video.Channel.Title;
}
}
@ -49,6 +49,7 @@ namespace YTManager.Controllers {
public async Task<IActionResult> GetVideos() {
// Get all the relevant videos.
var vids = await db.Videos
.Include(v => v.Channel)
.OrderByDescending(i => i.AddedtoDB)
.Take(max_per_query)
.ToListAsync();
@ -63,12 +64,12 @@ namespace YTManager.Controllers {
}
// Returns the most recent videos of a channel.
[HttpGet("fromchannel/{channelID}")]
public async Task<IActionResult> Get_Channel_Videos([FromRoute] string channelID) {
[HttpGet("fromchannel/{channelName}")]
public async Task<IActionResult> Get_Channel_Videos([FromRoute] string channelName) {
// Get all the relevant videos.
var vids = await db.Videos
.Include(v => v.channel)
.Where(v => v.channel.YoutubeID == channelID)
.Include(v => v.Channel)
.Where(v => v.Channel.Title == channelName)
.OrderByDescending(i => i.AddedtoDB)
.Take(max_per_query)
.ToListAsync();

View File

@ -1,25 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace YTManager.Migrations
{
public partial class added_refreshed : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "Refreshed",
table: "Channels",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Refreshed",
table: "Channels");
}
}
}

View File

@ -11,8 +11,8 @@ using YTManager;
namespace YTManager.Migrations
{
[DbContext(typeof(MediaDB))]
[Migration("20180220053952_added_refreshed")]
partial class added_refreshed
[Migration("20180224051602_initial")]
partial class initial
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{

View File

@ -5,7 +5,7 @@ using System.Collections.Generic;
namespace YTManager.Migrations
{
public partial class initiailmigration : Migration
public partial class initial : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
@ -17,6 +17,7 @@ namespace YTManager.Migrations
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn),
AddedtoDB = table.Column<DateTime>(nullable: false),
Description = table.Column<string>(nullable: false),
Refreshed = table.Column<DateTime>(nullable: false),
ThumbnailURL = table.Column<string>(nullable: false),
Title = table.Column<string>(nullable: false),
YoutubeID = table.Column<string>(nullable: false)

View File

@ -11,8 +11,8 @@ using YTManager;
namespace YTManager.Migrations
{
[DbContext(typeof(MediaDB))]
[Migration("20180220032847_initiailmigration")]
partial class initiailmigration
[Migration("20180224055707_fix_channel_relationship")]
partial class fix_channel_relationship
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
@ -31,6 +31,8 @@ namespace YTManager.Migrations
b.Property<string>("Description")
.IsRequired();
b.Property<DateTime>("Refreshed");
b.Property<string>("ThumbnailURL")
.IsRequired();
@ -53,8 +55,12 @@ namespace YTManager.Migrations
b.Property<string>("Name")
.IsRequired();
b.Property<long?>("VideoPrimaryKey");
b.HasKey("PrimaryKey");
b.HasIndex("VideoPrimaryKey");
b.ToTable("Tags");
});
@ -67,7 +73,7 @@ namespace YTManager.Migrations
b.Property<DateTime>("AddedtoDB");
b.Property<long?>("ChannelPrimaryKey");
b.Property<long>("ChannelPrimaryKey");
b.Property<string>("Description")
.IsRequired();
@ -88,11 +94,19 @@ namespace YTManager.Migrations
b.ToTable("Videos");
});
modelBuilder.Entity("YTManager.Models.Tag", b =>
{
b.HasOne("YTManager.Models.Video")
.WithMany("Tags")
.HasForeignKey("VideoPrimaryKey");
});
modelBuilder.Entity("YTManager.Models.Video", b =>
{
b.HasOne("YTManager.Models.Channel")
b.HasOne("YTManager.Models.Channel", "Channel")
.WithMany("Videos")
.HasForeignKey("ChannelPrimaryKey");
.HasForeignKey("ChannelPrimaryKey")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}

View File

@ -0,0 +1,82 @@
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);
}
}
}

View File

@ -54,8 +54,12 @@ namespace YTManager.Migrations
b.Property<string>("Name")
.IsRequired();
b.Property<long?>("VideoPrimaryKey");
b.HasKey("PrimaryKey");
b.HasIndex("VideoPrimaryKey");
b.ToTable("Tags");
});
@ -68,7 +72,7 @@ namespace YTManager.Migrations
b.Property<DateTime>("AddedtoDB");
b.Property<long?>("ChannelPrimaryKey");
b.Property<long>("ChannelPrimaryKey");
b.Property<string>("Description")
.IsRequired();
@ -89,11 +93,19 @@ namespace YTManager.Migrations
b.ToTable("Videos");
});
modelBuilder.Entity("YTManager.Models.Tag", b =>
{
b.HasOne("YTManager.Models.Video")
.WithMany("Tags")
.HasForeignKey("VideoPrimaryKey");
});
modelBuilder.Entity("YTManager.Models.Video", b =>
{
b.HasOne("YTManager.Models.Channel")
b.HasOne("YTManager.Models.Channel", "Channel")
.WithMany("Videos")
.HasForeignKey("ChannelPrimaryKey");
.HasForeignKey("ChannelPrimaryKey")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}

View File

@ -34,10 +34,10 @@ namespace YTManager.Models {
// What channel this video comes from.
[Required]
public Channel channel;
public Channel Channel { get; set; }
// Tag this video applies to.
[Required]
public List<Tag> Tags;
public List<Tag> Tags { get; set; }
}
}

View File

@ -59,7 +59,8 @@ namespace YTManager.Tasks {
ThumbnailURL = response.Items.First().Snippet.Thumbnails.Medium.Url,
YoutubeID = channelID,
AddedtoDB = DateTime.Now,
Refreshed = DateTime.MinValue
Refreshed = DateTime.MinValue,
Videos = null
};
}
@ -70,7 +71,7 @@ namespace YTManager.Tasks {
ops.UseNpgsql(dbstr);
// Get all the channels from the db that expired.
var threshold = DateTime.Now.Subtract(TimeSpan.FromSeconds(100));
var threshold = DateTime.Now.Subtract(TimeSpan.FromMinutes(60));
var channel_ids = await
(new MediaDB(ops.Options)).Channels
.Where(ch => ch.Refreshed < threshold)
@ -104,7 +105,7 @@ namespace YTManager.Tasks {
// Say what channel all the videos came from.
foreach (var v in newvids)
v.channel = channel;
v.Channel = channel;
// Say the channel has been refreshed.
channel.Refreshed = DateTime.Now;

View File

@ -0,0 +1,64 @@
body {
margin-left: 5%;
margin-right: 5%;
line-height: 1.6;
font-size: 18px;
color: #003636;
background-color: #F8F8F8;
max-width: 1280px;
margin: auto;
}
.pageheader {
max-width: 960px;
margin: auto;
}
.error {
background-color: red;
}
.curvedbottom {
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
}
#addnewchannelform {
width: inherit;
margin: auto;
}
.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 {
opacity: 0;
}
.apistatusicon {
animation-duration: 1s;
animation-name: spinny;
animation-iteration-count: infinite;
animation-direction: alternate;
}
@keyframes spinny {
from {
transform: rotate(-15deg);
}
to {
transform: rotate(15deg);
}
}

View File

@ -39,6 +39,6 @@
<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>
<script src="index.js"></script>
</body>
</html>

View File

@ -59,7 +59,7 @@ var addchanel0 = new Vue({
submit: function (event) {
// Send the channel to our API and request tables be updated.
var d = this.entry;
axios.post('/api/Channels/' + this.entry.yTChannelID)
axios.post('/api_raw/Channels/' + this.entry.yTChannelID)
.then(function (response) {
this.entry.title = "";
this.entry.description = "";
@ -229,9 +229,9 @@ var videostb = new Vue({
</thead>
<tbody>
<tr v-cloak v-for="video in Videos">
<td class="tinytext12px">{{video.id}}</td>
<td class="tinytext12px">{{video.title}}</td>
<td class="tinytext12px">{{video.description}}</td>
<td class="tinytext12px">{{video.id}}</td>
<td class="tinytext12px">{{video.channel}}</td>
</tr>
</tbody>

View File

@ -14,20 +14,11 @@
margin: auto;
}
.error {
background-color: red;
}
.curvedbottom {
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
}
#addnewchannelform {
width: inherit;
margin: auto;
}
.tinytext10px{ font-size: 10px; }
.tinytext12px{ font-size: 12px; }
.tinytext14px{ font-size: 14px; }
@ -45,20 +36,3 @@
.fade-enter, .fade-leave-to {
opacity: 0;
}
.apistatusicon {
animation-duration: 1s;
animation-name: spinny;
animation-iteration-count: infinite;
animation-direction: alternate;
}
@keyframes spinny {
from {
transform: rotate(-15deg);
}
to {
transform: rotate(15deg);
}
}