diff of ca95f9260092899b8ed6d7bf60137ad2df1e5adc
ca95f9260092899b8ed6d7bf60137ad2df1e5adc
diff --git a/aggressive-murja.asd b/aggressive-murja.asd
index 3e1a42a..4703adc 100644
--- a/aggressive-murja.asd
+++ b/aggressive-murja.asd
@@ -42,7 +42,8 @@
(:module "routes"
:components
- ((:file "login-routes")
+ ((:file "settings-routes")
+ (:file "login-routes")
(:file "post-routes")
(:file "media-routes")
(:file "root-routes")))
diff --git a/elm-frontti/src/Ajax_cmds.elm b/elm-frontti/src/Ajax_cmds.elm
index 57b7e00..a697a05 100644
--- a/elm-frontti/src/Ajax_cmds.elm
+++ b/elm-frontti/src/Ajax_cmds.elm
@@ -32,12 +32,16 @@ getPost post_id =
{ url = "/api/posts/post/" ++ (String.fromInt post_id)
, expect = Http.expectJson PostReceived Article.articleDecoder}
-getSettings : Cmd Msg
getSettings =
Http.get
{ url = "/api/settings/client-settings"
, expect = Http.expectJson SettingsReceived Settings.settingsDecoder}
+getSettingsAdmin =
+ Http.get
+ { url = "/api/settings/client-settings"
+ , expect = Http.expectJson AdminSettingsReceived Settings.settingsDecoder}
+
getTitles =
Http.get
{ url = "/api/posts/titles"
@@ -113,3 +117,13 @@ generateNewPost =
, timeout = Nothing
, tracker = Nothing
}
+
+saveSettings settings =
+ Http.request
+ { method = "PUT"
+ , headers = []
+ , url = "/api/settings/client-settings"
+ , body = Http.jsonBody (Settings.encodeSettings settings)
+ , expect = Http.expectWhatever SettingsSaved
+ , timeout = Nothing
+ , tracker = Nothing}
diff --git a/elm-frontti/src/Main.elm b/elm-frontti/src/Main.elm
index 212c705..14caebf 100644
--- a/elm-frontti/src/Main.elm
+++ b/elm-frontti/src/Main.elm
@@ -18,6 +18,7 @@ import User
import Topbar
import PostsAdmin
import PostEditor
+import SettingsEditor
import Medialist exposing (medialist)
import Image
import ImageSelector exposing (imageSelector)
@@ -64,7 +65,7 @@ subscriptions _ = Sub.batch
[ tags ReceivedTag
, aceStateUpdate AceStateUpdate]
-initialModel url key viewstate = Model viewstate Nothing False False [] Nothing LoggedOut key url Nothing Time.utc Nothing []
+initialModel url key viewstate = Model viewstate Nothing False False [] Nothing LoggedOut key url Nothing Time.utc []
viewStatePerUrl : Url.Url -> (ViewState, List (Cmd Msg))
viewStatePerUrl url =
@@ -106,6 +107,9 @@ viewStatePerUrl url =
, loadPostVersion post_id version_id])
RouteParser.NotFound -> (ShowError ("Couldn't parse url " ++ (Url.toString url)), [Cmd.none])
+ RouteParser.SettingsEditor -> (Loading, [ getSession
+ , getSettingsAdmin
+ , getTitles])
init _ url key =
let (viewstate, cmds) = (viewStatePerUrl url)
@@ -429,6 +433,53 @@ update msg model =
{settings | article = toggleUnlisted settings.article})
model.postEditorSettings}
, Cmd.none)
+ AdminSettingsReceived result ->
+ case result of
+ Ok new_settings ->
+ ({model
+ | settings = Just new_settings
+ , view_state = SettingsEditor}, Cmd.none)
+
+ Err http_error ->
+ ( model
+ , alert ("Error loading settings " ++ Debug.toString http_error))
+ SetTimeFormat tf ->
+ ({ model | settings = Maybe.map (\settings ->
+ { settings | time_format = tf})
+ model.settings}
+ , Cmd.none)
+ SetBlogTitle title ->
+ ({ model | settings = Maybe.map (\settings ->
+ { settings | blog_title = title})
+ model.settings}
+ , Cmd.none)
+ SetPageSize pg_size ->
+ case String.toInt pg_size of
+ Just page_size ->
+ ({ model | settings = Maybe.map (\settings ->
+ { settings | recent_post_count = page_size})
+ model.settings}
+ , Cmd.none)
+ Nothing ->
+ ( model
+ , alert "Page size should be a number")
+ SaveSettings ->
+ case model.settings of
+ Just settings ->
+ ( model
+ , saveSettings settings)
+ Nothing ->
+ ( model
+ , Cmd.none)
+ SettingsSaved result ->
+ case result of
+ Ok _ ->
+ ( model
+ , Cmd.none)
+
+ Err http_error ->
+ ( model
+ , alert ("Error saving settings " ++ Debug.toString http_error))
doGoHome_ model other_cmds =
(model, Cmd.batch (List.append [ getSettings
@@ -517,7 +568,8 @@ view model =
tag_index = editorSettings.selected_tag in
PostEditor.postEditor post tag_index model.showImageModal model.loadedImages model.draggingImages editorSettings settings model.zone model.loginState
Nothing -> [ div [] [ text "No post loaded" ]]
- MediaList -> [ medialist model.loadedImages model.medialist_state ])
+ MediaList -> [ medialist model.loadedImages model.medialist_state ]
+ SettingsEditor -> [ SettingsEditor.editor settings])
, div [id "sidebar"] [ User.loginView model.loginState
, (sidebarHistory model.titles )
, (case model.view_state of
diff --git a/elm-frontti/src/Message.elm b/elm-frontti/src/Message.elm
index 524504e..8eb44b0 100644
--- a/elm-frontti/src/Message.elm
+++ b/elm-frontti/src/Message.elm
@@ -28,6 +28,7 @@ type ViewState
| PostEditor
| MediaList -- list all the image blobs in db
| TaggedPostsView (List Article.Article)
+ | SettingsEditor
type alias User =
{ username : String
@@ -71,9 +72,7 @@ type alias Model =
, url : Url.Url
, postEditorSettings: Maybe PostEditorSettings
, zone : Time.Zone
- , postFromLocalStorage : Maybe Article.Article
- , titles : List Article.Title
- }
+ , titles : List Article.Title }
type Msg
= PageReceived (Result Http.Error P.Page)
@@ -125,14 +124,16 @@ type Msg
| NewPostGenerated (Result Http.Error Int)
| ToggleArticleUnlisted
| ToggleArticleHidden
-
-
+ | AdminSettingsReceived (Result Http.Error Settings.Settings)
+ | SetTimeFormat String
+ | SetBlogTitle String
+ | SetPageSize String
+ | SaveSettings
+ | SettingsSaved (Result Http.Error ())
-- ports
port reallySetupAce : String -> Cmd msg
port addImgToAce : String -> Cmd msg
-port clearPostFromLS : () -> Cmd msg
-
-- dumb shit that would deserve its own module
dangerouslySetInnerHTML: String -> Html.Attribute msg
diff --git a/elm-frontti/src/RouteParser.elm b/elm-frontti/src/RouteParser.elm
index 0a0dbb4..2ea17a7 100644
--- a/elm-frontti/src/RouteParser.elm
+++ b/elm-frontti/src/RouteParser.elm
@@ -12,6 +12,7 @@ type Route
| PostEditor Int
| TaggedPosts String
| PostVersion Int Int
+ | SettingsEditor
| Home
| NotFound
@@ -24,6 +25,7 @@ routeParser =
, map Post (s "blog" </> (s "post" </> int))
, map PostEditor (s "blog" </> (s "post" </> (s "edit" </> int)))
, map MediaManager (s "blog" </> (s "mediamanager"))
+ , map SettingsEditor (s "blog" </> (s "settings"))
, map TaggedPosts (s "blog" </> (s "tags" </> string))
, map PostAdmin (s "blog" </> (s "postadmin"))]
diff --git a/elm-frontti/src/Settings.elm b/elm-frontti/src/Settings.elm
index d3d4f34..900aa19 100644
--- a/elm-frontti/src/Settings.elm
+++ b/elm-frontti/src/Settings.elm
@@ -1,29 +1,22 @@
--- {
--- "time-format": "dd.MM.yyyy HH:mm",
--- "blog-title": "Murja.dev @ roland",
--- "recent-post-count": 6,
--- "xss-filter-posts?": false
--- }
-
-
module Settings exposing (..)
import Json.Decode as Decode exposing (Decoder, succeed)
import Json.Decode.Pipeline exposing (required)
import Json.Decode.Extra as Extra
-
-import Article
+import Json.Encode as Json exposing (..)
type alias Settings =
{ time_format : String
, blog_title : String
- , recent_post_count : Int
- , xss_filter_posts : Bool
- }
+ , recent_post_count : Int}
-settingsDecoder = Decode.map4 Settings
+settingsDecoder = Decode.map3 Settings
(Decode.field "time-format" Decode.string)
(Decode.field "blog-title" Decode.string)
(Decode.field "recent-post-count" Decode.int)
- (Decode.field "xss-filter-posts?" Decode.bool)
+encodeSettings settings =
+ object
+ [ ( "time-format", string settings.time_format )
+ , ( "blog-title", string settings.blog_title)
+ , ( "recent-post-count", int settings.recent_post_count)]
diff --git a/elm-frontti/src/SettingsEditor.elm b/elm-frontti/src/SettingsEditor.elm
new file mode 100644
index 0000000..226aa6c
--- /dev/null
+++ b/elm-frontti/src/SettingsEditor.elm
@@ -0,0 +1,32 @@
+module SettingsEditor exposing (..)
+
+import Html exposing (..)
+import Html.Attributes exposing (..)
+import Html.Events exposing (..)
+import Json.Decode as D
+
+import Message exposing (..)
+
+editor settings =
+ div [ class "form-grid" ]
+ [ label [ for "time-format"]
+ [ text "Time format "]
+ , input [ id "time-format"
+ , onInput SetTimeFormat
+ , value settings.time_format] []
+
+ , label [ for "title" ]
+ [ text "Title" ]
+ , input [ id "title"
+ , onInput SetBlogTitle
+ , value settings.blog_title] []
+
+ , label [ for "page_size" ]
+ [ text "Page size (posts)"]
+ , input [ id "page_size"
+ , onInput SetPageSize
+ , value (String.fromInt settings.recent_post_count)
+ , type_ "number"] []
+
+ , button [ onClick SaveSettings ]
+ [ text "Save settings"]]
diff --git a/elm-frontti/src/Topbar.elm b/elm-frontti/src/Topbar.elm
index af39075..a8e0993 100644
--- a/elm-frontti/src/Topbar.elm
+++ b/elm-frontti/src/Topbar.elm
@@ -13,18 +13,23 @@ import Browser.Navigation as Nav
import Button exposing (murja_button)
+topbar_list =
+ [ li [] [ murja_button [ onClick GoHome, attribute "data-testid" "home"]
+ [text "Home"]]
+ , li [] [ murja_button [ onClick (PushUrl "/blog/postadmin"), attribute "data-testid" "manage-posts-btn" ]
+ [text "Manage posts"]]
+ , li [] [ murja_button [ onClick (PushUrl "/blog/mediamanager")]
+ [text "Manage media"]]
+ , li [] [ murja_button [ onClick (PushUrl "/blog/settings")]
+ [ text "Settings" ]]
+ , li [] [ murja_button [ onClick GenNewPost
+ , attribute "data-testid" "new-post-btn" ]
+ [text "New post!"]]]
+
topbar state =
case state of
LoggedIn user ->
div [class "left-sidebar"] [ span [] [text ("Welcome, " ++ user.nickname)]
, User.user_avatar user
- , ul [] [ li [] [ murja_button [ onClick GoHome, attribute "data-testid" "home"]
- [text "Home"]]
- , li [] [ murja_button [ onClick (PushUrl "/blog/postadmin"), attribute "data-testid" "manage-posts-btn" ]
- [text "Manage posts"]]
- , li [] [ murja_button [ onClick (PushUrl "/blog/mediamanager")]
- [text "Manage media"]]
- , li [] [ murja_button [ onClick GenNewPost
- , attribute "data-testid" "new-post-btn" ]
- [text "New post!"]]]]
+ , ul [] topbar_list ]
_ -> div [] []
diff --git a/resources/css/murja.css b/resources/css/murja.css
index 6e5bac7..2e5eb73 100644
--- a/resources/css/murja.css
+++ b/resources/css/murja.css
@@ -249,6 +249,22 @@ header {
display: block;
}
+.form-grid
+{
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 5px;
+ height: fit-content;
+}
+
+.form-grid label {
+ grid-column: 1;
+}
+
+.form-grid input {
+ grid-column: 2;
+}
+
@media only screen and (max-device-width:480px)
{
diff --git a/resources/sql/017-settings-in-db.sql b/resources/sql/017-settings-in-db.sql
new file mode 100644
index 0000000..caf1362
--- /dev/null
+++ b/resources/sql/017-settings-in-db.sql
@@ -0,0 +1,16 @@
+CREATE TABLE IF NOT EXISTS blog.Settings
+(
+ key TEXT NOT NULL PRIMARY KEY,
+ value JSONB NOT NULL
+);
+
+INSERT INTO blog.Settings VALUES ('time-format', '"dd.MM.yyyy HH:mm"'),
+ ('blog-title', '"Murja.dev @ $HOSTNAME"'),
+ ('recent-post-count', '6')
+ON CONFLICT DO NOTHING;
+--for reasons unknown SERIAL is broken
+-- but then, the id is supposed to be stable, so this should be fine
+INSERT INTO blog.Permission (id, action) VALUES (13, 'update-settings') ON CONFLICT DO NOTHING;
+
+INSERT INTO blog.GroupPermissions VALUES ((select id from blog.Permission where action = 'update-settings')
+ , (select id from blog.UserGroup where name = 'Admins')) ON CONFLICT DO NOTHING;
diff --git a/src/migration-list.lisp b/src/migration-list.lisp
index 9e8321e..740fbf7 100644
--- a/src/migration-list.lisp
+++ b/src/migration-list.lisp
@@ -21,6 +21,7 @@
(defmigration "014-tag-hidden-unlisted-validator.up")
(defmigration "015-image-post-pairing-view.up")
(defmigration "016-hardcoded-hidden-unlisted")
+(defmigration "017-settings-in-db")
(defun prepare-e2e-migration ()
(postmodern:execute "DELETE FROM blog.Users")
diff --git a/src/routes/root-routes.lisp b/src/routes/root-routes.lisp
index 43bf373..1efabb1 100644
--- a/src/routes/root-routes.lisp
+++ b/src/routes/root-routes.lisp
@@ -50,10 +50,6 @@
((string= type "css") "text/css")
(t (error 'unknown-mime :file-type type)))))
-(defroute client-settings ("/api/settings/client-settings" :method :get
- :decorators (@json)) ()
- "{\"time-format\":\"dd.MM.yyyy HH:mm\",\"blog-title\":\"Murja.dev @ $HOSTNAME\",\"recent-post-count\":6,\"xss-filter-posts?\":false}")
-
(defun get-resource (file)
(let ((path (gethash file *allowed-resources*)))
(if path
@@ -115,3 +111,6 @@
(defroute sdkfpsokopfs ("/blog/post/:post/version/:ver" :method :get) ()
*root*)
+
+(defroute ddddddd ("/blog/settings" :method :get) ()
+ *root*)
diff --git a/src/routes/settings-routes.lisp b/src/routes/settings-routes.lisp
new file mode 100644
index 0000000..593b40f
--- /dev/null
+++ b/src/routes/settings-routes.lisp
@@ -0,0 +1,35 @@
+(defpackage murja.routes.settings-routes
+ (:use :cl)
+ (:import-from :com.inuoe.jzon :stringify :parse)
+ (:import-from :murja.middleware.auth :@authenticated :*user* :@can?)
+ (:import-from :murja.middleware.json :@json)
+ (:import-from :murja.middleware.db :@transaction)
+ (:import-from :easy-routes :defroute))
+
+(in-package :murja.routes.settings-routes)
+
+(defroute client-settings ("/api/settings/client-settings" :method :get
+ :decorators (@transaction
+ @json)) ()
+ (com.inuoe.jzon:stringify
+ (reduce (lambda (acc pair)
+ (destructuring-bind (k v) pair
+ (setf (gethash k acc) (com.inuoe.jzon:parse v))
+ acc))
+ (postmodern:query "SELECT key, value FROM blog.Settings")
+
+ :initial-value (make-hash-table))))
+
+(defroute update-setting ("/api/settings/client-settings" :method :put
+ :decorators (@transaction
+ @json
+ @authenticated
+ (@can? "update-settings"))) ()
+ (let ((req (alexandria:hash-table-alist
+ (parse (hunchentoot:raw-post-data :force-text t)))))
+ (dolist (p req)
+ (destructuring-bind (k . v) p
+ (format t "execute returned for ~a => ~a: ~a~%" k v
+ (postmodern:execute "UPDATE blog.Settings SET value = $2 WHERE key = $1" k (stringify v)))))
+ (setf (hunchentoot:return-code*) 204)
+ ""))