start frontend
This commit is contained in:
parent
81b824bb79
commit
7417a2c35c
34 changed files with 3592 additions and 14 deletions
179
.gitignore
vendored
179
.gitignore
vendored
|
|
@ -1,7 +1,174 @@
|
||||||
bin/
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
obj/
|
## files generated by popular Visual Studio add-ons.
|
||||||
/packages/
|
|
||||||
riderModule.iml
|
# User-specific files
|
||||||
/_ReSharper.Caches/
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
|
||||||
|
[Dd]ebug/
|
||||||
|
[Rr]elease/
|
||||||
|
x64/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_i.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*.log
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.log
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.Publish.xml
|
||||||
|
*.pubxml
|
||||||
|
*.azurePubxml
|
||||||
|
|
||||||
|
# NuGet Packages Directory
|
||||||
|
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
|
||||||
|
packages/
|
||||||
|
## TODO: If the tool you use requires repositories.config, also uncomment the next line
|
||||||
|
!packages/repositories.config
|
||||||
|
|
||||||
|
# Windows Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Windows Store app package directory
|
||||||
|
AppPackages/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
sql/
|
||||||
|
*.Cache
|
||||||
|
ClientBin/
|
||||||
|
[Ss]tyle[Cc]op.*
|
||||||
|
![Ss]tyle[Cc]op.targets
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
|
||||||
|
*.publishsettings
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file to a newer
|
||||||
|
# Visual Studio version. Backup files are not needed, because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
App_Data/*.mdf
|
||||||
|
App_Data/*.ldf
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# Windows detritus
|
||||||
|
# =========================
|
||||||
|
|
||||||
|
# Windows image file caches
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
Desktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Mac desktop service store files
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
_NCrunch*
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
mizuki.db
|
|
||||||
|
appsettings.json
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
PinkSea.Gateway/wwwroot/
|
||||||
|
|
||||||
|
mizuki.db*
|
||||||
|
|
||||||
|
uploads/
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Mizuki.Dtos;
|
using Mizuki.Dtos;
|
||||||
using Mizuki.Services;
|
using Mizuki.Services;
|
||||||
|
|
@ -24,7 +25,7 @@ public class LoginController(
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("login")]
|
[Route("login")]
|
||||||
public async Task<RedirectResult> Login(
|
public async Task<RedirectResult> Login(
|
||||||
LoginDataDto dto)
|
[FromForm] LoginDataDto dto)
|
||||||
{
|
{
|
||||||
if (!await userService.CheckPasswordForUser(dto.Username, dto.Password))
|
if (!await userService.CheckPasswordForUser(dto.Username, dto.Password))
|
||||||
{
|
{
|
||||||
|
|
@ -34,6 +35,8 @@ public class LoginController(
|
||||||
var user = await userService.GetUserForUsername(dto.Username);
|
var user = await userService.GetUserForUsername(dto.Username);
|
||||||
await loginService.LoginAsUser(user);
|
await loginService.LoginAsUser(user);
|
||||||
|
|
||||||
|
Console.WriteLine($"Logged in as {user.Username}");
|
||||||
|
|
||||||
return Redirect("/");
|
return Redirect("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,7 +60,7 @@ public class LoginController(
|
||||||
[Route("register")]
|
[Route("register")]
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<RedirectResult> Register(
|
public async Task<RedirectResult> Register(
|
||||||
LoginDataDto dto)
|
[FromForm] LoginDataDto dto)
|
||||||
{
|
{
|
||||||
if (await userService.UsernameTaken(dto.Username))
|
if (await userService.UsernameTaken(dto.Username))
|
||||||
return Redirect("/register?error=This user already exists.");
|
return Redirect("/register?error=This user already exists.");
|
||||||
|
|
@ -78,4 +81,25 @@ public class LoginController(
|
||||||
|
|
||||||
return Redirect("/");
|
return Redirect("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether we have logged in.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Either an OK or a forbidden result.</returns>
|
||||||
|
[Route("check")]
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<Results<Ok, ForbidHttpResult>> Check()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var user = await loginService.GetActiveUser();
|
||||||
|
Console.WriteLine($"Get active user returned {user.Username}");
|
||||||
|
return TypedResults.Ok();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Get active user returned FORBID");
|
||||||
|
return TypedResults.Forbid();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -18,7 +18,8 @@ public class ServeController(
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filename">The filename.</param>
|
/// <param name="filename">The filename.</param>
|
||||||
/// <returns>The file, or an error.</returns>
|
/// <returns>The file, or an error.</returns>
|
||||||
[Route("/{filename}")]
|
[Route("{filename}")]
|
||||||
|
[HttpGet]
|
||||||
public async Task<Results<FileStreamHttpResult, NotFound>> GetFile(
|
public async Task<Results<FileStreamHttpResult, NotFound>> GetFile(
|
||||||
[FromRoute] string filename)
|
[FromRoute] string filename)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
using Mizuki.Database.Models;
|
using Mizuki.Database.Models;
|
||||||
|
|
||||||
namespace Mizuki.Database;
|
namespace Mizuki.Database;
|
||||||
|
|
@ -23,6 +24,28 @@ public class MizukiDbContext : DbContext
|
||||||
{
|
{
|
||||||
modelBuilder.Entity<User>();
|
modelBuilder.Entity<User>();
|
||||||
modelBuilder.Entity<Upload>();
|
modelBuilder.Entity<Upload>();
|
||||||
|
|
||||||
|
if (Database.ProviderName == "Microsoft.EntityFrameworkCore.Sqlite")
|
||||||
|
{
|
||||||
|
// SQLite does not have proper support for DateTimeOffset via Entity Framework Core, see the limitations
|
||||||
|
// here: https://docs.microsoft.com/en-us/ef/core/providers/sqlite/limitations#query-limitations
|
||||||
|
// To work around this, when the Sqlite database provider is used, all model properties of type DateTimeOffset
|
||||||
|
// use the DateTimeOffsetToBinaryConverter
|
||||||
|
// Based on: https://github.com/aspnet/EntityFrameworkCore/issues/10784#issuecomment-415769754
|
||||||
|
// This only supports millisecond precision, but should be sufficient for most use cases.
|
||||||
|
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
|
||||||
|
{
|
||||||
|
var properties = entityType.ClrType.GetProperties().Where(p => p.PropertyType == typeof(DateTimeOffset)
|
||||||
|
|| p.PropertyType == typeof(DateTimeOffset?));
|
||||||
|
foreach (var property in properties)
|
||||||
|
{
|
||||||
|
modelBuilder
|
||||||
|
.Entity(entityType.Name)
|
||||||
|
.Property(property.Name)
|
||||||
|
.HasConversion(new DateTimeOffsetToBinaryConverter());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
|
||||||
10
Program.cs
10
Program.cs
|
|
@ -5,6 +5,9 @@ using Mizuki.Services;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
builder.Services.AddRazorPages();
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
|
||||||
builder.Services.AddValidatorsFromAssembly(
|
builder.Services.AddValidatorsFromAssembly(
|
||||||
Assembly.GetCallingAssembly());
|
Assembly.GetCallingAssembly());
|
||||||
|
|
||||||
|
|
@ -14,9 +17,6 @@ builder.Services.AddScoped<UploadService>();
|
||||||
builder.Services.AddScoped<UserService>();
|
builder.Services.AddScoped<UserService>();
|
||||||
builder.Services.AddDbContext<MizukiDbContext>();
|
builder.Services.AddDbContext<MizukiDbContext>();
|
||||||
builder.Services.AddHttpContextAccessor();
|
builder.Services.AddHttpContextAccessor();
|
||||||
|
|
||||||
var app = builder.Build();
|
|
||||||
|
|
||||||
builder.Services.AddAuthentication("MizukiAuth")
|
builder.Services.AddAuthentication("MizukiAuth")
|
||||||
.AddCookie("MizukiAuth", options =>
|
.AddCookie("MizukiAuth", options =>
|
||||||
{
|
{
|
||||||
|
|
@ -25,8 +25,10 @@ builder.Services.AddAuthentication("MizukiAuth")
|
||||||
options.AccessDeniedPath = "/";
|
options.AccessDeniedPath = "/";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
app.MapGet("/", () => "Hello World!");
|
app.MapControllers();
|
||||||
|
app.MapFallbackToFile("index.html");
|
||||||
|
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
|
||||||
|
|
@ -47,4 +47,17 @@ public class DriveService
|
||||||
|
|
||||||
File.Delete(path);
|
File.Delete(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves a file with the given filename.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filename">The filename.</param>
|
||||||
|
/// <param name="fileStream">The file stream.</param>
|
||||||
|
public void SaveFileByFilename(string filename, Stream fileStream)
|
||||||
|
{
|
||||||
|
var actualFilename = Path.GetFileName(filename);
|
||||||
|
var path = Path.Combine(UploadsFolder, actualFilename);
|
||||||
|
using var writeStream = File.OpenWrite(path);
|
||||||
|
fileStream.CopyTo(writeStream);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -50,7 +50,7 @@ public class LoginService(
|
||||||
var username = httpContextAccessor.HttpContext!
|
var username = httpContextAccessor.HttpContext!
|
||||||
.User
|
.User
|
||||||
.FindFirst(ClaimTypes.Name)!
|
.FindFirst(ClaimTypes.Name)!
|
||||||
.ToString();
|
.Value;
|
||||||
|
|
||||||
return await userService.GetUserForUsername(username);
|
return await userService.GetUserForUsername(username);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ public class UploadService(
|
||||||
|
|
||||||
await dbContext.Uploads.AddAsync(upload);
|
await dbContext.Uploads.AddAsync(upload);
|
||||||
await dbContext.SaveChangesAsync();
|
await dbContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
driveService.SaveFileByFilename(upload.Filename, file.OpenReadStream());
|
||||||
return upload;
|
return upload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
30
mizuki-frontend/.gitignore
vendored
Normal file
30
mizuki-frontend/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
3
mizuki-frontend/.vscode/extensions.json
vendored
Normal file
3
mizuki-frontend/.vscode/extensions.json
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"recommendations": ["Vue.volar"]
|
||||||
|
}
|
||||||
33
mizuki-frontend/README.md
Normal file
33
mizuki-frontend/README.md
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
# mizuki-frontend
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 in Vite.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||||
|
|
||||||
|
## Type Support for `.vue` Imports in TS
|
||||||
|
|
||||||
|
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||||
|
|
||||||
|
## Customize configuration
|
||||||
|
|
||||||
|
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||||
|
|
||||||
|
## Project Setup
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Hot-Reload for Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Type-Check, Compile and Minify for Production
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
1
mizuki-frontend/env.d.ts
vendored
Normal file
1
mizuki-frontend/env.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/// <reference types="vite/client" />
|
||||||
13
mizuki-frontend/index.html
Normal file
13
mizuki-frontend/index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Vite App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
2929
mizuki-frontend/package-lock.json
generated
Normal file
2929
mizuki-frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
28
mizuki-frontend/package.json
Normal file
28
mizuki-frontend/package.json
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"name": "mizuki-frontend",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "run-p type-check \"build-only {@}\" --",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"build-only": "vite build",
|
||||||
|
"type-check": "vue-tsc --build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"vue": "^3.5.13",
|
||||||
|
"vue-router": "^4.5.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tsconfig/node22": "^22.0.0",
|
||||||
|
"@types/node": "^22.10.2",
|
||||||
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
|
"@vue/tsconfig": "^0.7.0",
|
||||||
|
"npm-run-all2": "^7.0.2",
|
||||||
|
"typescript": "~5.6.3",
|
||||||
|
"vite": "^6.0.5",
|
||||||
|
"vite-plugin-vue-devtools": "^7.6.8",
|
||||||
|
"vue-tsc": "^2.1.10"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
mizuki-frontend/public/favicon.ico
Normal file
BIN
mizuki-frontend/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
11
mizuki-frontend/src/App.vue
Normal file
11
mizuki-frontend/src/App.vue
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { RouterView } from 'vue-router'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RouterView />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
0
mizuki-frontend/src/assets/base.css
Normal file
0
mizuki-frontend/src/assets/base.css
Normal file
1
mizuki-frontend/src/assets/main.css
Normal file
1
mizuki-frontend/src/assets/main.css
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
@import './base.css';
|
||||||
25
mizuki-frontend/src/components/FileList.vue
Normal file
25
mizuki-frontend/src/components/FileList.vue
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {onBeforeMount, ref} from "vue";
|
||||||
|
import type FileDto from "@/dto/file-dto.ts";
|
||||||
|
import FileListItem from "@/components/FileListItem.vue";
|
||||||
|
|
||||||
|
const files = ref<FileDto[]>([]);
|
||||||
|
|
||||||
|
onBeforeMount(async () => {
|
||||||
|
const items = await fetch('/api/file/all')
|
||||||
|
.then(i => i.json())
|
||||||
|
.then(j => j as FileDto[]);
|
||||||
|
|
||||||
|
files.value = items;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="files.length > 0">
|
||||||
|
<FileListItem v-for="file of files" :item="file" :key="file.filename" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
21
mizuki-frontend/src/components/FileListItem.vue
Normal file
21
mizuki-frontend/src/components/FileListItem.vue
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type FileDto from "@/dto/file-dto.ts";
|
||||||
|
import {computed} from "vue";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
item: FileDto
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const url = computed(() => {
|
||||||
|
return `http://localhost:5118/f/${props.item.filename}`;
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>{{ item.filename }} - {{ item.originalFilename }}</div>
|
||||||
|
<a :href="url">Download</a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
37
mizuki-frontend/src/components/Uploader.vue
Normal file
37
mizuki-frontend/src/components/Uploader.vue
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
const sendData = async () => {
|
||||||
|
const input = document.querySelector('input[type="file"]') as HTMLInputElement;
|
||||||
|
|
||||||
|
// noinspection JSIncompatibleTypesComparison
|
||||||
|
if (input.files === null || input.files.length < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = new FormData();
|
||||||
|
data.append('formFile', input.files[0]);
|
||||||
|
|
||||||
|
const resp = await fetch('/api/file/upload', {
|
||||||
|
method: 'POST',
|
||||||
|
body: data,
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
|
|
||||||
|
const json = resp.json();
|
||||||
|
|
||||||
|
if ('filename' in json) {
|
||||||
|
alert(`Your file is able to be downloaded at https://localhost:5881/f/${json.filename}!`);
|
||||||
|
} else if ('reason' in json) {
|
||||||
|
alert(json.reason);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<input type="file" name="formFile" />
|
||||||
|
<input type="submit" v-on:click.prevent="sendData" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
5
mizuki-frontend/src/dto/file-dto.ts
Normal file
5
mizuki-frontend/src/dto/file-dto.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
export default interface FileDto {
|
||||||
|
filename: string,
|
||||||
|
originalFilename: string,
|
||||||
|
sizeInBytes: number
|
||||||
|
}
|
||||||
11
mizuki-frontend/src/helpers/api.ts
Normal file
11
mizuki-frontend/src/helpers/api.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
export const checkIfLoggedIn = async () => {
|
||||||
|
try {
|
||||||
|
const resp = await fetch("/api/user/check", {
|
||||||
|
redirect: 'error',
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
|
return resp.ok;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
11
mizuki-frontend/src/main.ts
Normal file
11
mizuki-frontend/src/main.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import './assets/main.css'
|
||||||
|
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
|
||||||
|
app.use(router)
|
||||||
|
|
||||||
|
app.mount('#app')
|
||||||
27
mizuki-frontend/src/router/index.ts
Normal file
27
mizuki-frontend/src/router/index.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import HomeView from '../views/HomeView.vue'
|
||||||
|
import LoginView from "@/views/LoginView.vue";
|
||||||
|
import RegisterView from "@/views/RegisterView.vue";
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'home',
|
||||||
|
component: HomeView,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'login',
|
||||||
|
component: LoginView,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/register',
|
||||||
|
name: 'register',
|
||||||
|
component: RegisterView,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
32
mizuki-frontend/src/views/HomeView.vue
Normal file
32
mizuki-frontend/src/views/HomeView.vue
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted } from "vue";
|
||||||
|
import { checkIfLoggedIn } from "@/helpers/api.ts";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import Uploader from "@/components/Uploader.vue";
|
||||||
|
import FileList from "@/components/FileList.vue";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const loggedIn = await checkIfLoggedIn();
|
||||||
|
if (!loggedIn) {
|
||||||
|
await router.push('/login');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const logout = async () => {
|
||||||
|
await fetch('/api/user/logout');
|
||||||
|
window.location.reload();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<FileList />
|
||||||
|
<Uploader />
|
||||||
|
<a href="#" v-on:click.prevent="logout">Log out</a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
28
mizuki-frontend/src/views/LoginView.vue
Normal file
28
mizuki-frontend/src/views/LoginView.vue
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onBeforeMount, ref } from "vue";
|
||||||
|
import { checkIfLoggedIn } from "@/helpers/api.ts";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const loggedIn = ref<boolean>(true);
|
||||||
|
|
||||||
|
onBeforeMount(async () => {
|
||||||
|
loggedIn.value = await checkIfLoggedIn();
|
||||||
|
if (loggedIn.value) {
|
||||||
|
await router.push('/');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<form v-if="!loggedIn" action="/api/user/login" method="post">
|
||||||
|
<span>Username</span> <input type="text" name="Username" /><br />
|
||||||
|
<span>Password</span> <input type="password" name="Password" />
|
||||||
|
<input type="submit" value="Login" />
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
29
mizuki-frontend/src/views/RegisterView.vue
Normal file
29
mizuki-frontend/src/views/RegisterView.vue
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onBeforeMount, ref } from "vue";
|
||||||
|
import { checkIfLoggedIn } from "@/helpers/api.ts";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const loggedIn = ref<boolean>(true);
|
||||||
|
|
||||||
|
onBeforeMount(async () => {
|
||||||
|
loggedIn.value = await checkIfLoggedIn();
|
||||||
|
if (loggedIn.value) {
|
||||||
|
await router.push('/');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<form v-if="!loggedIn" method="post" action="/api/user/register">
|
||||||
|
<span>Username</span> <input type="text" name="Username" /><br />
|
||||||
|
<span>Password</span> <input type="password" name="Password" /><br />
|
||||||
|
<span>Repeat Password</span> <input type="password" name="ValidatePassword" /><br />
|
||||||
|
<input type="submit" value="Register" />
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
12
mizuki-frontend/tsconfig.app.json
Normal file
12
mizuki-frontend/tsconfig.app.json
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
|
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||||
|
"exclude": ["src/**/__tests__/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
mizuki-frontend/tsconfig.json
Normal file
11
mizuki-frontend/tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.app.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
18
mizuki-frontend/tsconfig.node.json
Normal file
18
mizuki-frontend/tsconfig.node.json
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"extends": "@tsconfig/node22/tsconfig.json",
|
||||||
|
"include": [
|
||||||
|
"vite.config.*",
|
||||||
|
"vitest.config.*",
|
||||||
|
"cypress.config.*",
|
||||||
|
"nightwatch.conf.*",
|
||||||
|
"playwright.config.*"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"noEmit": true,
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"types": ["node"]
|
||||||
|
}
|
||||||
|
}
|
||||||
29
mizuki-frontend/vite.config.ts
Normal file
29
mizuki-frontend/vite.config.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
vueDevTools(),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
'^/api': {
|
||||||
|
target: 'http://localhost:5118',
|
||||||
|
changeOrigin: true,
|
||||||
|
cookiePathRewrite: {
|
||||||
|
"*": "/",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
1
wwwroot/index.html
Normal file
1
wwwroot/index.html
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<h1>test</h1>
|
||||||
Loading…
Reference in a new issue