diff of 747a3de461479bc3d80f51e62be6e6351dd093f4
747a3de461479bc3d80f51e62be6e6351dd093f4
diff --git a/elm-frontti/src/Ajax_cmds.elm b/elm-frontti/src/Ajax_cmds.elm
index 72123e4..52492f6 100644
--- a/elm-frontti/src/Ajax_cmds.elm
+++ b/elm-frontti/src/Ajax_cmds.elm
@@ -6,7 +6,8 @@ import Page
import Feeds
import Message exposing (..)
import Http exposing (..)
-import Image as Image
+import Image
+import Initial
import Settings
import Logs
import Json.Decode as Json
@@ -211,3 +212,9 @@ submitUser user oldpasswd newpasswd =
{ url = "/api/user/submit"
, body = Http.jsonBody (User.encodeEditorUser user oldpasswd newpasswd)
, expect = Http.expectWhatever UserSubmitResult}
+
+postInitialData data =
+ Http.post
+ { url = "/api/initial"
+ , body = Http.jsonBody <| Initial.initialEncoder data
+ , expect = Http.expectWhatever PostInitialSuccess}
diff --git a/elm-frontti/src/Initial.elm b/elm-frontti/src/Initial.elm
new file mode 100644
index 0000000..2494c13
--- /dev/null
+++ b/elm-frontti/src/Initial.elm
@@ -0,0 +1,36 @@
+module Initial exposing (..)
+
+import Article exposing (decodeApply)
+import Json.Decode as Decode exposing (Decoder, succeed)
+import Json.Encode as Json
+
+type alias Initial =
+ { initial : Bool } -- http api can't return bare booleans (as "false"s are interpreted as 404 in the middlewares
+
+type alias InitialFormData =
+ { username : String
+ , nickname : String
+ , password : String
+ , domain : String
+ , blog_title : String
+ , rss_title : String
+ , rss_link : String
+ , rss_description : Maybe String
+ , rss_lang : String
+ , rss_email : String}
+
+initialDecoder = succeed Initial
+ |> decodeApply (Decode.field "initial" Decode.bool)
+
+initialEncoder i =
+ Json.object
+ [ ("username", Json.string i.username)
+ , ("nickname", Json.string i.nickname)
+ , ("password", Json.string i.password)
+ , ("domain", Json.string i.domain)
+ , ("blog_title", Json.string i.blog_title)
+ , ("rss_title", Json.string i.rss_title)
+ , ("rss_link", Json.string i.rss_link)
+ , ("rss_description", Json.string (Maybe.withDefault i.blog_title i.rss_description))
+ , ("rss_lang", Json.string i.rss_lang)
+ , ("rss_email", Json.string i.rss_email)]
diff --git a/elm-frontti/src/InitialForm.elm b/elm-frontti/src/InitialForm.elm
new file mode 100644
index 0000000..d7c524f
--- /dev/null
+++ b/elm-frontti/src/InitialForm.elm
@@ -0,0 +1,67 @@
+module InitialForm exposing (..)
+
+import Html exposing (..)
+import Html.Attributes exposing (..)
+import Html.Events exposing (onInput, onClick)
+
+import Message exposing (..)
+
+initialForm data =
+ div []
+ [ text "Welcome to your new Murja-blog. There doesn't seem to be any users in the db yet. Please provide the initial user- and rss-settings now. These can all be changed later in the ui."
+ , div [ class "initial-form" ]
+ [ h1 [] [ text "User settings"]
+ , span [] []
+ , h2 [] [ text "The initial admin user: " ]
+ , span [] []
+ , label [] [ text "Username (used when logging in): "
+ , input [ type_ "text"
+ , value data.username
+ , onInput SetInitialUsername ] []]
+ , label [] [ text "Nickname (shown in the ui): "
+ , input [ type_ "text"
+ , value data.nickname
+ , onInput SetInitialNickname ] []]
+ , label [] [ text "Password: "
+ , input [ type_ "password"
+ , value data.password
+ , onInput SetInitialPassword ] []]
+ , span [] []
+ , h1 [] [ text "Site settings"]
+ , span [] []
+ , label [] [ text "Domain (the dns name this site is reached by. This is used in session cookies. This should be left empty on dev.): "
+ , input [ type_ "text"
+ , value data.domain
+ , onInput SetInitialDomain ] []]
+ , label [] [ text "Blog's title: "
+ , input [ type_ "text"
+ , value data.blog_title
+ , onInput SetInitialBlog_Title ] []]
+
+ -- the stupid grid layout requires a few empty spans to lay h1 out into its own row
+ , h1 [] [ text "RSS-feed settings"]
+ , span [] []
+
+ , label [] [ text "Rss-feed's title: "
+ , input [ type_ "text"
+ , value data.rss_title
+ , onInput SetInitialRss_Title ] []]
+ , label [] [ text "Rss-feed's link: "
+ , input [ type_ "text"
+ , value data.rss_link
+ , onInput SetInitialRss_Link ] []]
+ , label [] [ text "Rss-feed's description: "
+ , input [ type_ "text"
+ , value <| Maybe.withDefault data.blog_title data.rss_description
+ , onInput SetInitialRss_Description ] []]
+ , label [] [ text "Rss-feed's language code: "
+ , input [ type_ "text"
+ , value data.rss_lang
+ , onInput SetInitialRss_Lang ] []]
+ , label [] [ text "Rss-feed's listed email contact address: "
+ , input [ type_ "text"
+ , value data.rss_email
+ , onInput SetInitialRss_Email ] []]]
+ , button
+ [ onClick <| SaveInitialData data ]
+ [ text "Start using murja"]]
diff --git a/elm-frontti/src/Main.elm b/elm-frontti/src/Main.elm
index 7cfa122..a672887 100644
--- a/elm-frontti/src/Main.elm
+++ b/elm-frontti/src/Main.elm
@@ -24,6 +24,7 @@ import Image
import Logviewer
import Logs
import ImageSelector exposing (imageSelector)
+import InitialForm
import DateTime exposing (DateTime)
import Json.Decode as Decode
@@ -131,6 +132,17 @@ viewStatePerUrl url =
, [ getSettings
, getSession
, getTitles])
+ RouteParser.InitialSetup -> ( InitialSetup { username = ""
+ , nickname = ""
+ , password = ""
+ , domain = ""
+ , blog_title = ""
+ , rss_title = ""
+ , rss_link = ""
+ , rss_description = Nothing
+ , rss_lang = ""
+ , rss_email = ""}
+ , [ getSettings ])
init _ url key =
let (viewstate, cmds) = (viewStatePerUrl url)
@@ -884,7 +896,99 @@ update msg model =
Err error ->
( { model | view_state = ShowError (errToString error) }
, Cmd.none)
-
+ SetInitialUsername usrname ->
+ case model.view_state of
+ InitialSetup formdata ->
+ ( { model | view_state
+ = InitialSetup { formdata
+ | username = usrname }}
+ , Cmd.none)
+ _ -> ( model, Cmd.none)
+ SetInitialNickname nckname ->
+ case model.view_state of
+ InitialSetup formdata ->
+ ( { model | view_state
+ = InitialSetup { formdata
+ | nickname = nckname }}
+ , Cmd.none)
+ _ -> ( model, Cmd.none)
+ SetInitialPassword passwd ->
+ case model.view_state of
+ InitialSetup formdata ->
+ ( { model | view_state
+ = InitialSetup { formdata
+ | password = passwd }}
+ , Cmd.none)
+ _ -> ( model, Cmd.none)
+ SetInitialDomain dmain ->
+ case model.view_state of
+ InitialSetup formdata ->
+ ( { model | view_state
+ = InitialSetup { formdata
+ | domain = dmain }}
+ , Cmd.none)
+ _ -> ( model, Cmd.none)
+ SetInitialBlog_Title title ->
+ case model.view_state of
+ InitialSetup formdata ->
+ ( { model | view_state
+ = InitialSetup { formdata
+ | blog_title = title }}
+ , Cmd.none)
+ _ -> ( model, Cmd.none)
+ SetInitialRss_Title title ->
+ case model.view_state of
+ InitialSetup formdata ->
+ ( { model | view_state
+ = InitialSetup { formdata
+ | rss_title = title }}
+ , Cmd.none)
+ _ -> ( model, Cmd.none)
+ SetInitialRss_Link link ->
+ case model.view_state of
+ InitialSetup formdata ->
+ ( { model | view_state
+ = InitialSetup { formdata
+ | rss_link = link }}
+ , Cmd.none)
+ _ -> ( model, Cmd.none)
+ SetInitialRss_Description descr ->
+ case model.view_state of
+ InitialSetup formdata ->
+ ( { model | view_state
+ = InitialSetup { formdata
+ | rss_description = (if descr == "" then
+ Nothing
+ else
+ Just descr)}}
+ , Cmd.none)
+ _ -> ( model, Cmd.none)
+ SetInitialRss_Lang lang ->
+ case model.view_state of
+ InitialSetup formdata ->
+ ( { model | view_state
+ = InitialSetup { formdata
+ | rss_lang = lang }}
+ , Cmd.none)
+ _ -> ( model, Cmd.none)
+ SetInitialRss_Email email ->
+ case model.view_state of
+ InitialSetup formdata ->
+ ( { model | view_state
+ = InitialSetup { formdata
+ | rss_email = email }}
+ , Cmd.none)
+ _ -> ( model, Cmd.none)
+ SaveInitialData data ->
+ ( model
+ , postInitialData data )
+ PostInitialSuccess res ->
+ case res of
+ Ok _ ->
+ doGoHome model
+ Err error ->
+ ( { model | view_state = ShowError (errToString error) }
+ , Cmd.none)
doGoHome_ model other_cmds =
(model, Cmd.batch (List.append [ getSettings
@@ -970,6 +1074,7 @@ blog_tab settings model =
UserSettings oldpasswd newpasswd usr_ -> case usr_ of
Just usr -> [ UserEditor.editor model.draggingImages oldpasswd newpasswd usr]
Nothing -> [ div [] [ text "Can't change user settings when there's no user"]]
+ InitialSetup data -> [ InitialForm.initialForm data]
)
rss_tab model settings =
diff --git a/elm-frontti/src/Message.elm b/elm-frontti/src/Message.elm
index 1f68597..c46a44d 100644
--- a/elm-frontti/src/Message.elm
+++ b/elm-frontti/src/Message.elm
@@ -14,7 +14,8 @@ import Url
import Title
import Feeds
import Image exposing (Image, ReferencingPost)
-import Logs
+import Logs
+import Initial
import File exposing (File)
import UUID exposing (UUID)
@@ -42,6 +43,10 @@ type ViewState
String
-- the view's user we're editing instead of the LoginState's user who is logged in here
(Maybe LoginUser)
+ | InitialSetup
+ Initial.InitialFormData
+
+
-- a simplified version of ViewState type for the main.elm's tabcomponent
type TabState
@@ -66,6 +71,7 @@ viewstate_to_tabstate vs =
Feeds _ _ -> RssFeeds
Logs _ _ _ -> Blog
UserSettings _ _ _ -> Blog
+ InitialSetup _ -> Blog
tabstate_to_str tb =
case tb of
@@ -246,6 +252,19 @@ type Msg
| SubmitChangedUser String String LoginUser
| UserSubmitResult (Result Http.Error ())
| UploadedOwnProfilePic (Result Http.Error Image.PostImageResponse)
+ | SetInitialUsername String
+ | SetInitialNickname String
+ | SetInitialPassword String
+ | SetInitialDomain String
+ | SetInitialBlog_Title String
+ | SetInitialRss_Title String
+ | SetInitialRss_Link String
+ | SetInitialRss_Description String
+ | SetInitialRss_Lang String
+ | SetInitialRss_Email String
+ | SaveInitialData Initial.InitialFormData
+ | PostInitialSuccess (Result Http.Error ())
+
-- ports
port reallySetupAce : String -> Cmd msg
port addImgToAce : String -> Cmd msg
diff --git a/elm-frontti/src/RouteParser.elm b/elm-frontti/src/RouteParser.elm
index a120757..b3e0cca 100644
--- a/elm-frontti/src/RouteParser.elm
+++ b/elm-frontti/src/RouteParser.elm
@@ -18,6 +18,7 @@ type Route
| Logs
| NotFound
| OwnUserSettings
+ | InitialSetup
routeParser =
oneOf
@@ -33,7 +34,8 @@ routeParser =
, map Logs (s "blog" </> (s "logs"))
, map PostAdmin (s "blog" </> (s "postadmin"))
, map FeedReader (s "blog" </> (s "feeds"))
- , map OwnUserSettings (s "blog" </> (s "usersettings"))]
+ , map OwnUserSettings (s "blog" </> (s "usersettings"))
+ , map InitialSetup (s "blog" </> (s "initial-setup"))]
url_to_route url =
diff --git a/resources/css/murja.css b/resources/css/murja.css
index 31b16f5..77b22cf 100644
--- a/resources/css/murja.css
+++ b/resources/css/murja.css
@@ -418,6 +418,22 @@ input:required {
border-radius: 10px;
}
+.initial-form {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ border: 2px solid #aaa;
+}
+
+.initial-form > label {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ margin: 10px;
+}
+
+.initial-form > label > input {
+}
+
@media only screen and (max-device-width:480px)
{
body {
diff --git a/src/middleware/db.lisp b/src/middleware/db.lisp
index b304628..be8da67 100644
--- a/src/middleware/db.lisp
+++ b/src/middleware/db.lisp
@@ -1,6 +1,7 @@
(defpackage murja.middleware.db
(:use :cl :postmodern)
(:export :connect-murjadb-toplevel
+ :@transaction
:with-db
:*automatic-tests-on?*))
diff --git a/src/middleware/json.lisp b/src/middleware/json.lisp
index 9041b3e..61e5cbe 100644
--- a/src/middleware/json.lisp
+++ b/src/middleware/json.lisp
@@ -1,5 +1,6 @@
(defpackage murja.middleware.json
- (:use :cl))
+ (:use :cl)
+ (:import-from :com.inuoe.jzon :stringify))
(in-package :murja.middleware.json)
@@ -7,6 +8,8 @@
(setf (hunchentoot:content-type*) "application/json")
(let ((result (funcall next)))
(if result
- result
+ (if (stringp result)
+ result
+ (stringify result))
(progn (setf (hunchentoot:return-code*) 404) ""))))
diff --git a/src/routes/login-routes.lisp b/src/routes/login-routes.lisp
index fa49c04..745d892 100644
--- a/src/routes/login-routes.lisp
+++ b/src/routes/login-routes.lisp
@@ -1,6 +1,7 @@
(defpackage murja.routes.login-routes
(:use :cl)
(:export :get-session-key :set-session-cookies)
+ (:import-from :cl-hash-util :hash)
(:import-from :murja.session :set-session-value)
(:import-from :lisp-fixup :sha-512)
(:import-from :murja.middleware.auth :@test-now :@authenticated :*user*)
@@ -8,7 +9,9 @@
(:import-from :murja.middleware.json :@json)
(:import-from :easy-routes :defroute)
- (:import-from :com.inuoe.jzon :parse :stringify))
+ (:import-from :com.inuoe.jzon :parse :stringify)
+ (:local-nicknames (:user-db :murja.users.user-db)
+ (:settings :murja.routes.settings-routes)))
(in-package :murja.routes.login-routes)
@@ -89,3 +92,29 @@
(progn
(setf (hunchentoot:return-code*) 401)
nil)))
+
+(defun save-initial-data-dump (username nickname password domain blog_title rss_title rss_link rss_description rss_lang rss_email)
+ (user-db:register-user username nickname "" password)
+ (user-db:cast-only-user-as-admin)
+
+ (settings:update-setting "domain" domain)
+ (settings:update-setting "blog-title" blog_title)
+ (settings:update-setting "rss-title" rss_title)
+ (settings:update-setting "rss-link" rss_link)
+ (settings:update-setting "rss-description" rss_description)
+ (settings:update-setting "rss-lang" rss_lang)
+ (settings:update-setting "rss-email" rss_email))
+
+
+(defroute initial-pageview? ("/api/initial" :method :post :decorators (@transaction
+ @json)) ()
+ (murja.json:bind-json (username nickname password domain blog_title rss_title rss_link rss_description rss_lang rss_email) () (hunchentoot:raw-post-data :force-text t)
+ (if (user-db:no-users?)
+ (progn
+ (save-initial-data-dump username nickname password domain blog_title rss_title rss_link rss_description rss_lang rss_email)
+ (setf (hunchentoot:return-code*) 204)
+ "")
+ (progn
+ (log:warn "Someone called POST /api/initial while there are users")
+ (setf (hunchentoot:return-code*) 500)
+ ""))))
diff --git a/src/routes/root-routes.lisp b/src/routes/root-routes.lisp
index ad4ae06..4976754 100644
--- a/src/routes/root-routes.lisp
+++ b/src/routes/root-routes.lisp
@@ -5,7 +5,8 @@
(:import-from :murja.middleware.db :@transaction)
(:import-from :murja.middleware.json :@json)
- (:import-from :easy-routes :defroute))
+ (:import-from :easy-routes :defroute)
+ (:local-nicknames (:user-db :murja.users.user-db)))
(in-package :murja.routes.root-routes)
@@ -65,6 +66,14 @@
;; (defroute resources ("/blog/resources/:file" :method :get) ()
;; (get-resource file))
+(defun @check-if-initial (next)
+ (if (user-db:no-users?)
+ (progn
+ (setf (hunchentoot:return-code*) 302)
+ (setf (hunchentoot:header-out :location) "/blog/initial-setup")
+ "")
+ (funcall next)))
+
(defvar *root*
"<!DOCTYPE html>
<html xmlns:of=\"http://ogp.me/ns#\"
@@ -81,10 +90,17 @@
</body>
</html>")
-(defroute root ("/" :method :get) ()
+(defroute root ("/" :method :get
+ :decorators (murja.middleware.db:@transaction
+ @check-if-initial)) ()
+ *root*)
+
+(defroute spurasdasdasd ("/blog/initial-setup" :method :get) ()
*root*)
-(defroute root-blg ("/blog/" :method :get) ()
+(defroute root-blg ("/blog/" :method :get
+ :decorators (murja.middleware.db:@transaction
+ @check-if-initial)) ()
*root*)
(defroute root-blg-new ("/blog/new_post" :method :get) ()
@@ -122,7 +138,7 @@
*root*)
-(defroute ddddddd ("/blog/page/:page" :method :get) ()
+(defroute ddddddd1234 ("/blog/page/:page" :method :get) ()
*root*)
(defroute sdfdsfopsf ("/blog/feeds" :method :get) ()
diff --git a/src/routes/settings-routes.lisp b/src/routes/settings-routes.lisp
index 94e3731..266e6c9 100644
--- a/src/routes/settings-routes.lisp
+++ b/src/routes/settings-routes.lisp
@@ -8,7 +8,7 @@
(:import-from :murja.middleware.json :@json)
(:import-from :murja.middleware.db :@transaction)
(:import-from :easy-routes :defroute)
- (:export :get-settings :*log-file*))
+ (:export :update-setting :get-settings :*log-file*))
(in-package :murja.routes.settings-routes)
@@ -27,7 +27,10 @@
(com.inuoe.jzon:stringify
(get-settings)))
-(defroute update-setting ("/api/settings/client-settings" :method :put
+(defun update-setting (k v)
+ (postmodern:execute "INSERT INTO blog.Settings (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = excluded.value" k (stringify v)))
+
+(defroute update-setting-route ("/api/settings/client-settings" :method :put
:decorators (@transaction
@json
@authenticated
@@ -37,7 +40,7 @@
(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)))))
+ (update-setting k v))))
(setf (hunchentoot:return-code*) 204)
""))
diff --git a/src/users/user-db.lisp b/src/users/user-db.lisp
index 01c5249..060960c 100644
--- a/src/users/user-db.lisp
+++ b/src/users/user-db.lisp
@@ -1,7 +1,7 @@
(defpackage :murja.users.user-db
(:use :cl :postmodern)
(:import-from :lisp-fixup :sha-512)
- (:export :patch-user-img* :get-session-user-by-id :search-with-id-and-pwd* :get-user-by-id :select-user-by-login :register-user :patch-user)
+ (:export :patch-user-img* :get-session-user-by-id :search-with-id-and-pwd* :get-user-by-id :select-user-by-login :register-user :patch-user :no-users? :cast-only-user-as-admin)
(:import-from :halisql :defqueries))
(in-package :murja.users.user-db)
@@ -40,8 +40,17 @@
img-location
(sha-512 password))))
+(defun cast-only-user-as-admin ()
+ (let ((c (caar (query "SELECT count(*) FROM blog.users"))))
+ (assert (equalp c 1))
+ (execute "INSERT INTO blog.groupmapping SELECT usr.id, grp.id, true FROM blog.users usr JOIN blog.usergroup grp ON grp.name = 'Admins'")))
+
;;(postmodern:connect-toplevel "blogdb" "blogadmin" "blog" "localhost")
(defun patch-user (usr)
(cl-hash-util:with-keys ("nickname" "username" "password" "id") usr
(patch-user* nickname username password id)))
+
+(defun no-users? ()
+ (caar (postmodern:query "SELECT NOT EXISTS (SELECT * FROM blog.users)")))
+