aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2024-01-22 12:20:57 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2024-01-22 12:20:57 -0500
commit03b6cee1ac70c01be5f97688b0af6da8cab84236 (patch)
treeaa994632faf48db087fdcfce714badb3074bb527
parent32553068b151712100826b6de6b0c68f7f46f9cc (diff)
optional auto-correct upload errors
-rw-r--r--back-end/src/Endpoints/BookmarkEndpoint.cs2
-rw-r--r--back-end/src/Model/BookmarkStore.cs36
-rw-r--r--ci/config/config.json3
-rw-r--r--ci/taskfile.yaml3
-rw-r--r--front-end/src/components/Bookmarks.vue34
5 files changed, 71 insertions, 7 deletions
diff --git a/back-end/src/Endpoints/BookmarkEndpoint.cs b/back-end/src/Endpoints/BookmarkEndpoint.cs
index b7825d6..ec46097 100644
--- a/back-end/src/Endpoints/BookmarkEndpoint.cs
+++ b/back-end/src/Endpoints/BookmarkEndpoint.cs
@@ -317,7 +317,7 @@ namespace SimpleBookmark.Endpoints
}
//Try to update the records
- ERRNO result = await Bookmarks.AddBulkAsync(sanitized, entity.Session.UserID, false, entity.EventCancellation);
+ ERRNO result = await Bookmarks.AddBulkAsync(sanitized, entity.Session.UserID, entity.RequestedTimeUtc, entity.EventCancellation);
webm.Result = $"Successfully added {result} of {batch.Length} bookmarks";
webm.Success = true;
diff --git a/back-end/src/Model/BookmarkStore.cs b/back-end/src/Model/BookmarkStore.cs
index fbd3213..8578976 100644
--- a/back-end/src/Model/BookmarkStore.cs
+++ b/back-end/src/Model/BookmarkStore.cs
@@ -19,10 +19,13 @@ using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using System.Collections.Generic;
+using VNLib.Utils;
using VNLib.Plugins.Extensions.Data;
-using VNLib.Plugins.Extensions.Data.Abstractions;
using VNLib.Plugins.Extensions.Loading;
+using VNLib.Plugins.Extensions.Data.Abstractions;
+
namespace SimpleBookmark.Model
{
@@ -113,6 +116,37 @@ namespace SimpleBookmark.Model
.ToArray();
}
+ public async Task<ERRNO> AddBulkAsync(IEnumerable<BookmarkEntry> bookmarks, string userId, DateTimeOffset now, CancellationToken cancellation)
+ {
+ //Init new db connection
+ await using SimpleBookmarkContext context = new(dbOptions.Value);
+ await context.OpenTransactionAsync(cancellation);
+
+ //Setup clean bookmark instances
+ bookmarks = bookmarks.Select(b => new BookmarkEntry
+ {
+ Id = GetNewRecordId(), //new uuid
+ UserId = userId, //Set userid
+ LastModified = now.DateTime,
+
+ //Allow reuse of created time
+ Created = b.Created,
+ Description = b.Description,
+ Name = b.Name,
+ Tags = b.Tags,
+ Url = b.Url,
+ });
+
+ //Filter out bookmarks that already exist
+ bookmarks = bookmarks.Where(b => !context.Bookmarks.Any(b2 => b2.UserId == userId && b2.Url == b.Url));
+
+ //Add bookmarks to db
+ context.AddRange(bookmarks);
+
+ //Commit transaction
+ return await context.SaveAndCloseAsync(true, cancellation);
+ }
+
private sealed class BookmarkQueryLookup : IDbQueryLookup<BookmarkEntry>
{
public IQueryable<BookmarkEntry> GetCollectionQueryBuilder(IDbContextHandle context, params string[] constraints)
diff --git a/ci/config/config.json b/ci/config/config.json
index c012728..4740cd3 100644
--- a/ci/config/config.json
+++ b/ci/config/config.json
@@ -124,7 +124,8 @@
"hot_reload": false,
"reload_delay_sec": 2,
"path": "plugins",
- "config_dir": "config"
+ "config_dir": "config",
+ "assets": "plugins/assets"
},
"disabled sys_log": {
diff --git a/ci/taskfile.yaml b/ci/taskfile.yaml
index 7f67fec..e59e080 100644
--- a/ci/taskfile.yaml
+++ b/ci/taskfile.yaml
@@ -19,7 +19,10 @@ tasks:
#clean out dist dir before building
- cmd: powershell -Command "rm -Recurse -Force ./dist"
ignore_error: true
+
#copy setup script for linux
+ - cmd: powershell -Command "mkdir lib -Force"
+ ignore_error: true
- cmd: powershell -Command "cp setup.sh lib/ -Force"
- task: install-plugins
diff --git a/front-end/src/components/Bookmarks.vue b/front-end/src/components/Bookmarks.vue
index 2a5a6d3..34c31a8 100644
--- a/front-end/src/components/Bookmarks.vue
+++ b/front-end/src/components/Bookmarks.vue
@@ -5,7 +5,7 @@ import { get, set, formatTimeAgo, useToggle, useTimestamp, useFileDialog, asyncC
import { useVuelidate } from '@vuelidate/core';
import { required, maxLength, minLength, helpers } from '@vuelidate/validators';
import { apiCall, useConfirm, useGeneralToaster, useVuelidateWrapper, useWait } from '@vnuge/vnlib.browser';
-import { clone, cloneDeep, join, defaultTo, every, filter, includes, isEmpty, isEqual, first, isString, chunk, map } from 'lodash-es';
+import { clone, cloneDeep, join, defaultTo, every, filter, includes, isEmpty, isEqual, first, isString, chunk, map, forEach } from 'lodash-es';
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
import { parseNetscapeBookmarkString } from './Boomarks/util.ts';
import type { Bookmark, BookmarkError } from '../store/bookmarks';
@@ -37,13 +37,13 @@ const addOrEditValidator = (buffer: Ref<Partial<Bookmark>>) => {
required: helpers.withMessage('Name cannot be empty', required),
safeName: helpers.withMessage('Bookmark name contains illegal characters', (value: string) => safeNameRegex.test(value)),
minLength: helpers.withMessage('Name must be at least 1 characters', minLength(1)),
- maxLength: helpers.withMessage('Name must have less than 128 characters', maxLength(128))
+ maxLength: helpers.withMessage('Name must have less than 100 characters', maxLength(100))
},
Url: {
required: helpers.withMessage('Url cannot be empty', required),
safeUrl: helpers.withMessage('Url contains illegal characters or is not a valid URL', (value: string) => safeUrlRegex.test(value)),
minLength: helpers.withMessage('Url must be at least 1 characters', minLength(1)),
- maxLength: helpers.withMessage('Url must have less than 128 characters', maxLength(128))
+ maxLength: helpers.withMessage('Url must have less than 200 characters', maxLength(200))
},
Description: {
maxLength: helpers.withMessage('Description must have less than 512 characters', maxLength(512))
@@ -222,6 +222,7 @@ const upload = (() => {
const file = computed(() => first(files.value));
const ignoreErrors = ref(false);
+ const fixErrors = ref(false);
const errors = ref<BookmarkError[]>([]);
const progress = ref<string[]>([]);
const progressPercent = ref(0);
@@ -259,6 +260,23 @@ const upload = (() => {
//parse the text into bookmarks
const bms = get(foundBookmarks);
+ if(get(fixErrors)){
+ //try to fix names
+ forEach(bms, bm => {
+ //If the name is not safe, replace all illegal characters
+ if(!safeNameRegex.test(bm.Name)){
+ bm.Name = bm.Name.replace(/[^a-zA-Z0-9_\-\|\. ]/g, ' ');
+ }
+ })
+
+ //truncate name length
+ forEach(bms, bm => {
+ if(bm.Name.length > 100){
+ bm.Name = bm.Name.substring(0, 100);
+ }
+ })
+ }
+
const chunks = chunk(bms, 20);
for(let i = 0; i < chunks.length; i++){
@@ -303,6 +321,7 @@ const upload = (() => {
open,
cancel,
submit,
+ fixErrors,
foundBookmarks,
progressPercent,
errors,
@@ -545,11 +564,18 @@ const upload = (() => {
</div>
</div>
<div class="flex flex-row items-center justify-between my-3 ">
- <div>
+ <div class="flex flex-row gap-4">
<div class="flex items-center">
<input id="ignore-errors" type="checkbox" v-model="upload.ignoreErrors" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded cursor-pointer focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
<label for="ignore-errors" class="text-sm font-medium ms-2">Ignore Errors</label>
</div>
+ <div class="flex items-center">
+ <input id="fix-errors" type="checkbox" v-model="upload.fixErrors" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded cursor-pointer focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
+ <label for="fix-errors" class="text-sm font-medium ms-2">Fix Errors</label>
+ </div>
+ </div>
+ <div>
+
</div>
<button type="submit" class="btn blue">
Upload