Removed frontend and moved out of sln into solely csproj style

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

View File

@ -4,7 +4,7 @@ using Hangfire.Storage;
namespace YTManager.Controllers {
[Produces("application/json")]
[Route("api/Admin")]
[Route("api/admin")]
public class AdminController : Controller {
// Get the mass update daemon job.
private static RecurringJobDto get_massupdatedaemon() {

View File

@ -1,25 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.12
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YTManager", "YTManager\YTManager.csproj", "{14F56CC1-67A1-477F-817C-A0A7B55AE954}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{14F56CC1-67A1-477F-817C-A0A7B55AE954}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{14F56CC1-67A1-477F-817C-A0A7B55AE954}.Debug|Any CPU.Build.0 = Debug|Any CPU
{14F56CC1-67A1-477F-817C-A0A7B55AE954}.Release|Any CPU.ActiveCfg = Release|Any CPU
{14F56CC1-67A1-477F-817C-A0A7B55AE954}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DB293C09-A2E4-4179-ADA9-BAE2093F425A}
EndGlobalSection
EndGlobal

View File

@ -1,39 +0,0 @@

ASP.NET MVC core dependencies have been added to the project.
(These dependencies include packages required to enable scaffolding)
However you may still need to do make changes to your project.
1. Suggested changes to Startup class:
1.1 Add a constructor:
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
1.2 Add MVC services:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
}
1.3 Configure web app to use use Configuration and use MVC routing:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}

View File

@ -1,15 +0,0 @@
<!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>

View File

@ -1,20 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" name="viewport" content="width=device-width, initial-scale=1"/>
<title>YT Manager</title>
<!-- Compressed CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/foundation/6.4.3/css/foundation.min.css" />
</head>
<body>
<div class="pageheader">
<h1>Dumb YT Manager</h1>
<p>Youtube banned my account and refuses to say why, taking all my subscribed channels with it. This is a simple scrubscribed channel manager, showing recent releases from each channel and finally proper sub-catagory functionality!</p>
</div>
<div id="app"></div>
<script src="./dist/build.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -1,31 +0,0 @@
{
"name": "dumbytmanager",
"version": "0.1.0",
"description": "Front end for the DumbYT Manager project",
"main": "index.js",
"repository": "https://gitea.hak8or.com/Almost_There/dumbytmanager",
"author": "hak8or",
"license": "MIT",
"devDependencies": {
"@types/lodash": "^4.14.104",
"axios": "^0.18.0",
"css-loader": "^0.28.10",
"style-loader": "^0.20.2",
"sweetalert2": "^7.13.3",
"ts-loader": "^4.0.0",
"typescript": "^2.7.2",
"uglifyjs-webpack-plugin": "^1.2.2",
"vue": "^2.5.13",
"vue-loader": "^14.1.1",
"vue-template-compiler": "^2.5.13",
"webpack": "^4.0.1",
"webpack-cli": "^2.0.9",
"webpack-dev-server": "^3.1.0"
},
"scripts": {
"start": "webpack-dev-server --mode development --open",
"dev": "webpack --mode development",
"build": "webpack --mode production",
"test": "echo \"Error: no test specified\" && exit 1"
}
}

View File

@ -1,10 +0,0 @@
# Frontend
## Description
Oh god, front end web development, specifically javascript.
This project makes use of the following:
- Vuejs
Seemingly resonable javascript framework that's well documented and actively developed.

View File

@ -1,126 +0,0 @@
<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>

View File

@ -1,40 +0,0 @@
<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, {PropOptions} from "vue";
import * as Dyt from "../dumbyt";
// Vue class for keeping state of the videos.
export default Vue.extend({
props: {
Channels: {
type: Array,
} as PropOptions<Dyt.Channel[]>,
}
});
</script>
<style>
.Grid_Small_Text {
font-size: 0.7em;
}
</style>

View File

@ -1,45 +0,0 @@
<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, {PropOptions} 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: "",
}
},
// Manually attaching functions to watchers of data.
watch: {
// Searchbox updater with debouncing.
searchbox_str (s: string) {
var v = _.debounce((s) => {
Dyt.search_videos(s).then(videos => {
this.$emit('search_complete', videos)
});
}, 400)
v(s);
}
}
});
</script>
<style scoped>
.searchbox {
font-size: 1.2em;
max-width: 40em;
margin-left: auto;
margin-right: auto;
margin-top: 2em;
}
</style>

View File

@ -1,52 +0,0 @@
<template>
<div>
<div class="grid-x large-up-6">
<div v-for="video in Videos" :key="video.ID" class="cell">
<div class="card ytcard">
<a :href="video.URL"><img :src="video.Thumbnail"></a>
<div class="card-section description">
<div class="descriptiontitle">{{ video.Title }}</div>
<div class="descriptionchannel">{{ video.Channel }}</div>
</div>
</div>
</div>
</div >
</div>
</template>
<script lang="ts">
import Vue, {PropOptions} from "vue";
import * as Dyt from "../dumbyt";
// Vue class for keeping state of the videos.
export default Vue.extend({
props: {
Videos: {
type: Array,
} as PropOptions<Dyt.Video[]>,
}
});
</script>
<style scoped>
.ytcard {
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
background-color:#ddd9d4;
margin: 6px 2px 6px;
}
.description {
padding: 7px 5px 7px;
}
.descriptiontitle {
font-size: 14px;
padding-bottom:10px;
}
.descriptionchannel {
font-size: 10px;
text-align: right;
}
</style>

View File

@ -1,43 +0,0 @@
<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, {PropOptions} from "vue";
import * as Dyt from "../dumbyt";
// Vue class for keeping state of the videos.
export default Vue.extend({
props: {
Videos: {
type: Array,
} as PropOptions<Dyt.Video[]>,
}
});
</script>
<style>
.Grid_Small_Text {
font-size: 0.7em;
}
</style>

View File

@ -1,210 +0,0 @@
import Axios from "axios";
// Base URL for API queries.
const API_BASE_URL = "/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);
}

View File

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

View File

@ -1,67 +0,0 @@
import Vue from "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 { Video, search_videos, Channel, search_channels } from "./dumbyt";
let MainApp = new Vue({
el: "#app",
template: `
<div>
<SCH v-on:search_complete="search_completed"/>
<VGC v-bind:Videos="Videos"/>
</div>
`,
components: {
SCH,
VGC
},
data() {
return {
Videos: Array<Video>()
}
},
methods: {
// Callback for when the search component got results back.
search_completed(videos : Array<Video>) : void {
this.Videos = videos;
}
}
});
let AdminApp = new Vue({
el: "#admin_app",
template: `
<div>
<SCH v-on:search_complete="search_completed"/>
<CADD/>
<CTBL v-bind:Channels="Channels"/>
<VTBL v-bind:Videos="Videos"/>
</div>
`,
components: {
SCH,
CADD,
CTBL,
VTBL
},
data() {
return {
Videos: Array<Video>(),
Channels: Array<Channel>()
}
},
methods: {
// Callback for when the search component got results back.
search_completed(videos : Array<Video>) : void {
this.Videos = videos;
}
},
mounted(){
// Populate the channels field immediatly on start up.
search_channels("").then(channels => this.Channels = channels);
}
});

View File

@ -1,4 +0,0 @@
declare module "*.vue" {
import Vue from "vue";
export default Vue;
}

View File

@ -1,15 +0,0 @@
{
"compilerOptions": {
"outDir": "./built/",
"sourceMap": true,
"strict": true,
"noImplicitReturns": true,
"module": "commonjs",
"moduleResolution": "node",
"target": "es6",
"noImplicitAny": true
},
"include": [
"./src/**/*"
]
}

View File

@ -1,65 +0,0 @@
var path = require('path')
var webpack = require('webpack')
module.exports = {
entry: './src/index.ts',
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'build.js'
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
// Since sass-loader (weirdly) has SCSS as its default parse mode, we map
// the "scss" and "sass" values for the lang attribute to the right configs here.
// other preprocessors should work out of the box, no loader config like this necessary.
'scss': 'vue-style-loader!css-loader!sass-loader',
'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax',
}
// other vue-loader options go here
}
},
{
test: /\.tsx?$/,
loader: 'ts-loader',
exclude: /node_modules/,
options: {
appendTsSuffixTo: [/\.vue$/],
}
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
},
resolve: {
extensions: ['.ts', '.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
},
devServer: {
historyApiFallback: true,
noInfo: true
},
performance: {
hints: false
},
devtool: '#eval-source-map'
}