From 0ae5a68a9d81547bb9b741458d94b5f1b7374027 Mon Sep 17 00:00:00 2001 From: ivarlovlie Date: Fri, 13 Jan 2023 20:21:20 +0100 Subject: feat: First dev release --- src/BlobBin.csproj | 25 +++++ src/DB.cs | 42 ++++++++ src/Dockerfile | 20 ++++ .../20230112230354_InitialCreate.Designer.cs | 114 ++++++++++++++++++++ src/Migrations/20230112230354_InitialCreate.cs | 68 ++++++++++++ src/Migrations/DBModelSnapshot.cs | 111 +++++++++++++++++++ src/Program.cs | 120 +++++++++++++++++++++ src/Properties/launchSettings.json | 15 +++ src/wwwroot/index.html | 80 ++++++++++++++ 9 files changed, 595 insertions(+) create mode 100644 src/BlobBin.csproj create mode 100644 src/DB.cs create mode 100644 src/Dockerfile create mode 100644 src/Migrations/20230112230354_InitialCreate.Designer.cs create mode 100644 src/Migrations/20230112230354_InitialCreate.cs create mode 100644 src/Migrations/DBModelSnapshot.cs create mode 100644 src/Program.cs create mode 100644 src/Properties/launchSettings.json create mode 100644 src/wwwroot/index.html (limited to 'src') 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 @@ + + + + net7.0 + enable + enable + Linux + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + .dockerignore + + + + 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 options) : base(options) { } + + public DbSet Files { get; set; } + public DbSet 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 @@ +// +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 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.2"); + + modelBuilder.Entity("BlobBin.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AutoDeleteAfter") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("Length") + .HasColumnType("INTEGER"); + + b.Property("MimeType") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PublicId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Singleton") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); + + modelBuilder.Entity("BlobBin.Paste", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AutoDeleteAfter") + .HasColumnType("TEXT"); + + b.Property("Content") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("Length") + .HasColumnType("INTEGER"); + + b.Property("MimeType") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PublicId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("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 +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Files", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: true), + Length = table.Column(type: "INTEGER", nullable: false), + PublicId = table.Column(type: "TEXT", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + CreatedBy = table.Column(type: "TEXT", nullable: false), + DeletedAt = table.Column(type: "TEXT", nullable: true), + PasswordHash = table.Column(type: "TEXT", nullable: true), + Singleton = table.Column(type: "INTEGER", nullable: false), + AutoDeleteAfter = table.Column(type: "TEXT", nullable: true), + MimeType = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Files", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Pastes", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: true), + Content = table.Column(type: "TEXT", nullable: true), + Length = table.Column(type: "INTEGER", nullable: false), + PublicId = table.Column(type: "TEXT", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + CreatedBy = table.Column(type: "TEXT", nullable: false), + DeletedAt = table.Column(type: "TEXT", nullable: true), + PasswordHash = table.Column(type: "TEXT", nullable: true), + Singleton = table.Column(type: "INTEGER", nullable: false), + AutoDeleteAfter = table.Column(type: "TEXT", nullable: true), + MimeType = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Pastes", x => x.Id); + }); + } + + /// + 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 @@ +// +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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AutoDeleteAfter") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("Length") + .HasColumnType("INTEGER"); + + b.Property("MimeType") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PublicId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Singleton") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); + + modelBuilder.Entity("BlobBin.Paste", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AutoDeleteAfter") + .HasColumnType("TEXT"); + + b.Property("Content") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("Length") + .HasColumnType("INTEGER"); + + b.Property("MimeType") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PublicId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("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(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 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 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 @@ + + + + + + Blobbin + + +

Blobbin

+

This is a web service you can upload files and texts to.

+
+
+ Upload a file +
+ + + + + + + +
+
+
+ Upload some text +
+ + + + + + + + + +
+
+
+ + \ No newline at end of file -- cgit v1.3