Merge pull request #6 from mateuszskoczek/features/setup_api

basics
This commit is contained in:
2024-07-03 22:19:55 +02:00
committed by GitHub
Unverified
88 changed files with 3389 additions and 644 deletions

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using WatchIt.Common.Query;
namespace WatchIt.Common.Model.Genres;

View File

@@ -0,0 +1,18 @@
using System.Text.Json.Serialization;
namespace WatchIt.Common.Model.Media;
public class MediaPhoto
{
[JsonPropertyName("media_id")]
public required long MediaId { get; set; }
[JsonPropertyName("image")]
public required byte[] Image { get; set; }
[JsonPropertyName("mime_type")]
public required string MimeType { get; set; }
[JsonPropertyName("background")]
public MediaPhotoBackground? Background { get; set; }
}

View File

@@ -0,0 +1,15 @@
using System.Text.Json.Serialization;
namespace WatchIt.Common.Model.Media;
public class MediaPhotoBackground
{
[JsonPropertyName("is_universal_background")]
public required bool IsUniversalBackground { get; set; }
[JsonPropertyName("first_gradient_color")]
public required byte[] FirstGradientColor { get; set; }
[JsonPropertyName("second_gradient_color")]
public required byte[] SecondGradientColor { get; set; }
}

View File

@@ -0,0 +1,36 @@
using System.Text;
using Microsoft.AspNetCore.Mvc;
using WatchIt.Common.Query;
namespace WatchIt.Common.Model.Media;
public class MediaPhotoQueryParameters : QueryParameters<MediaPhotoResponse>
{
#region PROPERTIES
[FromQuery(Name = "mime_type")]
public string? MimeType { get; set; }
[FromQuery(Name = "is_background")]
public bool? IsBackground { get; set; }
[FromQuery(Name = "is_universal_background")]
public bool? IsUniversalBackground { get; set; }
#endregion
#region PUBLIC METHODS
public override bool IsMeetingConditions(MediaPhotoResponse item) =>
(
TestString(item.MimeType, MimeType)
&&
TestBoolean(item.Background is not null, IsBackground)
&&
TestBoolean(item.Background!.IsUniversalBackground, IsUniversalBackground)
);
#endregion
}

View File

@@ -0,0 +1,38 @@
using WatchIt.Database.Model.Media;
namespace WatchIt.Common.Model.Media;
public class MediaPhotoRequest : MediaPhoto
{
public MediaPhotoImage CreateMediaPhotoImage() => new MediaPhotoImage
{
MediaId = MediaId,
Image = Image,
MimeType = MimeType
};
public MediaPhotoImageBackground? CreateMediaPhotoImageBackground(Guid mediaPhotoImageId) => Background is null ? null : new MediaPhotoImageBackground
{
Id = mediaPhotoImageId,
IsUniversalBackground = Background.IsUniversalBackground,
FirstGradientColor = Background.FirstGradientColor,
SecondGradientColor = Background.SecondGradientColor
};
public void UpdateMediaPhotoImage(MediaPhotoImage item)
{
item.MediaId = MediaId;
item.Image = Image;
item.MimeType = MimeType;
}
public void UpdateMediaPhotoImageBackground(MediaPhotoImageBackground item)
{
if (Background is not null)
{
item.IsUniversalBackground = Background.IsUniversalBackground;
item.FirstGradientColor = Background.FirstGradientColor;
item.SecondGradientColor = Background.SecondGradientColor;
}
}
}

View File

@@ -0,0 +1,47 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using WatchIt.Database.Model.Media;
namespace WatchIt.Common.Model.Media;
public class MediaPhotoResponse : MediaPhoto
{
#region PROPERTIES
[JsonPropertyName("id")]
public Guid Id { get; set; }
[JsonPropertyName("upload_date")]
public DateTime UploadDate { get; set; }
#endregion
#region CONSTRUCTORS
[JsonConstructor]
public MediaPhotoResponse() {}
[SetsRequiredMembers]
public MediaPhotoResponse(MediaPhotoImage mediaPhotoImage)
{
Id = mediaPhotoImage.Id;
MediaId = mediaPhotoImage.MediaId;
Image = mediaPhotoImage.Image;
MimeType = mediaPhotoImage.MimeType;
UploadDate = mediaPhotoImage.UploadDate;
if (mediaPhotoImage.MediaPhotoImageBackground is not null)
{
Background = new MediaPhotoBackground
{
IsUniversalBackground = mediaPhotoImage.MediaPhotoImageBackground.IsUniversalBackground,
FirstGradientColor = mediaPhotoImage.MediaPhotoImageBackground.FirstGradientColor,
SecondGradientColor = mediaPhotoImage.MediaPhotoImageBackground.SecondGradientColor
};
}
}
#endregion
}

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using WatchIt.Common.Query;
namespace WatchIt.Common.Model.Movies;

View File

@@ -6,7 +6,7 @@ public class MovieRequest : Movie
{
#region PUBLIC METHODS
public Media CreateMedia() => new Media
public Database.Model.Media.Media CreateMedia() => new Database.Model.Media.Media
{
Title = Title,
OriginalTitle = OriginalTitle,
@@ -21,7 +21,7 @@ public class MovieRequest : Movie
Budget = Budget,
};
public void UpdateMedia(Media media)
public void UpdateMedia(Database.Model.Media.Media media)
{
media.Title = Title;
media.OriginalTitle = OriginalTitle;

View File

@@ -8,6 +8,7 @@
<ItemGroup>
<ProjectReference Include="..\..\WatchIt.Database\WatchIt.Database.Model\WatchIt.Database.Model\WatchIt.Database.Model.csproj" />
<ProjectReference Include="..\WatchIt.Common.Query\WatchIt.Common.Query.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,11 +1,12 @@
using System.Reflection;
using System.Text;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Mvc;
namespace WatchIt.Common.Model;
namespace WatchIt.Common.Query;
public abstract class QueryParameters<T> where T : class
public abstract class QueryParameters
{
#region PROPERTIES
@@ -25,6 +26,90 @@ public abstract class QueryParameters<T> where T : class
#region PUBLIC METHODS
public override string ToString()
{
List<string> queries = new List<string>();
PropertyInfo[] properties = this.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
object? value = property.GetValue(this);
FromQueryAttribute? attribute = property.GetCustomAttributes<FromQueryAttribute>(true).FirstOrDefault();
if (value is not null && attribute is not null)
{
string query = $"{attribute.Name}={value}";
queries.Add(query);
}
}
return $"?{string.Join('&', queries)}";
}
#endregion
#region PRIVATE METHODS
protected static bool TestBoolean(bool property, bool? query) =>
(
query is null
||
property == query
);
protected static bool TestString(string? property, string? regexQuery) =>
(
string.IsNullOrEmpty(regexQuery)
||
(
!string.IsNullOrEmpty(property)
&&
new Regex(regexQuery).IsMatch(property)
)
);
protected static bool TestComparable(IComparable? property, IComparable? exact, IComparable? from, IComparable? to) =>
(
(
exact is null
||
(
property is not null
&&
property.CompareTo(exact) == 0
)
)
&&
(
from is null
||
(
property is not null
&&
property.CompareTo(from) > 0
)
)
&&
(
to is null
||
(
property is not null
&&
property.CompareTo(to) < 0
)
)
);
#endregion
}
public abstract class QueryParameters<T> : QueryParameters where T : class
{
#region PUBLIC METHODS
public abstract bool IsMeetingConditions(T item);
@@ -65,54 +150,4 @@ public abstract class QueryParameters<T> where T : class
}
#endregion
#region PRIVATE METHODS
protected bool TestString(string? property, string? regexQuery) =>
(
string.IsNullOrEmpty(regexQuery)
||
(
!string.IsNullOrEmpty(property)
&&
new Regex(regexQuery).IsMatch(property)
)
);
protected bool TestComparable(IComparable? property, IComparable? exact, IComparable? from, IComparable? to) =>
(
(
exact is null
||
(
property is not null
&&
property.CompareTo(exact) == 0
)
)
&&
(
from is null
||
(
property is not null
&&
property.CompareTo(from) > 0
)
)
&&
(
to is null
||
(
property is not null
&&
property.CompareTo(to) < 0
)
)
);
#endregion
}

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,43 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace WatchIt.Common.Services.HttpClient;
public class HttpClientService(System.Net.Http.HttpClient httpClient) : IHttpClientService
{
#region PUBLIC METHODS
public async Task<HttpResponse> SendRequestAsync(HttpRequest request)
{
HttpMethod method = request.MethodType switch
{
HttpMethodType.Get => HttpMethod.Get,
HttpMethodType.Post => HttpMethod.Post,
HttpMethodType.Put => HttpMethod.Put,
HttpMethodType.Patch => HttpMethod.Patch,
HttpMethodType.Delete => HttpMethod.Delete,
_ => throw new ArgumentOutOfRangeException()
};
HttpRequestMessage httpRequest = new HttpRequestMessage(method, request.FullUrl);
if (request.Body is not null)
{
string json = JsonSerializer.Serialize(request.Body);
HttpContent content = new StringContent(json);
content.Headers.ContentType!.MediaType = "application/json";
httpRequest.Content = content;
}
foreach (KeyValuePair<string, string> header in request.Headers)
{
httpRequest.Headers.Add(header.Key, header.Value);
}
HttpResponseMessage response = await httpClient.SendAsync(httpRequest);
return new HttpResponse(response);
}
#endregion
}

View File

@@ -0,0 +1,10 @@
namespace WatchIt.Common.Services.HttpClient;
public enum HttpMethodType
{
Get,
Post,
Put,
Patch,
Delete
}

View File

@@ -0,0 +1,32 @@
using System.Diagnostics.CodeAnalysis;
using WatchIt.Common.Query;
namespace WatchIt.Common.Services.HttpClient;
public class HttpRequest
{
#region PROPERTIES
public required HttpMethodType MethodType { get; set; }
public required string Url { get; set; }
public QueryParameters? Query { get; set; }
public object? Body { get; set; }
public Dictionary<string, string> Headers { get; } = new Dictionary<string, string>();
public string FullUrl => $"{Url}{(Query is not null ? Query.ToString() : string.Empty)}";
#endregion
#region CONSTRUCTORS
[SetsRequiredMembers]
public HttpRequest(HttpMethodType methodType, string url)
{
MethodType = methodType;
Url = url;
}
#endregion
}

View File

@@ -0,0 +1,107 @@
using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
namespace WatchIt.Common.Services.HttpClient;
public class HttpResponse
{
#region FIELDS
private HttpResponseMessage _message;
private Action? _2XXAction;
private Action? _400Action;
private Action? _401Action;
private Action? _403Action;
private Action? _404Action;
#endregion
#region CONSTRUCTORS
internal HttpResponse(HttpResponseMessage message)
{
_message = message;
}
#endregion
#region PUBLIC METHODS
public HttpResponse RegisterActionFor2XXSuccess(Action action)
{
_2XXAction = action;
return this;
}
public HttpResponse RegisterActionFor2XXSuccess<T>(Action<T> action)
{
_2XXAction = () => Invoke(action);
return this;
}
public HttpResponse RegisterActionFor400BadRequest(Action<IDictionary<string, string[]>> action)
{
_400Action = () => Invoke(action);
return this;
}
public HttpResponse RegisterActionFor401Unauthorized(Action action)
{
_401Action = action;
return this;
}
public HttpResponse RegisterActionFor403Forbidden(Action action)
{
_403Action = action;
return this;
}
public HttpResponse RegisterActionFor404NotFound(Action action)
{
_404Action = action;
return this;
}
public void ExecuteAction()
{
switch ((int)_message.StatusCode)
{
case >= 200 and <= 299: _2XXAction?.Invoke(); break;
case 400: _400Action?.Invoke(); break;
case 401: _401Action?.Invoke(); break;
case 403: _403Action?.Invoke(); break;
case 404: _404Action?.Invoke(); break;
}
}
#endregion
#region PRIVATE METHODS
private async void Invoke<T>(Action<T> action)
{
Stream streamData = await _message.Content.ReadAsStreamAsync();
T? data = await JsonSerializer.DeserializeAsync<T>(streamData);
action.Invoke(data!);
}
private async void Invoke(Action<IDictionary<string, string[]>> action)
{
Stream streamData = await _message.Content.ReadAsStreamAsync();
ValidationProblemDetails? data = await JsonSerializer.DeserializeAsync<ValidationProblemDetails>(streamData, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
});
action.Invoke(data!.Errors);
}
#endregion
}

View File

@@ -0,0 +1,6 @@
namespace WatchIt.Common.Services.HttpClient;
public interface IHttpClientService
{
Task<HttpResponse> SendRequestAsync(HttpRequest request);
}

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\WatchIt.Common.Query\WatchIt.Common.Query.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using WatchIt.Database.Model.Media;
namespace WatchIt.Database.Model.Configuration.Media;
public class MediaPhotoImageBackgroundConfiguration : IEntityTypeConfiguration<MediaPhotoImageBackground>
{
public void Configure(EntityTypeBuilder<MediaPhotoImageBackground> builder)
{
builder.HasKey(x => x.Id);
builder.HasIndex(x => x.Id)
.IsUnique();
builder.Property(x => x.Id)
.IsRequired();
builder.Property(x => x.IsUniversalBackground)
.IsRequired()
.HasDefaultValue(false);
builder.Property(x => x.FirstGradientColor)
.IsRequired()
.HasMaxLength(3);
builder.Property(x => x.SecondGradientColor)
.IsRequired()
.HasMaxLength(3);
}
}

View File

@@ -33,12 +33,8 @@ public class MediaPhotoImageConfiguration : IEntityTypeConfiguration<MediaPhotoI
.IsRequired()
.HasDefaultValueSql("now()");
builder.Property(x => x.IsMediaBackground)
.IsRequired()
.HasDefaultValue(false);
builder.Property(x => x.IsUniversalBackground)
.IsRequired()
.HasDefaultValue(false);
builder.HasOne(x => x.MediaPhotoImageBackground)
.WithOne(x => x.MediaPhotoImage)
.HasForeignKey<MediaPhotoImageBackground>();
}
}

View File

@@ -12,8 +12,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.5" />
</ItemGroup>
</Project>

View File

@@ -9,8 +9,6 @@ public class MediaPhotoImage
public required byte[] Image { get; set; }
public required string MimeType { get; set; }
public DateTime UploadDate { get; set; }
public bool IsMediaBackground { get; set; }
public bool IsUniversalBackground { get; set; }
#endregion
@@ -19,6 +17,7 @@ public class MediaPhotoImage
#region NAVIGATION
public virtual Media Media { get; set; } = null!;
public virtual MediaPhotoImageBackground? MediaPhotoImageBackground { get; set; }
#endregion
}

View File

@@ -0,0 +1,21 @@
namespace WatchIt.Database.Model.Media;
public class MediaPhotoImageBackground
{
#region PROPERTIES
public required Guid Id { get; set; }
public required bool IsUniversalBackground { get; set; }
public required byte[] FirstGradientColor { get; set; }
public required byte[] SecondGradientColor { get; set; }
#endregion
#region NAVIGATION
public virtual MediaPhotoImage MediaPhotoImage { get; set; } = null!;
#endregion
}

View File

@@ -51,6 +51,7 @@ public class DatabaseContext : DbContext
public virtual DbSet<MediaSeriesEpisode> MediaSeriesEpisodes { get; set; }
public virtual DbSet<MediaPosterImage> MediaPosterImages { get; set; }
public virtual DbSet<MediaPhotoImage> MediaPhotoImages { get; set; }
public virtual DbSet<MediaPhotoImageBackground> MediaPhotoImageBackgrounds { get; set; }
public virtual DbSet<MediaGenre> MediaGenres { get; set; }
public virtual DbSet<MediaProductionCountry> MediaProductionCountries { get; set; }

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace WatchIt.Database.Migrations
{
/// <inheritdoc />
public partial class _0001_Initial : Migration
public partial class Initial : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
@@ -62,13 +62,27 @@ namespace WatchIt.Database.Migrations
Id = table.Column<short>(type: "smallint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Name = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
Description = table.Column<string>(type: "text", nullable: true)
Description = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Genres", x => x.Id);
});
migrationBuilder.CreateTable(
name: "MediaPhotoImageBackgrounds",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
IsUniversalBackground = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false),
FirstGradientColor = table.Column<byte[]>(type: "bytea", maxLength: 3, nullable: false),
SecondGradientColor = table.Column<byte[]>(type: "bytea", maxLength: 3, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_MediaPhotoImageBackgrounds", x => x.Id);
});
migrationBuilder.CreateTable(
name: "MediaPosterImages",
columns: table => new
@@ -226,12 +240,22 @@ namespace WatchIt.Database.Migrations
Image = table.Column<byte[]>(type: "bytea", maxLength: -1, nullable: false),
MimeType = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
UploadDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
IsMediaBackground = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false),
IsUniversalBackground = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false)
MediaPhotoImageBackgroundId = table.Column<Guid>(type: "uuid", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_MediaPhotoImages", x => x.Id);
table.ForeignKey(
name: "FK_MediaPhotoImages_MediaPhotoImageBackgrounds_Id",
column: x => x.Id,
principalTable: "MediaPhotoImageBackgrounds",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_MediaPhotoImages_MediaPhotoImageBackgrounds_MediaPhotoImage~",
column: x => x.MediaPhotoImageBackgroundId,
principalTable: "MediaPhotoImageBackgrounds",
principalColumn: "Id");
table.ForeignKey(
name: "FK_MediaPhotoImages_Media_MediaId",
column: x => x.MediaId,
@@ -241,7 +265,7 @@ namespace WatchIt.Database.Migrations
});
migrationBuilder.CreateTable(
name: "MediaProductionCountrys",
name: "MediaProductionCountries",
columns: table => new
{
MediaId = table.Column<long>(type: "bigint", nullable: false),
@@ -249,15 +273,15 @@ namespace WatchIt.Database.Migrations
},
constraints: table =>
{
table.PrimaryKey("PK_MediaProductionCountrys", x => new { x.CountryId, x.MediaId });
table.PrimaryKey("PK_MediaProductionCountries", x => new { x.CountryId, x.MediaId });
table.ForeignKey(
name: "FK_MediaProductionCountrys_Countries_CountryId",
name: "FK_MediaProductionCountries_Countries_CountryId",
column: x => x.CountryId,
principalTable: "Countries",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_MediaProductionCountrys_Media_MediaId",
name: "FK_MediaProductionCountries_Media_MediaId",
column: x => x.MediaId,
principalTable: "Media",
principalColumn: "Id",
@@ -620,7 +644,7 @@ namespace WatchIt.Database.Migrations
migrationBuilder.InsertData(
table: "Accounts",
columns: new[] { "Id", "BackgroundPictureId", "Description", "Email", "GenderId", "IsAdmin", "LeftSalt", "Password", "ProfilePictureId", "RightSalt", "Username" },
values: new object[] { 1L, null, null, "root@watch.it", null, true, "qE]Q^g%tU\"6Uu^GfE:V:", new byte[] { 165, 250, 135, 31, 187, 161, 15, 246, 18, 232, 64, 25, 37, 173, 91, 111, 140, 177, 183, 84, 254, 177, 15, 235, 119, 219, 29, 169, 32, 108, 187, 121, 204, 51, 213, 28, 141, 89, 91, 226, 0, 23, 7, 91, 139, 230, 151, 104, 62, 91, 59, 6, 207, 26, 200, 141, 104, 5, 151, 201, 243, 163, 28, 248 }, null, "T7j)~.#%~ZtOFUZFK,K+", "root" });
values: new object[] { 1L, null, null, "root@watch.it", null, true, "Y&%]J>6Nc3&5~UUXnNxq", new byte[] { 68, 170, 8, 113, 134, 47, 98, 43, 96, 183, 126, 130, 204, 45, 4, 113, 81, 200, 244, 26, 54, 88, 161, 246, 84, 93, 159, 219, 12, 143, 128, 160, 198, 194, 47, 133, 216, 242, 158, 184, 43, 38, 134, 132, 175, 179, 42, 40, 0, 143, 111, 252, 156, 215, 17, 185, 12, 109, 119, 214, 211, 167, 32, 121 }, null, "MV1jo~o3Oa^;NWb\\Q)t_", "root" });
migrationBuilder.InsertData(
table: "Countries",
@@ -752,6 +776,12 @@ namespace WatchIt.Database.Migrations
column: "Id",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_MediaPhotoImageBackgrounds_Id",
table: "MediaPhotoImageBackgrounds",
column: "Id",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_MediaPhotoImages_Id",
table: "MediaPhotoImages",
@@ -763,6 +793,11 @@ namespace WatchIt.Database.Migrations
table: "MediaPhotoImages",
column: "MediaId");
migrationBuilder.CreateIndex(
name: "IX_MediaPhotoImages_MediaPhotoImageBackgroundId",
table: "MediaPhotoImages",
column: "MediaPhotoImageBackgroundId");
migrationBuilder.CreateIndex(
name: "IX_MediaPosterImages_Id",
table: "MediaPosterImages",
@@ -770,8 +805,8 @@ namespace WatchIt.Database.Migrations
unique: true);
migrationBuilder.CreateIndex(
name: "IX_MediaProductionCountrys_MediaId",
table: "MediaProductionCountrys",
name: "IX_MediaProductionCountries_MediaId",
table: "MediaProductionCountries",
column: "MediaId");
migrationBuilder.CreateIndex(
@@ -995,7 +1030,7 @@ namespace WatchIt.Database.Migrations
name: "MediaMovies");
migrationBuilder.DropTable(
name: "MediaProductionCountrys");
name: "MediaProductionCountries");
migrationBuilder.DropTable(
name: "RatingsMedia");
@@ -1057,6 +1092,9 @@ namespace WatchIt.Database.Migrations
migrationBuilder.DropTable(
name: "MediaSeries");
migrationBuilder.DropTable(
name: "MediaPhotoImageBackgrounds");
migrationBuilder.DropTable(
name: "Genders");

View File

@@ -12,15 +12,15 @@ using WatchIt.Database;
namespace WatchIt.Database.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20240418190404_0001_Initial")]
partial class _0001_Initial
[Migration("20240603131015_Fix")]
partial class Fix
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.4")
.HasAnnotation("ProductVersion", "8.0.5")
.HasAnnotation("Proxies:ChangeTracking", false)
.HasAnnotation("Proxies:CheckEquality", false)
.HasAnnotation("Proxies:LazyLoading", true)
@@ -111,9 +111,9 @@ namespace WatchIt.Database.Migrations
Email = "root@watch.it",
IsAdmin = true,
LastActive = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
LeftSalt = "qE]Q^g%tU\"6Uu^GfE:V:",
Password = new byte[] { 165, 250, 135, 31, 187, 161, 15, 246, 18, 232, 64, 25, 37, 173, 91, 111, 140, 177, 183, 84, 254, 177, 15, 235, 119, 219, 29, 169, 32, 108, 187, 121, 204, 51, 213, 28, 141, 89, 91, 226, 0, 23, 7, 91, 139, 230, 151, 104, 62, 91, 59, 6, 207, 26, 200, 141, 104, 5, 151, 201, 243, 163, 28, 248 },
RightSalt = "T7j)~.#%~ZtOFUZFK,K+",
LeftSalt = "@(0PF{b6Ot?HO*:yF5`L",
Password = new byte[] { 254, 122, 19, 59, 187, 100, 174, 87, 55, 108, 14, 10, 123, 186, 129, 243, 145, 136, 152, 220, 72, 170, 196, 93, 54, 88, 192, 115, 128, 76, 133, 9, 181, 99, 181, 8, 102, 123, 197, 251, 85, 167, 146, 28, 116, 249, 118, 87, 146, 8, 194, 238, 127, 19, 33, 28, 14, 222, 218, 170, 74, 40, 223, 232 },
RightSalt = "=pt,3T0#CfC1[}Zfp{/u",
Username = "root"
});
});
@@ -253,7 +253,8 @@ namespace WatchIt.Database.Migrations
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<short>("Id"));
b.Property<string>("Description")
.HasColumnType("text");
.HasMaxLength(1000)
.HasColumnType("character varying(1000)");
b.Property<string>("Name")
.IsRequired()
@@ -378,16 +379,6 @@ namespace WatchIt.Database.Migrations
.HasMaxLength(-1)
.HasColumnType("bytea");
b.Property<bool>("IsMediaBackground")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(false);
b.Property<bool>("IsUniversalBackground")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(false);
b.Property<long>("MediaId")
.HasColumnType("bigint");
@@ -411,6 +402,34 @@ namespace WatchIt.Database.Migrations
b.ToTable("MediaPhotoImages");
});
modelBuilder.Entity("WatchIt.Database.Model.Media.MediaPhotoImageBackground", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<byte[]>("FirstGradientColor")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("bytea");
b.Property<bool>("IsUniversalBackground")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(false);
b.Property<byte[]>("SecondGradientColor")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("bytea");
b.HasKey("Id");
b.HasIndex("Id")
.IsUnique();
b.ToTable("MediaPhotoImageBackgrounds");
});
modelBuilder.Entity("WatchIt.Database.Model.Media.MediaPosterImage", b =>
{
b.Property<Guid>("Id")
@@ -452,7 +471,7 @@ namespace WatchIt.Database.Migrations
b.HasIndex("MediaId");
b.ToTable("MediaProductionCountrys");
b.ToTable("MediaProductionCountries");
});
modelBuilder.Entity("WatchIt.Database.Model.Media.MediaSeries", b =>
@@ -1016,6 +1035,17 @@ namespace WatchIt.Database.Migrations
b.Navigation("Media");
});
modelBuilder.Entity("WatchIt.Database.Model.Media.MediaPhotoImageBackground", b =>
{
b.HasOne("WatchIt.Database.Model.Media.MediaPhotoImage", "MediaPhotoImage")
.WithOne("MediaPhotoImageBackground")
.HasForeignKey("WatchIt.Database.Model.Media.MediaPhotoImageBackground", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("MediaPhotoImage");
});
modelBuilder.Entity("WatchIt.Database.Model.Media.MediaProductionCountry", b =>
{
b.HasOne("WatchIt.Database.Model.Common.Country", "Country")
@@ -1302,6 +1332,11 @@ namespace WatchIt.Database.Migrations
b.Navigation("ViewCountsMedia");
});
modelBuilder.Entity("WatchIt.Database.Model.Media.MediaPhotoImage", b =>
{
b.Navigation("MediaPhotoImageBackground");
});
modelBuilder.Entity("WatchIt.Database.Model.Media.MediaPosterImage", b =>
{
b.Navigation("Media")

View File

@@ -0,0 +1,87 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace WatchIt.Database.Migrations
{
/// <inheritdoc />
public partial class Fix : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_MediaPhotoImages_MediaPhotoImageBackgrounds_Id",
table: "MediaPhotoImages");
migrationBuilder.DropForeignKey(
name: "FK_MediaPhotoImages_MediaPhotoImageBackgrounds_MediaPhotoImage~",
table: "MediaPhotoImages");
migrationBuilder.DropIndex(
name: "IX_MediaPhotoImages_MediaPhotoImageBackgroundId",
table: "MediaPhotoImages");
migrationBuilder.DropColumn(
name: "MediaPhotoImageBackgroundId",
table: "MediaPhotoImages");
migrationBuilder.UpdateData(
table: "Accounts",
keyColumn: "Id",
keyValue: 1L,
columns: new[] { "LeftSalt", "Password", "RightSalt" },
values: new object[] { "@(0PF{b6Ot?HO*:yF5`L", new byte[] { 254, 122, 19, 59, 187, 100, 174, 87, 55, 108, 14, 10, 123, 186, 129, 243, 145, 136, 152, 220, 72, 170, 196, 93, 54, 88, 192, 115, 128, 76, 133, 9, 181, 99, 181, 8, 102, 123, 197, 251, 85, 167, 146, 28, 116, 249, 118, 87, 146, 8, 194, 238, 127, 19, 33, 28, 14, 222, 218, 170, 74, 40, 223, 232 }, "=pt,3T0#CfC1[}Zfp{/u" });
migrationBuilder.AddForeignKey(
name: "FK_MediaPhotoImageBackgrounds_MediaPhotoImages_Id",
table: "MediaPhotoImageBackgrounds",
column: "Id",
principalTable: "MediaPhotoImages",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_MediaPhotoImageBackgrounds_MediaPhotoImages_Id",
table: "MediaPhotoImageBackgrounds");
migrationBuilder.AddColumn<Guid>(
name: "MediaPhotoImageBackgroundId",
table: "MediaPhotoImages",
type: "uuid",
nullable: true);
migrationBuilder.UpdateData(
table: "Accounts",
keyColumn: "Id",
keyValue: 1L,
columns: new[] { "LeftSalt", "Password", "RightSalt" },
values: new object[] { "Y&%]J>6Nc3&5~UUXnNxq", new byte[] { 68, 170, 8, 113, 134, 47, 98, 43, 96, 183, 126, 130, 204, 45, 4, 113, 81, 200, 244, 26, 54, 88, 161, 246, 84, 93, 159, 219, 12, 143, 128, 160, 198, 194, 47, 133, 216, 242, 158, 184, 43, 38, 134, 132, 175, 179, 42, 40, 0, 143, 111, 252, 156, 215, 17, 185, 12, 109, 119, 214, 211, 167, 32, 121 }, "MV1jo~o3Oa^;NWb\\Q)t_" });
migrationBuilder.CreateIndex(
name: "IX_MediaPhotoImages_MediaPhotoImageBackgroundId",
table: "MediaPhotoImages",
column: "MediaPhotoImageBackgroundId");
migrationBuilder.AddForeignKey(
name: "FK_MediaPhotoImages_MediaPhotoImageBackgrounds_Id",
table: "MediaPhotoImages",
column: "Id",
principalTable: "MediaPhotoImageBackgrounds",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_MediaPhotoImages_MediaPhotoImageBackgrounds_MediaPhotoImage~",
table: "MediaPhotoImages",
column: "MediaPhotoImageBackgroundId",
principalTable: "MediaPhotoImageBackgrounds",
principalColumn: "Id");
}
}
}

View File

@@ -17,7 +17,7 @@ namespace WatchIt.Database.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.4")
.HasAnnotation("ProductVersion", "8.0.5")
.HasAnnotation("Proxies:ChangeTracking", false)
.HasAnnotation("Proxies:CheckEquality", false)
.HasAnnotation("Proxies:LazyLoading", true)
@@ -108,9 +108,9 @@ namespace WatchIt.Database.Migrations
Email = "root@watch.it",
IsAdmin = true,
LastActive = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
LeftSalt = "qE]Q^g%tU\"6Uu^GfE:V:",
Password = new byte[] { 165, 250, 135, 31, 187, 161, 15, 246, 18, 232, 64, 25, 37, 173, 91, 111, 140, 177, 183, 84, 254, 177, 15, 235, 119, 219, 29, 169, 32, 108, 187, 121, 204, 51, 213, 28, 141, 89, 91, 226, 0, 23, 7, 91, 139, 230, 151, 104, 62, 91, 59, 6, 207, 26, 200, 141, 104, 5, 151, 201, 243, 163, 28, 248 },
RightSalt = "T7j)~.#%~ZtOFUZFK,K+",
LeftSalt = "@(0PF{b6Ot?HO*:yF5`L",
Password = new byte[] { 254, 122, 19, 59, 187, 100, 174, 87, 55, 108, 14, 10, 123, 186, 129, 243, 145, 136, 152, 220, 72, 170, 196, 93, 54, 88, 192, 115, 128, 76, 133, 9, 181, 99, 181, 8, 102, 123, 197, 251, 85, 167, 146, 28, 116, 249, 118, 87, 146, 8, 194, 238, 127, 19, 33, 28, 14, 222, 218, 170, 74, 40, 223, 232 },
RightSalt = "=pt,3T0#CfC1[}Zfp{/u",
Username = "root"
});
});
@@ -250,7 +250,8 @@ namespace WatchIt.Database.Migrations
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<short>("Id"));
b.Property<string>("Description")
.HasColumnType("text");
.HasMaxLength(1000)
.HasColumnType("character varying(1000)");
b.Property<string>("Name")
.IsRequired()
@@ -375,16 +376,6 @@ namespace WatchIt.Database.Migrations
.HasMaxLength(-1)
.HasColumnType("bytea");
b.Property<bool>("IsMediaBackground")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(false);
b.Property<bool>("IsUniversalBackground")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(false);
b.Property<long>("MediaId")
.HasColumnType("bigint");
@@ -408,6 +399,34 @@ namespace WatchIt.Database.Migrations
b.ToTable("MediaPhotoImages");
});
modelBuilder.Entity("WatchIt.Database.Model.Media.MediaPhotoImageBackground", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uuid");
b.Property<byte[]>("FirstGradientColor")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("bytea");
b.Property<bool>("IsUniversalBackground")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(false);
b.Property<byte[]>("SecondGradientColor")
.IsRequired()
.HasMaxLength(3)
.HasColumnType("bytea");
b.HasKey("Id");
b.HasIndex("Id")
.IsUnique();
b.ToTable("MediaPhotoImageBackgrounds");
});
modelBuilder.Entity("WatchIt.Database.Model.Media.MediaPosterImage", b =>
{
b.Property<Guid>("Id")
@@ -449,7 +468,7 @@ namespace WatchIt.Database.Migrations
b.HasIndex("MediaId");
b.ToTable("MediaProductionCountrys");
b.ToTable("MediaProductionCountries");
});
modelBuilder.Entity("WatchIt.Database.Model.Media.MediaSeries", b =>
@@ -1013,6 +1032,17 @@ namespace WatchIt.Database.Migrations
b.Navigation("Media");
});
modelBuilder.Entity("WatchIt.Database.Model.Media.MediaPhotoImageBackground", b =>
{
b.HasOne("WatchIt.Database.Model.Media.MediaPhotoImage", "MediaPhotoImage")
.WithOne("MediaPhotoImageBackground")
.HasForeignKey("WatchIt.Database.Model.Media.MediaPhotoImageBackground", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("MediaPhotoImage");
});
modelBuilder.Entity("WatchIt.Database.Model.Media.MediaProductionCountry", b =>
{
b.HasOne("WatchIt.Database.Model.Common.Country", "Country")
@@ -1299,6 +1329,11 @@ namespace WatchIt.Database.Migrations
b.Navigation("ViewCountsMedia");
});
modelBuilder.Entity("WatchIt.Database.Model.Media.MediaPhotoImage", b =>
{
b.Navigation("MediaPhotoImageBackground");
});
modelBuilder.Entity("WatchIt.Database.Model.Media.MediaPosterImage", b =>
{
b.Navigation("Media")

View File

@@ -7,20 +7,20 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Analyzers" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.4">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="8.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Analyzers" Version="8.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.4">
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.2" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
<PackageReference Include="SimpleToolkit.Extensions" Version="1.7.5" />
</ItemGroup>

View File

@@ -0,0 +1,83 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using WatchIt.Common.Model.Genres;
using WatchIt.Common.Model.Media;
using WatchIt.WebAPI.Services.Controllers.Media;
namespace WatchIt.WebAPI.Controllers;
[ApiController]
[Route("media")]
public class MediaController(IMediaControllerService mediaControllerService)
{
[HttpGet("{id}/genres")]
[AllowAnonymous]
[ProducesResponseType(typeof(IEnumerable<GenreResponse>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetGenres([FromRoute]long id) => await mediaControllerService.GetGenres(id);
[HttpPost("{id}/genres/{genre_id}")]
[Authorize]
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> PostGenre([FromRoute]long id, [FromRoute(Name = "genre_id")]short genreId) => await mediaControllerService.PostGenre(id, genreId);
[HttpDelete("{id}/genres/{genre_id}")]
[Authorize]
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> DeleteGenre([FromRoute]long id, [FromRoute(Name = "genre_id")]short genreId) => await mediaControllerService.DeleteGenre(id, genreId);
[HttpGet("{id}/photos/random_background")]
[AllowAnonymous]
[ProducesResponseType(typeof(MediaPhotoResponse), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetMediaRandomBackgroundPhoto([FromRoute]long id) => await mediaControllerService.GetMediaRandomBackgroundPhoto(id);
[HttpGet("photos/{photo_id}")]
[AllowAnonymous]
[ProducesResponseType(typeof(MediaPhotoResponse), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetPhoto([FromRoute(Name = "photo_id")] Guid photoId) => await mediaControllerService.GetPhoto(photoId);
[HttpGet("photos")]
[AllowAnonymous]
[ProducesResponseType(typeof(IEnumerable<MediaPhotoResponse>), StatusCodes.Status200OK)]
public async Task<ActionResult> GetPhotos(MediaPhotoQueryParameters query) => await mediaControllerService.GetPhotos(query);
[HttpGet("photos/random_background")]
[AllowAnonymous]
[ProducesResponseType(typeof(MediaPhotoResponse), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetRandomBackgroundPhoto() => await mediaControllerService.GetRandomBackgroundPhoto();
[HttpPost("photos")]
[Authorize]
[ProducesResponseType(typeof(MediaPhotoResponse), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
public async Task<ActionResult> PostPhoto([FromBody]MediaPhotoRequest body) => await mediaControllerService.PostPhoto(body);
[HttpPut("photos/{photo_id}")]
[Authorize]
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> PutPhoto([FromRoute(Name = "photo_id")]Guid photoId, [FromBody]MediaPhotoRequest body) => await mediaControllerService.PutPhoto(photoId, body);
[HttpDelete("photos/{photo_id}")]
[Authorize]
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> DeletePhoto([FromRoute(Name = "photo_id")]Guid photoId) => await mediaControllerService.DeletePhoto(photoId);
}

View File

@@ -43,26 +43,4 @@ public class MoviesController(IMoviesControllerService moviesControllerService)
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
public async Task<ActionResult> Delete([FromRoute] long id) => await moviesControllerService.Delete(id);
[HttpGet("{id}/genres")]
[AllowAnonymous]
[ProducesResponseType(typeof(IEnumerable<GenreResponse>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetGenres([FromRoute]long id) => await moviesControllerService.GetGenres(id);
[HttpPost("{id}/genres/{genre_id}")]
[Authorize]
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> PostGenre([FromRoute]long id, [FromRoute(Name = "genre_id")]short genreId) => await moviesControllerService.PostGenre(id, genreId);
[HttpDelete("{id}/genres/{genre_id}")]
[Authorize]
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> DeleteGenre([FromRoute]long id, [FromRoute(Name = "genre_id")]short genreId) => await moviesControllerService.DeleteGenre(id, genreId);
}

View File

@@ -14,6 +14,7 @@
<ProjectReference Include="..\..\WatchIt.Common\WatchIt.Common.Model\WatchIt.Common.Model.csproj" />
<ProjectReference Include="..\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Controllers\WatchIt.WebAPI.Services.Controllers.Accounts\WatchIt.WebAPI.Services.Controllers.Accounts.csproj" />
<ProjectReference Include="..\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Controllers\WatchIt.WebAPI.Services.Controllers.Genres\WatchIt.WebAPI.Services.Controllers.Genres.csproj" />
<ProjectReference Include="..\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Controllers\WatchIt.WebAPI.Services.Controllers.Media\WatchIt.WebAPI.Services.Controllers.Media.csproj" />
<ProjectReference Include="..\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Controllers\WatchIt.WebAPI.Services.Controllers.Movies\WatchIt.WebAPI.Services.Controllers.Movies.csproj" />
</ItemGroup>

View File

@@ -0,0 +1,18 @@
using WatchIt.Common.Model.Media;
using WatchIt.WebAPI.Services.Controllers.Common;
namespace WatchIt.WebAPI.Services.Controllers.Media;
public interface IMediaControllerService
{
Task<RequestResult> GetGenres(long mediaId);
Task<RequestResult> PostGenre(long mediaId, short genreId);
Task<RequestResult> DeleteGenre(long mediaId, short genreId);
Task<RequestResult> GetPhoto(Guid id);
Task<RequestResult> GetPhotos(MediaPhotoQueryParameters query);
Task<RequestResult> GetRandomBackgroundPhoto();
Task<RequestResult> GetMediaRandomBackgroundPhoto(long id);
Task<RequestResult> PostPhoto(MediaPhotoRequest data);
Task<RequestResult> PutPhoto(Guid photoId, MediaPhotoRequest data);
Task<RequestResult> DeletePhoto(Guid photoId);
}

View File

@@ -0,0 +1,202 @@
using Microsoft.EntityFrameworkCore;
using SimpleToolkit.Extensions;
using WatchIt.Common.Model.Genres;
using WatchIt.Common.Model.Media;
using WatchIt.Database;
using WatchIt.Database.Model.Media;
using WatchIt.WebAPI.Services.Controllers.Common;
using WatchIt.WebAPI.Services.Utility.User;
namespace WatchIt.WebAPI.Services.Controllers.Media;
public class MediaControllerService(DatabaseContext database, IUserService userService) : IMediaControllerService
{
#region PUBLIC METHODS
public async Task<RequestResult> GetGenres(long mediaId)
{
MediaMovie? item = await database.MediaMovies.FirstOrDefaultAsync(x => x.Id == mediaId);
if (item is null)
{
return RequestResult.NotFound();
}
IEnumerable<GenreResponse> genres = item.Media.MediaGenres.Select(x => new GenreResponse(x.Genre));
return RequestResult.Ok(genres);
}
public async Task<RequestResult> PostGenre(long mediaId, short genreId)
{
UserValidator validator = userService.GetValidator().MustBeAdmin();
if (!validator.IsValid)
{
return RequestResult.Forbidden();
}
Database.Model.Media.Media? mediaItem = await database.Media.FirstOrDefaultAsync(x => x.Id == mediaId);
Database.Model.Common.Genre? genreItem = await database.Genres.FirstOrDefaultAsync(x => x.Id == genreId);
if (mediaItem is null || genreItem is null)
{
return RequestResult.NotFound();
}
await database.MediaGenres.AddAsync(new MediaGenre
{
GenreId = genreId,
MediaId = mediaId,
});
await database.SaveChangesAsync();
return RequestResult.Ok();
}
public async Task<RequestResult> DeleteGenre(long mediaId, short genreId)
{
UserValidator validator = userService.GetValidator().MustBeAdmin();
if (!validator.IsValid)
{
return RequestResult.Forbidden();
}
MediaGenre? item = await database.MediaGenres.FirstOrDefaultAsync(x => x.MediaId == mediaId && x.GenreId == genreId);
if (item is null)
{
return RequestResult.NotFound();
}
database.MediaGenres.Attach(item);
database.MediaGenres.Remove(item);
await database.SaveChangesAsync();
return RequestResult.Ok();
}
public async Task<RequestResult> GetPhoto(Guid id)
{
MediaPhotoImage? item = await database.MediaPhotoImages.FirstOrDefaultAsync(x => x.Id == id);
if (item is null)
{
return RequestResult.NotFound();
}
MediaPhotoResponse data = new MediaPhotoResponse(item);
return RequestResult.Ok(data);
}
public async Task<RequestResult> GetPhotos(MediaPhotoQueryParameters query)
{
IEnumerable<MediaPhotoResponse> data = await database.MediaPhotoImages.Select(x => new MediaPhotoResponse(x)).ToListAsync();
data = query.PrepareData(data);
return RequestResult.Ok(data);
}
public Task<RequestResult> GetRandomBackgroundPhoto()
{
MediaPhotoImage? image = database.MediaPhotoImages.Where(x => x.MediaPhotoImageBackground != null && x.MediaPhotoImageBackground.IsUniversalBackground).Random();
if (image is null)
{
return Task.FromResult<RequestResult>(RequestResult.NotFound());
}
MediaPhotoResponse data = new MediaPhotoResponse(image);
return Task.FromResult<RequestResult>(RequestResult.Ok(data));
}
public Task<RequestResult> GetMediaRandomBackgroundPhoto(long id)
{
MediaPhotoImage? image = database.MediaPhotoImages.Where(x => x.MediaId == id && x.MediaPhotoImageBackground != null).Random();
if (image is null)
{
return Task.FromResult<RequestResult>(RequestResult.NotFound());
}
MediaPhotoResponse data = new MediaPhotoResponse(image);
return Task.FromResult<RequestResult>(RequestResult.Ok(data));
}
public async Task<RequestResult> PostPhoto(MediaPhotoRequest data)
{
UserValidator validator = userService.GetValidator().MustBeAdmin();
if (!validator.IsValid)
{
return RequestResult.Forbidden();
}
MediaPhotoImage item = data.CreateMediaPhotoImage();
await database.MediaPhotoImages.AddAsync(item);
await database.SaveChangesAsync();
MediaPhotoImageBackground? background = data.CreateMediaPhotoImageBackground(item.Id);
if (background is not null)
{
await database.MediaPhotoImageBackgrounds.AddAsync(background);
await database.SaveChangesAsync();
}
return RequestResult.Created($"photos/{item.Id}", new MediaPhotoResponse(item));
}
public async Task<RequestResult> PutPhoto(Guid photoId, MediaPhotoRequest data)
{
UserValidator validator = userService.GetValidator().MustBeAdmin();
if (!validator.IsValid)
{
return RequestResult.Forbidden();
}
MediaPhotoImage? item = await database.MediaPhotoImages.FirstOrDefaultAsync(x => x.Id == photoId);
if (item is null)
{
return RequestResult.NotFound();
}
data.UpdateMediaPhotoImage(item);
if (item.MediaPhotoImageBackground is null && data.Background is not null)
{
MediaPhotoImageBackground background = data.CreateMediaPhotoImageBackground(item.Id)!;
await database.MediaPhotoImageBackgrounds.AddAsync(background);
}
else if (item.MediaPhotoImageBackground is not null && data.Background is null)
{
database.MediaPhotoImageBackgrounds.Attach(item.MediaPhotoImageBackground);
database.MediaPhotoImageBackgrounds.Remove(item.MediaPhotoImageBackground);
}
else if (item.MediaPhotoImageBackground is not null && data.Background is not null)
{
data.UpdateMediaPhotoImageBackground(item.MediaPhotoImageBackground);
}
await database.SaveChangesAsync();
return RequestResult.Ok();
}
public async Task<RequestResult> DeletePhoto(Guid photoId)
{
UserValidator validator = userService.GetValidator().MustBeAdmin();
if (!validator.IsValid)
{
return RequestResult.Forbidden();
}
MediaPhotoImage? item = await database.MediaPhotoImages.FirstOrDefaultAsync(x => x.Id == photoId);
if (item is null)
{
return RequestResult.NotFound();
}
if (item.MediaPhotoImageBackground is not null)
{
database.MediaPhotoImageBackgrounds.Attach(item.MediaPhotoImageBackground);
database.MediaPhotoImageBackgrounds.Remove(item.MediaPhotoImageBackground);
await database.SaveChangesAsync();
}
database.MediaPhotoImages.Attach(item);
database.MediaPhotoImages.Remove(item);
await database.SaveChangesAsync();
return RequestResult.Ok();
}
#endregion
}

View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\WatchIt.Common\WatchIt.Common.Model\WatchIt.Common.Model.csproj" />
<ProjectReference Include="..\..\..\..\WatchIt.Database\WatchIt.Database.Model\WatchIt.Database.Model\WatchIt.Database.Model.csproj" />
<ProjectReference Include="..\..\..\..\WatchIt.Database\WatchIt.Database\WatchIt.Database.csproj" />
<ProjectReference Include="..\..\WatchIt.WebAPI.Services.Utility\WatchIt.WebAPI.Services.Utility.User\WatchIt.WebAPI.Services.Utility.User.csproj" />
<ProjectReference Include="..\WatchIt.WebAPI.Services.Controllers.Common\WatchIt.WebAPI.Services.Controllers.Common.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SimpleToolkit.Extensions" Version="1.7.7" />
</ItemGroup>
</Project>

View File

@@ -10,7 +10,4 @@ public interface IMoviesControllerService
Task<RequestResult> Post(MovieRequest data);
Task<RequestResult> Put(long id, MovieRequest data);
Task<RequestResult> Delete(long id);
Task<RequestResult> GetGenres(long movieId);
Task<RequestResult> PostGenre(long movieId, short genreId);
Task<RequestResult> DeleteGenre(long movieId, short genreId);
}

View File

@@ -1,11 +1,9 @@
using Microsoft.EntityFrameworkCore;
using WatchIt.Common.Model.Genres;
using WatchIt.Common.Model.Movies;
using WatchIt.Database;
using WatchIt.Database.Model.Media;
using WatchIt.WebAPI.Services.Controllers.Common;
using WatchIt.WebAPI.Services.Utility.User;
using Genre = WatchIt.Database.Model.Common.Genre;
namespace WatchIt.WebAPI.Services.Controllers.Movies;
@@ -110,62 +108,5 @@ public class MoviesControllerService(DatabaseContext database, IUserService user
return RequestResult.Ok();
}
public async Task<RequestResult> GetGenres(long movieId)
{
MediaMovie? item = await database.MediaMovies.FirstOrDefaultAsync(x => x.Id == movieId);
if (item is null)
{
return RequestResult.NotFound();
}
IEnumerable<GenreResponse> genres = item.Media.MediaGenres.Select(x => new GenreResponse(x.Genre));
return RequestResult.Ok(genres);
}
public async Task<RequestResult> PostGenre(long movieId, short genreId)
{
UserValidator validator = userService.GetValidator().MustBeAdmin();
if (!validator.IsValid)
{
return RequestResult.Forbidden();
}
MediaMovie? movieItem = await database.MediaMovies.FirstOrDefaultAsync(x => x.Id == movieId);
Genre? genreItem = await database.Genres.FirstOrDefaultAsync(x => x.Id == genreId);
if (movieItem is null || genreItem is null)
{
return RequestResult.NotFound();
}
await database.MediaGenres.AddAsync(new MediaGenre
{
GenreId = genreId,
MediaId = movieId,
});
return RequestResult.Ok();
}
public async Task<RequestResult> DeleteGenre(long movieId, short genreId)
{
UserValidator validator = userService.GetValidator().MustBeAdmin();
if (!validator.IsValid)
{
return RequestResult.Forbidden();
}
MediaGenre? item = await database.MediaGenres.FirstOrDefaultAsync(x => x.MediaId == movieId && x.GenreId == genreId);
if (item is null)
{
return RequestResult.NotFound();
}
database.MediaGenres.Attach(item);
database.MediaGenres.Remove(item);
await database.SaveChangesAsync();
return RequestResult.Ok();
}
#endregion
}

View File

@@ -0,0 +1,21 @@
using FluentValidation;
using Microsoft.EntityFrameworkCore.Scaffolding.Metadata;
using WatchIt.Common.Model.Media;
using WatchIt.Database;
namespace WatchIt.WebAPI.Validators.Media;
public class MediaPhotoRequestValidator : AbstractValidator<MediaPhotoRequest>
{
public MediaPhotoRequestValidator(DatabaseContext database)
{
RuleFor(x => x.MediaId).MustBeIn(database.Media, x => x.Id).WithMessage("Media does not exists");
RuleFor(x => x.Image).NotEmpty();
RuleFor(x => x.MimeType).Matches(@"\w+/.+").WithMessage("Incorrect mimetype");
When(x => x.Background is not null, () =>
{
RuleFor(x => x.Background!.FirstGradientColor).Must(x => x.Length == 3).WithMessage("First gradient color has to be 3 byte long");
RuleFor(x => x.Background!.SecondGradientColor).Must(x => x.Length == 3).WithMessage("Second gradient color has to be 3 byte long");
});
}
}

View File

@@ -9,6 +9,7 @@ using Microsoft.IdentityModel.Tokens;
using WatchIt.Database;
using WatchIt.WebAPI.Services.Controllers.Accounts;
using WatchIt.WebAPI.Services.Controllers.Genres;
using WatchIt.WebAPI.Services.Controllers.Media;
using WatchIt.WebAPI.Services.Controllers.Movies;
using WatchIt.WebAPI.Services.Utility.Configuration;
using WatchIt.WebAPI.Services.Utility.Tokens;
@@ -141,6 +142,7 @@ public static class Program
builder.Services.AddSingleton<IAccountsControllerService, AccountsControllerService>();
builder.Services.AddSingleton<IGenresControllerService, GenresControllerService>();
builder.Services.AddSingleton<IMoviesControllerService, MoviesControllerService>();
builder.Services.AddSingleton<IMediaControllerService, MediaControllerService>();
return builder;
}

View File

@@ -11,7 +11,7 @@
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.4">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@@ -0,0 +1,13 @@
using Microsoft.Extensions.Configuration;
using WatchIt.Website.Services.Utility.Configuration.Model;
namespace WatchIt.Website.Services.Utility.Configuration;
public class ConfigurationService(IConfiguration configuration) : IConfigurationService
{
#region PROPERTIES
public ConfigurationData Data => configuration.Get<ConfigurationData>()!;
#endregion
}

View File

@@ -0,0 +1,8 @@
using WatchIt.Website.Services.Utility.Configuration.Model;
namespace WatchIt.Website.Services.Utility.Configuration;
public interface IConfigurationService
{
ConfigurationData Data { get; }
}

View File

@@ -0,0 +1,9 @@
namespace WatchIt.Website.Services.Utility.Configuration.Model;
public class Accounts
{
public string Base { get; set; }
public string Register { get; set; }
public string Authenticate { get; set; }
public string AuthenticateRefresh { get; set; }
}

View File

@@ -0,0 +1,8 @@
namespace WatchIt.Website.Services.Utility.Configuration.Model;
public class ConfigurationData
{
public Logging Logging { get; set; }
public string AllowedHosts { get; set; }
public Endpoints Endpoints { get; set; }
}

View File

@@ -0,0 +1,10 @@
namespace WatchIt.Website.Services.Utility.Configuration.Model;
public class Endpoints
{
public string Base { get; set; }
public Accounts Accounts { get; set; }
public Genres Genres { get; set; }
public Movies Movies { get; set; }
public Media Media { get; set; }
}

View File

@@ -0,0 +1,11 @@
namespace WatchIt.Website.Services.Utility.Configuration.Model;
public class Genres
{
public string Base { get; set; }
public string GetAll { get; set; }
public string Get { get; set; }
public string Post { get; set; }
public string Put { get; set; }
public string Delete { get; set; }
}

View File

@@ -0,0 +1,7 @@
namespace WatchIt.Website.Services.Utility.Configuration.Model;
public class LogLevel
{
public string Default { get; set; }
public string Microsoft_AspNetCore { get; set; }
}

View File

@@ -0,0 +1,6 @@
namespace WatchIt.Website.Services.Utility.Configuration.Model;
public class Logging
{
public LogLevel LogLevel { get; set; }
}

View File

@@ -0,0 +1,16 @@
namespace WatchIt.Website.Services.Utility.Configuration.Model;
public class Media
{
public string Base { get; set; }
public string GetGenres { get; set; }
public string PostGenre { get; set; }
public string DeleteGenre { get; set; }
public string GetPhotoMediaRandomBackground { get; set; }
public string GetPhoto { get; set; }
public string GetPhotos { get; set; }
public string GetPhotoRandomBackground { get; set; }
public string PostPhoto { get; set; }
public string PutPhoto { get; set; }
public string DeletePhoto { get; set; }
}

View File

@@ -0,0 +1,14 @@
namespace WatchIt.Website.Services.Utility.Configuration.Model;
public class Movies
{
public string Base { get; set; }
public string GetAll { get; set; }
public string Get { get; set; }
public string Post { get; set; }
public string Put { get; set; }
public string Delete { get; set; }
public string GetGenres { get; set; }
public string PostGenre { get; set; }
public string DeleteGenre { get; set; }
}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,63 @@
using WatchIt.Common.Model.Accounts;
using WatchIt.Common.Services.HttpClient;
using WatchIt.Website.Services.Utility.Configuration;
using WatchIt.Website.Services.Utility.Configuration.Model;
using WatchIt.Website.Services.WebAPI.Common;
namespace WatchIt.Website.Services.WebAPI.Accounts;
public class AccountsWebAPIService(IHttpClientService httpClientService, IConfigurationService configurationService) : BaseWebAPIService(configurationService), IAccountsWebAPIService
{
#region PUBLIC METHODS
public async Task Register(RegisterRequest data, Action<RegisterResponse> createdAction, Action<IDictionary<string, string[]>> badRequestAction)
{
string url = GetUrl(EndpointsConfiguration.Accounts.Register);
HttpRequest request = new HttpRequest(HttpMethodType.Post, url)
{
Body = data,
};
HttpResponse response = await httpClientService.SendRequestAsync(request);
response.RegisterActionFor2XXSuccess(createdAction)
.RegisterActionFor400BadRequest(badRequestAction)
.ExecuteAction();
}
public async Task Authenticate(AuthenticateRequest data, Action<AuthenticateResponse> successAction, Action<IDictionary<string, string[]>> badRequestAction, Action unauthorizedAction)
{
string url = GetUrl(EndpointsConfiguration.Accounts.Authenticate);
HttpRequest request = new HttpRequest(HttpMethodType.Post, url)
{
Body = data,
};
HttpResponse response = await httpClientService.SendRequestAsync(request);
response.RegisterActionFor2XXSuccess(successAction)
.RegisterActionFor400BadRequest(badRequestAction)
.RegisterActionFor401Unauthorized(unauthorizedAction)
.ExecuteAction();
}
public async Task AuthenticateRefresh(Action<AuthenticateResponse> successAction, Action unauthorizedAction, Action forbiddenAction)
{
string url = GetUrl(EndpointsConfiguration.Accounts.AuthenticateRefresh);
HttpRequest request = new HttpRequest(HttpMethodType.Post, url);
HttpResponse response = await httpClientService.SendRequestAsync(request);
response.RegisterActionFor2XXSuccess(successAction)
.RegisterActionFor401Unauthorized(unauthorizedAction)
.RegisterActionFor403Forbidden(forbiddenAction)
.ExecuteAction();
}
#endregion
#region PRIVATE METHODS
protected override string GetServiceBase() => EndpointsConfiguration.Accounts.Base;
#endregion
}

View File

@@ -0,0 +1,10 @@
using WatchIt.Common.Model.Accounts;
namespace WatchIt.Website.Services.WebAPI.Accounts;
public interface IAccountsWebAPIService
{
Task Register(RegisterRequest data, Action<RegisterResponse> createdAction, Action<IDictionary<string, string[]>> badRequestAction);
Task Authenticate(AuthenticateRequest data, Action<AuthenticateResponse> successAction, Action<IDictionary<string, string[]>> badRequestAction, Action unauthorizedAction);
Task AuthenticateRefresh(Action<AuthenticateResponse> successAction, Action unauthorizedAction, Action forbiddenAction);
}

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\WatchIt.Common\WatchIt.Common.Model\WatchIt.Common.Model.csproj" />
<ProjectReference Include="..\..\..\..\WatchIt.Common\WatchIt.Common.Services\WatchIt.Common.Services.HttpClient\WatchIt.Common.Services.HttpClient.csproj" />
<ProjectReference Include="..\..\WatchIt.Website.Services.Utility\WatchIt.Website.Services.Utility.Configuration\WatchIt.Website.Services.Utility.Configuration.csproj" />
<ProjectReference Include="..\WatchIt.Website.Services.WebAPI.Common\WatchIt.Website.Services.WebAPI.Common.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,25 @@
using WatchIt.Website.Services.Utility.Configuration;
using WatchIt.Website.Services.Utility.Configuration.Model;
namespace WatchIt.Website.Services.WebAPI.Common;
public abstract class BaseWebAPIService(IConfigurationService configurationService)
{
#region FIELDS
protected Endpoints EndpointsConfiguration => configurationService.Data.Endpoints;
#endregion
#region PRIVATE METHODS
protected abstract string GetServiceBase();
protected string GetUrl(string suffix) => string.Concat(EndpointsConfiguration.Base, GetServiceBase(), suffix);
protected string GetUrl(string suffix, params object[] format) => string.Concat(EndpointsConfiguration.Base, GetServiceBase(), string.Format(suffix, format));
#endregion
}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Utility\WatchIt.WebAPI.Services.Utility.Configuration\WatchIt.WebAPI.Services.Utility.Configuration.csproj" />
<ProjectReference Include="..\..\WatchIt.Website.Services.Utility\WatchIt.Website.Services.Utility.Configuration\WatchIt.Website.Services.Utility.Configuration.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,11 @@
using WatchIt.Common.Model.Genres;
using WatchIt.Common.Model.Media;
namespace WatchIt.Website.Services.WebAPI.Media;
public interface IMediaWebAPIService
{
Task GetGenres(long mediaId, Action<IEnumerable<GenreResponse>> successAction, Action notFoundAction);
Task PostGenre(long mediaId, long genreId, Action successAction, Action unauthorizedAction, Action forbiddenAction, Action notFoundAction);
Task GetPhotoRandomBackground(Action<MediaPhotoResponse> successAction, Action notFoundAction);
}

View File

@@ -0,0 +1,61 @@
using WatchIt.Common.Model.Genres;
using WatchIt.Common.Model.Media;
using WatchIt.Common.Services.HttpClient;
using WatchIt.Website.Services.Utility.Configuration;
using WatchIt.Website.Services.Utility.Configuration.Model;
using WatchIt.Website.Services.WebAPI.Common;
namespace WatchIt.Website.Services.WebAPI.Media;
public class MediaWebAPIService(IHttpClientService httpClientService, IConfigurationService configurationService) : BaseWebAPIService(configurationService), IMediaWebAPIService
{
#region PUBLIC METHODS
public async Task GetGenres(long mediaId, Action<IEnumerable<GenreResponse>>? successAction = null, Action? notFoundAction = null)
{
string url = GetUrl(EndpointsConfiguration.Media.GetGenres, mediaId);
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
HttpResponse response = await httpClientService.SendRequestAsync(request);
response.RegisterActionFor2XXSuccess(successAction)
.RegisterActionFor404NotFound(notFoundAction)
.ExecuteAction();
}
public async Task PostGenre(long mediaId, long genreId, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null, Action? notFoundAction = null)
{
string url = GetUrl(EndpointsConfiguration.Media.PostGenre, mediaId, genreId);
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
HttpResponse response = await httpClientService.SendRequestAsync(request);
response.RegisterActionFor2XXSuccess(successAction)
.RegisterActionFor401Unauthorized(unauthorizedAction)
.RegisterActionFor403Forbidden(forbiddenAction)
.RegisterActionFor404NotFound(notFoundAction)
.ExecuteAction();
}
public async Task GetPhotoRandomBackground(Action<MediaPhotoResponse>? successAction = null, Action? notFoundAction = null)
{
string url = GetUrl(EndpointsConfiguration.Media.GetPhotoRandomBackground);
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
HttpResponse response = await httpClientService.SendRequestAsync(request);
response.RegisterActionFor2XXSuccess(successAction)
.RegisterActionFor404NotFound(notFoundAction)
.ExecuteAction();
}
#endregion
#region PRIVATE METHODS
protected override string GetServiceBase() => EndpointsConfiguration.Media.Base;
#endregion
}

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\WatchIt.Common\WatchIt.Common.Model\WatchIt.Common.Model.csproj" />
<ProjectReference Include="..\..\..\..\WatchIt.Common\WatchIt.Common.Services\WatchIt.Common.Services.HttpClient\WatchIt.Common.Services.HttpClient.csproj" />
<ProjectReference Include="..\..\WatchIt.Website.Services.Utility\WatchIt.Website.Services.Utility.Configuration\WatchIt.Website.Services.Utility.Configuration.csproj" />
<ProjectReference Include="..\WatchIt.Website.Services.WebAPI.Common\WatchIt.Website.Services.WebAPI.Common.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -6,8 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<base href="/"/>
<link rel="stylesheet" href="bootstrap/bootstrap.min.css"/>
<link rel="stylesheet" href="app.css"/>
<link rel="stylesheet" href="WatchIt.Website.styles.css"/>
<link rel="stylesheet" href="app.css?version=0.2"/>
<link rel="stylesheet" href="WatchIt.Website.styles.css?version=0.2"/>
<link rel="icon" type="image/png" href="favicon.png"/>
<HeadOutlet @rendermode="InteractiveServer"/>
</head>

View File

@@ -1,23 +0,0 @@
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu/>
</div>
<main>
<div class="top-row px-4">
<a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
</div>
<article class="content px-4">
@Body
</article>
</main>
</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>

View File

@@ -1,96 +0,0 @@
.page {
position: relative;
display: flex;
flex-direction: column;
}
main {
flex: 1;
}
.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}
.top-row ::deep a, .top-row ::deep .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
text-decoration: none;
}
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
text-decoration: underline;
}
.top-row ::deep a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 640.98px) {
.top-row {
justify-content: space-between;
}
.top-row ::deep a, .top-row ::deep .btn-link {
margin-left: 0;
}
}
@media (min-width: 641px) {
.page {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.top-row {
position: sticky;
top: 0;
z-index: 1;
}
.top-row.auth ::deep a:first-child {
flex: 1;
text-align: right;
width: 0;
}
.top-row, article {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}

View File

@@ -1,29 +0,0 @@
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">WatchIt.Website</a>
</div>
</div>
<input type="checkbox" title="Navigation menu" class="navbar-toggler"/>
<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Counter
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="weather">
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Weather
</NavLink>
</div>
</nav>
</div>

View File

@@ -1,105 +0,0 @@
.navbar-toggler {
appearance: none;
cursor: pointer;
width: 3.5rem;
height: 2.5rem;
color: white;
position: absolute;
top: 0.5rem;
right: 1rem;
border: 1px solid rgba(255, 255, 255, 0.1);
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1);
}
.navbar-toggler:checked {
background-color: rgba(255, 255, 255, 0.5);
}
.top-row {
height: 3.5rem;
background-color: rgba(0,0,0,0.4);
}
.navbar-brand {
font-size: 1.1rem;
}
.bi {
display: inline-block;
position: relative;
width: 1.25rem;
height: 1.25rem;
margin-right: 0.75rem;
top: -1px;
background-size: cover;
}
.bi-house-door-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
}
.bi-plus-square-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
}
.bi-list-nested-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
}
.nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}
.nav-item:first-of-type {
padding-top: 1rem;
}
.nav-item:last-of-type {
padding-bottom: 1rem;
}
.nav-item ::deep .nav-link {
color: #d7d7d7;
background: none;
border: none;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
width: 100%;
}
.nav-item ::deep a.active {
background-color: rgba(255,255,255,0.37);
color: white;
}
.nav-item ::deep .nav-link:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
.nav-scrollable {
display: none;
}
.navbar-toggler:checked ~ .nav-scrollable {
display: block;
}
@media (min-width: 641px) {
.navbar-toggler {
display: none;
}
.nav-scrollable {
/* Never collapse the sidebar for wide screens */
display: block;
/* Allow sidebar to scroll for tall menus */
height: calc(100vh - 3.5rem);
overflow-y: auto;
}
}

View File

@@ -1,19 +0,0 @@
@page "/counter"
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}

View File

@@ -1,36 +0,0 @@
@page "/Error"
@using System.Diagnostics
<PageTitle>Error</PageTitle>
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more 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>
@code{
[CascadingParameter] private HttpContext? HttpContext { get; set; }
private string? RequestId { get; set; }
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
protected override void OnInitialized() =>
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
}

View File

@@ -1,7 +0,0 @@
@page "/"
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.

View File

@@ -1,66 +0,0 @@
@page "/weather"
<PageTitle>Weather</PageTitle>
<h1>Weather</h1>
<p>This component demonstrates showing data.</p>
@if (forecasts == null)
{
<p>
<em>Loading...</em>
</p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
// Simulate asynchronous loading to demonstrate a loading indicator
await Task.Delay(500);
var startDate = DateOnly.FromDateTime(DateTime.Now);
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
}
private class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}

View File

@@ -0,0 +1,3 @@
@inherits LayoutComponentBase
@Body

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,99 @@
@using System.Diagnostics
@using System.Text
@using WatchIt.Common.Model.Media
@using WatchIt.Website.Services.WebAPI.Media
@inherits LayoutComponentBase
@if (loaded)
{
<div class="container-xl">
<div class="row align-items-center m-2 rounded-3 header panel">
<div class="col-sm-4">
<a class="logo" href="/">
WatchIt
</a>
</div>
<div class="col-sm-4">
<p>Menu</p>
</div>
<div class="col-sm-4">
<div class="d-flex flex-row-reverse">
@if (signedIn)
{
<p>test</p>
}
else
{
<a class="main-button" href="/auth">Sign in or up</a>
}
</div>
</div>
</div>
<div class="row body-content">
<div class="col-sm-12">
@Body
</div>
</div>
</div>
<style>
body {
background: url('@background') no-repeat center center fixed;
}
.logo, .main-button {
background-image: linear-gradient(45deg, @firstGradientColor, @secondGradientColor);
}
</style>
}
@code
{
#region SERVICES
[Inject] public ILogger<MainLayout> Logger { get; set; } = default!;
[Inject] public IMediaWebAPIService MediaWebAPIService { get; set; } = default!;
#endregion
#region FIELDS
private bool loaded = false;
private string background = "assets/background_temp.jpg";
private string firstGradientColor = "#c6721c";
private string secondGradientColor = "#85200c";
private bool signedIn = false;
#endregion
#region METHODS
protected override async Task OnInitializedAsync()
{
Action<MediaPhotoResponse> backgroundSuccess = (data) =>
{
string imageBase64 = Convert.ToBase64String(data.Image);
string firstColor = BitConverter.ToString(data.Background.FirstGradientColor)
.Replace("-", string.Empty);
string secondColor = BitConverter.ToString(data.Background.SecondGradientColor)
.Replace("-", string.Empty);
background = $"data:{data.MimeType};base64,{imageBase64}";
firstGradientColor = $"#{firstColor}";
secondGradientColor = $"#{secondColor}";
};
await MediaWebAPIService.GetPhotoRandomBackground(backgroundSuccess, null);
loaded = true;
}
#endregion
}

View File

@@ -0,0 +1,21 @@
body {
background-size: cover;
}
.logo {
font-size: 40px;
}
.header {
position: sticky;
top: 10px;
height: 60px;
}
.body-content {
padding-top: 100px;
}
h1 {
margin: 0;
}

View File

@@ -0,0 +1,73 @@
@page "/auth"
@layout EmptyLayout
<PageTitle>WatchIt - @(_authType == AuthType.SignIn ? "Sign in" : "Sign up")</PageTitle>
<div class="h-100 d-flex align-items-center justify-content-center">
<div class="d-inline-flex flex-column justify-content-center panel rounded-3">
<a class="logo" href="/">
WatchIt
</a>
@if (_authType == AuthType.SignIn)
{
}
</div>
</div>
<style>
body {
background: url('@(_background)') no-repeat center center fixed;
}
.logo {
background-image: linear-gradient(45deg, @_firstGradientColor, @_secondGradientColor);
}
</style>
@code {
#region ENUMS
private enum AuthType
{
SignIn,
SignUp
}
#endregion
#region FIELDS
private AuthType _authType = AuthType.SignIn;
private string _background = "assets/background_temp.jpg";
private string _firstGradientColor = "#c6721c";
private string _secondGradientColor = "#85200c";
#endregion
#region METHODS
protected override Task OnInitializedAsync()
{
return base.OnInitializedAsync();
}
#endregion
}

View File

@@ -0,0 +1,8 @@
body {
background-size: cover;
}
.logo {
font-size: 60px;
margin: 10px;
}

View File

@@ -0,0 +1,50 @@
@page "/"
<PageTitle>WatchIt</PageTitle>
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<h1>Hello, world!</h1>
Welcome to your new app.
<h1>Hello, world!</h1>
Welcome to your new app.
<h1>Hello, world!</h1>
Welcome to your new app.
<h1>Hello, world!</h1>
Welcome to your new app.
<h1>Hello, world!</h1>
Welcome to your new app.
<h1>Hello, world!</h1>
Welcome to your new app.
<h1>Hello, world!</h1>
Welcome to your new app.
<h1>Hello, world!</h1>
Welcome to your new app.
<h1>Hello, world!</h1>
Welcome to your new app.
<h1>Hello, world!</h1>
Welcome to your new app.
<h1>Hello, world!</h1>
Welcome to your new app.
</div>
</div>
</div>

View File

@@ -1,18 +1,20 @@
using WatchIt.Website.Components;
using WatchIt.Common.Services.HttpClient;
using WatchIt.Website.Services.Utility.Configuration;
using WatchIt.Website.Services.WebAPI.Accounts;
using WatchIt.Website.Services.WebAPI.Media;
namespace WatchIt.Website;
public class Program
public static class Program
{
#region PUBLIC METHODS
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
var app = builder.Build();
WebApplication app = WebApplication.CreateBuilder(args)
.SetupServices()
.SetupApplication()
.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
@@ -32,4 +34,34 @@ public class Program
app.Run();
}
#endregion
#region PRIVATE METHODS
private static WebApplicationBuilder SetupServices(this WebApplicationBuilder builder)
{
builder.Services.AddHttpClient();
// Utility
builder.Services.AddSingleton<IHttpClientService, HttpClientService>();
builder.Services.AddSingleton<IConfigurationService, ConfigurationService>();
// WebAPI
builder.Services.AddSingleton<IAccountsWebAPIService, AccountsWebAPIService>();
builder.Services.AddSingleton<IMediaWebAPIService, MediaWebAPIService>();
return builder;
}
private static WebApplicationBuilder SetupApplication(this WebApplicationBuilder builder)
{
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
return builder;
}
#endregion
}

View File

@@ -13,4 +13,30 @@
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\WatchIt.Common\WatchIt.Common.Services\WatchIt.Common.Services.HttpClient\WatchIt.Common.Services.HttpClient.csproj" />
<ProjectReference Include="..\WatchIt.Website.Services\WatchIt.Website.Services.Utility\WatchIt.Website.Services.Utility.Configuration\WatchIt.Website.Services.Utility.Configuration.csproj" />
<ProjectReference Include="..\WatchIt.Website.Services\WatchIt.Website.Services.WebAPI\WatchIt.Website.Services.WebAPI.Accounts\WatchIt.Website.Services.WebAPI.Accounts.csproj" />
<ProjectReference Include="..\WatchIt.Website.Services\WatchIt.Website.Services.WebAPI\WatchIt.Website.Services.WebAPI.Media\WatchIt.Website.Services.WebAPI.Media.csproj" />
</ItemGroup>
<ItemGroup>
<_ContentIncludedByDefault Remove="Components\Layout\EmptyLayout.razor" />
<_ContentIncludedByDefault Remove="Components\Layout\MainLayout.razor" />
<_ContentIncludedByDefault Remove="Components\Pages\Counter.razor" />
<_ContentIncludedByDefault Remove="Components\Pages\Error.razor" />
<_ContentIncludedByDefault Remove="Components\Pages\Home.razor" />
<_ContentIncludedByDefault Remove="Components\Pages\Weather.razor" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="Layout\MainLayout.razor" />
<AdditionalFiles Include="Pages\Home.razor" />
</ItemGroup>
<ItemGroup>
<Folder Include="Components\" />
<Folder Include="wwwroot\assets\" />
</ItemGroup>
</Project>

View File

@@ -7,4 +7,4 @@
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using WatchIt.Website
@using WatchIt.Website.Components
@using WatchIt.Website.Layout

View File

@@ -5,5 +5,46 @@
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
"AllowedHosts": "*",
"Endpoints": {
"Base": "https://localhost:7160",
"Accounts": {
"Base": "/accounts",
"Register": "/register",
"Authenticate": "/authenticate",
"AuthenticateRefresh": "/authenticate-refresh"
},
"Genres": {
"Base": "/genres",
"GetAll": "",
"Get": "/{0}",
"Post": "",
"Put": "/{0}",
"Delete": "/{0}"
},
"Movies": {
"Base": "/movies",
"GetAll": "",
"Get": "/{0}",
"Post": "",
"Put": "/{0}",
"Delete": "/{0}",
"GetGenres": "/{0}/genres",
"PostGenre": "{0}/genres/{1}",
"DeleteGenre": "{0}/genres/{1}"
},
"Media": {
"Base": "/media",
"GetGenres": "/{0}/genres",
"PostGenre": "/{0}/genres/{1}",
"DeleteGenre": "/{0}/genres/{1}",
"GetPhotoMediaRandomBackground": "/{0}/photos/random_background",
"GetPhoto": "/photos/{0}",
"GetPhotos": "/photos",
"GetPhotoRandomBackground": "/photos/random_background",
"PostPhoto": "/photos",
"PutPhoto": "/photos/{0}",
"DeletePhoto": "/photos/{0}"
}
}
}

View File

@@ -1,51 +1,80 @@
html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
@font-face {
font-family: Belanosima;
src: url(fonts/Belanosima-Regular.ttf) format('truetype');
}
a, .btn-link {
color: #006bb7;
@font-face {
font-family: Belanosima;
src: url(fonts/Belanosima-Bold.ttf) format('truetype');
font-weight: bold;
}
.btn-primary {
body, html {
background-color: transparent;
height: 100%;
margin: 0;
padding: 0;
}
.logo {
font-family: Belanosima;
text-decoration: none;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.panel {
background-color: rgba(0, 0, 0, 0.8);
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(25px);
z-index: 1000;
}
.main-button {
--r:10px;
--b:2px;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
padding: 5px 10px;
border-radius: var(--r);
display: block;
align-items: self-end;
position: relative;
z-index:0;
text-decoration: none;
transition: 0.3s;
font-family: Belanosima;
}
.main-button::before {
content:"";
position:absolute;
z-index:-1;
inset: 0;
border: var(--b) solid transparent;
border-radius: var(--r);
background: inherit;
background-origin: border-box;
background-clip: border-box;
-webkit-mask:
linear-gradient(#fff 0 0) padding-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
-webkit-mask-repeat: no-repeat;
}
.main-button:hover {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
-webkit-text-fill-color: #fff;
-webkit-background-clip: border-box;
background-clip: border-box;
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}
.content {
padding-top: 1.1rem;
}
h1:focus {
outline: none;
}
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
.invalid {
outline: 1px solid #e50000;
}
.validation-message {
color: #e50000;
}
.blazor-error-boundary {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
padding: 1rem 1rem 1rem 3.7rem;
color: white;
}
.blazor-error-boundary::after {
content: "An error has occurred."
}
.darker-border-checkbox.form-check-input {
border-color: #929292;
.main-button:hover::before {
-webkit-mask:none;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

View File

@@ -50,6 +50,32 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchIt.Website", "WatchIt.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchIt.WebAPI.Services.Controllers.Movies", "WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Controllers\WatchIt.WebAPI.Services.Controllers.Movies\WatchIt.WebAPI.Services.Controllers.Movies.csproj", "{69BB6A9E-B673-42AB-A516-6B2513E21FDC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WatchIt.Website.Services", "WatchIt.Website.Services", "{A82972D0-9A60-4B3F-AE46-9F304D79137F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WatchIt.Website.Services.WebAPI", "WatchIt.Website.Services.WebAPI", "{46E3711F-18BD-4004-AF53-EA4D8643D92F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchIt.Website.Services.WebAPI.Accounts", "WatchIt.Website\WatchIt.Website.Services\WatchIt.Website.Services.WebAPI\WatchIt.Website.Services.WebAPI.Accounts\WatchIt.Website.Services.WebAPI.Accounts.csproj", "{68B7E892-9074-4034-8AFC-2474D7D5BE29}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchIt.Website.Services.WebAPI.Genres", "WatchIt.Website\WatchIt.Website.Services\WatchIt.Website.Services.WebAPI\WatchIt.Website.Services.WebAPI.Genres\WatchIt.Website.Services.WebAPI.Genres.csproj", "{A98D06A6-9C95-4449-9F4E-1D31BBE1D9B1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchIt.Website.Services.WebAPI.Movies", "WatchIt.Website\WatchIt.Website.Services\WatchIt.Website.Services.WebAPI\WatchIt.Website.Services.WebAPI.Movies\WatchIt.Website.Services.WebAPI.Movies.csproj", "{539404EB-BDFD-46F8-8F21-6A231ABED9B1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WatchIt.Website.Services.Utility", "WatchIt.Website.Services.Utility", "{130BC8F5-82CE-4EDF-AECB-21594DD41849}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WatchIt.Common.Services", "WatchIt.Common.Services", "{882A9795-4AC0-4556-9750-6582B2701EFA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchIt.Common.Query", "WatchIt.Common\WatchIt.Common.Query\WatchIt.Common.Query.csproj", "{6C3AE7B4-18C5-42D3-B254-460027E50143}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchIt.Common.Services.HttpClient", "WatchIt.Common\WatchIt.Common.Services\WatchIt.Common.Services.HttpClient\WatchIt.Common.Services.HttpClient.csproj", "{A4A75CCA-0DEE-4F1E-9816-60674CA807FA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchIt.Website.Services.Utility.Configuration", "WatchIt.Website\WatchIt.Website.Services\WatchIt.Website.Services.Utility\WatchIt.Website.Services.Utility.Configuration\WatchIt.Website.Services.Utility.Configuration.csproj", "{0DBBE7EA-05FE-481F-8814-6FA0BC9E571F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchIt.WebAPI.Services.Controllers.Media", "WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Controllers\WatchIt.WebAPI.Services.Controllers.Media\WatchIt.WebAPI.Services.Controllers.Media.csproj", "{3156AD7B-D6EC-4EB6-AEE8-4FBAF14C18E4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchIt.Website.Services.WebAPI.Media", "WatchIt.Website\WatchIt.Website.Services\WatchIt.Website.Services.WebAPI\WatchIt.Website.Services.WebAPI.Media\WatchIt.Website.Services.WebAPI.Media.csproj", "{1D64B7B5-650D-4AF3-AC33-A8D1F0999906}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchIt.Website.Services.WebAPI.Common", "WatchIt.Website\WatchIt.Website.Services\WatchIt.Website.Services.WebAPI\WatchIt.Website.Services.WebAPI.Common\WatchIt.Website.Services.WebAPI.Common.csproj", "{2D62ED42-489E-4888-9479-E5A50A0E7D70}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -77,6 +103,19 @@ Global
{2EC6FD28-C580-45FA-B6A7-92A6BF0CCC54} = {CEC468DB-CC49-47D3-9E3E-1CC9530C3CE7}
{46CE78A1-3EC3-4112-AAAD-26EEB8D8B194} = {4CB91BF6-87F1-4088-A943-62548CD1F9F4}
{69BB6A9E-B673-42AB-A516-6B2513E21FDC} = {CEC468DB-CC49-47D3-9E3E-1CC9530C3CE7}
{A82972D0-9A60-4B3F-AE46-9F304D79137F} = {4CB91BF6-87F1-4088-A943-62548CD1F9F4}
{46E3711F-18BD-4004-AF53-EA4D8643D92F} = {A82972D0-9A60-4B3F-AE46-9F304D79137F}
{68B7E892-9074-4034-8AFC-2474D7D5BE29} = {46E3711F-18BD-4004-AF53-EA4D8643D92F}
{A98D06A6-9C95-4449-9F4E-1D31BBE1D9B1} = {46E3711F-18BD-4004-AF53-EA4D8643D92F}
{539404EB-BDFD-46F8-8F21-6A231ABED9B1} = {46E3711F-18BD-4004-AF53-EA4D8643D92F}
{130BC8F5-82CE-4EDF-AECB-21594DD41849} = {A82972D0-9A60-4B3F-AE46-9F304D79137F}
{882A9795-4AC0-4556-9750-6582B2701EFA} = {E98C42C1-26E5-4939-8C22-72C253DE874B}
{6C3AE7B4-18C5-42D3-B254-460027E50143} = {E98C42C1-26E5-4939-8C22-72C253DE874B}
{A4A75CCA-0DEE-4F1E-9816-60674CA807FA} = {882A9795-4AC0-4556-9750-6582B2701EFA}
{0DBBE7EA-05FE-481F-8814-6FA0BC9E571F} = {130BC8F5-82CE-4EDF-AECB-21594DD41849}
{3156AD7B-D6EC-4EB6-AEE8-4FBAF14C18E4} = {CEC468DB-CC49-47D3-9E3E-1CC9530C3CE7}
{1D64B7B5-650D-4AF3-AC33-A8D1F0999906} = {46E3711F-18BD-4004-AF53-EA4D8643D92F}
{2D62ED42-489E-4888-9479-E5A50A0E7D70} = {46E3711F-18BD-4004-AF53-EA4D8643D92F}
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{23383776-1F27-4B5D-8C7C-57BFF75FA473}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -147,5 +186,41 @@ Global
{69BB6A9E-B673-42AB-A516-6B2513E21FDC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{69BB6A9E-B673-42AB-A516-6B2513E21FDC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{69BB6A9E-B673-42AB-A516-6B2513E21FDC}.Release|Any CPU.Build.0 = Release|Any CPU
{68B7E892-9074-4034-8AFC-2474D7D5BE29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{68B7E892-9074-4034-8AFC-2474D7D5BE29}.Debug|Any CPU.Build.0 = Debug|Any CPU
{68B7E892-9074-4034-8AFC-2474D7D5BE29}.Release|Any CPU.ActiveCfg = Release|Any CPU
{68B7E892-9074-4034-8AFC-2474D7D5BE29}.Release|Any CPU.Build.0 = Release|Any CPU
{A98D06A6-9C95-4449-9F4E-1D31BBE1D9B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A98D06A6-9C95-4449-9F4E-1D31BBE1D9B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A98D06A6-9C95-4449-9F4E-1D31BBE1D9B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A98D06A6-9C95-4449-9F4E-1D31BBE1D9B1}.Release|Any CPU.Build.0 = Release|Any CPU
{539404EB-BDFD-46F8-8F21-6A231ABED9B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{539404EB-BDFD-46F8-8F21-6A231ABED9B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{539404EB-BDFD-46F8-8F21-6A231ABED9B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{539404EB-BDFD-46F8-8F21-6A231ABED9B1}.Release|Any CPU.Build.0 = Release|Any CPU
{6C3AE7B4-18C5-42D3-B254-460027E50143}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6C3AE7B4-18C5-42D3-B254-460027E50143}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6C3AE7B4-18C5-42D3-B254-460027E50143}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6C3AE7B4-18C5-42D3-B254-460027E50143}.Release|Any CPU.Build.0 = Release|Any CPU
{A4A75CCA-0DEE-4F1E-9816-60674CA807FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A4A75CCA-0DEE-4F1E-9816-60674CA807FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A4A75CCA-0DEE-4F1E-9816-60674CA807FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A4A75CCA-0DEE-4F1E-9816-60674CA807FA}.Release|Any CPU.Build.0 = Release|Any CPU
{0DBBE7EA-05FE-481F-8814-6FA0BC9E571F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0DBBE7EA-05FE-481F-8814-6FA0BC9E571F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0DBBE7EA-05FE-481F-8814-6FA0BC9E571F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0DBBE7EA-05FE-481F-8814-6FA0BC9E571F}.Release|Any CPU.Build.0 = Release|Any CPU
{3156AD7B-D6EC-4EB6-AEE8-4FBAF14C18E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3156AD7B-D6EC-4EB6-AEE8-4FBAF14C18E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3156AD7B-D6EC-4EB6-AEE8-4FBAF14C18E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3156AD7B-D6EC-4EB6-AEE8-4FBAF14C18E4}.Release|Any CPU.Build.0 = Release|Any CPU
{1D64B7B5-650D-4AF3-AC33-A8D1F0999906}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1D64B7B5-650D-4AF3-AC33-A8D1F0999906}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1D64B7B5-650D-4AF3-AC33-A8D1F0999906}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1D64B7B5-650D-4AF3-AC33-A8D1F0999906}.Release|Any CPU.Build.0 = Release|Any CPU
{2D62ED42-489E-4888-9479-E5A50A0E7D70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2D62ED42-489E-4888-9479-E5A50A0E7D70}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2D62ED42-489E-4888-9479-E5A50A0E7D70}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2D62ED42-489E-4888-9479-E5A50A0E7D70}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal