summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorivarlovlie <git@ivarlovlie.no>2023-01-13 20:21:20 +0100
committerivarlovlie <git@ivarlovlie.no>2023-01-13 20:21:20 +0100
commit0ae5a68a9d81547bb9b741458d94b5f1b7374027 (patch)
tree485384d892cdfe4dc6af79f3cf0abe11d2e552b1 /src
parent971c00b8f5977db9422eeafa47b262c1030bd4af (diff)
downloadblob-bin-0ae5a68a9d81547bb9b741458d94b5f1b7374027.tar.xz
blob-bin-0ae5a68a9d81547bb9b741458d94b5f1b7374027.zip
feat: First dev release
Diffstat (limited to 'src')
-rw-r--r--src/BlobBin.csproj25
-rw-r--r--src/DB.cs42
-rw-r--r--src/Dockerfile20
-rw-r--r--src/Migrations/20230112230354_InitialCreate.Designer.cs114
-rw-r--r--src/Migrations/20230112230354_InitialCreate.cs68
-rw-r--r--src/Migrations/DBModelSnapshot.cs111
-rw-r--r--src/Program.cs120
-rw-r--r--src/Properties/launchSettings.json15
-rw-r--r--src/wwwroot/index.html80
9 files changed, 595 insertions, 0 deletions
diff --git a/src/BlobBin.csproj b/src/BlobBin.csproj
new file mode 100644
index 0000000..263a517
--- /dev/null
+++ b/src/BlobBin.csproj
@@ -0,0 +1,25 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+ <PropertyGroup>
+ <TargetFramework>net7.0</TargetFramework>
+ <Nullable>enable</Nullable>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="IOL.Helpers" Version="3.1.0" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.2">
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ <PrivateAssets>all</PrivateAssets>
+ </PackageReference>
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.2" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Content Include="..\.dockerignore">
+ <Link>.dockerignore</Link>
+ </Content>
+ </ItemGroup>
+
+</Project>
diff --git a/src/DB.cs b/src/DB.cs
new file mode 100644
index 0000000..a736dc2
--- /dev/null
+++ b/src/DB.cs
@@ -0,0 +1,42 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace BlobBin;
+
+public class DB : DbContext
+{
+ public DB(DbContextOptions<DB> options) : base(options) { }
+
+ public DbSet<File> Files { get; set; }
+ public DbSet<Paste> Pastes { get; set; }
+}
+
+public class UploadEntityBase
+{
+ public UploadEntityBase() {
+ Id = Guid.NewGuid();
+ CreatedAt = DateTime.UtcNow;
+ }
+
+ public Guid Id { get; set; }
+ public string PublicId { get; set; }
+ public DateTime CreatedAt { get; set; }
+ public string CreatedBy { get; set; }
+ public DateTime? DeletedAt { get; set; }
+ public string? PasswordHash { get; set; }
+ public bool Singleton { get; set; }
+ public string? AutoDeleteAfter { get; set; }
+ public string? MimeType { get; set; }
+}
+
+public class File : UploadEntityBase
+{
+ public string? Name { get; set; }
+ public long Length { get; set; }
+}
+
+public class Paste : UploadEntityBase
+{
+ public string? Name { get; set; }
+ public string? Content { get; set; }
+ public long Length { get; set; }
+} \ No newline at end of file
diff --git a/src/Dockerfile b/src/Dockerfile
new file mode 100644
index 0000000..9b1f38e
--- /dev/null
+++ b/src/Dockerfile
@@ -0,0 +1,20 @@
+FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
+WORKDIR /app
+EXPOSE 80
+EXPOSE 443
+
+FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
+WORKDIR /src
+COPY ["BlobBin/BlobBin.csproj", "BlobBin/"]
+RUN dotnet restore "BlobBin/BlobBin.csproj"
+COPY . .
+WORKDIR "/src/BlobBin"
+RUN dotnet build "BlobBin.csproj" -c Release -o /app/build
+
+FROM build AS publish
+RUN dotnet publish "BlobBin.csproj" -c Release -o /app/publish
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "BlobBin.dll"]
diff --git a/src/Migrations/20230112230354_InitialCreate.Designer.cs b/src/Migrations/20230112230354_InitialCreate.Designer.cs
new file mode 100644
index 0000000..b5a06c3
--- /dev/null
+++ b/src/Migrations/20230112230354_InitialCreate.Designer.cs
@@ -0,0 +1,114 @@
+// <auto-generated />
+using System;
+using BlobBin;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace BlobBin.Migrations
+{
+ [DbContext(typeof(DB))]
+ [Migration("20230112230354_InitialCreate")]
+ partial class InitialCreate
+ {
+ /// <inheritdoc />
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "7.0.2");
+
+ modelBuilder.Entity("BlobBin.File", b =>
+ {
+ b.Property<Guid>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property<string>("AutoDeleteAfter")
+ .HasColumnType("TEXT");
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("CreatedBy")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("TEXT");
+
+ b.Property<long>("Length")
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("MimeType")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Name")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("PasswordHash")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("PublicId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property<bool>("Singleton")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.ToTable("Files");
+ });
+
+ modelBuilder.Entity("BlobBin.Paste", b =>
+ {
+ b.Property<Guid>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property<string>("AutoDeleteAfter")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Content")
+ .HasColumnType("TEXT");
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("CreatedBy")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("TEXT");
+
+ b.Property<long>("Length")
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("MimeType")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Name")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("PasswordHash")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("PublicId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property<bool>("Singleton")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.ToTable("Pastes");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Migrations/20230112230354_InitialCreate.cs b/src/Migrations/20230112230354_InitialCreate.cs
new file mode 100644
index 0000000..97f9c36
--- /dev/null
+++ b/src/Migrations/20230112230354_InitialCreate.cs
@@ -0,0 +1,68 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace BlobBin.Migrations
+{
+ /// <inheritdoc />
+ public partial class InitialCreate : Migration
+ {
+ /// <inheritdoc />
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "Files",
+ columns: table => new
+ {
+ Id = table.Column<Guid>(type: "TEXT", nullable: false),
+ Name = table.Column<string>(type: "TEXT", nullable: true),
+ Length = table.Column<long>(type: "INTEGER", nullable: false),
+ PublicId = table.Column<string>(type: "TEXT", nullable: false),
+ CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false),
+ CreatedBy = table.Column<string>(type: "TEXT", nullable: false),
+ DeletedAt = table.Column<DateTime>(type: "TEXT", nullable: true),
+ PasswordHash = table.Column<string>(type: "TEXT", nullable: true),
+ Singleton = table.Column<bool>(type: "INTEGER", nullable: false),
+ AutoDeleteAfter = table.Column<string>(type: "TEXT", nullable: true),
+ MimeType = table.Column<string>(type: "TEXT", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Files", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Pastes",
+ columns: table => new
+ {
+ Id = table.Column<Guid>(type: "TEXT", nullable: false),
+ Name = table.Column<string>(type: "TEXT", nullable: true),
+ Content = table.Column<string>(type: "TEXT", nullable: true),
+ Length = table.Column<long>(type: "INTEGER", nullable: false),
+ PublicId = table.Column<string>(type: "TEXT", nullable: false),
+ CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false),
+ CreatedBy = table.Column<string>(type: "TEXT", nullable: false),
+ DeletedAt = table.Column<DateTime>(type: "TEXT", nullable: true),
+ PasswordHash = table.Column<string>(type: "TEXT", nullable: true),
+ Singleton = table.Column<bool>(type: "INTEGER", nullable: false),
+ AutoDeleteAfter = table.Column<string>(type: "TEXT", nullable: true),
+ MimeType = table.Column<string>(type: "TEXT", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Pastes", x => x.Id);
+ });
+ }
+
+ /// <inheritdoc />
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "Files");
+
+ migrationBuilder.DropTable(
+ name: "Pastes");
+ }
+ }
+}
diff --git a/src/Migrations/DBModelSnapshot.cs b/src/Migrations/DBModelSnapshot.cs
new file mode 100644
index 0000000..8c9fb26
--- /dev/null
+++ b/src/Migrations/DBModelSnapshot.cs
@@ -0,0 +1,111 @@
+// <auto-generated />
+using System;
+using BlobBin;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace BlobBin.Migrations
+{
+ [DbContext(typeof(DB))]
+ partial class DBModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "7.0.2");
+
+ modelBuilder.Entity("BlobBin.File", b =>
+ {
+ b.Property<Guid>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property<string>("AutoDeleteAfter")
+ .HasColumnType("TEXT");
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("CreatedBy")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("TEXT");
+
+ b.Property<long>("Length")
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("MimeType")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Name")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("PasswordHash")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("PublicId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property<bool>("Singleton")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.ToTable("Files");
+ });
+
+ modelBuilder.Entity("BlobBin.Paste", b =>
+ {
+ b.Property<Guid>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property<string>("AutoDeleteAfter")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Content")
+ .HasColumnType("TEXT");
+
+ b.Property<DateTime>("CreatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("CreatedBy")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property<DateTime?>("DeletedAt")
+ .HasColumnType("TEXT");
+
+ b.Property<long>("Length")
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("MimeType")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Name")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("PasswordHash")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("PublicId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property<bool>("Singleton")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.ToTable("Pastes");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Program.cs b/src/Program.cs
new file mode 100644
index 0000000..01ca76b
--- /dev/null
+++ b/src/Program.cs
@@ -0,0 +1,120 @@
+global using BlobBin;
+using IOL.Helpers;
+using Microsoft.EntityFrameworkCore;
+using File = BlobBin.File;
+
+var builder = WebApplication.CreateBuilder(args);
+builder.Services.AddDbContext<DB>(opt => opt.UseSqlite("data source=main.db"));
+var app = builder.Build();
+
+app.UseFileServer();
+app.UseStatusCodePages();
+app.MapGet("/upload-link", GetUploadLink);
+app.MapPost("/upload/{id}", UploadBig);
+app.MapPost("/upload", UploadSimple);
+app.MapPost("/text", UploadText);
+app.MapGet("/b/{id}", GetBlob);
+app.Run();
+
+IResult GetUploadLink(HttpContext context, DB db) {
+ var file = new File {
+ CreatedBy = context.Request.Headers["X-Forwarded-For"].ToString()
+ };
+ db.Files.Add(file);
+ db.SaveChanges();
+ return Results.Text(
+ context.Request.GetRequestHost()
+ + "/upload/"
+ + file.Id
+ );
+}
+
+async Task<IResult> UploadSimple(HttpContext context, DB db) {
+ if (!context.Request.Form.Files.Any()) {
+ return Results.BadRequest("No files was found in request");
+ }
+
+ var file = new File {
+ CreatedBy = context.Request.Headers["X-Forwarded-For"].ToString(),
+ Singleton = context.Request.Form["singleton"] == "on",
+ AutoDeleteAfter = context.Request.Form["autoDeleteAfter"],
+ Length = context.Request.Form.Files[0].Length,
+ Name = context.Request.Form.Files[0].FileName,
+ MimeType = context.Request.Form.Files[0].ContentType,
+ PublicId = GetUnusedBlobId(db)
+ };
+
+ if (context.Request.Form["password"].ToString().HasValue()) {
+ file.PasswordHash = PasswordHelper.HashPassword(context.Request.Form["password"]);
+ }
+
+
+ await using var write = System.IO.File.OpenWrite(
+ Path.Combine(GetFilesDirectoryPath(), file.Id.ToString())
+ );
+ await context.Request.Form.Files[0].CopyToAsync(write);
+ db.Files.Add(file);
+ db.SaveChanges();
+ return Results.Text(
+ context.Request.GetRequestHost()
+ + "/b/"
+ + file.PublicId
+ );
+}
+
+IResult UploadBig(HttpContext context, DB db) {
+ return Results.Ok();
+}
+
+IResult UploadText(HttpContext context, DB db) {
+ return Results.Ok();
+}
+
+async Task<IResult> GetBlob(string id, DB db) {
+ var file = db.Files.FirstOrDefault(c => c.PublicId == id.Trim());
+ if (file == default) return Results.NotFound();
+ var reader = await System.IO.File.ReadAllBytesAsync(
+ Path.Combine(
+ GetFilesDirectoryPath(), file.Id.ToString()
+ )
+ );
+ return Results.File(reader, file.MimeType, file.Name);
+}
+
+string GetFilesDirectoryPath() {
+ var filesDirectoryPath = Path.Combine(
+ Directory.GetCurrentDirectory(),
+ "AppData",
+ "files"
+ );
+ Directory.CreateDirectory(filesDirectoryPath);
+ return filesDirectoryPath;
+}
+
+string GetUnusedBlobId(DB db) {
+ string id() => RandomString.Generate(3);
+ var res = id();
+ while (db.Files.Any(c => c.PublicId == res)) {
+ res = id();
+ }
+
+ return res;
+}
+
+class BlobBase
+{
+ public string Password { get; set; }
+ public bool Singleton { get; set; }
+ public string AutoDeleteAfter { get; set; }
+}
+
+class PasteRequest : BlobBase
+{
+ public string Text { get; set; }
+ public string Mime { get; set; }
+}
+
+class UploadRequest : BlobBase
+{
+ public IFormFile? File { get; set; }
+} \ No newline at end of file
diff --git a/src/Properties/launchSettings.json b/src/Properties/launchSettings.json
new file mode 100644
index 0000000..e19510b
--- /dev/null
+++ b/src/Properties/launchSettings.json
@@ -0,0 +1,15 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": false,
+ "launchUrl": "",
+ "applicationUrl": "http://localhost:5033",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/src/wwwroot/index.html b/src/wwwroot/index.html
new file mode 100644
index 0000000..fa25951
--- /dev/null
+++ b/src/wwwroot/index.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <style>
+ body {
+ font-family: sans-serif;
+ }
+
+ form {
+ width: 100%;
+ max-width: 300px;
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+ }
+
+ #forms {
+ display: flex;
+ flex-direction: row;
+ gap: 15px;
+ flex-wrap: wrap;
+ }
+
+ #forms summary {
+ width: 300px;
+ cursor: pointer;
+ }
+ </style>
+ <title>Blobbin</title>
+</head>
+<body>
+<h1>Blobbin</h1>
+<p>This is a web service you can upload files and texts to.</p>
+<main id="forms">
+ <details>
+ <summary>Upload a file</summary>
+ <form action="/upload" enctype="multipart/form-data" method="post">
+ <input type="file" id="file" name="files" required>
+ <label for="file-password">Password (optional)</label>
+ <input type="password" name="password" id="file-password">
+ <label for="file-auto-delete">
+ Automatically delete after (optional)
+ <span class="label-description"
+ title="blank=never, <number><unit>, unit can be d=day,w=week,h=hour,m=minute">?</span>
+ </label>
+ <input type="text"
+ id="file-auto-delete"
+ name="autoDeleteAfter">
+ <label for="file-singleton">
+ <input type="checkbox" name="singleton" id="file-singleton">
+ Delete after first open</label>
+ <input type="submit">
+ </form>
+ </details>
+ <details>
+ <summary>Upload some text</summary>
+ <form action="/text" method="post">
+ <textarea id="text" name="text" required></textarea>
+ <label for="text-password">Mimetype (default: text/plain)</label>
+ <input type="password" name="mime" id="text-mimetype">
+ <label for="text-password">Password (optional)</label>
+ <input type="password" name="password" id="text-password">
+ <label for="text-auto-delete">
+ Automatically delete after (optional)
+ <span class="label-description"
+ title="blank=never, <number><unit>, unit can be d=day,w=week,h=hour,m=minute">?</span>
+ </label>
+ <input type="text"
+ id="text-auto-delete"
+ name="autoDeleteAfter">
+ <label for="text-singleton">
+ <input type="checkbox" id="text-singleton" name="singleton">
+ Delete after first open</label>
+ <input type="submit">
+ </form>
+ </details>
+</main>
+</body>
+</html> \ No newline at end of file