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)")))
+