Initial Commit

Inital Commit of Code base, nothing tested.
This commit is contained in:
Mario Steele 2025-07-19 04:02:09 -05:00
commit 0144221712
31 changed files with 1304 additions and 0 deletions

25
.dockerignore Normal file
View file

@ -0,0 +1,25 @@
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.idea
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md

7
.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
bin/
obj/
/packages/
.idea/
riderModule.iml
/_ReSharper.Caches/
FreeTubeSync.db

21
FreeTubeSync.sln Normal file
View file

@ -0,0 +1,21 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FreeTubeSync", "FreeTubeSync\FreeTubeSync.csproj", "{E55FECEB-2D88-4FD4-90BF-713EB30D60BB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{47A66AFA-4FD3-40F3-ADE0-27EE0527211B}"
ProjectSection(SolutionItems) = preProject
compose.yaml = compose.yaml
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E55FECEB-2D88-4FD4-90BF-713EB30D60BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E55FECEB-2D88-4FD4-90BF-713EB30D60BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E55FECEB-2D88-4FD4-90BF-713EB30D60BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E55FECEB-2D88-4FD4-90BF-713EB30D60BB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,37 @@
using FreeTubeSync.Model;
using Microsoft.EntityFrameworkCore;
namespace FreeTubeSync.Database;
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite("Data Source=FreeTubeSync.db");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Setting>()
.ToTable("Settings")
.HasKey(s => s._id);
modelBuilder.Entity<Setting>()
.Property(s => s.ValueJson)
.HasColumnName("Value")
.IsRequired();
}
public DbSet<History> Histories { get; set; }
public DbSet<Playlist> Playlists { get; set; }
public DbSet<Profile> Profiles { get; set; }
public DbSet<SearchHistory> SearchHistories { get; set; }
public DbSet<Setting> Settings { get; set; }
public DbSet<Subscription> Subscriptions { get; set; }
public DbSet<Video> Videos { get; set; }
}

23
FreeTubeSync/Dockerfile Normal file
View file

@ -0,0 +1,23 @@
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER $APP_UID
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["FreeTubeSync2/FreeTubeSync2.csproj", "FreeTubeSync2/"]
RUN dotnet restore "FreeTubeSync2/FreeTubeSync2.csproj"
COPY . .
WORKDIR "/src/FreeTubeSync2"
RUN dotnet build "./FreeTubeSync2.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./FreeTubeSync2.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "FreeTubeSync2.dll"]

View file

@ -0,0 +1,35 @@
using FreeTubeSync.Model;
namespace FreeTubeSync.EndPoints;
public static class HistoryEndpoint
{
public static void MapHistoryEndpoints(this WebApplication app)
{
var group = app.MapGroup("history");
group.MapGet("/", async (IRepository<History> repository, CancellationToken ct) =>
{
var results = await repository.GetAllAsync(ct);
return Results.Ok(results);
});
group.MapPost("/", async (IRepository<History> repository, CancellationToken ct, History history) =>
{
var results = await repository.GetByIdAsync(history._id, ct);
if (results == null)
await repository.AddAsync(history, ct);
else
await repository.UpdateAsync(history, ct);
return Results.Ok();
});
group.MapDelete("/{id}", async (IRepository<History> repository, CancellationToken ct, string id) =>
{
var result = await repository.GetByIdAsync(id, ct);
if (result == null) return Results.NotFound();
await repository.DeleteAsync(result, ct);
return Results.Ok();
});
}
}

View file

@ -0,0 +1,35 @@
using FreeTubeSync.Model;
namespace FreeTubeSync.EndPoints;
public static class PlaylistEndpoint
{
public static void MapPlaylistEndpoints(this WebApplication app)
{
var group = app.MapGroup("playlists");
group.MapGet("/", async (IRepository<Playlist> repository, CancellationToken ct) =>
{
var results = await repository.GetAllAsync(ct);
return Results.Ok(results);
});
group.MapPost("/", async (IRepository<Playlist> repository, CancellationToken ct, Playlist playlist) =>
{
var results = await repository.GetByIdAsync(playlist._id, ct);
if (results == null)
await repository.AddAsync(playlist, ct);
else
await repository.UpdateAsync(playlist, ct);
return Results.Ok();
});
group.MapDelete("/{id}", async (IRepository<Playlist> repository, CancellationToken ct, string id) =>
{
var result = await repository.GetByIdAsync(id, ct);
if (result == null) return Results.NotFound();
await repository.DeleteAsync(result, ct);
return Results.Ok();
});
}
}

View file

@ -0,0 +1,35 @@
using FreeTubeSync.Model;
namespace FreeTubeSync.EndPoints;
public static class ProfileEndpoint
{
public static void MapProfileEndpoints(this WebApplication app)
{
var group = app.MapGroup("profile");
group.MapGet("/", async (IRepository<Profile> repository, CancellationToken ct) =>
{
var results = await repository.GetAllAsync(ct);
return Results.Ok(results);
});
group.MapPost("/", async (IRepository<Profile> repository, CancellationToken ct, Profile profile) =>
{
var res = await repository.GetByIdAsync(profile._id, ct);
if (res == null)
await repository.AddAsync(profile, ct);
else
await repository.UpdateAsync(profile, ct);
return Results.Ok();
});
group.MapDelete("/{id}", async (IRepository<Profile> repository, CancellationToken ct, string id) =>
{
var result = await repository.GetByIdAsync(id, ct);
if (result == null) return Results.NotFound();
await repository.DeleteAsync(result, ct);
return Results.Ok();
});
}
}

View file

@ -0,0 +1,36 @@
using FreeTubeSync.Model;
namespace FreeTubeSync.EndPoints;
public static class SearchHistoryEndpoint
{
public static void MapSearchHistoryEndpoints(this WebApplication app)
{
var group = app.MapGroup("searchHistory");
group.MapGet("/", async (IRepository<History> repository, CancellationToken ct) =>
{
var result = await repository.GetAllAsync(ct);
return Results.Ok(result);
});
group.MapPost("/", async (IRepository<History> repository, CancellationToken ct, History history) =>
{
var result = await repository.GetByIdAsync(history._id, ct);
if (result != null)
await repository.UpdateAsync(history, ct);
else
await repository.AddAsync(history, ct);
return Results.Ok();
});
group.MapDelete("/{id}", async (IRepository<History> repository, CancellationToken ct, string id) =>
{
var result = await repository.GetByIdAsync(id, ct);
if (result == null) return Results.NotFound();
await repository.DeleteAsync(result, ct);
return Results.Ok();
});
}
}

View file

@ -0,0 +1,37 @@
using FreeTubeSync.Model;
using FreeTubeSync.SpecialResponses;
namespace FreeTubeSync.EndPoints;
public static class SettingEndpoint
{
public static void MapSettingEndpoints(this WebApplication app)
{
var group = app.MapGroup("settings");
group.MapGet("/", async (IRepository<Setting> repository, CancellationToken ct) =>
{
var settings = await repository.GetAllAsync(ct);
var response = settings.MapToResponse();
return Results.Ok(response);
});
group.MapPost("/", async (IRepository<Setting> repository, CancellationToken ct, Setting setting) =>
{
var res = await repository.GetByIdAsync(setting._id, ct);
if (res == null)
await repository.AddAsync(setting, ct);
else
await repository.UpdateAsync(setting, ct);
return Results.Ok();
});
group.MapDelete("/{id}", async (IRepository<Setting> repository, CancellationToken ct, string id) =>
{
var result = await repository.GetByIdAsync(id, ct);
if (result == null) return Results.NotFound();
await repository.DeleteAsync(result, ct);
return Results.Ok();
});
}
}

View file

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<Content Include="..\.dockerignore">
<Link>.dockerignore</Link>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.7" />
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="9.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="9.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="9.0.3" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,10 @@
namespace FreeTubeSync;
public interface IRepository<TEntity> where TEntity : class
{
Task AddAsync(TEntity entity, CancellationToken ct);
Task UpdateAsync(TEntity entity, CancellationToken ct);
Task DeleteAsync(TEntity entity, CancellationToken ct);
Task<TEntity?> GetByIdAsync(string id, CancellationToken ct);
Task<IEnumerable<TEntity>> GetAllAsync(CancellationToken ct);
}

View file

@ -0,0 +1,248 @@
// <auto-generated />
using FreeTubeSync.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace FreeTubeSync.Migrations
{
[DbContext(typeof(DataContext))]
[Migration("20250719082332_Init")]
partial class Init
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.7");
modelBuilder.Entity("FreeTubeSync.Model.History", b =>
{
b.Property<string>("_id")
.HasColumnType("TEXT");
b.Property<string>("author")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("authorId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("description")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("isLive")
.HasColumnType("INTEGER");
b.Property<string>("lastViewedPlaylistItemId")
.HasColumnType("TEXT");
b.Property<string>("lastViewedPlaylistType")
.IsRequired()
.HasColumnType("TEXT");
b.Property<long>("lengthSeconds")
.HasColumnType("INTEGER");
b.Property<long>("published")
.HasColumnType("INTEGER");
b.Property<long>("timeWatched")
.HasColumnType("INTEGER");
b.Property<string>("title")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("type")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("videoId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<long>("viewCount")
.HasColumnType("INTEGER");
b.Property<float>("watchProgress")
.HasColumnType("REAL");
b.HasKey("_id");
b.ToTable("Histories");
});
modelBuilder.Entity("FreeTubeSync.Model.Playlist", b =>
{
b.Property<string>("_id")
.HasColumnType("TEXT");
b.Property<int>("createdAt")
.HasColumnType("INTEGER");
b.Property<int>("lastUpdatedAt")
.HasColumnType("INTEGER");
b.Property<string>("playlistName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("protected")
.HasColumnType("INTEGER");
b.HasKey("_id");
b.ToTable("Playlists");
});
modelBuilder.Entity("FreeTubeSync.Model.Profile", b =>
{
b.Property<string>("_id")
.HasColumnType("TEXT");
b.Property<string>("bgColor")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("textColor")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("_id");
b.ToTable("Profiles");
});
modelBuilder.Entity("FreeTubeSync.Model.SearchHistory", b =>
{
b.Property<string>("_id")
.HasColumnType("TEXT");
b.Property<int>("lastUpdatedAt")
.HasColumnType("INTEGER");
b.HasKey("_id");
b.ToTable("SearchHistories");
});
modelBuilder.Entity("FreeTubeSync.Model.Setting", b =>
{
b.Property<string>("_id")
.HasColumnType("TEXT");
b.Property<string>("ValueJson")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Value");
b.HasKey("_id");
b.ToTable("Settings", (string)null);
});
modelBuilder.Entity("FreeTubeSync.Model.Subscription", b =>
{
b.Property<string>("id")
.HasColumnType("TEXT");
b.Property<string>("Profile_id")
.HasColumnType("TEXT");
b.Property<string>("name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("thumbnail")
.HasColumnType("TEXT");
b.HasKey("id");
b.HasIndex("Profile_id");
b.ToTable("Subscriptions");
});
modelBuilder.Entity("FreeTubeSync.Model.Video", b =>
{
b.Property<string>("videoId")
.HasColumnType("TEXT");
b.Property<string>("Playlist_id")
.HasColumnType("TEXT");
b.Property<string>("author")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("authorId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("lengthSeconds")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("playlistItemId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("pubished")
.HasColumnType("INTEGER");
b.Property<int>("timeAdded")
.HasColumnType("INTEGER");
b.Property<string>("title")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("type")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("videoId");
b.HasIndex("Playlist_id");
b.ToTable("Videos");
});
modelBuilder.Entity("FreeTubeSync.Model.Subscription", b =>
{
b.HasOne("FreeTubeSync.Model.Profile", null)
.WithMany("subscriptions")
.HasForeignKey("Profile_id");
});
modelBuilder.Entity("FreeTubeSync.Model.Video", b =>
{
b.HasOne("FreeTubeSync.Model.Playlist", null)
.WithMany("videos")
.HasForeignKey("Playlist_id");
});
modelBuilder.Entity("FreeTubeSync.Model.Playlist", b =>
{
b.Navigation("videos");
});
modelBuilder.Entity("FreeTubeSync.Model.Profile", b =>
{
b.Navigation("subscriptions");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,171 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace FreeTubeSync.Migrations
{
/// <inheritdoc />
public partial class Init : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Histories",
columns: table => new
{
_id = table.Column<string>(type: "TEXT", nullable: false),
videoId = table.Column<string>(type: "TEXT", nullable: false),
title = table.Column<string>(type: "TEXT", nullable: false),
author = table.Column<string>(type: "TEXT", nullable: false),
authorId = table.Column<string>(type: "TEXT", nullable: false),
published = table.Column<long>(type: "INTEGER", nullable: false),
description = table.Column<string>(type: "TEXT", nullable: false),
viewCount = table.Column<long>(type: "INTEGER", nullable: false),
lengthSeconds = table.Column<long>(type: "INTEGER", nullable: false),
watchProgress = table.Column<float>(type: "REAL", nullable: false),
timeWatched = table.Column<long>(type: "INTEGER", nullable: false),
isLive = table.Column<bool>(type: "INTEGER", nullable: false),
type = table.Column<string>(type: "TEXT", nullable: false),
lastViewedPlaylistType = table.Column<string>(type: "TEXT", nullable: false),
lastViewedPlaylistItemId = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Histories", x => x._id);
});
migrationBuilder.CreateTable(
name: "Playlists",
columns: table => new
{
_id = table.Column<string>(type: "TEXT", nullable: false),
playlistName = table.Column<string>(type: "TEXT", nullable: false),
@protected = table.Column<bool>(name: "protected", type: "INTEGER", nullable: false),
createdAt = table.Column<int>(type: "INTEGER", nullable: false),
lastUpdatedAt = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Playlists", x => x._id);
});
migrationBuilder.CreateTable(
name: "Profiles",
columns: table => new
{
_id = table.Column<string>(type: "TEXT", nullable: false),
name = table.Column<string>(type: "TEXT", nullable: false),
bgColor = table.Column<string>(type: "TEXT", nullable: false),
textColor = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Profiles", x => x._id);
});
migrationBuilder.CreateTable(
name: "SearchHistories",
columns: table => new
{
_id = table.Column<string>(type: "TEXT", nullable: false),
lastUpdatedAt = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SearchHistories", x => x._id);
});
migrationBuilder.CreateTable(
name: "Settings",
columns: table => new
{
_id = table.Column<string>(type: "TEXT", nullable: false),
Value = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Settings", x => x._id);
});
migrationBuilder.CreateTable(
name: "Videos",
columns: table => new
{
videoId = table.Column<string>(type: "TEXT", nullable: false),
title = table.Column<string>(type: "TEXT", nullable: false),
author = table.Column<string>(type: "TEXT", nullable: false),
authorId = table.Column<string>(type: "TEXT", nullable: false),
lengthSeconds = table.Column<string>(type: "TEXT", nullable: false),
pubished = table.Column<int>(type: "INTEGER", nullable: false),
timeAdded = table.Column<int>(type: "INTEGER", nullable: false),
playlistItemId = table.Column<string>(type: "TEXT", nullable: false),
type = table.Column<string>(type: "TEXT", nullable: false),
Playlist_id = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Videos", x => x.videoId);
table.ForeignKey(
name: "FK_Videos_Playlists_Playlist_id",
column: x => x.Playlist_id,
principalTable: "Playlists",
principalColumn: "_id");
});
migrationBuilder.CreateTable(
name: "Subscriptions",
columns: table => new
{
id = table.Column<string>(type: "TEXT", nullable: false),
name = table.Column<string>(type: "TEXT", nullable: false),
thumbnail = table.Column<string>(type: "TEXT", nullable: true),
Profile_id = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Subscriptions", x => x.id);
table.ForeignKey(
name: "FK_Subscriptions_Profiles_Profile_id",
column: x => x.Profile_id,
principalTable: "Profiles",
principalColumn: "_id");
});
migrationBuilder.CreateIndex(
name: "IX_Subscriptions_Profile_id",
table: "Subscriptions",
column: "Profile_id");
migrationBuilder.CreateIndex(
name: "IX_Videos_Playlist_id",
table: "Videos",
column: "Playlist_id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Histories");
migrationBuilder.DropTable(
name: "SearchHistories");
migrationBuilder.DropTable(
name: "Settings");
migrationBuilder.DropTable(
name: "Subscriptions");
migrationBuilder.DropTable(
name: "Videos");
migrationBuilder.DropTable(
name: "Profiles");
migrationBuilder.DropTable(
name: "Playlists");
}
}
}

View file

@ -0,0 +1,245 @@
// <auto-generated />
using FreeTubeSync.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace FreeTubeSync.Migrations
{
[DbContext(typeof(DataContext))]
partial class DataContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.7");
modelBuilder.Entity("FreeTubeSync.Model.History", b =>
{
b.Property<string>("_id")
.HasColumnType("TEXT");
b.Property<string>("author")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("authorId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("description")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("isLive")
.HasColumnType("INTEGER");
b.Property<string>("lastViewedPlaylistItemId")
.HasColumnType("TEXT");
b.Property<string>("lastViewedPlaylistType")
.IsRequired()
.HasColumnType("TEXT");
b.Property<long>("lengthSeconds")
.HasColumnType("INTEGER");
b.Property<long>("published")
.HasColumnType("INTEGER");
b.Property<long>("timeWatched")
.HasColumnType("INTEGER");
b.Property<string>("title")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("type")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("videoId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<long>("viewCount")
.HasColumnType("INTEGER");
b.Property<float>("watchProgress")
.HasColumnType("REAL");
b.HasKey("_id");
b.ToTable("Histories");
});
modelBuilder.Entity("FreeTubeSync.Model.Playlist", b =>
{
b.Property<string>("_id")
.HasColumnType("TEXT");
b.Property<int>("createdAt")
.HasColumnType("INTEGER");
b.Property<int>("lastUpdatedAt")
.HasColumnType("INTEGER");
b.Property<string>("playlistName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("protected")
.HasColumnType("INTEGER");
b.HasKey("_id");
b.ToTable("Playlists");
});
modelBuilder.Entity("FreeTubeSync.Model.Profile", b =>
{
b.Property<string>("_id")
.HasColumnType("TEXT");
b.Property<string>("bgColor")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("textColor")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("_id");
b.ToTable("Profiles");
});
modelBuilder.Entity("FreeTubeSync.Model.SearchHistory", b =>
{
b.Property<string>("_id")
.HasColumnType("TEXT");
b.Property<int>("lastUpdatedAt")
.HasColumnType("INTEGER");
b.HasKey("_id");
b.ToTable("SearchHistories");
});
modelBuilder.Entity("FreeTubeSync.Model.Setting", b =>
{
b.Property<string>("_id")
.HasColumnType("TEXT");
b.Property<string>("ValueJson")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("Value");
b.HasKey("_id");
b.ToTable("Settings", (string)null);
});
modelBuilder.Entity("FreeTubeSync.Model.Subscription", b =>
{
b.Property<string>("id")
.HasColumnType("TEXT");
b.Property<string>("Profile_id")
.HasColumnType("TEXT");
b.Property<string>("name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("thumbnail")
.HasColumnType("TEXT");
b.HasKey("id");
b.HasIndex("Profile_id");
b.ToTable("Subscriptions");
});
modelBuilder.Entity("FreeTubeSync.Model.Video", b =>
{
b.Property<string>("videoId")
.HasColumnType("TEXT");
b.Property<string>("Playlist_id")
.HasColumnType("TEXT");
b.Property<string>("author")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("authorId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("lengthSeconds")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("playlistItemId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("pubished")
.HasColumnType("INTEGER");
b.Property<int>("timeAdded")
.HasColumnType("INTEGER");
b.Property<string>("title")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("type")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("videoId");
b.HasIndex("Playlist_id");
b.ToTable("Videos");
});
modelBuilder.Entity("FreeTubeSync.Model.Subscription", b =>
{
b.HasOne("FreeTubeSync.Model.Profile", null)
.WithMany("subscriptions")
.HasForeignKey("Profile_id");
});
modelBuilder.Entity("FreeTubeSync.Model.Video", b =>
{
b.HasOne("FreeTubeSync.Model.Playlist", null)
.WithMany("videos")
.HasForeignKey("Playlist_id");
});
modelBuilder.Entity("FreeTubeSync.Model.Playlist", b =>
{
b.Navigation("videos");
});
modelBuilder.Entity("FreeTubeSync.Model.Profile", b =>
{
b.Navigation("subscriptions");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,43 @@
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
namespace FreeTubeSync.Model;
[SuppressMessage("ReSharper", "InconsistentNaming")]
public class History
{
[Key]
public string _id { get; set; } = string.Empty;
public string videoId { get; set; } = string.Empty;
public string title { get; set; } = string.Empty;
public string author { get; set; } = string.Empty;
public string authorId { get; set; } = string.Empty;
public long published { get; set; }
public string description { get; set; } = string.Empty;
public long viewCount { get; set; }
public long lengthSeconds { get; set; }
public float watchProgress { get; set; }
public long timeWatched { get; set; }
public bool isLive { get; set; }
public string type { get; set; } = string.Empty;
public string lastViewedPlaylistType { get; set; } = string.Empty;
public string? lastViewedPlaylistItemId { get; set; }
public void Update(History other)
{
if (other.videoId != videoId) videoId = other.videoId;
if (other.title != title) title = other.title;
if (other.author != author) author = other.author;
if (other.authorId != authorId) authorId = other.authorId;
if (other.published != published) published = other.published;
if (other.description != description) description = other.description;
if (other.viewCount != viewCount) viewCount = other.viewCount;
if (other.lengthSeconds != lengthSeconds) lengthSeconds = other.lengthSeconds;
if (!other.watchProgress.Equals(watchProgress)) watchProgress = other.watchProgress;
if (other.timeWatched != timeWatched) timeWatched = other.timeWatched;
if (other.isLive != isLive) isLive = other.isLive;
if (other.type != type) type = other.type;
if (other.lastViewedPlaylistType != lastViewedPlaylistType) lastViewedPlaylistType = other.lastViewedPlaylistType;
if (other.lastViewedPlaylistItemId != lastViewedPlaylistItemId) lastViewedPlaylistItemId = other.lastViewedPlaylistItemId;
}
}

View file

@ -0,0 +1,25 @@
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
namespace FreeTubeSync.Model;
[SuppressMessage("ReSharper", "InconsistentNaming")]
public class Playlist
{
[Key]
public string _id { get; set; } = string.Empty;
public string playlistName { get; set; } = string.Empty;
public bool @protected { get; set; }
public List<Video> videos { get; set; } = [];
public int createdAt { get; set; }
public int lastUpdatedAt { get; set; }
public void Update(Playlist other)
{
if (other.playlistName != playlistName) playlistName = other.playlistName;
if (other.@protected != @protected) @protected = other.@protected;
if (other.videos.Count != videos.Count) videos = other.videos;
if (other.createdAt != createdAt) createdAt = other.createdAt;
if (other.lastUpdatedAt != lastUpdatedAt) lastUpdatedAt = other.lastUpdatedAt;
}
}

View file

@ -0,0 +1,23 @@
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
namespace FreeTubeSync.Model;
[SuppressMessage("ReSharper", "InconsistentNaming")]
public class Profile
{
[Key]
public string _id { get; set; } = string.Empty;
public string name { get; set; } = string.Empty;
public string bgColor { get; set; } = string.Empty;
public string textColor { get; set; } = string.Empty;
public List<Subscription> subscriptions { get; set; } = [];
public void Update(Profile other)
{
if (other.name != name) name = other.name;
if (other.bgColor != bgColor) bgColor = other.bgColor;
if (other.textColor != textColor) textColor = other.textColor;
if (other.subscriptions.Count != subscriptions.Count) subscriptions = other.subscriptions;
}
}

View file

@ -0,0 +1,17 @@
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
namespace FreeTubeSync.Model;
[SuppressMessage("ReSharper", "InconsistentNaming")]
public class SearchHistory
{
[Key]
public string _id { get; set; } = string.Empty;
public int lastUpdatedAt { get; set; }
public void Update(SearchHistory other)
{
if (other.lastUpdatedAt != lastUpdatedAt) lastUpdatedAt = other.lastUpdatedAt;
}
}

View file

@ -0,0 +1,25 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
namespace FreeTubeSync.Model;
[SuppressMessage("ReSharper", "InconsistentNaming")]
public class Setting
{
[Key]
#pragma warning disable CS8618
public string _id { get; set; } = string.Empty;
public string? ValueJson { get; set; }
#pragma warning restore CS8618
[NotMapped]
public object Value
{
#pragma warning disable CS8603
get => string.IsNullOrEmpty(ValueJson) ? null : JsonSerializer.Deserialize<object>(ValueJson);
#pragma warning restore CS8603
set => ValueJson = JsonSerializer.Serialize(value);
}
}

View file

@ -0,0 +1,13 @@
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
namespace FreeTubeSync.Model;
[SuppressMessage("ReSharper", "InconsistentNaming")]
public class Subscription
{
[Key]
public required string id { get; set; }
public required string name { get; set; }
public string? thumbnail { get; set; }
}

View file

@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
namespace FreeTubeSync.Model;
[SuppressMessage("ReSharper", "InconsistentNaming")]
public class Video
{
[Key] public string videoId { get; set; } = string.Empty;
public string title { get; set; } = string.Empty;
public string author { get; set; } = string.Empty;
public string authorId { get; set; } = string.Empty;
public string lengthSeconds { get; set; } = string.Empty;
public int pubished { get; set; }
public int timeAdded { get; set; }
public string playlistItemId { get; set; } = string.Empty;
public string type { get; set; } = string.Empty;
}

24
FreeTubeSync/Program.cs Normal file
View file

@ -0,0 +1,24 @@
using FreeTubeSync;
using FreeTubeSync.Database;
using FreeTubeSync.EndPoints;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<DataContext>();
builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
// Add services to the container.
var app = builder.Build();
app.MapHistoryEndpoints();
app.MapPlaylistEndpoints();
app.MapProfileEndpoints();
app.MapSearchHistoryEndpoints();
app.MapSettingEndpoints();
app.Run();

View file

@ -0,0 +1,29 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:18186",
"sslPort": 0
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5183",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": false,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View file

@ -0,0 +1,35 @@
using FreeTubeSync.Database;
using Microsoft.EntityFrameworkCore;
namespace FreeTubeSync;
public class Repository<TEntity>(DataContext dbContext) : IRepository<TEntity> where TEntity : class
{
public async Task AddAsync(TEntity entity, CancellationToken ct)
{
dbContext.Set<TEntity>().Add(entity);
await dbContext.SaveChangesAsync(ct);
}
public async Task UpdateAsync(TEntity entity, CancellationToken ct)
{
dbContext.Set<TEntity>().Update(entity);
await dbContext.SaveChangesAsync(ct);
}
public async Task DeleteAsync(TEntity entity, CancellationToken ct)
{
dbContext.Set<TEntity>().Remove(entity);
await dbContext.SaveChangesAsync(ct);
}
public async Task<TEntity?> GetByIdAsync(string id, CancellationToken ct)
{
return await dbContext.Set<TEntity>().FindAsync(id, ct);
}
public async Task<IEnumerable<TEntity>> GetAllAsync(CancellationToken ct)
{
return await dbContext.Set<TEntity>().ToListAsync<TEntity>(ct);
}
}

View file

@ -0,0 +1,20 @@
using FreeTubeSync.Model;
namespace FreeTubeSync.SpecialResponses;
public static class Mappings
{
public static SettingResponse MapToResponse(this Setting setting)
{
return new SettingResponse
{
_id = setting._id,
value = setting.Value
};
}
public static IEnumerable<SettingResponse> MapToResponse(this IEnumerable<Setting> settings)
{
return settings.Select(MapToResponse);
}
}

View file

@ -0,0 +1,9 @@
using FreeTubeSync.Model;
namespace FreeTubeSync.SpecialResponses;
public class SettingResponse
{
public string _id { get; set; }
public object value { get; set; }
}

View file

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View file

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

7
compose.yaml Normal file
View file

@ -0,0 +1,7 @@
services:
freetubesync2:
image: freetubesync2
build:
context: .
dockerfile: FreeTubeSync2/Dockerfile

7
global.json Normal file
View file

@ -0,0 +1,7 @@
{
"sdk": {
"version": "8.0.0",
"rollForward": "latestMinor",
"allowPrerelease": false
}
}