WIP on saving uploads (#2)
This commit is contained in:
parent
feada6f11f
commit
9307ace24a
@ -878,6 +878,15 @@ type SettingsModel =
|
||||
}
|
||||
|
||||
|
||||
/// View model for uploading a file
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
type UploadFileModel =
|
||||
{ /// The upload destination
|
||||
destination : string
|
||||
}
|
||||
|
||||
|
||||
/// A message displayed to the user
|
||||
[<CLIMutable; NoComparison; NoEquality>]
|
||||
type UserMessage =
|
||||
{ /// The level of the message
|
||||
|
@ -223,8 +223,8 @@ let register () =
|
||||
Template.RegisterTag<UserLinksTag> "user_links"
|
||||
|
||||
[ // Domain types
|
||||
typeof<CustomFeed>; typeof<Episode>; typeof<Episode option>; typeof<MetaItem>; typeof<Page>; typeof<RssOptions>
|
||||
typeof<TagMap>; typeof<WebLog>
|
||||
typeof<CustomFeed>; typeof<Episode>; typeof<Episode option>; typeof<MetaItem>; typeof<Page>
|
||||
typeof<RssOptions>; typeof<TagMap>; typeof<UploadDestination>; typeof<WebLog>
|
||||
// View models
|
||||
typeof<DashboardModel>; typeof<DisplayCategory>; typeof<DisplayCustomFeed>; typeof<DisplayPage>
|
||||
typeof<DisplayUpload>; typeof<EditCategoryModel>; typeof<EditCustomFeedModel>; typeof<EditPageModel>
|
||||
|
@ -107,11 +107,11 @@ module Asset =
|
||||
|
||||
/// The primary myWebLog router
|
||||
let router : HttpHandler = choose [
|
||||
GET >=> choose [
|
||||
GET_HEAD >=> choose [
|
||||
route "/" >=> Post.home
|
||||
]
|
||||
subRoute "/admin" (requireUser >=> choose [
|
||||
GET >=> choose [
|
||||
GET_HEAD >=> choose [
|
||||
subRoute "/categor" (choose [
|
||||
route "ies" >=> Admin.listCategories
|
||||
route "ies/bare" >=> Admin.listCategoriesBare
|
||||
@ -144,7 +144,8 @@ let router : HttpHandler = choose [
|
||||
])
|
||||
route "/theme/update" >=> Admin.themeUpdatePage
|
||||
subRoute "/upload" (choose [
|
||||
route "s" >=> Upload.list
|
||||
route "s" >=> Upload.list
|
||||
route "/new" >=> Upload.showNew
|
||||
])
|
||||
route "/user/edit" >=> User.edit
|
||||
]
|
||||
@ -176,6 +177,9 @@ let router : HttpHandler = choose [
|
||||
])
|
||||
])
|
||||
route "/theme/update" >=> Admin.updateTheme
|
||||
subRoute "/upload" (choose [
|
||||
route "/save" >=> Upload.save
|
||||
])
|
||||
route "/user/save" >=> User.save
|
||||
]
|
||||
])
|
||||
|
@ -71,9 +71,13 @@ let serve (urlParts : string seq) : HttpHandler = fun next ctx -> task {
|
||||
|
||||
// ADMIN
|
||||
|
||||
open System.Text.RegularExpressions
|
||||
open DotLiquid
|
||||
open MyWebLog.ViewModels
|
||||
|
||||
/// Turn a string into a lowercase URL-safe slug
|
||||
let makeSlug it = ((Regex """\s+""").Replace ((Regex "[^A-z0-9 ]").Replace (it, ""), "-")).ToLowerInvariant ()
|
||||
|
||||
// GET /admin/uploads
|
||||
let list : HttpHandler = fun next ctx -> task {
|
||||
let webLog = ctx.WebLog
|
||||
@ -114,3 +118,50 @@ let list : HttpHandler = fun next ctx -> task {
|
||||
|}
|
||||
|> viewForTheme "admin" "upload-list" next ctx
|
||||
}
|
||||
|
||||
// GET /admin/upload/new
|
||||
let showNew : HttpHandler = fun next ctx -> task {
|
||||
return!
|
||||
Hash.FromAnonymousObject {|
|
||||
csrf = csrfToken ctx
|
||||
destination = UploadDestination.toString ctx.WebLog.uploads
|
||||
page_title = "Upload a File"
|
||||
|}
|
||||
|> viewForTheme "admin" "upload-new" next ctx
|
||||
}
|
||||
|
||||
// POST /admin/upload/save
|
||||
let save : HttpHandler = fun next ctx -> task {
|
||||
if ctx.Request.HasFormContentType && ctx.Request.Form.Files.Count > 0 then
|
||||
let upload = Seq.head ctx.Request.Form.Files
|
||||
let fileName = String.Join ('.', makeSlug (Path.GetFileNameWithoutExtension upload.FileName),
|
||||
Path.GetExtension(upload.FileName).ToLowerInvariant ())
|
||||
let webLog = ctx.WebLog
|
||||
let localNow = WebLog.localTime webLog DateTime.Now
|
||||
let year = localNow.ToString "yyyy"
|
||||
let month = localNow.ToString "MM"
|
||||
let! form = ctx.BindFormAsync<UploadFileModel> ()
|
||||
|
||||
match UploadDestination.parse form.destination with
|
||||
| Database ->
|
||||
use stream = new MemoryStream ()
|
||||
do! upload.CopyToAsync stream
|
||||
let file =
|
||||
{ id = UploadId.create ()
|
||||
webLogId = webLog.id
|
||||
path = Permalink $"{year}/{month}/{fileName}"
|
||||
updatedOn = DateTime.UtcNow
|
||||
data = stream.ToArray ()
|
||||
}
|
||||
do! ctx.Data.Upload.add file
|
||||
| Disk ->
|
||||
let fullPath = Path.Combine ("wwwroot", webLog.slug, year, month)
|
||||
let _ = Directory.CreateDirectory fullPath
|
||||
use stream = new FileStream (Path.Combine (fullPath, fileName), FileMode.Create)
|
||||
do! upload.CopyToAsync stream
|
||||
|
||||
do! addMessage ctx { UserMessage.success with message = $"File uploaded to {form.destination} successfully" }
|
||||
return! redirectToGet (WebLog.relativeUrl ctx.WebLog (Permalink "admin/uploads")) next ctx
|
||||
else
|
||||
return! RequestErrors.BAD_REQUEST "Bad request; no file present" next ctx
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ module MyWebLog.Maintenance
|
||||
|
||||
open System
|
||||
open System.IO
|
||||
open System.Text.RegularExpressions
|
||||
open Microsoft.Extensions.DependencyInjection
|
||||
open MyWebLog.Data
|
||||
|
||||
@ -24,7 +23,7 @@ let private doCreateWebLog (args : string[]) (sp : IServiceProvider) = task {
|
||||
let webLogId = WebLogId.create ()
|
||||
let userId = WebLogUserId.create ()
|
||||
let homePageId = PageId.create ()
|
||||
let slug = ((Regex """\s+""").Replace ((Regex "[^A-z0-9 ]").Replace (args[2], ""), "-")).ToLowerInvariant ()
|
||||
let slug = Handlers.Upload.makeSlug args[2]
|
||||
|
||||
do! data.WebLog.add
|
||||
{ WebLog.empty with
|
||||
|
31
src/admin-theme/upload-new.liquid
Normal file
31
src/admin-theme/upload-new.liquid
Normal file
@ -0,0 +1,31 @@
|
||||
<h2>{{ page_title }}</h2>
|
||||
<article>
|
||||
<form action="{{ "admin/upload/save" | relative_link }}"
|
||||
method="post" class="container" enctype="multipart/form-data" hx-boost="false">
|
||||
<input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6 pb-3">
|
||||
<div class="form-floating">
|
||||
<input type="file" id="file" name="file" class="form-control" placeholder="File" required>
|
||||
<label for="file">File to Upload</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 pb-3 d-flex align-self-center justify-content-around">
|
||||
Destination<br>
|
||||
<div class="btn-group" role="group" aria-label="Upload destination button group">
|
||||
<input type="radio" name="destination" id="destination_db" class="btn-check" value="database"
|
||||
{%- if destination == "database" %} checked="checked"{% endif %}>
|
||||
<label class="btn btn-outline-primary" for="destination_db">Database</label>
|
||||
<input type="radio" name="destination" id="destination_disk" class="btn-check" value="disk"
|
||||
{%- if destination == "disk" %} checked="checked"{% endif %}>
|
||||
<label class="btn btn-outline-secondary" for="destination_disk">Disk</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row pb-3">
|
||||
<div class="col text-center">
|
||||
<button type="submit" class="btn btn-primary">Upload File</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</article>
|
Loading…
Reference in New Issue
Block a user