Merge branch 'Eric' into 'master'

Merge Eric into Master

See merge request limited_dev/imageboard!4
This commit is contained in:
limited_dev 2023-02-11 15:05:21 +00:00
commit b63eb1973c
59 changed files with 1309 additions and 376 deletions

25
.dockerignore Normal file
View file

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

3
.gitignore vendored
View file

@ -1,7 +1,10 @@
.idea/
ImageBoardServerApp/bin/
ImageBoardServerApp/obj/
ImageBoardServerApp/wwwroot/img/dynamic
*.db
*.db-shm
*.db-wal
Migrations/
## Ignore Visual Studio temporary files, build results, and

View file

@ -1,6 +1,7 @@
<Router AppAssembly="@typeof(App).Assembly">
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
<FocusOnNavigate RouteData="@routeData" Selector="h1"/>
</Found>
<NotFound>
@ -8,9 +9,10 @@
<LayoutView Layout="@typeof(MainLayout)">
<h3>404</h3>
<div class="Error404">
<img src="static/1.jpeg" alt="noimageFound"/>
<img src="img/static/err/1.jpeg" alt="noimageFound"/>
<p role="alert">Sorry, nothing found. Please go back to the main page. Or watch the tree and find the hidden Cat..</p>
</div>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>

View file

@ -0,0 +1,61 @@
using System.Security.Claims;
using ImageBoardServerApp.Data;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
namespace ImageBoardServerApp.Auth;
public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly ProtectedSessionStorage _sessionStorage;
private ClaimsPrincipal _anonymous = new ClaimsPrincipal(new ClaimsIdentity());
public CustomAuthenticationStateProvider(ProtectedSessionStorage sessionStorage)
{
_sessionStorage = sessionStorage;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
try
{
var userSessionStorageResult = await _sessionStorage.GetAsync<UserData>("UserSession");
var userSession = userSessionStorageResult.Success ? userSessionStorageResult.Value : null;
if (userSession == null)
return await Task.FromResult(new AuthenticationState(_anonymous));
var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
{
//new Claim(ClaimTypes.Email, userSession.Email),
new Claim(ClaimTypes.Name, userSession.Email),
new Claim(ClaimTypes.Role, userSession.Role)
}, "CustomAuth"));
return await Task.FromResult(new AuthenticationState(claimsPrincipal));
}
catch
{
return await Task.FromResult(new AuthenticationState(_anonymous));
}
}
public async Task UpdateAuthenticationStateAsync(UserData session)
{
ClaimsPrincipal claimsPrincipal;
if (session != null)
{
await _sessionStorage.SetAsync("UserSession", session);
claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
{
new Claim(ClaimTypes.Email, session.Email),
new Claim(ClaimTypes.Email, session.Role)
}));
}
else
{
await _sessionStorage.DeleteAsync("UserSession");
claimsPrincipal = _anonymous;
}
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(claimsPrincipal)));
}
}

View file

@ -1,21 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ImageBoardServerApp.Data;
public class AccountData
{
[Required]
[DatabaseGenerated((DatabaseGeneratedOption.Identity))]
[Key]
public int AccountID { get; set; }
[Required]
public string Email { get; set; }
[Required]
public string Password { get; set; }
[Required]
public int PermissionInteger { get; set; }
}

View file

@ -8,7 +8,7 @@ internal sealed class AppDBContext : DbContext
public DbSet<PostData> Posts { get; set; }
public DbSet<ImageData> Images { get; set; }
public DbSet<CommentData> Comments { get; set; }
public DbSet<AccountData> Accounts { get; set; }
public DbSet<ReportData> Reports { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlite("Data Source=./Data/Nils.db");
@ -32,5 +32,18 @@ internal sealed class AppDBContext : DbContext
mb.Entity<UserData>()
.HasMany(user => user.Comments)
.WithOne(comment => comment.User);
mb.Entity<ReportData>()
.HasOne(report => report.UserReported)
.WithMany(user => user.RecivedReports);
mb.Entity<ReportData>()
.HasOne(report => report.UserReporter)
.WithMany(user => user.SubmittedReports);
mb.Entity<ReportData>()
.HasOne(report => report.ReportedPost)
.WithOne(post => post.Report);
mb.Entity<ReportData>()
.HasOne(report => report.ReportedComment)
.WithOne(comment => comment.Report);
}
}

View file

@ -1,6 +1,5 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using ImageBoardServerApp.Shared.Components;
namespace ImageBoardServerApp.Data;
@ -21,12 +20,12 @@ public class CommentData
public int UserID { get; set; }
//[ForeignKey("UserID")]
public UserData User { get; set; }
public virtual UserData User { get; set; }
//[ForeignKey("ImageID")]
public virtual ImageData Image { get; set; }
public virtual ImageData? Image { get; set; }
public int ImageID { get; set; }
public int? ImageID { get; set; }
[Required]
public string Content { get; set; }
@ -37,4 +36,9 @@ public class CommentData
[Required]
public string Board { get; set; }
[Required]
public long CreatedAt { get; set; }
public ReportData? Report { get; set; }
}

View file

@ -15,7 +15,7 @@ public class ImageData
public string Board { get; set; }
[Required]
public Byte[] Image { get; set; }
public string ImageLocation { get; set; }
public PostData Post { get; set; }

View file

@ -31,7 +31,7 @@ public class PostData
[Required]
public string Content { get; set; }
public string Interactions { get; set; }
public int Interactions { get; set; }
[Required]
public long CreatedAt { get; set; }
@ -40,4 +40,6 @@ public class PostData
public string Board { get; set; }
public List<CommentData> Comments { get; set; }
public ReportData? Report { get; set; }
}

View file

@ -0,0 +1,42 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ImageBoardServerApp.Data;
public class ReportData
{
[Required]
[DatabaseGenerated((DatabaseGeneratedOption.Identity))]
[Key]
public int ReportID { get; set; }
[Required]
public int UserReportedID { get; set; }
[Required]
public UserData UserReported { get; set; }
[Required]
public int UserReporterID { get; set; }
[Required]
public UserData UserReporter { get; set; }
public int? ReportedCommentID { get; set; }
public CommentData? ReportedComment { get; set; }
public int? ReportedPostID { get; set; }
public PostData? ReportedPost { get; set; }
[Required]
public string Type { get; set; }
[Required]
public string ReportReason { get; set; }
public string ReportExlaination { get; set; }
}

View file

@ -1,53 +0,0 @@
using Microsoft.EntityFrameworkCore;
namespace ImageBoardServerApp.Data.Repository;
public static class AccountsRepository
{
public static async Task<List<AccountData>> getAccountsAsync()
{
await using var db = new AppDBContext();
return await db.Accounts.ToListAsync();
}
public static async Task<List<AccountData>> getAccountsByMailAsync(string email)
{
await using var db = new AppDBContext();
return await db.Accounts
.Where(acc => acc.Email.Equals(email))
.ToListAsync();
}
public static async Task<AccountData> getAccountByIdAsync(int accountId)
{
await using var db = new AppDBContext();
return await db.Accounts.FirstOrDefaultAsync(post => post.AccountID == accountId);
}
public static async Task<int> createAccountAsync(AccountData accountToCreate)
{
await using var db = new AppDBContext();
await db.Accounts.AddAsync(accountToCreate);
if (await db.SaveChangesAsync() >= 1)
{
Console.WriteLine($"Created post with ID: {accountToCreate.AccountID}");
return accountToCreate.AccountID;
}
return -1;
}
public static async Task<bool> updateAccountAsync(AccountData accountToUpdate)
{
await using var db = new AppDBContext();
db.Accounts.Update(accountToUpdate);
return await db.SaveChangesAsync() >= 1;
}
public static async Task<bool> deleteAccountAsync(int postId)
{
await using var db = new AppDBContext();
AccountData accountToDelete = await getAccountByIdAsync(postId);
db.Remove(accountToDelete);
return await db.SaveChangesAsync() >= 1;
}
}

View file

@ -22,9 +22,24 @@ public static class CommentsRepository
public static async Task<CommentData> getCommentByIdAsync(int postId)
{
await using var db = new AppDBContext();
return await db.Comments.FirstOrDefaultAsync(comment => comment.PostID == postId);
return await db.Comments
.Where(comment => comment.CommentID == postId)
.Include(comment => comment.Image)
.Include(comment => comment.Post)
.FirstOrDefaultAsync();
}
/*public static async Task<PostData> getPostByIdAsync(int postId)
{
await using var db = new AppDBContext();
return await db.Posts
.Where(post => post.PostID == postId)
.Include(post => post.Image)
.Include(post => post.Comments)
.FirstOrDefaultAsync();
//return await db.Posts.FirstOrDefaultAsync(post => post.PostID == postId);
}*/
public static async Task<int> createCommentAsync(CommentData commentData)
{
await using var db = new AppDBContext();

View file

@ -16,13 +16,21 @@ public static class PostsRepository
return await db.Posts
.Where(post => post.Board.Equals(board))
.Include(post => post.Image)
.Include(post => post.Comments)
.Include(post => post.User)
.ToListAsync();
}
public static async Task<PostData> getPostByIdAsync(int postId)
{
await using var db = new AppDBContext();
return await db.Posts.FirstOrDefaultAsync(post => post.PostID == postId);
return await db.Posts
.Where(post => post.PostID == postId)
.Include(post => post.Image)
.Include(post => post.Comments)
.Include(post => post.User)
.FirstOrDefaultAsync();
//return await db.Posts.FirstOrDefaultAsync(post => post.PostID == postId);
}
public static async Task<int> createPostAsync(PostData postToCreate)

View file

@ -0,0 +1,45 @@
using Microsoft.EntityFrameworkCore;
namespace ImageBoardServerApp.Data.Repository;
public static class ReportsRepository
{
public static async Task<List<ReportData>> getReportsAsync()
{
await using var db = new AppDBContext();
return await db.Reports.ToListAsync();
}
public static async Task<ReportData> getReportByIdAsync(int reportId)
{
await using var db = new AppDBContext();
return await db.Reports.FirstOrDefaultAsync(report => report.ReportID == reportId);
}
public static async Task<int> createReportAsync(ReportData reportData)
{
await using var db = new AppDBContext();
await db.Reports.AddAsync(reportData);
if (await db.SaveChangesAsync() >= 1)
{
return reportData.ReportID;
}
return -1;
}
public static async Task<bool> updateReportAsync(ReportData reportData)
{
await using var db = new AppDBContext();
db.Reports.Update(reportData);
return await db.SaveChangesAsync() >= 1;
}
public static async Task<bool> deleteReportAsync(int reportId)
{
await using var db = new AppDBContext();
ReportData reportData = await getReportByIdAsync(reportId);
db.Remove(reportData);
return await db.SaveChangesAsync() >= 1;
}
}

View file

@ -16,6 +16,12 @@ public static class UsersRepository
return await db.Users.FirstOrDefaultAsync(user => user.UserID == userId);
}
public static async Task<UserData> getUserByEmailAsync(string email)
{
await using var db = new AppDBContext();
return await db.Users.FirstOrDefaultAsync(user => user.Email == email);
}
public static async Task<int> createUserAsync(UserData userToCreate)
{
await using var db = new AppDBContext();

View file

@ -13,10 +13,7 @@ public class UserData
public int UserID { get; set; }
[Required]
public string Ipv4Address { get; set; }
[Required]
public bool Banned { get; set; }
public long TimeBanned { get; set; }
[Required]
public long lastActionTimeStamp { get; set; }
@ -24,4 +21,20 @@ public class UserData
public List<PostData> Posts { get; set; }
public List<CommentData> Comments { get; set; }
[Required]
public string Email { get; set; }
[Required]
public string Password { get; set; }
[Required]
public int PermissionInteger { get; set; }
[Required]
public string Role { get; set; }
public List<ReportData> SubmittedReports { get; set; }
public List<ReportData> RecivedReports { get; set; }
}

View file

@ -0,0 +1,20 @@
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["ImageBoardServerApp/ImageBoardServerApp.csproj", "ImageBoardServerApp/"]
RUN dotnet restore "ImageBoardServerApp/ImageBoardServerApp.csproj"
COPY . .
WORKDIR "/src/BlazorServerTest"
RUN dotnet build "BlazorServerTest.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "ImageBoardServerApp.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ImageBoardServerApp.dll"]

View file

@ -19,7 +19,8 @@
</ItemGroup>
<ItemGroup>
<Folder Include="wwwroot\static" />
<Folder Include="wwwroot\img\dynamic\comment\m" />
<Folder Include="wwwroot\img\dynamic\op\m" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,50 @@
@page "/login"
@using ImageBoardServerApp.Data.Repository
@using ImageBoardServerApp.Auth
@inject IJSRuntime js
@inject AuthenticationStateProvider authStateProvider
@inject NavigationManager navManager
<h3>Login to bulletbroards</h3>
<div>
<form>
<div>
<label for="email">Email:</label>
<input type="email" id="email" @bind="Email" />
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" @bind="Password" />
</div>
<a @onclick="login" href="javascript:void(0)">[Login]</a>
</form>
</div>
@code {
private string Email { get; set; }
private string Password { get; set; }
private bool verified;
private async Task login()
{
Console.WriteLine("loggin you in...");
var user = await UsersRepository.getUserByEmailAsync(Email);
if (user == null)
{
await js.InvokeVoidAsync("alert", "User does not exist");
verified = false;
return;
}
verified = BCrypt.Net.BCrypt.Verify(Password, user.Password);
if (verified)
{
verified = true;
var customAuthStateProvider = (CustomAuthenticationStateProvider)authStateProvider;
await customAuthStateProvider.UpdateAuthenticationStateAsync(user);
navManager.NavigateTo("/", true);
return;
}
await js.InvokeVoidAsync("alert", $"Wrong Password");
}
}

View file

@ -0,0 +1,67 @@
@page "/register"
@using ImageBoardServerApp.Data.Repository
@using ImageBoardServerApp.Auth
@inject IJSRuntime js
@inject AuthenticationStateProvider authStateProvider
@inject NavigationManager navManager
<h3>Register to bulletbroards</h3>
<div>
<form>
<div>
<label for="email">Email:</label>
<input type="email" id="email" @bind="Email" />
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" @bind="Password" />
</div>
<a @onclick="login" href="javascript:void(0)">[Register]</a>
</form>
</div>
@code {
private string Email { get; set; }
private string Password { get; set; }
private bool verified;
private async Task login()
{
Console.WriteLine("Registering...");
UserData userToCreate = new UserData()
{
Email = Email,
Password = BCrypt.Net.BCrypt.HashPassword(Password),
Role = "User",
PermissionInteger = 1,
TimeBanned = -1
};
if (await UsersRepository.getUserByEmailAsync(Email) != null)
{
return;
}
await UsersRepository.createUserAsync(userToCreate);
Console.WriteLine("loggin you in...");
var user = await UsersRepository.getUserByEmailAsync(Email);
if (user == null)
{
await js.InvokeVoidAsync("alert", "User does not exist");
verified = false;
return;
}
verified = BCrypt.Net.BCrypt.Verify(Password, user.Password);
if (verified)
{
verified = true;
var customAuthStateProvider = (CustomAuthenticationStateProvider)authStateProvider;
await customAuthStateProvider.UpdateAuthenticationStateAsync(user);
navManager.NavigateTo("/", true);
return;
}
await js.InvokeVoidAsync("alert", $"Wrong Password");
}
}
@code {
}

View file

@ -1,16 +0,0 @@
@page "/b/"
@using System.ComponentModel.DataAnnotations
@using ImageBoardServerApp.Data
<Board board="@b"/>
@code {
private BoardData b { get; set; } = new()
{
BoardID = 0,
maxThreads = 10,
Tag = "b",
Topic = "Random"
};
}

View file

@ -0,0 +1,40 @@
@page "/"
@using ImageBoardServerApp.Data.Repository
@using ImageBoardServerApp.Auth
@inject AuthenticationStateProvider authStateProvider
<h1>BulletBoard</h1>
<span>This is a simple Imageboard made in Razor.</span>
<br/>
<span>We're currently hosting @amountOfPosts Threads, @amountOfComments Comments and @amountOfUsers Users.</span>
<sr/>
<span>@Details</span>
@code{
private string Details { get; set; }
private int amountOfPosts = -1;
private int amountOfComments = -1;
private int amountOfUsers = -1;
protected override async Task OnInitializedAsync()
{
var cauthStateProvder = (CustomAuthenticationStateProvider)authStateProvider;
var user = await cauthStateProvder.GetAuthenticationStateAsync();
if (user.User.Identity.IsAuthenticated)
{
var usr = user.User.Identity.Name;
Details = $"Welcome {usr}";
}
else
{
Details = "Please log in first.";
}
var posts = await PostsRepository.getPostsAsync();
amountOfPosts = posts.Count;
var comments = await CommentsRepository.getCommentsAsync();
amountOfComments = comments.Count;
var users = await UsersRepository.getUsersAsync();
amountOfUsers = users.Count;
}
}

View file

@ -0,0 +1,14 @@
.loginlink{
display: block;
font-size: 10px;
color: #c6cfd0;
}
.imblue{
color: #0a53be;
cursor: pointer;
}
.loginwrapper{
display: flex;
}

View file

@ -0,0 +1,20 @@
@page "/rules"
<div class="rules_headline">
<h3>Rules</h3>
</div>
<ul type="1" class="rules_list">
<li>You may not post stuff that would get us into trouble with the feds.</li>
<li>You may not post NSFW / NSFL on this platform.</li>
<li>You may not post political content on this platform.</li>
<li>You may not plan or participate in "raids" on this platform.</li>
<li>You have to be atleast 18 years old to post.</li>
<li>You may not post through VPNs, Proxies or the TOR network.</li>
<li>You may not post posts, which are not of the same topic as the target board.</li>
</ul>
@code {
}

View file

@ -3,8 +3,9 @@
font-weight: 1000;
/*the text have to be in a biger Pixel number I do not know how to do it */
}
.rules_list{
text-align:right;
text-align:left;
display:block;
font-size: 14px;
font-weight: 60 ;

View file

@ -0,0 +1,17 @@
@page "/m/"
@using System.ComponentModel.DataAnnotations
@using ImageBoardServerApp.Data
<img class="banner" src="img/static/banner/mban.png" alt="No Banner found"/>
<Board board="@m"/>
@code {
private BoardData m { get; set; } = new()
{
BoardID = 0,
maxThreads = 10,
Tag = "m",
Topic = "Main"
};
}

View file

@ -0,0 +1,3 @@
.banner{
justify-content: center;
}

View file

@ -0,0 +1,17 @@
@page "/report/{type}/{board}/{id}"
@using System.ComponentModel.DataAnnotations
<h3>Report</h3>
@code {
[Parameter]
[Required]
public string type { get; set; }
[Parameter]
[Required]
public string board { get; set; }
[Parameter]
[Required]
public string id { get; set; }
}

View file

@ -0,0 +1,44 @@
@page "/{boardName}/thread/{threadId}"
@using System.ComponentModel.DataAnnotations
@using ImageBoardServerApp.Data.Repository
@inject NavigationManager NavigationManager
<h3>Thread #@threadId on /@boardName/</h3>
<Post post="@post" showOpenThread="false"/>
<hr/>
@foreach (var comment in post.Comments)
{
<Comment comment="comment"/>
<hr/>
}
<CommentForm post="post"/>
@code {
[Parameter]
[Required]
public string boardName { get; set; }
[Parameter]
[Required]
public string threadId { get; set; }
private PostData post;
protected override async Task OnInitializedAsync()
{
try
{
post = await PostsRepository.getPostByIdAsync(int.Parse(threadId));
}
catch (FormatException fe)
{
NavigationManager.NavigateTo("/notfound");
return;
}
if(post.Board != boardName)
NavigationManager.NavigateTo("/notfound");
if(post == null)
NavigationManager.NavigateTo("/notfound");
}
}

View file

@ -1,9 +0,0 @@
@page "/"
<h1>BulletBoard</h1>
<div>
This is a simple Imageboard made in Razor.
</div>
<div>
</div>

View file

@ -1,58 +0,0 @@
@page "/Login"
@using ImageBoardServerApp.Data.Repository
<h3>Login</h3>
<div>
<form>
<div>
<label for="email">Email:</label>
<input type="email" id="email" @bind="Email" />
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" @bind="Password" />
</div>
<button type="submit" @onclick="SubmitForm">Submit</button>
</form>
@if (tried)
{
@if (verified)
{
<span>Verifed!</span>
}
else
{
<span>False login</span>
}
}
else
{
<span>Plz login</span>
}
</div>
@code {
private string Email { get; set; }
private string Password { get; set; }
private bool verified = false;
private bool tried = false;
private async Task SubmitForm()
{
tried = true;
AccountData target = (await AccountsRepository.getAccountsByMailAsync(Email))[0];
if (target == null)
{
verified = false;
return;
}
verified = BCrypt.Net.BCrypt.Verify(Password, target.Password);
if (verified)
{
verified = true;
return;
}
verified = false;
}
}

View file

@ -0,0 +1,32 @@
@page "/modmenu"
@using ImageBoardServerApp.Auth
@inject AuthenticationStateProvider authStateProvider
@inject NavigationManager navManager
<h3>ModMenu</h3>
<AuthorizeView>
<Authorized>
<span>Welcome @mail to the mod menu</span>
<div>
<a href="/modmenu/reports">[Reports]</a>
<a href="/modmenu/users">[Users]</a>
</div>
</Authorized>
<NotAuthorized>
<a href="/login">You do not have permission to view this menu.</a>
</NotAuthorized>
</AuthorizeView>
@code {
private string mail { get; set; } = "";
protected override async Task OnInitializedAsync()
{
var cauthStateProvder = (CustomAuthenticationStateProvider)authStateProvider;
var user = await cauthStateProvder.GetAuthenticationStateAsync();
if (user.User.Identity.IsAuthenticated)
{
mail = user.User.Identity.Name;
}
}
}

View file

@ -0,0 +1,5 @@
@page "/modmenu/reports"
<Reports />
@code {
}

View file

@ -0,0 +1,6 @@
@page "/modmenu/users"
<h3>UsersPage</h3>
@code {
}

View file

@ -1,19 +0,0 @@
@page "/rules"
<div class="rules_headline">
<h3>Rules</h3>
</div>
<ul type="1" class="rules_list">
<li>nudes are forbidden!</li>
<li>nacket pictures are forbidden!</li>
<li>no political statements!</li>
<li>Trees are nice!</li>
<li>Cats are nice!</li>
<li>Be mentely Unstable!:-)</li>
</ul>
@code {
}

View file

@ -15,7 +15,7 @@
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css"/>
<link href="css/site.css" rel="stylesheet"/>
<link href="ImageBoardServerApp.styles.css" rel="stylesheet"/>
<link rel="icon" type="image/png" href="favicon.png"/>
<link rel="icon" type="image/png" href="/img/static/logo.png"/>
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered"/>
</head>
<body>

View file

@ -1,14 +1,15 @@
using ImageBoardServerApp.Data;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using ImageBoardServerApp.Auth;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddAuthenticationCore();
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddScoped<ProtectedSessionStorage>();
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
var app = builder.Build();

View file

@ -23,7 +23,7 @@
{
@foreach(var post in posts)
{
<Post post="@post"></Post>
<Post post="@post" showOpenThread="true"></Post>
<hr/>
}
}

View file

@ -0,0 +1,108 @@
@using System.ComponentModel.DataAnnotations
@using ImageBoardServerApp.Auth
@using ImageBoardServerApp.Data
@using ImageBoardServerApp.Data.Repository
@inject AuthenticationStateProvider authStateProvider
<div class="threadHeader">
<span>[</span>
<a class="toggleOpened" onclick="@ToggleOpened">@toggleText</a>
<span>]</span>
<span class="name">@comment.Username</span>
@if (@comment.User.Role != "User")
{
<span class="@comment.User.Role" >##@comment.User.Role</span>
}
<span class="date">@getTimeFromUnix(comment.CreatedAt)</span>
<span class="post-id">No.@comment.CommentID</span>
</div>
@if (opened)
{
<div class="threadContent">
<div class="threadImage">
@if (image != null)
{
<img src="@($"{image.ImageLocation}")" alt="No Image found" />
}
</div>
<div class="threadTextContainer">
@foreach (string s in @comment.Content.Split("\n"))
{
@if (s.StartsWith(">"))
{
<span class="threadText greenText">@s</span>
}
else
{
<span class='threadText'>@s</span>
}
}
</div>
</div>
<div class="threadFooter">
<span>[</span>
<a @onclick="@deletePost" href="javascript:void(0)">Delete</a>
<span>]</span>
<span>[</span>
<a class="report" href="/report/comment/@comment.Board/@comment.CommentID" target="_blank">Report</a>
<span>]</span>
</div>
}
@code {
private async Task deletePost()
{
var cauthStateProvder = (CustomAuthenticationStateProvider)authStateProvider;
var user = await cauthStateProvder.GetAuthenticationStateAsync();
var usr = user.User;
UserData foundusr = await UsersRepository.getUserByEmailAsync(usr.Identity.Name);
if (foundusr.PermissionInteger >= 50 || comment.UserID == foundusr.UserID)
{
await CommentsRepository.deleteCommentAsync(comment.CommentID);
}
}
private static DateTime getTimeFromUnix(double javaTimeStamp)
{
var dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
dateTime = dateTime.AddMilliseconds( javaTimeStamp ).ToLocalTime();
return dateTime;
}
private ImageData image;
protected override async Task OnInitializedAsync()
{
int i;
try
{
i = (int)comment.ImageID;
}
catch (InvalidOperationException ioe)
{
i = -1;
}
if (i != null)
{
image = await ImagesRepository.getImageByIdAsync(i);
}
}
private bool opened = true;
private string toggleText = "-";
private void ToggleOpened()
{
opened = !opened;
toggleText = opened ? "-" : "+";
}
[Parameter]
[Required]
public CommentData comment { get; set; }
}

View file

@ -0,0 +1,70 @@
.toggleOpened{
color: #0a58ca;
text-decoration: none;
}
.toggleOpened:hover{
color: #0a58ca; !important;
cursor: pointer;
}
.title{
color: #1e5aaf;
}
.name{
color: #339305;
}
.threadHeader{
text-align: left;
}
.threadFooter{
text-align: right; !important;
align-self: end; !important;
}
.threadContent{
text-align: left;
display: flex;
}
.greenText{
color: #3caf03;
}
.threadImage{
margin: 6px;
max-width: 500px;
max-height: 500px;
padding: 5px;
}
.threadImage img{
max-width:150px;
width: 100%;
}
.threadImage img:hover{
/*transform: scale(3);*/
max-width:500px;
width: 100%;
}
.threadText{
display: grid;
}
.threadTextContainer{
margin: 0;
}
.Admin{
color: #ff191c;
}
.Mod{
color: #af13d7;
}

View file

@ -0,0 +1,155 @@
@using System.ComponentModel.DataAnnotations
@using ImageBoardServerApp.Auth
@using ImageBoardServerApp.Data.Repository
@inject NavigationManager navigationManager
@inject IWebHostEnvironment env
@inject AuthenticationStateProvider authStateProvider
<div>
<span>[</span>
<a class="toggleOpened" onclick="@ToggleOpened">@toggleText</a>
<span>]</span>
</div>
@if (opened)
{
<div class="pd centered">
<span>Comment on @post.Title in /@post.Board/</span>
<div class="centered formContent">
<div>
<div class="pd centered marg">
<RadzenTextBox Placeholder="Username (Anonymous)" MaxLength="15" @bind-Value="@postUsername" Class="w-100"/>
</div>
<div class="pd centered marg">
<RadzenTextArea Placeholder="Comment..." @bind-Value="@postContent" Cols="30" Rows="6" Class="w-100"/>
</div>
</div>
</div>
@if (hasErr)
{
<span class="postError">@postErr</span>
}
<div class="pd centered marg">
<FormInfo/>
<InputFile OnChange="@SingleUpload" type="file" accept="image/*"/>
<RadzenButton class="pd" Click="@onPostClick" Text="Post!"></RadzenButton>
</div>
</div>
}
@code {
private bool opened = false;
private string toggleText = "Open Comment Formula";
private void ToggleOpened()
{
opened = !opened;
toggleText = opened ? "Close Comment Formula" : "Open Comment Formula";
}
[Parameter]
[Required]
public PostData post { get; set; }
string postUsername { get; set; } = "Anonymous";
string postContent { get; set; } = "";
private IBrowserFile selectedFile;
private async Task SingleUpload(InputFileChangeEventArgs e)
{
selectedFile = e.GetMultipleFiles()[0];
this.StateHasChanged();
}
string postErr { get; set; }
bool hasErr { get; set; } = false;
private async Task onPostClick()
{
var cauthStateProvder = (CustomAuthenticationStateProvider)authStateProvider;
var user = await cauthStateProvder.GetAuthenticationStateAsync();
var usr = user.User;
UserData foundusr = await UsersRepository.getUserByEmailAsync(usr.Identity.Name);
if (foundusr == null)
{
hasErr = true;
postErr = "You are not logged in.";
return;
}
int userID = foundusr.UserID;
if (foundusr.TimeBanned != -1)
{
hasErr = true;
postErr = "You are banned and may not comment.";
//Maybe redirect to /banned?
return;
}
foundusr.lastActionTimeStamp = DateTimeOffset.UnixEpoch.ToUnixTimeMilliseconds();
await UsersRepository.updateUserAsync(foundusr);
bool hasImage = selectedFile != null;
CommentData commentToCreate;
if (hasImage)
{
Stream stream = selectedFile.OpenReadStream(maxAllowedSize: 512000 * 4); // max 2MB
var file = Path.GetRandomFileName() + "." + selectedFile.Name.Split(".")[selectedFile.Name.Split(".").Length - 1];
var path = $"{env.WebRootPath}/img/dynamic/comment/{@post.Board}/{@file}";
FileStream fs = File.Create(path);
await stream.CopyToAsync(fs);
stream.Close();
fs.Close();
var imageToUpload = new ImageData
{
Board = post.Board,
ImageLocation = $"/img/dynamic/comment/{@post.Board}/{@file}"
};
int imageID = await ImagesRepository.createImageAsync(imageToUpload);
commentToCreate = new CommentData()
{
PostID = post.PostID,
UserID = userID,
ImageID = imageID,
Content = postContent,
Username = postUsername,
Board = post.Board,
CreatedAt = DateTimeOffset.Now.ToUnixTimeMilliseconds()
};
}
else
{
commentToCreate = new CommentData()
{
PostID = post.PostID,
UserID = userID,
Content = postContent,
Username = postUsername,
Board = post.Board,
CreatedAt = DateTimeOffset.Now.ToUnixTimeMilliseconds()
};
}
int commentId = await CommentsRepository.createCommentAsync(commentToCreate);
if (commentId == -1)
{
//Open comment unsucessfull
navigationManager.NavigateTo("/UnSuccessfulPost");
hasErr = true;
postErr = "There was an error and the comment could not be created. Please notify the admin.";
Console.WriteLine("Shit sucks and did not work.");
return;
}
//comment successfull
Console.WriteLine("Post created");
navigationManager.NavigateTo($"/{post.Board}/thread/{post.PostID}", true, true);
opened = false;
}
}

View file

@ -0,0 +1,44 @@
.toggleOpened{
color: #0a58ca;
text-decoration: none;
}
.toggleOpened:hover{
color: #0a58ca; !important;
cursor: pointer;
}
.centered {
text-align: center;
justify-content: center;
align-items: center;
}
.pd {
padding: 5px;
}
.marg{
margin: 2px
}
.formImage{
margin: 6px;
max-width: 200px;
max-height: 200px;
padding: 5px;
}
.formImage img{
max-width:150px;
width: 100%;
}
.formContent{
text-align: left;
display: flex;
}
.postError{
color: #ff191c;
}

View file

@ -0,0 +1,5 @@
<ul class="notesInfo">
<li>The max. image size is 2MiB.</li>
<li>Supported file types are: jpeg, png & gif</li>
<li>Read the rules before posting</li>
</ul>

View file

@ -0,0 +1,8 @@
.notesInfo{
font-weight: 60;
display: block;
text-align: center;
font-size: 10px;
color: #c6cfd0;
/*the text sice can stay like it is, if you do not want to change it.*/
}

View file

@ -0,0 +1,161 @@
@using Radzen
@using System.ComponentModel.DataAnnotations
@using System.IO.Pipelines
@using System.Net.Mime
@using System.Reflection
@using System.Runtime.CompilerServices
@using ImageBoardServerApp.Auth
@using ImageBoardServerApp.Data
@using ImageBoardServerApp.Data.Repository
@inject NavigationManager NavigationManager
@inject IWebHostEnvironment env
@inject AuthenticationStateProvider authStateProvider
<div>
<span>[</span>
<a class="toggleOpened" onclick="@ToggleOpened">@toggleText</a>
<span>]</span>
</div>
@if (opened)
{
<div class="pd centered">
<span>Post to /@board.Tag/ - @board.Topic</span>
<div class="centered formContent">
<div>
<div class="pd centered marg">
<RadzenTextBox Placeholder="Username (Anonymous)" MaxLength="15" @bind-Value="@postUsername" Class="w-100"/>
</div>
<div class="pd centered marg">
<RadzenTextBox Placeholder="Title" MaxLength="20" @bind-Value="@postTitle" Class="w-100"/>
</div>
<div class="pd centered marg">
<RadzenTextArea Placeholder="Content..." @bind-Value="@postContent" Cols="30" Rows="6" Class="w-100"/>
</div>
</div>
</div>
@if (hasErr)
{
<span class="postError">@postErr</span>
}
<div class="pd centered marg">
<FormInfo/>
<InputFile OnChange="@SingleUpload" type="file" accept="image/*"/>
<RadzenButton class="pd" Click="@onPostClick" Text="Post!"></RadzenButton>
</div>
</div>
}
@code {
private bool opened = false;
private string toggleText = "Open Post Formula";
private void ToggleOpened()
{
opened = !opened;
toggleText = opened ? "Close Post Formula" : "Open Post Formula";
}
[Parameter]
[Required]
public BoardData board { get; set; } = new BoardData();
string postUsername { get; set; } = "Anonymous";
string postTitle { get; set; } = "";
string postContent { get; set; } = "";
private IBrowserFile selectedFile;
private async Task SingleUpload(InputFileChangeEventArgs e)
{
selectedFile = e.GetMultipleFiles()[0];
this.StateHasChanged();
}
string postErr { get; set; }
bool hasErr { get; set; } = false;
private async Task onPostClick()
{
var cauthStateProvder = (CustomAuthenticationStateProvider)authStateProvider;
var user = await cauthStateProvder.GetAuthenticationStateAsync();
var usr = user.User;
UserData foundusr = await UsersRepository.getUserByEmailAsync(usr.Identity.Name);
if (foundusr == null)
{
hasErr = true;
postErr = "You are not logged in.";
return;
}
int userID = foundusr.UserID;
if (foundusr.TimeBanned != -1)
{
hasErr = true;
postErr = "You are banned and may not post.";
//Maybe redirect to /banned?
return;
}
foundusr.lastActionTimeStamp = DateTimeOffset.UnixEpoch.ToUnixTimeMilliseconds();
await UsersRepository.updateUserAsync(foundusr);
//TODO Add check if data is image
if (selectedFile == null || selectedFile.Size >= 512000 * 4)
{
hasErr = true;
postErr = "You did not attach a file or the selected file is bigger then the 2MiB file limit.";
return;
}
Stream stream = selectedFile.OpenReadStream(maxAllowedSize: 512000 * 4); // max 2MB
var file = Path.GetRandomFileName() + "." + selectedFile.Name.Split(".")[selectedFile.Name.Split(".").Length - 1];
var path = $"{env.WebRootPath}/img/dynamic/op/{@board.Tag}/{@file}";
FileStream fs = File.Create(path);
await stream.CopyToAsync(fs);
stream.Close();
fs.Close();
var imageToUpload = new ImageData
{
Board = board.Tag,
ImageLocation = $"/img/dynamic/op/{@board.Tag}/{@file}"
};
int imageID = await ImagesRepository.createImageAsync(imageToUpload);
var postToPost = new PostData
{
UserID = userID,
ImageID = imageID,
Username = postUsername,
Title = postTitle,
Content = postContent,
Interactions = 0,
CreatedAt = DateTimeOffset.Now.ToUnixTimeMilliseconds(),
Board = board.Tag
};
int postId = await PostsRepository.createPostAsync(postToPost);
if (postId != -1)
{
//Open post successfull
NavigationManager.NavigateTo($"/{board.Tag}/thread/{postId}", true, true);
Console.WriteLine("Post created");
}
else
{
//Open post unsucessfull
hasErr = true;
postErr = "There was an error and the post could not be created. Please notify the admin.";
Console.WriteLine("Shit sucks and did not work.");
}
}
}

View file

@ -21,3 +21,24 @@
.marg{
margin: 2px
}
.formImage{
margin: 6px;
max-width: 200px;
max-height: 200px;
padding: 5px;
}
.formImage img{
max-width:150px;
width: 100%;
}
.formContent{
text-align: left;
display: flex;
}
.postError{
color: #ff191c;
}

View file

@ -1,12 +1,22 @@
@using System.ComponentModel.DataAnnotations
@using ImageBoardServerApp.Auth
@using ImageBoardServerApp.Data
@using ImageBoardServerApp.Data.Repository
@inject AuthenticationStateProvider authStateProvider
<div class="threadHeader">
@if (showOpenThread)
{
<span>[</span>
<a class="toggleOpened" onclick="@ToggleOpened">@toggleText</a>
<span>]</span>
}
<span class="title">@post.Title</span>
<span class="name">@post.Username</span>
@if (post.User.Role != "User")
{
<span class="@post.User.Role" >##@post.User.Role</span>
}
<span class="date">@getTimeFromUnix(post.CreatedAt)</span>
<span class="post-id">No.@post.PostID</span>
</div>
@ -14,10 +24,9 @@
{
<div class="threadContent">
<div class="threadImage">
<!-- TODO: Make Images Required. -->
@if (@post.Image != null)
{
<img src="@($"data:image/jpeg;base64,{Convert.ToBase64String(@post.Image.Image)}")" alt="No Image found" />
<img src="@($"{@post.Image.ImageLocation}")" alt="No Image found" />
}
else
{
@ -26,28 +35,43 @@
</div>
<div class="threadTextContainer">
@foreach (string s in @post.Content.Split("\n"))
{
@if (@s.StartsWith(">"))
{
<span class="threadText greenText">@s</span>
}
else
{
<span class='threadText'>@s</span>
}
}
</div>
</div>
<div class="threadFooter">
<!--<RadzenButton class="bump" Text="bump"></RadzenButton> -->
<span>[</span>
<a class="report" href="/report/@post.Board/@post.PostID" target="_blank">Report</a>
<a @onclick="@deletePost" href="javascript:void(0)">Delete</a>
<span>]</span>
<span>[</span>
<a class="openThread" href="/@post.Board/@post.PostID" target="_blank">(@post.Interactions) Open Thread</a>
<a class="report" href="/report/op/@post.Board/@post.PostID" target="_blank">Report</a>
<span>]</span>
@if (showOpenThread)
{
<span>[</span>
<a class="openThread" href="/@post.Board/thread/@post.PostID">(@post.Comments.Count) View Thread</a>
<span>]</span>
}
else
{
<span>[</span>
<span class="openThread">@post.Comments.Count Comments</span>
<span>]</span>
}
</div>
}
@code {
private static DateTime getTimeFromUnix(double javaTimeStamp)
{
var dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
@ -55,6 +79,18 @@
return dateTime;
}
private async Task deletePost()
{
var cauthStateProvder = (CustomAuthenticationStateProvider)authStateProvider;
var user = await cauthStateProvder.GetAuthenticationStateAsync();
var usr = user.User;
UserData foundusr = await UsersRepository.getUserByEmailAsync(usr.Identity.Name);
if (foundusr.PermissionInteger >= 50 || post.UserID == foundusr.UserID)
{
await PostsRepository.deletePostAsync(post.PostID);
}
}
private bool opened = true;
private string toggleText = "-";
@ -68,4 +104,9 @@
[Parameter]
[Required]
public PostData post { get; set; }
[Parameter]
[Required]
public bool showOpenThread { get; set; }
}

View file

@ -16,6 +16,14 @@
color: #339305;
}
.Admin{
color: #ff191c;
}
.Mod{
color: #af13d7;
}
.threadHeader{
text-align: left;
}
@ -30,6 +38,10 @@
display: flex;
}
.greenText{
color: #3caf03;
}
.threadImage{
margin: 6px;
max-width: 500px;
@ -43,9 +55,9 @@
}
.threadImage img:hover{
transform: scale(3);
/*max-width:500px;
width: 100%; */
/*transform: scale(3);*/
max-width:500px;
width: 100%;
}
.threadText{

View file

@ -1,139 +0,0 @@
@using Radzen
@using System.ComponentModel.DataAnnotations
@using System.IO.Pipelines
@using System.Net.Mime
@using System.Reflection
@using System.Runtime.CompilerServices
@using ImageBoardServerApp.Data
@using ImageBoardServerApp.Data.Repository
@inject NavigationManager NavigationManager
<div>
<span>[</span>
<a class="toggleOpened" onclick="@ToggleOpened">@toggleText</a>
<span>]</span>
</div>
@if (opened)
{
<div class="pd centered">
<span>Post to /@board.Tag/ - @board.Topic</span>
<div class="pd centered marg">
<RadzenTextBox Placeholder="Username (Anonymous)" MaxLength="15" Change=@(args => OnChange(args, "username")) Class="w-100"/>
</div>
<div class="pd centered marg">
<RadzenTextBox Placeholder="Title" MaxLength="20" Change=@(args => OnChange(args, "title")) Class="w-100"/>
</div>
<div class="pd centered marg">
<RadzenTextArea Placeholder="Content..." @bind-Value="@postContent" Cols="30" Rows="6" Change=@(args => OnChange(args, "Content")) Class="w-100"/>
</div>
<div class="pd centered marg">
<InputFile OnChange="@SingleUpload" type="file" accept="image/*"/>
<RadzenButton class="pd" Click="@onPostClick" Text="Post!"></RadzenButton>
</div>
</div>
}
@code {
private bool opened = false;
private string toggleText = "Open Post Formula";
private void ToggleOpened()
{
opened = !opened;
toggleText = opened ? "Close Post Formula" : "Open Post Formula";
}
[Parameter]
[Required]
public BoardData board { get; set; } = new BoardData();
string postUsername = "Anonymous";
string postTitle = "";
string postContent = "";
void OnChange(string value, string name)
{
switch (name)
{
case "title":
postTitle = value;
break;
case "username":
postUsername = value;
break;
case "content":
postContent = value;
break;
default:
Console.WriteLine("not found.");
break;
}
Console.WriteLine($"Smth changed!: {value}");
}
private async Task SingleUpload(InputFileChangeEventArgs e)
{
MemoryStream ms = new MemoryStream();
await e.File.OpenReadStream().CopyToAsync(ms);
var bytes = ms.ToArray();
image = bytes;
Console.WriteLine("File has been selected!");
ms.Close();
}
private Byte[] image;
private async Task onPostClick()
{
var userToCreate = new UserData
{
Ipv4Address = "192.168.178.101",
Banned = false,
lastActionTimeStamp = DateTime.Now.Millisecond
};
int userID = await UsersRepository.createUserAsync(userToCreate);
//TODO Add check if data is image
var imageToUpload = new ImageData
{
Board = board.Tag,
Image = image
};
int imageID = await ImagesRepository.createImageAsync(imageToUpload);
var postToPost = new PostData
{
UserID = userID,
ImageID = imageID,
Username = postUsername,
Title = postTitle,
Content = postContent,
Interactions = "",
CreatedAt = DateTimeOffset.Now.ToUnixTimeMilliseconds(),
Board = board.Tag
};
int postId = await PostsRepository.createPostAsync(postToPost);
if (postId != -1)
{
//Open post successfull
NavigationManager.NavigateTo("/SuccessfulPost");
Console.WriteLine("Post created");
}
else
{
//Open post unsucessfull
NavigationManager.NavigateTo("/UnSuccessfulPost");
Console.WriteLine("Shit sucks and did not work.");
}
}
}

View file

@ -0,0 +1,5 @@
<h3>Reports</h3>
@code {
}

View file

@ -1,4 +1,7 @@
@inherits LayoutComponentBase
@using ImageBoardServerApp.Auth
@inject AuthenticationStateProvider authStateProvider
@inject NavigationManager navManager
<PageTitle>BulletBoard</PageTitle>
@ -8,9 +11,19 @@
</div>
<main>
<div class="top-row px-4">
<a href="/faq">[FAQ]</a>
<a href="/rules">[Rules]</a>
<AuthorizeView>
<Authorized>
<a @onclick="logout" href="javascript:void(0)">[Logout @mail]</a>
</Authorized>
<NotAuthorized>
<a href="/register">[Register]</a>
<a href="/login">[Login]</a>
</NotAuthorized>
</AuthorizeView>
</div>
<article class="content px-4">
@ -18,3 +31,26 @@
</article>
</main>
</div>
@code
{
private string mail { get; set; } = "";
protected override async Task OnInitializedAsync()
{
var cauthStateProvder = (CustomAuthenticationStateProvider)authStateProvider;
var user = await cauthStateProvder.GetAuthenticationStateAsync();
if (user.User.Identity.IsAuthenticated)
{
mail = user.User.Identity.Name;
}
}
private async Task logout()
{
var customAuthStateProvider = (CustomAuthenticationStateProvider) authStateProvider;
await customAuthStateProvider.UpdateAuthenticationStateAsync(null);
navManager.NavigateTo("/", true);
}
}

View file

@ -15,8 +15,8 @@
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="b">
<span class="oi oi-list-rich" aria-hidden="true"></span> /b/ - Random
<NavLink class="nav-link" href="m">
<span class="oi oi-list-rich" aria-hidden="true"></span> /m/ - Main
</NavLink>
</div>
</nav>

View file

@ -6,11 +6,16 @@
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using ImageBoardServerApp
@using ImageBoardServerApp.Shared
@using Radzen
@using Radzen.Blazor
@using ImageBoardServerApp
@using ImageBoardServerApp.Pages
@using ImageBoardServerApp.Pages.Boards
@using ImageBoardServerApp.Pages.Accounts
@using ImageBoardServerApp.Pages.Basic
@using ImageBoardServerApp.Pages.Components
@using ImageBoardServerApp.Pages.Status
@using ImageBoardServerApp.Shared
@using ImageBoardServerApp.Shared.Components
@using ImageBoardServerApp.Shared.Components.Forms
@using ImageBoardServerApp.Data

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

View file

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB