commit
2ebfd26334
@ -0,0 +1,186 @@
|
||||
/packages/
|
||||
/_ReSharper.Caches/
|
||||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/rider,dotnetcore,jetbrains+all
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=rider,dotnetcore,jetbrains+all
|
||||
|
||||
### DotnetCore ###
|
||||
# .NET Core build folders
|
||||
bin/
|
||||
obj/
|
||||
|
||||
# Common node modules locations
|
||||
/node_modules
|
||||
/wwwroot/node_modules
|
||||
|
||||
### JetBrains+all ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### JetBrains+all Patch ###
|
||||
# Ignores the whole .idea folder and all .iml files
|
||||
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
|
||||
|
||||
.idea/
|
||||
|
||||
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
|
||||
|
||||
*.iml
|
||||
modules.xml
|
||||
.idea/misc.xml
|
||||
*.ipr
|
||||
|
||||
# Sonarlint plugin
|
||||
.idea/sonarlint
|
||||
|
||||
### Rider ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
|
||||
# Generated files
|
||||
|
||||
# Sensitive or high-churn files
|
||||
|
||||
# Gradle
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
|
||||
# Mongo Explorer plugin
|
||||
|
||||
# File-based project format
|
||||
|
||||
# IntelliJ
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
|
||||
# JIRA plugin
|
||||
|
||||
# Cursive Clojure plugin
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
|
||||
# Editor-based Rest Client
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/rider,dotnetcore,jetbrains+all
|
||||
|
||||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/macos
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=macos
|
||||
|
||||
### macOS ###
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/macos
|
||||
|
||||
database.db
|
@ -0,0 +1,24 @@
|
||||
Be Gay, Do Crimes License
|
||||
|
||||
Copyright (c) 2022 Laura Hausmann
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
Be Gay
|
||||
Do Crimes
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -0,0 +1,20 @@
|
||||
using LinqToDB;
|
||||
using MediaManager.database;
|
||||
using MediaManager.database.Tables;
|
||||
|
||||
namespace MediaManager;
|
||||
|
||||
public static class AuthUtil {
|
||||
public static string GetRemoteUser(HttpContext ctx, Database.DbConn db) {
|
||||
#if (DEBUG)
|
||||
const string remoteUser = "zotan";
|
||||
#else
|
||||
var remoteUser = ctx.Request.Headers["Remote-User"];
|
||||
#endif
|
||||
|
||||
if (!db.Users.Any(p => p.Username == remoteUser)) {
|
||||
db.InsertWithInt32Identity(new User {Username = remoteUser});
|
||||
}
|
||||
return remoteUser;
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="database" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="linq2db" Version="4.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.8" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.8" />
|
||||
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.115" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -0,0 +1,91 @@
|
||||
using LinqToDB;
|
||||
using LinqToDB.Data;
|
||||
using MediaManager.database;
|
||||
using MediaManager.database.Tables;
|
||||
|
||||
namespace MediaManager;
|
||||
|
||||
public static class Migrations {
|
||||
private const int DbVer = 1;
|
||||
|
||||
private static readonly List<Migration> _migrations = new() {
|
||||
};
|
||||
|
||||
public static void RunMigrations() {
|
||||
using var db = new Database.DbConn();
|
||||
var ccolor = Console.ForegroundColor;
|
||||
|
||||
if (!db.DataProvider.GetSchemaProvider().GetSchema(db).Tables.Any()) {
|
||||
Console.ForegroundColor = ConsoleColor.DarkCyan;
|
||||
Console.Write("Running migration: ");
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine("Initialize Database");
|
||||
|
||||
db.CreateTable<DbInfo>();
|
||||
db.CreateTable<User>();
|
||||
db.CreateTable<Movie>();
|
||||
db.CreateTable<Show>();
|
||||
|
||||
db.InsertWithIdentity(new DbInfo { DbVer = DbVer });
|
||||
}
|
||||
else if (db.DataProvider.GetSchemaProvider().GetSchema(db).Tables.All(t => t.TableName != "DbInfo")) {
|
||||
db.CreateTable<DbInfo>();
|
||||
db.InsertWithIdentity(new DbInfo { DbVer = 0 });
|
||||
}
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine($"Database version: {db.DbInfo.ToList().First().DbVer}");
|
||||
|
||||
var migrationsToRun = _migrations.FindAll(p => p.IntroducedWithDbVer > db.DbInfo.First().DbVer);
|
||||
if (migrationsToRun.Count == 0) {
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine("No migrations to run.");
|
||||
}
|
||||
else {
|
||||
new Migration(0, "BEGIN TRANSACTION").Run(db);
|
||||
try {
|
||||
migrationsToRun.ForEach(p => p.Run(db));
|
||||
}
|
||||
catch {
|
||||
Console.ForegroundColor = ConsoleColor.DarkRed;
|
||||
Console.WriteLine($"Migrating to database version {DbVer} failed.");
|
||||
new Migration(0, "ROLLBACK TRANSACTION").Run(db);
|
||||
Console.ForegroundColor = ConsoleColor.DarkYellow;
|
||||
Console.WriteLine("Rolled back migrations.");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
new Migration(0, "COMMIT TRANSACTION").Run(db);
|
||||
|
||||
var newdb = new Database.DbConn();
|
||||
var dbinfo = newdb.DbInfo.First();
|
||||
dbinfo.DbVer = DbVer;
|
||||
newdb.Update(dbinfo);
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.DarkGreen;
|
||||
Console.WriteLine($"Database version is now: {DbVer}");
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine("Finished running migrations.");
|
||||
}
|
||||
|
||||
Console.ForegroundColor = ccolor;
|
||||
}
|
||||
|
||||
private class Migration {
|
||||
private readonly string _sql;
|
||||
public readonly int IntroducedWithDbVer;
|
||||
|
||||
public Migration(int introducedWithDbVer, string sql) {
|
||||
IntroducedWithDbVer = introducedWithDbVer;
|
||||
_sql = sql;
|
||||
}
|
||||
|
||||
public void Run(DataConnection db) {
|
||||
Console.ForegroundColor = ConsoleColor.DarkCyan;
|
||||
Console.Write("Running migration: ");
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine(_sql);
|
||||
db.Execute(_sql);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
@page
|
||||
@using MediaManager.database
|
||||
@model MediaManager.Pages.AddMovie
|
||||
@{
|
||||
ViewData["Title"] = "Add Movie";
|
||||
}
|
||||
|
||||
<div class="text-center">
|
||||
<h1 class="display-5">
|
||||
Add Movie
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="title" class="form-label">Title</label>
|
||||
<input type="text" maxlength="100" class="form-control" id="title" name="title" autofocus required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="year" class="form-label">Year</label>
|
||||
<input type="number" min="1800" max="2100" class="form-control" id="year" name="year" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="rating" class="form-label">Rating</label>
|
||||
<input type="number" min="1" max="10" class="form-control" id="rating" name="rating">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="rewatchability" class="form-label">Rewatchability</label>
|
||||
<input type="number" min="1" max="10" class="form-control" id="rewatchability" name="rewatchability">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="watchcount" class="form-label">Watch count</label>
|
||||
<input type="number" min="0" class="form-control" id="watchcount" name="watchcount" value="0" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="lastwatch" class="form-label">Last watch</label>
|
||||
<input type="date" class="form-control" id="lastwatch" name="lastwatch">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="comment" class="form-label">Comment</label>
|
||||
<input type="text" maxlength="100" class="form-control" id="comment" name="comment">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-secondary" name="another" value="true">Add another</button>
|
||||
<button type="submit" class="btn btn-primary" name="another" value="false">Add</button>
|
||||
</form>
|
@ -0,0 +1,38 @@
|
||||
using LinqToDB;
|
||||
using MediaManager.database;
|
||||
using MediaManager.database.Tables;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace MediaManager.Pages;
|
||||
|
||||
public class AddMovie : PageModel {
|
||||
public User AuthorizedUser;
|
||||
|
||||
public void OnGet() {
|
||||
using var db = new Database.DbConn();
|
||||
AuthorizedUser = db.Users.FirstOrDefault(p => p.Username == AuthUtil.GetRemoteUser(HttpContext, db))!;
|
||||
}
|
||||
|
||||
public void OnPost() {
|
||||
using var db = new Database.DbConn();
|
||||
AuthorizedUser = db.Users.FirstOrDefault(p => p.Username == AuthUtil.GetRemoteUser(HttpContext, db))!;
|
||||
|
||||
if (!Request.Form.ContainsKey("title") || !Request.Form.ContainsKey("year") || !Request.Form.ContainsKey("watchcount"))
|
||||
return;
|
||||
|
||||
var movie = new Movie { UserId = AuthorizedUser.UserId, Title = Request.Form["title"], Year = int.Parse(Request.Form["year"]), WatchCount = int.Parse(Request.Form["watchcount"]) };
|
||||
|
||||
if (Request.Form.ContainsKey("rating") && !string.IsNullOrEmpty(Request.Form["rating"]))
|
||||
movie.Rating = int.Parse(Request.Form["rating"]);
|
||||
if (Request.Form.ContainsKey("rewatchability") && !string.IsNullOrEmpty(Request.Form["rewatchability"]))
|
||||
movie.Rewatchability = int.Parse(Request.Form["rewatchability"]);
|
||||
if (Request.Form.ContainsKey("lastwatch") && !string.IsNullOrEmpty(Request.Form["lastwatch"]))
|
||||
movie.LastSeen = DateTime.Parse(Request.Form["lastwatch"]);
|
||||
if (Request.Form.ContainsKey("comment") && !string.IsNullOrEmpty(Request.Form["comment"]))
|
||||
movie.Comment = Request.Form["comment"];
|
||||
|
||||
db.InsertWithIdentity(movie);
|
||||
|
||||
Response.Redirect(Request.Form["another"] == "true" ? "/AddMovie" : "/Movies");
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
@page
|
||||
@using MediaManager.database
|
||||
@using MediaManager.database.Tables
|
||||
@model MediaManager.Pages.AddShow
|
||||
@{
|
||||
ViewData["Title"] = "Add Show";
|
||||
}
|
||||
|
||||
<div class="text-center">
|
||||
<h1 class="display-5">
|
||||
Add Show
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="title" class="form-label">Title</label>
|
||||
<input type="text" maxlength="100" class="form-control" id="title" name="title" autofocus required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="year" class="form-label">Year</label>
|
||||
<input type="number" min="1800" max="2100" class="form-control" id="year" name="year" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="rating" class="form-label">Rating</label>
|
||||
<input type="number" min="0" max="10" class="form-control" id="rating" name="rating">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="rewatchability" class="form-label">Rewatchability</label>
|
||||
<input type="number" min="0" max="10" class="form-control" id="rewatchability" name="rewatchability">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="watchcount" class="form-label">Watch count</label>
|
||||
<input type="number" min="0" class="form-control" id="watchcount" name="watchcount" value="0" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="lastwatch" class="form-label">Last watch</label>
|
||||
<input type="date" class="form-control" id="lastwatch" name="lastwatch">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="status" class="form-label">Status</label>
|
||||
<select class="form-select" id="status" name="status">
|
||||
<option value="1" selected>Unwatched</option>
|
||||
<option value="2">First Watch</option>
|
||||
<option value="3">Waiting</option>
|
||||
<option value="4">Finished</option>
|
||||
<option value="5">Rewatch</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="seeneps" class="form-label">Episodes seen</label>
|
||||
<input type="number" min="0" class="form-control" id="seeneps" name="seeneps" value="0" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="totaleps" class="form-label">Episodes total</label>
|
||||
<input type="number" min="1" class="form-control" id="totaleps" name="totaleps" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="comment" class="form-label">Comment</label>
|
||||
<input type="text" maxlength="100" class="form-control" id="comment" name="comment">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-secondary" name="another" value="true">Add another</button>
|
||||
<button type="submit" class="btn btn-primary" name="another" value="false">Add</button>
|
||||
</form>
|
@ -0,0 +1,63 @@
|
||||
using LinqToDB;
|
||||
using MediaManager.database;
|
||||
using MediaManager.database.Tables;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace MediaManager.Pages;
|
||||
|
||||
public class AddShow : PageModel {
|
||||
public User AuthorizedUser;
|
||||
|
||||
public void OnGet() {
|
||||
using var db = new Database.DbConn();
|
||||
AuthorizedUser = db.Users.FirstOrDefault(p => p.Username == AuthUtil.GetRemoteUser(HttpContext, db))!;
|
||||
}
|
||||
|
||||
public void OnPost() {
|
||||
using var db = new Database.DbConn();
|
||||
AuthorizedUser = db.Users.FirstOrDefault(p => p.Username == AuthUtil.GetRemoteUser(HttpContext, db))!;
|
||||
|
||||
if (!Request.Form.ContainsKey("title")
|
||||
|| !Request.Form.ContainsKey("year")
|
||||
|| !Request.Form.ContainsKey("watchcount")
|
||||
|| !Request.Form.ContainsKey("totaleps")
|
||||
|| !Request.Form.ContainsKey("seeneps")
|
||||
|| !Request.Form.ContainsKey("status"))
|
||||
return;
|
||||
|
||||
var show = new Show {
|
||||
UserId = AuthorizedUser.UserId,
|
||||
Title = Request.Form["title"],
|
||||
Year = int.Parse(Request.Form["year"]),
|
||||
WatchCount = int.Parse(Request.Form["watchcount"]),
|
||||
TotalEpisodes = int.Parse(Request.Form["totaleps"]),
|
||||
SeenEpisodes = int.Parse(Request.Form["seeneps"]),
|
||||
WatchStatus = (WatchStatus)int.Parse(Request.Form["status"])
|
||||
};
|
||||
|
||||
if (Request.Form.ContainsKey("rating") && !string.IsNullOrEmpty(Request.Form["rating"]))
|
||||
show.Rating = int.Parse(Request.Form["rating"]);
|
||||
if (Request.Form.ContainsKey("rewatchability") && !string.IsNullOrEmpty(Request.Form["rewatchability"]))
|
||||
show.Rewatchability = int.Parse(Request.Form["rewatchability"]);
|
||||
if (Request.Form.ContainsKey("lastwatch") && !string.IsNullOrEmpty(Request.Form["lastwatch"]))
|
||||
show.LastSeen = DateTime.Parse(Request.Form["lastwatch"]);
|
||||
if (Request.Form.ContainsKey("comment") && !string.IsNullOrEmpty(Request.Form["comment"]))
|
||||
show.Comment = Request.Form["comment"];
|
||||
|
||||
if (show.SeenEpisodes >= show.TotalEpisodes) {
|
||||
show.SeenEpisodes = show.TotalEpisodes;
|
||||
if (show.WatchCount == 0)
|
||||
show.WatchCount++;
|
||||
if (show.WatchStatus != WatchStatus.Waiting)
|
||||
show.WatchStatus = WatchStatus.Finished;
|
||||
}
|
||||
|
||||
else if (show.SeenEpisodes > 0 && show.WatchStatus == WatchStatus.Unwatched) {
|
||||
show.WatchStatus = show.WatchCount == 0 ? WatchStatus.FirstWatch : WatchStatus.Rewatch;
|
||||
}
|
||||
|
||||
db.InsertWithIdentity(show);
|
||||
|
||||
Response.Redirect(Request.Form["another"] == "true" ? "/AddShow" : "/Shows");
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
@page "{id}"
|
||||
@using MediaManager.database
|
||||
@model MediaManager.Pages.EditMovie
|
||||
@{
|
||||
ViewData["Title"] = "Edit Movie";
|
||||
if (Request.Method == "POST" && Request.Form["action"] == "delete") {
|
||||
return;
|
||||
}
|
||||
var movie = new Database.DbConn().Movies.First(p => p.MovieId == int.Parse(RouteData.Values["id"]!.ToString()!));
|
||||
if (movie.UserId != Model.AuthorizedUser.UserId) {
|
||||
Response.Redirect("/");
|
||||
return;
|
||||
}}
|
||||
|
||||
<div class="text-center">
|
||||
<h1 class="display-5">
|
||||
Edit Movie
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="title" class="form-label">Title</label>
|
||||
<input type="text" maxlength="100" class="form-control" id="title" name="title" value="@movie.Title" autofocus required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="year" class="form-label">Year</label>
|
||||
<input type="number" min="1800" max="2100" class="form-control" id="year" name="year" value="@movie.Year" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="rating" class="form-label">Rating</label>
|
||||
<input type="number" min="0" max="10" class="form-control" id="rating" name="rating" value="@movie.Rating" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="rewatchability" class="form-label">Rewatchability</label>
|
||||
<input type="number" min="0" max="10" class="form-control" id="rewatchability" name="rewatchability" value="@movie.Rewatchability" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="watchcount" class="form-label">Watch count</label>
|
||||
<input type="number" min="0" class="form-control" id="watchcount" name="watchcount" value="@movie.WatchCount" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="lastwatch" class="form-label">Last watch</label>
|
||||
<input type="date" class="form-control" id="lastwatch" name="lastwatch" value="@movie.LastSeen.ToString("yyyy-MM-dd")">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="comment" class="form-label">Comment</label>
|
||||
<input type="text" maxlength="100" class="form-control" id="comment" name="comment" value="@movie.Comment">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" name="action" value="save">Save</button>
|
||||
<button type="submit" class="btn btn-danger" name="action" value="delete">Delete</button>
|
||||
</form>
|
@ -0,0 +1,67 @@
|
||||
using LinqToDB;
|
||||
using MediaManager.database;
|
||||
using MediaManager.database.Tables;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace MediaManager.Pages;
|
||||
|
||||
public class EditMovie : PageModel {
|
||||
public User AuthorizedUser;
|
||||
|
||||
public void OnGet() {
|
||||
using var db = new Database.DbConn();
|
||||
var movieId = int.Parse(RouteData.Values["id"]!.ToString()!);
|
||||
var movie = db.Movies.First(p => p.MovieId == movieId);
|
||||
|
||||
AuthorizedUser = db.Users.FirstOrDefault(p => p.Username == AuthUtil.GetRemoteUser(HttpContext, db))!;
|
||||
|
||||
if (movie.UserId != AuthorizedUser.UserId) {
|
||||
Response.Redirect("/");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Request.Query.ContainsKey("action") && Request.Query["action"] == "autoinc") {
|
||||
if (movie.UserId != AuthorizedUser.UserId) {
|
||||
Response.Redirect("/");
|
||||
return;
|
||||
}
|
||||
|
||||
movie.WatchCount++;
|
||||
movie.LastSeen = DateTime.Now;
|
||||
|
||||
db.Update(movie);
|
||||
Response.Redirect("/Movies");
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPost() {
|
||||
using var db = new Database.DbConn();
|
||||
var movieId = int.Parse(RouteData.Values["id"]!.ToString()!);
|
||||
var movie = db.Movies.First(p => p.MovieId == movieId);
|
||||
|
||||
AuthorizedUser = db.Users.FirstOrDefault(p => p.Username == AuthUtil.GetRemoteUser(HttpContext, db))!;
|
||||
|
||||
if (movie.UserId != AuthorizedUser.UserId) {
|
||||
Response.Redirect("/");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Request.Form["action"] == "delete") {
|
||||
db.Delete(movie);
|
||||
Response.Redirect("/Movies");
|
||||
return;
|
||||
}
|
||||
|
||||
movie.Title = Request.Form["title"];
|
||||
movie.Year = int.Parse(Request.Form["year"]);
|
||||
movie.WatchCount = int.Parse(Request.Form["watchcount"]);
|
||||
movie.Rating = int.Parse(Request.Form["rating"]);
|
||||
movie.Rewatchability = int.Parse(Request.Form["rewatchability"]);
|
||||
movie.LastSeen = string.IsNullOrEmpty(Request.Form["lastwatch"]) ? DateTime.MinValue : DateTime.Parse(Request.Form["lastwatch"]);
|
||||
movie.Comment = Request.Form["comment"];
|
||||
|
||||
db.Update(movie);
|
||||
|
||||
Response.Redirect("/Movies");
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
@page "{id}"
|
||||
@using MediaManager.database
|
||||
@using MediaManager.database.Tables
|
||||
@model MediaManager.Pages.EditShow
|
||||
@{
|
||||
ViewData["Title"] = "Edit Show";
|
||||
if (Request.Method == "POST" && Request.Form["action"] == "delete") {
|
||||
return;
|
||||
}
|
||||
var show = new Database.DbConn().Shows.First(p => p.ShowId == int.Parse(RouteData.Values["id"]!.ToString()!));
|
||||
if (show.UserId != Model.AuthorizedUser.UserId) {
|
||||
Response.Redirect("/");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
<div class="text-center">
|
||||
<h1 class="display-5">
|
||||
Edit Show
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="title" class="form-label">Title</label>
|
||||
<input type="text" maxlength="100" class="form-control" id="title" name="title" value="@show.Title" autofocus required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="year" class="form-label">Year</label>
|
||||
<input type="number" min="1800" max="2100" class="form-control" id="year" name="year" value="@show.Year" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="rating" class="form-label">Rating</label>
|
||||
<input type="number" min="0" max="10" class="form-control" id="rating" name="rating" value="@show.Rating" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="rewatchability" class="form-label">Rewatchability</label>
|
||||
<input type="number" min="0" max="10" class="form-control" id="rewatchability" name="rewatchability" value="@show.Rewatchability" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="watchcount" class="form-label">Watch count</label>
|
||||
<input type="number" min="0" class="form-control" id="watchcount" name="watchcount" value="@show.WatchCount" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="lastwatch" class="form-label">Last watch</label>
|
||||
<input type="date" class="form-control" id="lastwatch" name="lastwatch" value="@show.LastSeen.ToString("yyyy-MM-dd")">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="status" class="form-label">Status</label>
|
||||
<select class="form-select" id="status" name="status">
|
||||
@if (show.WatchStatus == WatchStatus.Unwatched) {
|
||||
<option value="1" selected>Unwatched</option>
|
||||
}
|
||||
else {
|
||||
<option value="1">Unwatched</option>
|
||||
}
|
||||
@if (show.WatchStatus == WatchStatus.FirstWatch) {
|
||||
<option value="2" selected>First Watch</option>
|
||||
}
|
||||
else {
|
||||
<option value="2">First Watch</option>
|
||||
}
|
||||
@if (show.WatchStatus == WatchStatus.Waiting) {
|
||||
<option value="3" selected>Waiting</option>
|
||||
}
|
||||
else {
|
||||
<option value="3">Waiting</option>
|
||||
}
|
||||
@if (show.WatchStatus == WatchStatus.Finished) {
|
||||
<option value="4" selected>Finished</option>
|
||||
}
|
||||
else {
|
||||
<option value="4">Finished</option>
|
||||
}
|
||||
@if (show.WatchStatus == WatchStatus.Rewatch) {
|
||||
<option value="5" selected>Rewatch</option>
|
||||
}
|
||||
else {
|
||||
<option value="5">Rewatch</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="seeneps" class="form-label">Episodes seen</label>
|
||||
<input type="number" min="0" class="form-control" id="seeneps" name="seeneps" value="@show.SeenEpisodes" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="totaleps" class="form-label">Episodes total</label>
|
||||
<input type="number" min="1" class="form-control" id="totaleps" name="totaleps" value="@show.TotalEpisodes" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="comment" class="form-label">Comment</label>
|
||||
<input type="text" maxlength="100" class="form-control" id="comment" name="comment" value="@show.Comment">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" name="action" value="save">Save</button>
|
||||
<button type="submit" class="btn btn-danger" name="action" value="delete">Delete</button>
|
||||
</form>
|
@ -0,0 +1,107 @@
|
||||
using LinqToDB;
|
||||
using MediaManager.database;
|
||||
using MediaManager.database.Tables;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace MediaManager.Pages;
|
||||
|
||||
public class EditShow : PageModel {
|
||||
public User AuthorizedUser;
|
||||
|
||||
public void OnGet() {
|
||||
using var db = new Database.DbConn();
|
||||
var showId = int.Parse(RouteData.Values["id"]!.ToString()!);
|
||||
var show = db.Shows.First(p => p.ShowId == showId);
|
||||
|
||||
AuthorizedUser = db.Users.FirstOrDefault(p => p.Username == AuthUtil.GetRemoteUser(HttpContext, db))!;
|
||||
|
||||
if (show.UserId != AuthorizedUser.UserId) {
|
||||
Response.Redirect("/");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Request.Query.ContainsKey("action") && Request.Query["action"] == "autoinc") {
|
||||
if (show.WatchStatus == WatchStatus.Finished) {
|
||||
Response.Redirect("/Shows");
|
||||
return;
|
||||
}
|
||||
|
||||
show.LastSeen = DateTime.Now;
|
||||
show.SeenEpisodes++;
|
||||
|
||||
if (show.SeenEpisodes > show.TotalEpisodes)
|
||||
show.TotalEpisodes = show.SeenEpisodes;
|
||||
|
||||
show.WatchStatus = show.WatchStatus switch {
|
||||
WatchStatus.Waiting => WatchStatus.FirstWatch,
|
||||
WatchStatus.Unwatched => WatchStatus.FirstWatch,
|
||||
_ => show.WatchStatus
|
||||
};
|
||||
|
||||
db.Update(show);
|
||||
Response.Redirect("/Shows");
|
||||
}
|
||||
|
||||
if (Request.Query.ContainsKey("action") && Request.Query["action"] == "rewatch") {
|
||||
show.SeenEpisodes = 0;
|
||||
show.WatchStatus = WatchStatus.Rewatch;
|
||||
|
||||
db.Update(show);
|
||||
Response.Redirect("/Shows");
|
||||
}
|
||||
|
||||
if (Request.Query.ContainsKey("action") && Request.Query["action"] == "finish") {
|
||||
show.WatchStatus = WatchStatus.Finished;
|
||||
show.WatchCount++;
|
||||
db.Update(show);
|
||||
Response.Redirect("/Shows");
|
||||
}
|
||||
|
||||
if (Request.Query.ContainsKey("action") && Request.Query["action"] == "waiting") {
|
||||
show.WatchStatus = WatchStatus.Waiting;
|
||||
db.Update(show);
|
||||
Response.Redirect("/Shows");
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPost() {
|
||||
using var db = new Database.DbConn();
|
||||
var showId = int.Parse(RouteData.Values["id"]!.ToString()!);
|
||||
var show = db.Shows.First(p => p.ShowId == showId);
|
||||
|
||||
AuthorizedUser = db.Users.FirstOrDefault(p => p.Username == AuthUtil.GetRemoteUser(HttpContext, db))!;
|
||||
|
||||
if (show.UserId != AuthorizedUser.UserId) {
|
||||
Response.Redirect("/");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Request.Form["action"] == "delete") {
|
||||
db.Delete(show);
|
||||
Response.Redirect("/Shows");
|
||||
return;
|
||||
}
|
||||
|
||||
show.Title = Request.Form["title"];
|
||||
show.Year = int.Parse(Request.Form["year"]);
|
||||
show.WatchCount = int.Parse(Request.Form["watchcount"]);
|
||||
show.Rating = int.Parse(Request.Form["rating"]);
|
||||
show.Rewatchability = int.Parse(Request.Form["rewatchability"]);
|
||||
show.LastSeen = string.IsNullOrEmpty(Request.Form["lastwatch"]) ? DateTime.MinValue : DateTime.Parse(Request.Form["lastwatch"]);
|
||||
show.WatchStatus = (WatchStatus)int.Parse(Request.Form["status"]);
|
||||
show.SeenEpisodes = int.Parse(Request.Form["seeneps"]);
|
||||
show.TotalEpisodes = int.Parse(Request.Form["totaleps"]);
|
||||
show.Comment = Request.Form["comment"];
|
||||
|
||||
if (show.SeenEpisodes >= show.TotalEpisodes) {
|
||||
show.SeenEpisodes = show.TotalEpisodes;
|
||||
if (show.WatchStatus != WatchStatus.Waiting) {
|
||||
show.WatchStatus = WatchStatus.Finished;
|
||||
}
|
||||
}
|
||||
|
||||
db.Update(show);
|
||||
|
||||
Response.Redirect("/Shows");
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
๏ปฟ@page
|
||||
@model ErrorModel
|
||||
@{
|
||||
ViewData["Title"] = "Error";
|
||||
}
|
||||
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
@if (Model.ShowRequestId) {
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||
It can result in displaying sensitive information from exceptions to end users.
|
||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||
and restarting the app.
|
||||
</p>
|
@ -0,0 +1,20 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace MediaManager.Pages;
|
||||
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true), IgnoreAntiforgeryToken]
|
||||
public class ErrorModel : PageModel {
|
||||
private readonly ILogger<ErrorModel> _logger;
|
||||
|
||||
public ErrorModel(ILogger<ErrorModel> logger) => _logger = logger;
|
||||
|
||||
public string? RequestId { get; set; }
|
||||
|
||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
|
||||
public void OnGet() {
|
||||
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
๏ปฟ@page
|
||||
@model IndexModel
|
||||
@{
|
||||
ViewData["Title"] = "Home";
|
||||
}
|
||||
|
||||
<div class="text-center">
|
||||
<h1 class="display-4">
|
||||
Welcome, @Model.AuthorizedUser.Username
|
||||
</h1>
|
||||
<p>Please select a category in the header menu to continue.</p>
|
||||
</div>
|
@ -0,0 +1,84 @@
|
||||
@page
|
||||
@using MediaManager.database
|
||||
@model MediaManager.Pages.Movies
|
||||
@{
|
||||
ViewData["Title"] = "Movies";
|
||||
}
|
||||
|
||||
<div class="text-center">
|
||||
<h1 class="display-5">
|
||||
Movies
|
||||
<a class="btn btn-lg btn-primary" href="/AddMovie">Add</a>
|
||||
|
||||
</h1>
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Title</th>
|
||||
<th scope="col">Rating</th>
|
||||
<th scope="col">Rewatchability</th>
|
||||
<th scope="col">Watch count</th>
|
||||
<th scope="col">Last Watch</th>
|
||||
<th scope="col">Comment</th>
|
||||
<th scope="col">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var movie in new Database.DbConn().Movies.Where(p => p.UserId == Model.AuthorizedUser.UserId).OrderBy(p => p.Title)) {
|
||||
<tr>
|
||||
<td>
|
||||
<b>@movie.Title</b>
|
||||
<br/>
|
||||
<small>@movie.Year</small>
|
||||
</td>
|
||||
<td class="td-progress">
|
||||
@if (movie.Rating > 0) {
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-@(movie.Rating)0" role="progressbar" style="width: @(movie.Rating * 10)%">@(movie.Rating)</div>
|
||||
</div>
|
||||
}
|
||||
else {
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-00" role="progressbar" style="width: 0"></div>
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td class="td-progress">
|
||||
@if (movie.Rewatchability > 0) {
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-@(movie.Rewatchability)0" role="progressbar" style="width: @(movie.Rewatchability * 10)%">@(movie.Rewatchability)</div>
|
||||
</div>
|
||||
}
|
||||
else {
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-00" role="progressbar" style="width: 0"></div>
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td class="td-progress">
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-@(Math.Min(movie.WatchCount, 5) * 2)0" role="progressbar" style="width: @(Math.Min(movie.WatchCount, 5) * 20)%">@(movie.WatchCount)</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
@if (movie.LastSeen.Year > 1800) {
|
||||
@movie.LastSeen.ToString("yyyy-MM-dd")
|
||||
}
|
||||
else {
|
||||
@Html.Raw("-")
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@movie.Comment
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group" role="group">
|
||||
<a class="btn btn-sm btn-secondary" href="/EditMovie/@movie.MovieId?action=autoinc">W+1</a>
|
||||
<a class="btn btn-sm btn-primary" href="/EditMovie/@movie.MovieId">Edit</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
@ -0,0 +1,14 @@
|
||||
using MediaManager.database;
|
||||
using MediaManager.database.Tables;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace MediaManager.Pages;
|
||||
|
||||
public class Movies : PageModel {
|
||||
public User AuthorizedUser;
|
||||
|
||||
public void OnGet() {
|
||||
using var db = new Database.DbConn();
|
||||
AuthorizedUser = db.Users.FirstOrDefault(p => p.Username == AuthUtil.GetRemoteUser(HttpContext, db))!;
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
๏ปฟ<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>@ViewData["Title"] - MediaManager</title>
|
||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css"/>
|
||||
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true"/>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" asp-area="" asp-page="/Index">MediaManager</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
|
||||
aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
|
||||
<ul class="navbar-nav flex-grow-1">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="" asp-page="/Movies">Movies</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="" asp-page="/Shows">Shows</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="" asp-page="/Games">Games</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
<div class="container">
|
||||
<main role="main" class="pb-3">
|
||||
@RenderBody()
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer class="border-top footer text-muted">
|
||||
<div class="container">
|
||||
© 2022 - MediaManager - <a asp-area="" asp-page="/Privacy">Privacy</a>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="~/lib/jquery/dist/jquery.min.js"></script>
|
||||
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="~/js/site.js" asp-append-version="true"></script>
|
||||
|
||||
@await RenderSectionAsync("Scripts", false)
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,2 @@
|
||||
๏ปฟ<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
|
||||
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
|
@ -0,0 +1,119 @@
|
||||
@page
|
||||
@using MediaManager.database
|
||||
@using MediaManager.database.Tables
|
||||
@model MediaManager.Pages.Shows
|
||||
@{
|
||||
ViewData["Title"] = "Shows";
|
||||
}
|
||||
|
||||
<div class="text-center">
|
||||
<h1 class="display-5">
|
||||
Shows
|
||||
<a class="btn btn-lg btn-primary" href="/AddShow">Add</a>
|
||||
|
||||
</h1>
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Title</th>
|
||||
<th scope="col">Rating</th>
|
||||
<th scope="col">Rewatchability</th>
|
||||
<th scope="col">Watch count</th>
|
||||
<th scope="col">Last Watch</th>
|
||||
<th scope="col">Status</th>
|
||||
<th scope="col">Progress</th>
|
||||
<th scope="col">Comment</th>
|
||||
<th scope="col">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var show in new Database.DbConn().Shows.Where(p => p.UserId == Model.AuthorizedUser.UserId).OrderBy(p => p.Title)) {
|
||||
<tr>
|
||||
<td>
|
||||
<b>@show.Title</b>
|
||||
<br/>
|
||||
<small>@show.Year</small>
|
||||
</td>
|
||||
<td class="td-progress">
|
||||
@if (show.Rating > 0) {
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-@(show.Rating)0" role="progressbar" style="width: @(show.Rating * 10)%">@(show.Rating)</div>
|
||||
</div>
|
||||
}
|
||||
else {
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-00" role="progressbar" style="width: 0"></div>
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td class="td-progress">
|
||||
@if (show.Rewatchability > 0) {
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-@(show.Rewatchability)0" role="progressbar" style="width: @(show.Rewatchability * 10)%">@(show.Rewatchability)</div>
|
||||
</div>
|
||||
}
|
||||
else {
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-00" role="progressbar" style="width: 0"></div>
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td class="td-progress">
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-@(Math.Min(show.WatchCount, 5) * 2)0" role="progressbar" style="width: @(Math.Min(show.WatchCount, 5) * 20)%">@(show.WatchCount)</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
@if (show.LastSeen.Year > 2000) {
|
||||
@show.LastSeen.ToString("yyyy-MM-dd")
|
||||
}
|
||||
else {
|
||||
@Html.Raw("-")
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@Enum.GetName(show.WatchStatus)!.Replace("FirstWatch", "First Watch")
|
||||
</td>
|
||||
<td class="td-progress">
|
||||
<div class="progress position-relative">
|
||||
@{
|
||||
var progressf = 100d / show.TotalEpisodes * show.SeenEpisodes;
|
||||
var progress = (int)progressf;
|
||||
}
|
||||
<div class="progress-bar progress-@(progress - progress % 10)" role="progressbar" style="width: @progress%"></div>
|
||||
@if (progress >= 90) {
|
||||
<small class="justify-content-center d-flex position-absolute w-100" style="color: #fff">@show.SeenEpisodes / @show.TotalEpisodes</small>
|
||||
}
|
||||
else {
|
||||
<small class="justify-content-center d-flex position-absolute w-100">@show.SeenEpisodes / @show.TotalEpisodes</small>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
@show.Comment
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group" role="group">
|
||||
@if (show.SeenEpisodes == show.TotalEpisodes && show.WatchStatus == WatchStatus.FirstWatch) {
|
||||
<a class="btn btn-sm btn-secondary" href="/EditShow/@show.ShowId?action=waiting">Waiting</a>
|
||||
}
|
||||
@if (show.SeenEpisodes == show.TotalEpisodes && (show.WatchStatus == WatchStatus.FirstWatch || show.WatchStatus == WatchStatus.Rewatch)) {
|
||||
<a class="btn btn-sm btn-success" href="/EditShow/@show.ShowId?action=finish">Finish</a>
|
||||
}
|
||||
@if (show.SeenEpisodes < show.TotalEpisodes && (show.WatchStatus == WatchStatus.Unwatched || show.WatchStatus == WatchStatus.FirstWatch || show.WatchStatus == WatchStatus.Rewatch)) {
|
||||
<a class="btn btn-sm btn-secondary" href="/EditShow/@show.ShowId?action=autoinc">W+1</a>
|
||||
}
|
||||
else if (show.WatchStatus == WatchStatus.Finished) {
|
||||
<a class="btn btn-sm btn-success" href="/EditShow/@show.ShowId?action=rewatch">Start rewatch</a>
|
||||
}
|
||||
else if (show.WatchStatus == WatchStatus.Waiting) {
|
||||
<a class="btn btn-sm btn-success" href="/EditShow/@show.ShowId?action=autoinc">New episode (W+1)</a>
|
||||
}
|
||||
<a class="btn btn-sm btn-primary" href="/EditShow/@show.ShowId">Edit</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
@ -0,0 +1,14 @@
|
||||
using MediaManager.database;
|
||||
using MediaManager.database.Tables;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace MediaManager.Pages;
|
||||
|
||||
public class Shows : PageModel {
|
||||
public User AuthorizedUser;
|
||||
|
||||
public void OnGet() {
|
||||
using var db = new Database.DbConn();
|
||||
AuthorizedUser = db.Users.FirstOrDefault(p => p.Username == AuthUtil.GetRemoteUser(HttpContext, db))!;
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
๏ปฟ@using MediaManager
|
||||
@namespace MediaManager.Pages
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
@ -0,0 +1,3 @@
|
||||
๏ปฟ@{
|
||||
Layout = "_Layout";
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
using LinqToDB;
|
||||
using LinqToDB.Data;
|
||||
using MediaManager;
|
||||
using MediaManager.database;
|
||||
using MediaManager.database.Tables;
|
||||
|
||||
DataConnection.DefaultSettings = new Database.Settings();
|
||||
|
||||
Migrations.RunMigrations();
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddRazorPages();
|
||||
builder.Services.AddSession(options => {
|
||||
options.IdleTimeout = TimeSpan.MaxValue;
|
||||
options.Cookie.HttpOnly = true;
|
||||
options.Cookie.IsEssential = true;
|
||||
});
|
||||
|
||||
#if (DEBUG)
|
||||
builder.Services.AddControllers().AddRazorRuntimeCompilation();
|
||||
builder.Services.AddControllers();
|
||||
#else
|
||||
builder.Services.AddControllers();
|
||||
#endif
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (!app.Environment.IsDevelopment()) {
|
||||
app.UseExceptionHandler("/Error");
|
||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.UseSession();
|
||||
app.UseRouting();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoints(endpoints => {
|
||||
endpoints.MapRazorPages();
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
|
||||
app.Run();
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"DetailedErrors": true,
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
using LinqToDB;
|
||||
using LinqToDB.Configuration;
|
||||
using LinqToDB.Data;
|
||||
using MediaManager.database.Tables;
|
||||
|
||||
namespace MediaManager.database;
|
||||
|
||||
public class Database {
|
||||
public class ConnectionStringSettings : IConnectionStringSettings {
|
||||
public string ConnectionString { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string ProviderName { get; set; }
|
||||
public bool IsGlobal => false;
|
||||
}
|
||||
|
||||
public class Settings : ILinqToDBSettings {
|
||||
public IEnumerable<IDataProviderSettings> DataProviders => Enumerable.Empty<IDataProviderSettings>();
|
||||
|
||||
public string DefaultConfiguration => "SQLite";
|
||||
public string DefaultDataProvider => "SQLite";
|
||||
|
||||
public IEnumerable<IConnectionStringSettings> ConnectionStrings {
|
||||
get { yield return new ConnectionStringSettings { Name = "db", ProviderName = "SQLite", ConnectionString = @"Data Source=database.db;" }; }
|
||||
}
|
||||
}
|
||||
|
||||
public class DbConn : DataConnection {
|
||||
public DbConn() : base("db") { }
|
||||
public ITable<DbInfo> DbInfo => this.GetTable<DbInfo>();
|
||||
public ITable<User> Users => this.GetTable<User>();
|
||||
public ITable<Movie> Movies => this.GetTable<Movie>();
|
||||
public ITable<Show> Shows => this.GetTable<Show>();
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
using LinqToDB.Mapping;
|
||||
|
||||
namespace MediaManager.database.Tables;
|
||||
|
||||
[Table(Name = "DbInfo")]
|
||||
public class DbInfo {
|
||||
[Column(Name = "ID"), PrimaryKey, Identity, NotNull] public int Id { get; set; }
|
||||
[Column(Name = "DbVer"), NotNull] public int DbVer { get; set; }
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
using LinqToDB.Mapping;
|
||||
|
||||
namespace MediaManager.database.Tables;
|
||||
|
||||
[Table(Name = "Movies")]
|
||||
public class Movie {
|
||||
[Column(Name = "MovieID"), PrimaryKey, Identity, NotNull] public int MovieId { get; set; }
|
||||
[Column(Name = "UserID"), NotNull] public int UserId { get; set; }
|
||||
[Column(Name = "Year"), NotNull] public int Year { get; set; }
|
||||
[Column(Name = "Title"), NotNull] public string Title { get; set; }
|
||||
[Column(Name = "Rating")] public int Rating { get; set; }
|
||||
[Column(Name = "Rewatchability")] public int Rewatchability { get; set; }
|
||||
[Column(Name = "WatchCount")] public int WatchCount { get; set; }
|
||||
[Column(Name = "LastSeen")] public DateTime LastSeen { get; set; }
|
||||
[Column(Name = "Comment")] public string Comment { get; set; }
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
using LinqToDB.Mapping;
|
||||
|
||||
namespace MediaManager.database.Tables;
|
||||
|
||||
[Table(Name = "Shows")]
|
||||
public class Show {
|
||||
[Column(Name = "ShowID"), PrimaryKey, Identity, NotNull] public int ShowId { get; set; }
|
||||
[Column(Name = "UserID"), NotNull] public int UserId { get; set; }
|
||||
[Column(Name = "Year"), NotNull] public int Year { get; set; }
|
||||
[Column(Name = "Title"), NotNull] public string Title { get; set; }
|
||||
[Column(Name = "Rating")] public int Rating { get; set; }
|
||||
[Column(Name = "Rewatchability")] public int Rewatchability { get; set; }
|
||||
[Column(Name = "TotalEpisodes")] public int TotalEpisodes { get; set; }
|
||||
[Column(Name = "SeenEpisodes")] public int SeenEpisodes { get; set; }
|
||||
[Column(Name = "WatchCount")] public int WatchCount { get; set; }
|
||||
[Column(Name = "LastSeen")] public DateTime LastSeen { get; set; }
|
||||
[Column(Name = "Comment")] public string Comment { get; set; }
|
||||
[Column(Name = "WatchStatus")] public WatchStatus WatchStatus { get; set; }
|
||||
}
|
||||
|
||||
public enum WatchStatus {
|
||||
Unwatched = 1,
|
||||
FirstWatch = 2,
|
||||
Waiting = 3,
|
||||
Finished = 4,
|
||||
Rewatch = 5
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
using LinqToDB.Mapping;
|
||||
|
||||
namespace MediaManager.database.Tables;
|
||||
|
||||
[Table(Name = "Users")]
|
||||
public class User {
|
||||
[Column(Name = "UserID"), PrimaryKey, Identity, NotNull] public int UserId { get; set; }
|
||||
|
||||
[Column(Name = "Username"), NotNull] public string Username { get; set; }
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
html {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
html {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
/* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
|
||||
for details on configuring this project to bundle and minify static web assets. */
|
||||
|
||||
a.navbar-brand {
|
||||
white-space: normal;
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0077cc;
|
||||
}
|
||||
|
||||
td {
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac< |