Compare commits
2 Commits
40bd228836
...
5f8fb01a9f
Author | SHA1 | Date | |
---|---|---|---|
5f8fb01a9f | |||
4544be2999 |
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "C#: <project-name> Debug",
|
||||
"type": "dotnet",
|
||||
"request": "launch",
|
||||
"projectPath": "${workspaceFolder}/<relative-path-to-project-folder><project-name>.csproj"
|
||||
}
|
||||
|
||||
]
|
||||
}
|
89
AdapterContext.cs
Normal file
89
AdapterContext.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using Db.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
public class AdapterContext : DbContext
|
||||
{
|
||||
public AdapterContext(DbContextOptions<AdapterContext> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
// Configure your entity mappings here
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
modelBuilder.Entity<VideoTag>(e =>
|
||||
{
|
||||
e.HasKey(vt => new { vt.VideoId, vt.TagId });
|
||||
e.HasOne(vt => vt.Video)
|
||||
.WithMany(v => v.VideoTags)
|
||||
.HasForeignKey(vt => vt.VideoId);
|
||||
e.HasOne(vt => vt.Tag)
|
||||
.WithMany(t => t.VideoTags)
|
||||
.HasForeignKey(vt => vt.TagId);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Video>(e =>
|
||||
{
|
||||
e.HasKey(v => v.Id);
|
||||
e.Property(v => v.Id).IsRequired();
|
||||
e.Property(v => v.Extension).IsRequired();
|
||||
|
||||
// delete videoTags when a video is deleted but not the tags
|
||||
e.HasMany(v => v.VideoTags)
|
||||
.WithOne(vt => vt.Video)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Tag>(e =>
|
||||
{
|
||||
e.HasKey(t => t.Name);
|
||||
e.Property(t => t.Name).IsRequired();
|
||||
|
||||
// delete videoTags when a tag is deleted but not the videos
|
||||
e.HasMany(t => t.VideoTags)
|
||||
.WithOne(vt => vt.Tag)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Setting>(e =>
|
||||
{
|
||||
e.HasKey(s => s.Name);
|
||||
e.Property(s => s.Name).IsRequired();
|
||||
e.Property(s => s.Value).IsRequired();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Tags a video can have (e.g. "funny", "cat", "dog")
|
||||
public DbSet<Tag> Tags { get; set; }
|
||||
|
||||
// Videos with tags, relative paths, and other metadata
|
||||
public DbSet<Video> Videos { get; set; }
|
||||
// Settings for the adapter, such as video path
|
||||
public DbSet<Setting> Settings { get; set; }
|
||||
public DbSet<VideoTag> VideoTags { get; set; }
|
||||
|
||||
public async Task<T?> GetSettingAsync<T>(SettingName settingName)
|
||||
{
|
||||
var setting = await Settings.FindAsync(settingName);
|
||||
return setting != null ? (T?)Convert.ChangeType(setting.Value, typeof(T)) : default;
|
||||
}
|
||||
|
||||
public async Task SetSettingAsync<T>(SettingName settingName, T value)
|
||||
{
|
||||
var setting = await Settings.FindAsync(settingName);
|
||||
if (setting == null)
|
||||
{
|
||||
setting = new Setting { Name = settingName, Value = value?.ToString() ?? string.Empty };
|
||||
Settings.Add(setting);
|
||||
}
|
||||
else
|
||||
{
|
||||
setting.Value = value?.ToString() ?? string.Empty;
|
||||
Settings.Update(setting);
|
||||
}
|
||||
await SaveChangesAsync();
|
||||
}
|
||||
}
|
241
Controllers/HomeController.cs
Normal file
241
Controllers/HomeController.cs
Normal file
@@ -0,0 +1,241 @@
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Db.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TagVid.Helpers;
|
||||
using TagVid.Models;
|
||||
|
||||
namespace TagVid.Controllers;
|
||||
|
||||
public class HomeController : Controller
|
||||
{
|
||||
private readonly ILogger<HomeController> _logger;
|
||||
private readonly AdapterContext _adapterContext;
|
||||
|
||||
public HomeController(ILogger<HomeController> logger, AdapterContext adapterContext)
|
||||
{
|
||||
_logger = logger;
|
||||
_adapterContext = adapterContext;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
// check if the video path was set
|
||||
var pathSettings = await _adapterContext.GetSettingAsync<string>(SettingName.VideoPath);
|
||||
if (string.IsNullOrEmpty(pathSettings))
|
||||
{
|
||||
_logger.LogWarning("Video path is not set. Please configure the video path in settings.");
|
||||
return RedirectToAction("Settings", "Home");
|
||||
}
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("Home/Index/{id}")]
|
||||
public async Task<IActionResult> Index(string id)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Settings()
|
||||
{
|
||||
var vm = new SettingsViewModel
|
||||
{
|
||||
VideoPath = await _adapterContext.GetSettingAsync<string>(SettingName.VideoPath)
|
||||
};
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Settings(SettingsViewModel model)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
|
||||
// Validate the video path
|
||||
if (string.IsNullOrWhiteSpace(model.VideoPath))
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.VideoPath), "Video path cannot be empty.");
|
||||
return View(model);
|
||||
}
|
||||
|
||||
// Check if the path exists
|
||||
if (!Directory.Exists(model.VideoPath))
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.VideoPath), "The specified video path does not exist.");
|
||||
return View(model);
|
||||
}
|
||||
|
||||
// Check if the path is a directory
|
||||
if (!System.IO.File.GetAttributes(model.VideoPath).HasFlag(FileAttributes.Directory))
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.VideoPath), "The specified path must be a directory.");
|
||||
return View(model);
|
||||
}
|
||||
|
||||
// Save settings to the database
|
||||
await _adapterContext.SetSettingAsync(SettingName.VideoPath, model.VideoPath);
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Upload(IFormFile videoFile)
|
||||
{
|
||||
_logger.LogInformation("Upload called with file: {FileName}", videoFile?.Name);
|
||||
if (videoFile == null || videoFile.Length == 0)
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
// Validate the video file
|
||||
// no size limit for now, but could be added later
|
||||
|
||||
if (!videoFile.ContentType.StartsWith("video/"))
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
// Save the video file to the configured path
|
||||
var videoPath = await _adapterContext.GetSettingAsync<string>(SettingName.VideoPath);
|
||||
var ext = Path.GetExtension(videoFile.FileName);
|
||||
|
||||
if (string.IsNullOrEmpty(videoPath) || !Directory.Exists(videoPath))
|
||||
{
|
||||
return RedirectToAction("Settings");
|
||||
}
|
||||
|
||||
// asp.net docu suggests not using .FileName due to security reasons
|
||||
var id = Guid.NewGuid().ToString();
|
||||
|
||||
var filePath = Path.Combine(videoPath, $"{id}{ext}");
|
||||
using (var stream = new FileStream(filePath, FileMode.Create))
|
||||
{
|
||||
await videoFile.CopyToAsync(stream);
|
||||
}
|
||||
|
||||
var data = await new FFProbeHelper<HomeController>(_logger).GetVideoMetadataAsync(filePath);
|
||||
|
||||
var video = new Video
|
||||
{
|
||||
Id = id,
|
||||
Duration = data?.Duration ?? TimeSpan.Zero,
|
||||
Width = data?.Width ?? 0,
|
||||
Height = data?.Height ?? 0,
|
||||
Extension = ext,
|
||||
VideoTags = []
|
||||
};
|
||||
|
||||
_adapterContext.Videos.Add(video);
|
||||
await _adapterContext.SaveChangesAsync();
|
||||
|
||||
// redirect to the video details page
|
||||
return RedirectToAction("Details", "Home", new { id = video.Id });
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Details(string id)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var video = await _adapterContext.Videos
|
||||
.Include(v => v.VideoTags)
|
||||
.ThenInclude(vt => vt.Tag)
|
||||
.FirstOrDefaultAsync(v => v.Id == id);
|
||||
|
||||
if (video == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var vm = new DetailsViewModel
|
||||
{
|
||||
VideoId = id,
|
||||
Title = video?.Title ?? string.Empty,
|
||||
Tags = video?.VideoTags.Select(vt => vt.Tag.Name).ToList() ?? []
|
||||
};
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Details(DetailsViewModel videoModel)
|
||||
{
|
||||
if (string.IsNullOrEmpty(videoModel.VideoId))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var existingVideo = await _adapterContext.Videos
|
||||
.Include(v => v.VideoTags)
|
||||
.ThenInclude(vt => vt.Tag)
|
||||
.FirstOrDefaultAsync(v => v.Id == videoModel.VideoId);
|
||||
|
||||
if (existingVideo == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
// Update video title
|
||||
existingVideo.Title = videoModel.Title;
|
||||
// Update tags
|
||||
existingVideo.VideoTags.Clear();
|
||||
foreach (var tagName in videoModel.Tags)
|
||||
{
|
||||
var tag = await _adapterContext.Tags.FirstOrDefaultAsync(t => t.Name == tagName);
|
||||
if (tag == null)
|
||||
{
|
||||
tag = new Tag { Name = tagName };
|
||||
_adapterContext.Tags.Add(tag);
|
||||
}
|
||||
existingVideo.VideoTags.Add(new VideoTag { Video = existingVideo, Tag = tag });
|
||||
}
|
||||
_adapterContext.Videos.Update(existingVideo);
|
||||
await _adapterContext.SaveChangesAsync();
|
||||
|
||||
return RedirectToAction("Details", new { id = videoModel.VideoId });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Delete(string id)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
var video = await _adapterContext.Videos.FindAsync(id);
|
||||
if (video == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
// Delete the video file from the file system
|
||||
var videoPath = await _adapterContext.GetSettingAsync<string>(SettingName.VideoPath);
|
||||
if (!string.IsNullOrEmpty(videoPath))
|
||||
{
|
||||
var filePath = Path.Combine(videoPath, $"{video.Id}{video.Extension}");
|
||||
if (System.IO.File.Exists(filePath))
|
||||
{
|
||||
System.IO.File.Delete(filePath);
|
||||
}
|
||||
}
|
||||
// Remove the video from the database
|
||||
_adapterContext.Videos.Remove(video);
|
||||
await _adapterContext.SaveChangesAsync();
|
||||
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public IActionResult Error()
|
||||
{
|
||||
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||
}
|
||||
}
|
123
Migrations/20250726215357_InitialMigration.Designer.cs
generated
Normal file
123
Migrations/20250726215357_InitialMigration.Designer.cs
generated
Normal file
@@ -0,0 +1,123 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace TagVid.Migrations
|
||||
{
|
||||
[DbContext(typeof(AdapterContext))]
|
||||
[Migration("20250726215357_InitialMigration")]
|
||||
partial class InitialMigration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Db.Models.Setting", b =>
|
||||
{
|
||||
b.Property<int>("Name")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Name");
|
||||
|
||||
b.ToTable("Settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Db.Models.Tag", b =>
|
||||
{
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("Name");
|
||||
|
||||
b.ToTable("Tags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Db.Models.Video", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<TimeSpan>("Duration")
|
||||
.HasColumnType("time");
|
||||
|
||||
b.Property<string>("Extension")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("Height")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("Width")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Videos");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Db.Models.VideoTag", b =>
|
||||
{
|
||||
b.Property<string>("VideoId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("TagId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("VideoId", "TagId");
|
||||
|
||||
b.HasIndex("TagId");
|
||||
|
||||
b.ToTable("VideoTags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Db.Models.VideoTag", b =>
|
||||
{
|
||||
b.HasOne("Db.Models.Tag", "Tag")
|
||||
.WithMany("VideoTags")
|
||||
.HasForeignKey("TagId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Db.Models.Video", "Video")
|
||||
.WithMany("VideoTags")
|
||||
.HasForeignKey("VideoId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Tag");
|
||||
|
||||
b.Navigation("Video");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Db.Models.Tag", b =>
|
||||
{
|
||||
b.Navigation("VideoTags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Db.Models.Video", b =>
|
||||
{
|
||||
b.Navigation("VideoTags");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
99
Migrations/20250726215357_InitialMigration.cs
Normal file
99
Migrations/20250726215357_InitialMigration.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace TagVid.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialMigration : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Settings",
|
||||
columns: table => new
|
||||
{
|
||||
Name = table.Column<int>(type: "int", nullable: false),
|
||||
Value = table.Column<string>(type: "nvarchar(max)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Settings", x => x.Name);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Tags",
|
||||
columns: table => new
|
||||
{
|
||||
Name = table.Column<string>(type: "nvarchar(450)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Tags", x => x.Name);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Videos",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "nvarchar(450)", nullable: false),
|
||||
Title = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
Duration = table.Column<TimeSpan>(type: "time", nullable: false),
|
||||
Width = table.Column<int>(type: "int", nullable: false),
|
||||
Height = table.Column<int>(type: "int", nullable: false),
|
||||
Extension = table.Column<string>(type: "nvarchar(max)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Videos", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "VideoTags",
|
||||
columns: table => new
|
||||
{
|
||||
VideoId = table.Column<string>(type: "nvarchar(450)", nullable: false),
|
||||
TagId = table.Column<string>(type: "nvarchar(450)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_VideoTags", x => new { x.VideoId, x.TagId });
|
||||
table.ForeignKey(
|
||||
name: "FK_VideoTags_Tags_TagId",
|
||||
column: x => x.TagId,
|
||||
principalTable: "Tags",
|
||||
principalColumn: "Name",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_VideoTags_Videos_VideoId",
|
||||
column: x => x.VideoId,
|
||||
principalTable: "Videos",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_VideoTags_TagId",
|
||||
table: "VideoTags",
|
||||
column: "TagId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Settings");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "VideoTags");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Tags");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Videos");
|
||||
}
|
||||
}
|
||||
}
|
120
Migrations/AdapterContextModelSnapshot.cs
Normal file
120
Migrations/AdapterContextModelSnapshot.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace TagVid.Migrations
|
||||
{
|
||||
[DbContext(typeof(AdapterContext))]
|
||||
partial class AdapterContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Db.Models.Setting", b =>
|
||||
{
|
||||
b.Property<int>("Name")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Name");
|
||||
|
||||
b.ToTable("Settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Db.Models.Tag", b =>
|
||||
{
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("Name");
|
||||
|
||||
b.ToTable("Tags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Db.Models.Video", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<TimeSpan>("Duration")
|
||||
.HasColumnType("time");
|
||||
|
||||
b.Property<string>("Extension")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("Height")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("Width")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Videos");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Db.Models.VideoTag", b =>
|
||||
{
|
||||
b.Property<string>("VideoId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("TagId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("VideoId", "TagId");
|
||||
|
||||
b.HasIndex("TagId");
|
||||
|
||||
b.ToTable("VideoTags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Db.Models.VideoTag", b =>
|
||||
{
|
||||
b.HasOne("Db.Models.Tag", "Tag")
|
||||
.WithMany("VideoTags")
|
||||
.HasForeignKey("TagId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Db.Models.Video", "Video")
|
||||
.WithMany("VideoTags")
|
||||
.HasForeignKey("VideoId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Tag");
|
||||
|
||||
b.Navigation("Video");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Db.Models.Tag", b =>
|
||||
{
|
||||
b.Navigation("VideoTags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Db.Models.Video", b =>
|
||||
{
|
||||
b.Navigation("VideoTags");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
8
Models/DetailsViewModel.cs
Normal file
8
Models/DetailsViewModel.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace TagVid.Models;
|
||||
|
||||
public class DetailsViewModel
|
||||
{
|
||||
public string VideoId { get; set; } = string.Empty;
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public List<string> Tags { get; set; } = new List<string>();
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
namespace tagvid.Models;
|
||||
namespace TagVid.Models;
|
||||
|
||||
public class ErrorViewModel
|
||||
{
|
11
Models/SettingsViewModel.cs
Normal file
11
Models/SettingsViewModel.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace TagVid.Models;
|
||||
|
||||
public class SettingsViewModel
|
||||
{
|
||||
[Required(ErrorMessage = "Video path is required.")]
|
||||
[Display(Name = "Video Path")]
|
||||
public string? VideoPath { get; set; } = null;
|
||||
public string? ErrorMessage { get; set; }
|
||||
}
|
@@ -1,8 +1,26 @@
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddControllersWithViews();
|
||||
|
||||
// increase the default upload size limit
|
||||
builder.Services.Configure<FormOptions>(options =>
|
||||
{
|
||||
options.MultipartBodyLengthLimit = long.MaxValue; // Set to a very high value
|
||||
});
|
||||
|
||||
builder.WebHost.ConfigureKestrel(serverOptions =>
|
||||
{
|
||||
serverOptions.Limits.MaxRequestBodySize = long.MaxValue; // Set to a very high value
|
||||
});
|
||||
|
||||
// Configure Entity Framework Core with SQL Server
|
||||
builder.Services.AddDbContext<AdapterContext>(options =>
|
||||
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
8
SettingName.cs
Normal file
8
SettingName.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
public enum SettingName
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a the absolute path of the folder where videos are stored.
|
||||
/// </summary>
|
||||
VideoPath,
|
||||
|
||||
}
|
70
Views/Home/Details.cshtml
Normal file
70
Views/Home/Details.cshtml
Normal file
@@ -0,0 +1,70 @@
|
||||
@model DetailsViewModel
|
||||
|
||||
@using (Html.BeginForm("Details", "Home", FormMethod.Post )) {
|
||||
@Html.AntiForgeryToken()
|
||||
@Html.HiddenFor(m => m.VideoId)
|
||||
|
||||
<div class="mb-3">
|
||||
@Html.LabelFor(m => m.Title)
|
||||
@Html.TextBoxFor(m => m.Title, new { @class = "form-control", @required = "required" })
|
||||
@Html.ValidationMessageFor(m => m.Title)
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
@Html.LabelFor(m => m.Tags)
|
||||
@foreach (var tag in Model.Tags)
|
||||
{
|
||||
<span class="badge rounded-pill text-bg-secondary" data-tag-name="@tag">
|
||||
@Html.DisplayFor(m => tag)
|
||||
<button type="button" class="btn-close btn-sm" aria-label="Close" onclick="removeTag('@tag')"></button>
|
||||
</span>
|
||||
}
|
||||
<input type="text" id="newTag" class="form-control mt-2" placeholder="Add new tag" />
|
||||
@for (int i = 0; i < Model.Tags.Count; i++)
|
||||
{
|
||||
<input type="hidden" class="hiddenTag" name="Tags[@i]" value="@Model.Tags.ElementAt(i)" />
|
||||
}
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Update Video</button>
|
||||
}
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#newTag').on('keypress', function(e) {
|
||||
if (e.which === 13) { // Enter key
|
||||
e.preventDefault();
|
||||
var tagName = $(this).val().trim();
|
||||
if (tagName) {
|
||||
$(this).val(''); // Clear input
|
||||
// check if a hidden input for this tag already exists
|
||||
var existingTag = $(`input[value='${tagName}']`);
|
||||
if (existingTag.length > 0) {
|
||||
return;
|
||||
}
|
||||
var tagCount = $('.hiddenTag').length;
|
||||
// Add the tag to a new hidden input
|
||||
var hiddenInput = `<input type="hidden" class="hiddenTag" name="Tags[${tagCount}]" value="${tagName}" />`;
|
||||
|
||||
|
||||
var tagHtml = `<span class="badge rounded-pill text-bg-secondary" data-tag-name="${tagName}">
|
||||
${tagName}
|
||||
<button type="button" class="btn-close btn-sm" aria-label="Close" onclick="removeTag('${tagName}')"></button>
|
||||
</span>`;
|
||||
$(this).after(hiddenInput);
|
||||
$(this).before(tagHtml);
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.removeTag = function(tagName) {
|
||||
// Remove the hidden input for the tag
|
||||
$(`input[value='${tagName}']`).remove();
|
||||
// Remove the badge from the UI
|
||||
$(`span[data-tag-name='${tagName}']`).remove();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
}
|
1
Views/Home/Index.cshtml
Normal file
1
Views/Home/Index.cshtml
Normal file
@@ -0,0 +1 @@
|
||||
|
15
Views/Home/Settings.cshtml
Normal file
15
Views/Home/Settings.cshtml
Normal file
@@ -0,0 +1,15 @@
|
||||
@model SettingsViewModel
|
||||
|
||||
@using (Html.BeginForm("Settings", "Home", FormMethod.Post)) {
|
||||
@Html.AntiForgeryToken()
|
||||
|
||||
<h2>Settings</h2>
|
||||
|
||||
<div class="mb-3">
|
||||
@Html.LabelFor(m => m.VideoPath, new { @class = "form-label" })
|
||||
@Html.TextBoxFor(m => m.VideoPath, new { @class = "form-control" })
|
||||
@Html.ValidationMessageFor(m => m.VideoPath, "", new { @class = "text-danger" })
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Save Settings</button>
|
||||
}
|
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@ViewData["Title"] - tagvid</title>
|
||||
<title>Tagvid</title>
|
||||
<script type="importmap"></script>
|
||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
||||
@@ -24,9 +24,14 @@
|
||||
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
|
||||
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Settings">Settings</a>
|
||||
</li>
|
||||
</ul>
|
||||
<form class="d-flex" role="upload" method="post" asp-controller="Home" asp-action="Upload" enctype="multipart/form-data">
|
||||
<input type="file" class="form-control me-2" id="videoFileInput" name="videoFile" required accept="video/*" />
|
||||
<input type="hidden" name="previousUrl" value="@Context.Request.Path" />
|
||||
<button type="submit" class="btn btn-outline-success">Upload</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -37,11 +42,6 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer class="border-top footer text-muted">
|
||||
<div class="container">
|
||||
© 2025 - tagvid - <a asp-area="" asp-controller="Home" asp-action="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>
|
4
Views/_ViewImports.cshtml
Normal file
4
Views/_ViewImports.cshtml
Normal file
@@ -0,0 +1,4 @@
|
||||
@using TagVid
|
||||
@using TagVid.Models
|
||||
@using Db.Models
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
@@ -1,6 +0,0 @@
|
||||
namespace adapter;
|
||||
|
||||
public class Class1
|
||||
{
|
||||
|
||||
}
|
@@ -1,9 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
13
appsettings.json
Normal file
13
appsettings.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Information",
|
||||
"Microsoft.EntityFrameworkCore.Database.Command": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Server=192.168.178.36;Database=TagVid;User Id=sa;Password=TngxW9Xp8QxXyb3GMxYz; Integrated Security=False; trustServerCertificate=true; Encrypt=False; MultipleActiveResultSets=True; Connection Timeout=30;"
|
||||
}
|
||||
}
|
13
db/models/Setting.cs
Normal file
13
db/models/Setting.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Db.Models;
|
||||
|
||||
public class Setting
|
||||
{
|
||||
public required SettingName Name { get; set; }
|
||||
public required string Value { get; set; }
|
||||
|
||||
// methods to convert Value to specific types that are not connected to entity framework
|
||||
public T GetValue<T>()
|
||||
{
|
||||
return (T)Convert.ChangeType(Value, typeof(T));
|
||||
}
|
||||
}
|
10
db/models/Tag.cs
Normal file
10
db/models/Tag.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Db.Models;
|
||||
|
||||
public class Tag
|
||||
{
|
||||
// We use the name of the tag as the Id to ensure uniqueness
|
||||
public required string Name { get; set; }
|
||||
|
||||
|
||||
public ICollection<VideoTag> VideoTags { get; set; } = [];
|
||||
}
|
19
db/models/Video.cs
Normal file
19
db/models/Video.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace Db.Models;
|
||||
|
||||
public class Video
|
||||
{
|
||||
// Disable warning as Id is required but not set in the constructor.
|
||||
// This is to ensure that the Id is set by the database when the entity is added
|
||||
#pragma warning disable CS8618
|
||||
public string Id { get; set; }
|
||||
#pragma warning restore CS8618
|
||||
|
||||
public string? Title { get; set; }
|
||||
public TimeSpan Duration { get; set; }
|
||||
public int Width { get; set; }
|
||||
public int Height { get; set; }
|
||||
public string Extension { get; set; } = string.Empty; // File extension of the video file
|
||||
|
||||
|
||||
public ICollection<VideoTag> VideoTags { get; set; } = [];
|
||||
}
|
10
db/models/VideoTag.cs
Normal file
10
db/models/VideoTag.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Db.Models;
|
||||
|
||||
public class VideoTag
|
||||
{
|
||||
public string VideoId { get; set; }
|
||||
public Video Video { get; set; }
|
||||
|
||||
public string TagId { get; set; }
|
||||
public Tag Tag { get; set; }
|
||||
}
|
60
helpers/FFProbeHelper.cs
Normal file
60
helpers/FFProbeHelper.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace TagVid.Helpers;
|
||||
|
||||
public class VideoMetadata
|
||||
{
|
||||
public int Width { get; set; }
|
||||
public int Height { get; set; }
|
||||
public TimeSpan Duration { get; set; }
|
||||
}
|
||||
|
||||
public class FFProbeHelper<T> where T : class
|
||||
{
|
||||
private static ILogger<T> _logger;
|
||||
|
||||
public FFProbeHelper(ILogger<T> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
public async Task<VideoMetadata?> GetVideoMetadataAsync(string filePath)
|
||||
{
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "ffprobe",
|
||||
Arguments = $"-v quiet -print_format json -show_streams \"{filePath}\"",
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using var process = new Process { StartInfo = startInfo };
|
||||
process.Start();
|
||||
|
||||
string output = await process.StandardOutput.ReadToEndAsync();
|
||||
await process.WaitForExitAsync();
|
||||
|
||||
using var jsonDoc = JsonDocument.Parse(output);
|
||||
var streams = jsonDoc.RootElement.GetProperty("streams");
|
||||
|
||||
var videoStream = streams.EnumerateArray()
|
||||
.FirstOrDefault(s => s.GetProperty("codec_type").GetString() == "video");
|
||||
|
||||
if (videoStream.ValueKind == JsonValueKind.Undefined)
|
||||
return null;
|
||||
videoStream.TryGetProperty("duration", out var durEl1);
|
||||
|
||||
_logger.LogInformation($"{videoStream.GetProperty("width")}x{videoStream.GetProperty("height")}, Duration: {durEl1}");
|
||||
var metadata = new VideoMetadata
|
||||
{
|
||||
Width = videoStream.GetProperty("width").GetInt32(),
|
||||
Height = videoStream.GetProperty("height").GetInt32(),
|
||||
Duration = durEl1.ValueKind == JsonValueKind.String
|
||||
? TimeSpan.FromSeconds(double.Parse(durEl1.GetString() ?? "0"))
|
||||
: TimeSpan.FromSeconds(durEl1.GetDouble())
|
||||
};
|
||||
|
||||
return metadata;
|
||||
}
|
||||
}
|
19
tagvid.csproj
Normal file
19
tagvid.csproj
Normal file
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
42
tagvid.sln
42
tagvid.sln
@@ -1,48 +1,24 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
VisualStudioVersion = 17.5.2.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tagvid", "tagvid\tagvid.csproj", "{89456151-48D7-47F2-96AE-B27E62947059}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "adapter", "adapter\adapter.csproj", "{505FCE94-EFFC-436D-BC39-315F7D793475}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tagvid", "tagvid.csproj", "{147974D8-F7CC-E7DF-9998-54200E639E4B}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{89456151-48D7-47F2-96AE-B27E62947059}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{89456151-48D7-47F2-96AE-B27E62947059}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{89456151-48D7-47F2-96AE-B27E62947059}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{89456151-48D7-47F2-96AE-B27E62947059}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{89456151-48D7-47F2-96AE-B27E62947059}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{89456151-48D7-47F2-96AE-B27E62947059}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{89456151-48D7-47F2-96AE-B27E62947059}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{89456151-48D7-47F2-96AE-B27E62947059}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{89456151-48D7-47F2-96AE-B27E62947059}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{89456151-48D7-47F2-96AE-B27E62947059}.Release|x64.Build.0 = Release|Any CPU
|
||||
{89456151-48D7-47F2-96AE-B27E62947059}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{89456151-48D7-47F2-96AE-B27E62947059}.Release|x86.Build.0 = Release|Any CPU
|
||||
{505FCE94-EFFC-436D-BC39-315F7D793475}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{505FCE94-EFFC-436D-BC39-315F7D793475}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{505FCE94-EFFC-436D-BC39-315F7D793475}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{505FCE94-EFFC-436D-BC39-315F7D793475}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{505FCE94-EFFC-436D-BC39-315F7D793475}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{505FCE94-EFFC-436D-BC39-315F7D793475}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{505FCE94-EFFC-436D-BC39-315F7D793475}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{505FCE94-EFFC-436D-BC39-315F7D793475}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{505FCE94-EFFC-436D-BC39-315F7D793475}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{505FCE94-EFFC-436D-BC39-315F7D793475}.Release|x64.Build.0 = Release|Any CPU
|
||||
{505FCE94-EFFC-436D-BC39-315F7D793475}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{505FCE94-EFFC-436D-BC39-315F7D793475}.Release|x86.Build.0 = Release|Any CPU
|
||||
{147974D8-F7CC-E7DF-9998-54200E639E4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{147974D8-F7CC-E7DF-9998-54200E639E4B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{147974D8-F7CC-E7DF-9998-54200E639E4B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{147974D8-F7CC-E7DF-9998-54200E639E4B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {BB1808E1-0EBB-4D0E-9A3C-0B133B761E43}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
@@ -1,31 +0,0 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using tagvid.Models;
|
||||
|
||||
namespace tagvid.Controllers;
|
||||
|
||||
public class HomeController : Controller
|
||||
{
|
||||
private readonly ILogger<HomeController> _logger;
|
||||
|
||||
public HomeController(ILogger<HomeController> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public IActionResult Index()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult Privacy()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public IActionResult Error()
|
||||
{
|
||||
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||
}
|
||||
}
|
@@ -1,8 +0,0 @@
|
||||
@{
|
||||
ViewData["Title"] = "Home Page";
|
||||
}
|
||||
|
||||
<div class="text-center">
|
||||
<h1 class="display-4">Welcome</h1>
|
||||
<p>Learn about <a href="https://learn.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
|
||||
</div>
|
@@ -1,3 +0,0 @@
|
||||
@using tagvid
|
||||
@using tagvid.Models
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\adapter\adapter.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user