diff --git a/.gitignore b/.gitignore
index 4d318a8..6f92a01 100644
--- a/.gitignore
+++ b/.gitignore
@@ -116,8 +116,3 @@ dist
.yarn/install-state.gz
.pnp.*
-static/
-
-.idea
-
-elm-stuff/
diff --git a/todo/LICENSE b/LICENSE
similarity index 100%
rename from todo/LICENSE
rename to LICENSE
diff --git a/todo/README.md b/README.md
similarity index 100%
rename from todo/README.md
rename to README.md
diff --git a/todo/config.json b/config.json
similarity index 50%
rename from todo/config.json
rename to config.json
index 948073a..87abf0b 100644
--- a/todo/config.json
+++ b/config.json
@@ -3,7 +3,5 @@
"https": false,
"alter_db": true,
"port": 8080,
- "db_url": "postgres://postgres:@127.0.0.1/todo",
- "cert": "",
- "cert_key": ""
+ "db_url": "postgres://postgres:@127.0.0.1/todo"
}
\ No newline at end of file
diff --git a/elm-deprecated/elm.json b/elm-deprecated/elm.json
deleted file mode 100644
index 01ba003..0000000
--- a/elm-deprecated/elm.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
- "type": "application",
- "source-directories": [
- "src"
- ],
- "elm-version": "0.19.1",
- "dependencies": {
- "direct": {
- "elm/browser": "1.0.2",
- "elm/core": "1.0.5",
- "elm/html": "1.0.0",
- "elm/http": "2.0.0",
- "elm/json": "1.1.3",
- "elm/time": "1.0.0",
- "elm/url": "1.0.0",
- "f0i/iso8601": "1.1.2",
- "mdgriffith/elm-ui": "1.1.8"
- },
- "indirect": {
- "elm/bytes": "1.0.8",
- "elm/file": "1.0.5",
- "elm/virtual-dom": "1.0.2"
- }
- },
- "test-dependencies": {
- "direct": {},
- "indirect": {}
- }
-}
diff --git a/elm-deprecated/index.html b/elm-deprecated/index.html
deleted file mode 100644
index 6c718e8..0000000
--- a/elm-deprecated/index.html
+++ /dev/null
@@ -1,14025 +0,0 @@
-
-
-
-
- Main
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/elm-deprecated/src/About.elm b/elm-deprecated/src/About.elm
deleted file mode 100644
index b65252d..0000000
--- a/elm-deprecated/src/About.elm
+++ /dev/null
@@ -1,9 +0,0 @@
-module About exposing (..)
-
-import Element exposing (..)
-import Model exposing (..)
-
-
-getPage : Model -> Element Msg
-getPage _ =
- text "About page."
diff --git a/elm-deprecated/src/Account.elm b/elm-deprecated/src/Account.elm
deleted file mode 100644
index 35a5d69..0000000
--- a/elm-deprecated/src/Account.elm
+++ /dev/null
@@ -1,9 +0,0 @@
-module Account exposing (..)
-
-import Element exposing (..)
-import Model exposing (..)
-
-
-getPage : Model -> Element Msg
-getPage _ =
- text "Account page."
diff --git a/elm-deprecated/src/Api.elm b/elm-deprecated/src/Api.elm
deleted file mode 100644
index acdbec6..0000000
--- a/elm-deprecated/src/Api.elm
+++ /dev/null
@@ -1,290 +0,0 @@
-port module Api exposing (Cred, addServerError, application, decodeErrors, delete, get, login, logout, post, put, register, settings, storeCredWith, username, viewerChanges)
-
-{-| This module is responsible for communicating to the Conduit API.
-It exposes an opaque Endpoint type which is guaranteed to point to the correct URL.
--}
-
-import Api.Endpoint as Endpoint exposing (Endpoint)
-import Avatar exposing (Avatar)
-import Browser
-import Browser.Navigation as Nav
-import Http exposing (Body, Expect)
-import Json.Decode as Decode exposing (Decoder, Value, decodeString, field, string)
-import Json.Encode as Encode
-import Url exposing (Url)
-import Username exposing (Username)
-
-
-
--- CRED
-
-
-{-| The authentication credentials for the Viewer (that is, the currently logged-in user.)
-This includes:
-
- - The cred's Username
- - The cred's authentication token
- By design, there is no way to access the token directly as a String.
- It can be encoded for persistence, and it can be added to a header
- to a HttpBuilder for a request, but that's it.
- This token should never be rendered to the end user, and with this API, it
- can't be!
-
--}
-type Cred
- = Cred Username String
-
-
-username : Cred -> Username
-username (Cred val _) =
- val
-
-
-credHeader : Cred -> Http.Header
-credHeader (Cred _ str) =
- Http.header "authorization" ("Token " ++ str)
-
-
-{-| It's important that this is never exposed!
-We expose `login` and `application` instead, so we can be certain that if anyone
-ever has access to a `Cred` value, it came from either the login API endpoint
-or was passed in via flags.
--}
-credDecoder : Decoder Cred
-credDecoder =
- Decode.succeed Cred
- |> field "username" Username.decoder
- |> field "token" Decode.string
-
-
-
--- PERSISTENCE
-
-
-decode : Decoder (Cred -> viewer) -> Value -> Result Decode.Error viewer
-decode decoder value =
- -- It's stored in localStorage as a JSON String;
- -- first decode the Value as a String, then
- -- decode that String as JSON.
- Decode.decodeValue Decode.string value
- |> Result.andThen (\str -> Decode.decodeString (Decode.field "user" (decoderFromCred decoder)) str)
-
-
-port onStoreChange : (Value -> msg) -> Sub msg
-
-
-viewerChanges : (Maybe viewer -> msg) -> Decoder (Cred -> viewer) -> Sub msg
-viewerChanges toMsg decoder =
- onStoreChange (\value -> toMsg (decodeFromChange decoder value))
-
-
-decodeFromChange : Decoder (Cred -> viewer) -> Value -> Maybe viewer
-decodeFromChange viewerDecoder val =
- -- It's stored in localStorage as a JSON String;
- -- first decode the Value as a String, then
- -- decode that String as JSON.
- Decode.decodeValue (storageDecoder viewerDecoder) val
- |> Result.toMaybe
-
-
-storeCredWith : Cred -> Avatar -> Cmd msg
-storeCredWith (Cred uname token) avatar =
- let
- json =
- Encode.object
- [ ( "user"
- , Encode.object
- [ ( "username", Username.encode uname )
- , ( "token", Encode.string token )
- , ( "image", Avatar.encode avatar )
- ]
- )
- ]
- in
- storeCache (Just json)
-
-
-logout : Cmd msg
-logout =
- storeCache Nothing
-
-
-port storeCache : Maybe Value -> Cmd msg
-
-
-
--- SERIALIZATION
--- APPLICATION
-
-
-application :
- Decoder (Cred -> viewer)
- ->
- { init : Maybe viewer -> Url -> Nav.Key -> ( model, Cmd msg )
- , onUrlChange : Url -> msg
- , onUrlRequest : Browser.UrlRequest -> msg
- , subscriptions : model -> Sub msg
- , update : msg -> model -> ( model, Cmd msg )
- , view : model -> Browser.Document msg
- }
- -> Program Value model msg
-application viewerDecoder config =
- let
- init flags url navKey =
- let
- maybeViewer =
- Decode.decodeValue Decode.string flags
- |> Result.andThen (Decode.decodeString (storageDecoder viewerDecoder))
- |> Result.toMaybe
- in
- config.init maybeViewer url navKey
- in
- Browser.application
- { init = init
- , onUrlChange = config.onUrlChange
- , onUrlRequest = config.onUrlRequest
- , subscriptions = config.subscriptions
- , update = config.update
- , view = config.view
- }
-
-
-storageDecoder : Decoder (Cred -> viewer) -> Decoder viewer
-storageDecoder viewerDecoder =
- Decode.field "user" (decoderFromCred viewerDecoder)
-
-
-
--- HTTP
-
-
-get : Endpoint -> Maybe Cred -> Decoder a -> Cmd a
-get url maybeCred decoder =
- Endpoint.request
- { method = "GET"
- , url = url
- , expect = Http.expectJson decoder
- , headers =
- case maybeCred of
- Just cred ->
- [ credHeader cred ]
-
- Nothing ->
- []
- , body = Http.emptyBody
- , timeout = Nothing
- , withCredentials = False
- }
-
-
-put : Endpoint -> Cred -> Body -> Decoder a -> Cmd a
-put url cred body decoder =
- Endpoint.request
- { method = "PUT"
- , url = url
- , expect = Http.expectJson decoder
- , headers = [ credHeader cred ]
- , body = body
- , timeout = Nothing
- , withCredentials = False
- }
-
-
-post : Endpoint -> Maybe Cred -> Body -> Decoder a -> Cmd a
-post url maybeCred body decoder =
- Endpoint.request
- { method = "POST"
- , url = url
- , expect = Http.expectJson decoder
- , headers =
- case maybeCred of
- Just cred ->
- [ credHeader cred ]
-
- Nothing ->
- []
- , body = body
- , timeout = Nothing
- , withCredentials = False
- }
-
-
-delete : Endpoint -> Cred -> Body -> Decoder a -> Cmd a
-delete url cred body decoder =
- Endpoint.request
- { method = "DELETE"
- , url = url
- , expect = Http.expectJson decoder
- , headers = [ credHeader cred ]
- , body = body
- , timeout = Nothing
- , withCredentials = False
- }
-
-
-login : Http.Body -> Decoder (Cred -> a) -> Cmd a
-login body decoder =
- post Endpoint.login Nothing body (Decode.field "user" (decoderFromCred decoder))
-
-
-register : Http.Body -> Decoder (Cred -> a) -> Cmd a
-register body decoder =
- post Endpoint.users Nothing body (Decode.field "user" (decoderFromCred decoder))
-
-
-settings : Cred -> Http.Body -> Decoder (Cred -> a) -> Cmd a
-settings cred body decoder =
- put Endpoint.user cred body (Decode.field "user" (decoderFromCred decoder))
-
-
-decoderFromCred : Decoder (Cred -> a) -> Decoder a
-decoderFromCred decoder =
- Decode.map2 (\fromCred cred -> fromCred cred)
- decoder
- credDecoder
-
-
-
--- ERRORS
-
-
-addServerError : List String -> List String
-addServerError list =
- "Server error" :: list
-
-
-{-| Many API endpoints include an "errors" field in their BadStatus responses.
--}
-decodeErrors : Http.Error -> List String
-decodeErrors error =
- case error of
- Http.BadStatus errid ->
- [ Int.toString errid ]
-
- err ->
- [ "Server error" ]
-
-
-errorsDecoder : Decoder (List String)
-errorsDecoder =
- Decode.keyValuePairs (Decode.list Decode.string)
- |> Decode.map (List.concatMap fromPair)
-
-
-fromPair : ( String, List String ) -> List String
-fromPair ( field, errors ) =
- List.map (\error -> field ++ " " ++ error) errors
-
-
-
--- LOCALSTORAGE KEYS
-
-
-cacheStorageKey : String
-cacheStorageKey =
- "cache"
-
-
-credStorageKey : String
-credStorageKey =
- "cred"
diff --git a/elm-deprecated/src/Api/Endpoint.elm b/elm-deprecated/src/Api/Endpoint.elm
deleted file mode 100644
index f4a4f05..0000000
--- a/elm-deprecated/src/Api/Endpoint.elm
+++ /dev/null
@@ -1,99 +0,0 @@
-module Api.Endpoint exposing (Endpoint, login, request, tags, todo, todoList, user, users)
-
-import Http
-import Todo.UUID as UUID exposing (UUID)
-import Url.Builder exposing (QueryParameter)
-import Username exposing (Username)
-
-
-{-| Http.request, except it takes an Endpoint instead of a Url.
--}
-request :
- { body : Http.Body
- , expect : Http.Expect a
- , headers : List Http.Header
- , method : String
- , timeout : Maybe Float
- , url : Endpoint
- , tracker : Maybe String
- }
- -> Cmd a
-request config =
- Http.request
- { body = config.body
- , expect = config.expect
- , headers = config.headers
- , method = config.method
- , timeout = config.timeout
- , url = unwrap config.url
- , tracker = config.tracker
- }
-
-
-
--- TYPES
-
-
-{-| Get a URL to the Conduit API.
-This is not publicly exposed, because we want to make sure the only way to get one of these URLs is from this module.
--}
-type Endpoint
- = Endpoint String
-
-
-unwrap : Endpoint -> String
-unwrap (Endpoint str) =
- str
-
-
-url : List String -> List QueryParameter -> Endpoint
-url paths queryParams =
- -- NOTE: Url.Builder takes care of percent-encoding special URL characters.
- -- See https://package.elm-lang.org/packages/elm/url/latest/Url#percentEncode
- Url.Builder.crossOrigin "https://conduit.productionready.io"
- ("api" :: paths)
- queryParams
- |> Endpoint
-
-
-
--- ENDPOINTS
-
-
-login : Endpoint
-login =
- url [ "users", "login" ] []
-
-
-user : Endpoint
-user =
- url [ "user" ] []
-
-
-users : Endpoint
-users =
- url [ "users" ] []
-
-
-follow : Username -> Endpoint
-follow uname =
- url [ "profiles", Username.toString uname, "follow" ] []
-
-
-
--- ARTICLE ENDPOINTS
-
-
-todo : UUID -> Endpoint
-todo uuid =
- url [ "articles", UUID.toString uuid ] []
-
-
-todoList : List QueryParameter -> Endpoint
-todoList params =
- url [ "articles" ] params
-
-
-tags : Endpoint
-tags =
- url [ "tags" ] []
diff --git a/elm-deprecated/src/Asset.elm b/elm-deprecated/src/Asset.elm
deleted file mode 100644
index daa023b..0000000
--- a/elm-deprecated/src/Asset.elm
+++ /dev/null
@@ -1,46 +0,0 @@
-module Asset exposing (Image, defaultAvatar, error, loading, src)
-
-{-| Assets, such as images, videos, and audio. (We only have images for now.)
-We should never expose asset URLs directly; this module should be in charge of
-all of them. One source of truth!
--}
-
-import Html exposing (Attribute, Html)
-import Html.Attributes as Attr
-
-
-type Image
- = Image String
-
-
-
--- IMAGES
-
-
-error : Image
-error =
- image "error.jpg"
-
-
-loading : Image
-loading =
- image "loading.svg"
-
-
-defaultAvatar : Image
-defaultAvatar =
- image "smiley-cyrus.jpg"
-
-
-image : String -> Image
-image filename =
- Image ("/assets/images/" ++ filename)
-
-
-
--- USING IMAGES
-
-
-src : Image -> Attribute msg
-src (Image url) =
- Attr.src url
diff --git a/elm-deprecated/src/Avatar.elm b/elm-deprecated/src/Avatar.elm
deleted file mode 100644
index 7ecafb3..0000000
--- a/elm-deprecated/src/Avatar.elm
+++ /dev/null
@@ -1,56 +0,0 @@
-module Avatar exposing (Avatar, decoder, encode, src, toMaybeString)
-
-import Asset
-import Html exposing (Attribute)
-import Html.Attributes
-import Json.Decode as Decode exposing (Decoder)
-import Json.Encode as Encode exposing (Value)
-
-
-
--- TYPES
-
-
-type Avatar
- = Avatar (Maybe String)
-
-
-
--- CREATE
-
-
-decoder : Decoder Avatar
-decoder =
- Decode.map Avatar (Decode.nullable Decode.string)
-
-
-
--- TRANSFORM
-
-
-encode : Avatar -> Value
-encode (Avatar maybeUrl) =
- case maybeUrl of
- Just url ->
- Encode.string url
-
- Nothing ->
- Encode.null
-
-
-src : Avatar -> Attribute msg
-src (Avatar maybeUrl) =
- case maybeUrl of
- Nothing ->
- Asset.src Asset.defaultAvatar
-
- Just "" ->
- Asset.src Asset.defaultAvatar
-
- Just url ->
- Html.Attributes.src url
-
-
-toMaybeString : Avatar -> Maybe String
-toMaybeString (Avatar maybeUrl) =
- maybeUrl
diff --git a/elm-deprecated/src/CommonElements.elm b/elm-deprecated/src/CommonElements.elm
deleted file mode 100644
index 2a9bdd1..0000000
--- a/elm-deprecated/src/CommonElements.elm
+++ /dev/null
@@ -1,31 +0,0 @@
--- this is a list of common styled elements
-
-
-module CommonElements exposing (..)
-
-import Element exposing (..)
-import Element.Font as Font
-
-
-bolded : String -> Element msg
-bolded content =
- el
- [ Font.bold ]
- (text content)
-
-
-namedLink : String -> String -> Element msg
-namedLink path name =
- link []
- { url = "/#" ++ path
- , label = text name
- }
-
-
-debugLog : a -> a
-debugLog value =
- let
- _ =
- Debug.log "value: " value
- in
- value
diff --git a/elm-deprecated/src/Email.elm b/elm-deprecated/src/Email.elm
deleted file mode 100644
index 0181842..0000000
--- a/elm-deprecated/src/Email.elm
+++ /dev/null
@@ -1,38 +0,0 @@
-module Email exposing (Email, decoder, encode, toString)
-
-import Json.Decode as Decode exposing (Decoder)
-import Json.Encode as Encode exposing (Value)
-
-
-{-| An email address.
-Having this as a custom type that's separate from String makes certain
-mistakes impossible. Consider this function:
-updateEmailAddress : Email -> String -> Http.Request
-updateEmailAddress email password = ...
-(The server needs your password to confirm that you should be allowed
-to update the email address.)
-Because Email is not a type alias for String, but is instead a separate
-custom type, it is now impossible to mix up the argument order of the
-email and the password. If we do, it won't compile!
-If Email were instead defined as `type alias Email = String`, we could
-call updateEmailAddress password email and it would compile (and never
-work properly).
-This way, we make it impossible for a bug like that to compile!
--}
-type Email
- = Email String
-
-
-toString : Email -> String
-toString (Email str) =
- str
-
-
-encode : Email -> Value
-encode (Email str) =
- Encode.string str
-
-
-decoder : Decoder Email
-decoder =
- Decode.map Email Decode.string
diff --git a/elm-deprecated/src/Home.elm b/elm-deprecated/src/Home.elm
deleted file mode 100644
index 462b0a2..0000000
--- a/elm-deprecated/src/Home.elm
+++ /dev/null
@@ -1,9 +0,0 @@
-module Home exposing (..)
-
-import Element exposing (..)
-import Model exposing (..)
-
-
-getPage : Model -> Element Msg
-getPage _ =
- text "Home page."
diff --git a/elm-deprecated/src/Login.elm b/elm-deprecated/src/Login.elm
deleted file mode 100644
index 9aa8067..0000000
--- a/elm-deprecated/src/Login.elm
+++ /dev/null
@@ -1,40 +0,0 @@
-module Login exposing (..)
-
-import CommonElements exposing (..)
-import Element exposing (..)
-import Element.Input as Input
-import Model exposing (..)
-
-
-getPage : Model -> Element Msg
-getPage model =
- column []
- [ loginBox model
- , namedLink "/signup" "Sign Up"
- ]
-
-
-loginBox : Model -> Element Msg
-loginBox model =
- column []
- [ text "Log in"
- , Input.email
- [ Input.focusedOnLoad
- ]
- { onChange = CredentialsChange "loginUsername"
- , text = Maybe.withDefault "" model.storage.loginUsername
- , placeholder = Just (Input.placeholder [] (text ""))
- , label = Input.labelHidden "email"
- }
- , Input.currentPassword []
- { onChange = CredentialsChange "loginPassword"
- , text = Maybe.withDefault "" model.storage.loginPassword
- , placeholder = Just (Input.placeholder [] (text ""))
- , label = Input.labelHidden "password"
- , show = False
- }
- , Input.button []
- { onPress = Just SubmitLogin
- , label = text "Log In"
- }
- ]
diff --git a/elm-deprecated/src/Main.elm b/elm-deprecated/src/Main.elm
deleted file mode 100644
index c1166ca..0000000
--- a/elm-deprecated/src/Main.elm
+++ /dev/null
@@ -1,178 +0,0 @@
-module Main exposing (..)
-
-import Browser
-import Browser.Navigation as Nav
-import CommonElements exposing (..)
-import Element exposing (..)
-import NavRow exposing (..)
-import PageState exposing (..)
-import Session exposing (Session)
-import Url
-import Viewer exposing (..)
-
-
-type Model
- = Redirect Session
- | NotFound Session
- | About Session
- | Home Home.Model
- | Account Account.Model
- | Login Login.Model
- | Signup Signup.Model
- | Todo Todo.Model
- | Editor (Maybe UUID) Editor.Model
-
-
-type Msg
- = UrlChange Url.Url
- | Request Browser.UrlRequest
- | GotHomeMsg Home.Msg
- | GotAccountMsg Account.Msg
- | GotLoginMsg Login.Msg
- | GotSignupMsg Signup.Msg
- | GotEditorMsg Editor.Msg
- | GotTodoMsg Todo.Msg
- | GotSession Session
-
-
-init : Maybe Viewer -> Url.Url -> Nav.Key -> ( Model, Cmd Msg )
-init maybeViewer url key =
- changeRouteTo (Route.fromUrl url)
- (Redirect (Session.fromViewer navKey maybeViewer))
-
-
-toSession : Model -> Session
-toSession page =
- case page of
- Redirect session ->
- session
-
- NotFound session ->
- session
-
- About session ->
- session
-
- Home home ->
- Home.toSession home
-
- Account settings ->
- Settings.toSession settings
-
- Login login ->
- Login.toSession login
-
- Signup signup ->
- Signup.toSession signup
-
- Todo todo ->
- Todo.toSession todo
-
- Editor _ editor ->
- Editor.toSession editor
-
-
-changeRouteTo : Maybe Route -> Model -> ( Model, Cmd Msg )
-changeRouteTo maybeRoute model =
- let
- session =
- toSession model
- in
- case maybeRoute of
- Nothing ->
- ( NotFound session, Cmd.none )
-
- Just Route.Root ->
- ( model, Route.replaceUrl (Session.navKey session) Route.Home )
-
- Just Route.Logout ->
- ( model, Api.logout )
-
- Just Route.NewTodo ->
- Editor.initNew session
- |> updateWith (Editor Nothing) GotEditorMsg model
-
- Just (Route.EditTodo slug) ->
- Editor.initEdit session slug
- |> updateWith (Editor (Just slug)) GotEditorMsg model
-
- Just Route.Account ->
- Account.init session
- |> updateWith Account GotAccountMsg model
-
- Just Route.Home ->
- Home.init session
- |> updateWith Home GotHomeMsg model
-
- Just Route.Login ->
- Login.init session
- |> updateWith Login GotLoginMsg model
-
- Just Route.Signup ->
- Signup.init session
- |> updateWith Signup GotSignupMsg model
-
- Just (Route.Profile username) ->
- Profile.init session username
- |> updateWith (Profile username) GotProfileMsg model
-
- Just (Route.Todo slug) ->
- Todo.init session slug
- |> updateWith Todo GotArticleMsg model
-
-
-update : Msg -> Model -> ( Model, Cmd Msg )
-update msg model =
- case ( msg, model ) of
- ( _, _ ) ->
- ( model, Cmd.none )
-
-
-updateWith : (subModel -> Model) -> (subMsg -> Msg) -> Model -> ( subModel, Cmd subMsg ) -> ( Model, Cmd Msg )
-updateWith toModel toMsg model ( subModel, subCmd ) =
- ( toModel subModel
- , Cmd.map toMsg subCmd
- )
-
-
-
--- view takes current application state and is responsible
--- for rendering the document based on it
-
-
-view : Model -> Browser.Document Msg
-view model =
- { title = "Elm App Test"
- , body =
- [ layout [ height fill ]
- (column [ width fill ]
- [ getNavRow model
- , getMainContent model
- ]
- )
- ]
- }
-
-
-
--- subscriptions takes the current state and
--- maybe returns the subscriptions based on it (?)
--- might be used to subscribe to different events based on
--- the url, etc.
-
-
-subscriptions : Model -> Sub Msg
-subscriptions _ =
- Sub.none
-
-
-main : Program Value Model Msg
-main =
- Api.application Viewer.decoder
- { init = init
- , view = view
- , update = update
- , subscriptions = subscriptions
- , onUrlChange = UrlChange -- these are actual types from Msg
- , onUrlRequest = Request
- }
diff --git a/elm-deprecated/src/NavRow.elm b/elm-deprecated/src/NavRow.elm
deleted file mode 100644
index 9edcd2d..0000000
--- a/elm-deprecated/src/NavRow.elm
+++ /dev/null
@@ -1,57 +0,0 @@
-module NavRow exposing (..)
-
-import CommonElements exposing (..)
-import Element exposing (..)
-import Element.Region as Region
-import Model exposing (..)
-import Request exposing (..)
-import Url
-
-
-getNavRow : Model -> Element Msg
-getNavRow model =
- row
- [ Region.navigation
-
- --, explain Debug.todo
- , paddingXY 10 5
- , spacing 10
- , width fill
- ]
- [ namedLink "/" "TODOAPP"
- , getDebugInfo model
- , getCurrentUser model
- ]
-
-
-
--- temp function to get current page url
--- and links in case shit breaks
-
-
-getDebugInfo : Model -> Element Msg
-getDebugInfo model =
- row
- [ centerX ]
- [ text "Current URL: "
- , bolded (Url.toString model.url)
- , column []
- [ namedLink "/" "root"
- , namedLink "/login" "login"
- , namedLink "/signup" "signup"
- , namedLink "/account" "account"
- , namedLink "/about" "about"
- ]
- ]
-
-
-getCurrentUser : Model -> Element Msg
-getCurrentUser model =
- el []
- (case model.user of
- Just user ->
- namedLink "/account" user.email
-
- _ ->
- namedLink "/login" "Log In"
- )
diff --git a/elm-deprecated/src/PageState.elm b/elm-deprecated/src/PageState.elm
deleted file mode 100644
index e2982fd..0000000
--- a/elm-deprecated/src/PageState.elm
+++ /dev/null
@@ -1,66 +0,0 @@
-module PageState exposing (getMainContent)
-
-import About as AboutPage
-import Account as AccountPage
-import Element exposing (..)
-import Home as HomePage
-import Login as LoginPage
-import Model exposing (..)
-import Signup as SignupPage
-import Url
-import Url.Parser as Parser exposing (..)
-
-
-type Route
- = About
- | Account
- | Home
- | Login
- | Signup
- | NotFound
-
-
-getMainContent : Model -> Element Msg
-getMainContent model =
- el []
- (case consume (toRoute model.url) of
- About ->
- AboutPage.getPage model
-
- Account ->
- AccountPage.getPage model
-
- Home ->
- HomePage.getPage model
-
- Login ->
- LoginPage.getPage model
-
- Signup ->
- SignupPage.getPage model
-
- _ ->
- text "Page not found."
- )
-
-
-routeParser : Parser (Route -> a) a
-routeParser =
- oneOf
- [ Parser.map Home top
- , Parser.map Account (s "account")
- , Parser.map About (s "about")
- , Parser.map Login (s "login")
- , Parser.map Signup (s "signup")
- ]
-
-
-toRoute : Url.Url -> Maybe Route
-toRoute url =
- { url | path = Maybe.withDefault "" url.fragment, fragment = Nothing }
- |> Parser.parse routeParser
-
-
-consume : Maybe Route -> Route
-consume route =
- Maybe.withDefault NotFound route
diff --git a/elm-deprecated/src/Route.elm b/elm-deprecated/src/Route.elm
deleted file mode 100644
index 484d8a9..0000000
--- a/elm-deprecated/src/Route.elm
+++ /dev/null
@@ -1,98 +0,0 @@
-module Route exposing (Route(..), fromUrl, href, replaceUrl)
-
-import Browser.Navigation as Nav
-import Html exposing (Attribute)
-import Html.Attributes as Attr
-import Todo.UUID as UUID
-import Url exposing (Url)
-import Url.Parser as Parser exposing ((>), Parser, oneOf, s, string)
-import Username exposing (Username)
-
-
-
--- ROUTING
-
-
-type Route
- = Home
- | Login
- | Logout
- | Signup
- | Account
- | Todo UUID.UUID
- | NewTodo
- | EditTodo UUID.UUID
-
-
-parser : Parser (Route -> a) a
-parser =
- oneOf
- [ Parser.map Home Parser.top
- , Parser.map Login (s "login")
- , Parser.map Logout (s "logout")
- , Parser.map Account (s "account")
- , Parser.map Signup (s "signup")
- , Parser.map Todo (s "article" > UUID.urlParser)
- , Parser.map NewTodo (s "editor")
- , Parser.map EditTodo (s "editor" > UUID.urlParser)
- ]
-
-
-
--- PUBLIC HELPERS
-
-
-href : Route -> Attribute msg
-href targetRoute =
- Attr.href (routeToString targetRoute)
-
-
-replaceUrl : Nav.Key -> Route -> Cmd msg
-replaceUrl key route =
- Nav.replaceUrl key (routeToString route)
-
-
-fromUrl : Url -> Maybe Route
-fromUrl url =
- -- The RealWorld spec treats the fragment like a path.
- -- This makes it *literally* the path, so we can proceed
- -- with parsing as if it had been a normal path all along.
- { url | path = Maybe.withDefault "" url.fragment, fragment = Nothing }
- |> Parser.parse parser
-
-
-
--- INTERNAL
-
-
-routeToString : Route -> String
-routeToString page =
- "#/" ++ String.join "/" (routeToPieces page)
-
-
-routeToPieces : Route -> List String
-routeToPieces page =
- case page of
- Home ->
- []
-
- Login ->
- [ "login" ]
-
- Logout ->
- [ "logout" ]
-
- Signup ->
- [ "Signup" ]
-
- Account ->
- [ "account" ]
-
- Todo uuid ->
- [ "article", UUID.toString uuid ]
-
- NewTodo ->
- [ "editor" ]
-
- EditTodo uuid ->
- [ "editor", UUID.toString uuid ]
diff --git a/elm-deprecated/src/Session.elm b/elm-deprecated/src/Session.elm
deleted file mode 100644
index 2a8e994..0000000
--- a/elm-deprecated/src/Session.elm
+++ /dev/null
@@ -1,75 +0,0 @@
-module Session exposing (Session, changes, cred, fromViewer, navKey, viewer)
-
-import Api exposing (Cred)
-import Avatar exposing (Avatar)
-import Browser.Navigation as Nav
-import Json.Decode as Decode exposing (Decoder)
-import Json.Encode as Encode exposing (Value)
-import Profile exposing (Profile)
-import Time
-import Viewer exposing (Viewer)
-
-
-
--- TYPES
-
-
-type Session
- = LoggedIn Nav.Key Viewer
- | Guest Nav.Key
-
-
-
--- INFO
-
-
-viewer : Session -> Maybe Viewer
-viewer session =
- case session of
- LoggedIn _ val ->
- Just val
-
- Guest _ ->
- Nothing
-
-
-cred : Session -> Maybe Cred
-cred session =
- case session of
- LoggedIn _ val ->
- Just (Viewer.cred val)
-
- Guest _ ->
- Nothing
-
-
-navKey : Session -> Nav.Key
-navKey session =
- case session of
- LoggedIn key _ ->
- key
-
- Guest key ->
- key
-
-
-
--- CHANGES
-
-
-changes : (Session -> msg) -> Nav.Key -> Sub msg
-changes toMsg key =
- Api.viewerChanges (\maybeViewer -> toMsg (fromViewer key maybeViewer)) Viewer.decoder
-
-
-fromViewer : Nav.Key -> Maybe Viewer -> Session
-fromViewer key maybeViewer =
- -- It's stored in localStorage as a JSON String;
- -- first decode the Value as a String, then
- -- decode that String as JSON.
- case maybeViewer of
- Just viewerVal ->
- LoggedIn key viewerVal
-
- Nothing ->
- Guest key
diff --git a/elm-deprecated/src/Signup.elm b/elm-deprecated/src/Signup.elm
deleted file mode 100644
index 64218be..0000000
--- a/elm-deprecated/src/Signup.elm
+++ /dev/null
@@ -1,53 +0,0 @@
-module Signup exposing (..)
-
-import CommonElements exposing (..)
-import Element exposing (..)
-import Element.Input as Input
-import Model exposing (..)
-
-
-getPage : Model -> Element Msg
-getPage model =
- el [] (signupBox model)
-
-
-signupBox : Model -> Element Msg
-signupBox model =
- column []
- [ text "Sign up"
- , Input.email
- [ Input.focusedOnLoad
- ]
- { onChange = CredentialsChange "loginUsername"
- , text = Maybe.withDefault "" model.storage.loginUsername
- , placeholder = Just (Input.placeholder [] (text "email"))
- , label = Input.labelHidden "email"
- }
- , Input.newPassword []
- { onChange = CredentialsChange "loginPassword"
- , text = Maybe.withDefault "" model.storage.loginPassword
- , placeholder = Just (Input.placeholder [] (text "password"))
- , label = Input.labelHidden "password"
- , show = False
- }
- , Input.newPassword []
- { onChange = CredentialsChange "confirmPassword"
- , text = Maybe.withDefault "" model.storage.signupConfirmPassword
- , placeholder = Just (Input.placeholder [] (text "confirm password"))
- , label = Input.labelHidden "confirm password"
- , show = False
- }
- , Input.button []
- { onPress = ensurePasswordMatch model
- , label = text "Sign Up"
- }
- ]
-
-
-ensurePasswordMatch : Model -> Maybe Msg
-ensurePasswordMatch model =
- if model.storage.signupConfirmPassword == model.storage.loginPassword then
- Just SubmitSignup
-
- else
- Just SignupPasswordMismatch
diff --git a/elm-deprecated/src/Todo.elm b/elm-deprecated/src/Todo.elm
deleted file mode 100644
index 2e97eef..0000000
--- a/elm-deprecated/src/Todo.elm
+++ /dev/null
@@ -1,253 +0,0 @@
-module Todo exposing (Full, Preview, Todo, author, body, favorite, favoriteButton, fetch, fromPreview, fullDecoder, mapAuthor, metadata, previewDecoder, unfavorite, unfavoriteButton, uuid)
-
-{-| The interface to the Todo data structure.
-This includes:
-
- - The Todo type itself
- - Ways to make HTTP requests to retrieve and modify Todos
- - Ways to access information about an Todo
- - Converting between various types
-
--}
-
-import Api exposing (Cred)
-import Api.Endpoint as Endpoint
-import Author exposing (Author)
-import Element exposing (..)
-import Http
-import Iso8601
-import Json.Decode as Decode exposing (Decoder)
-import Json.Encode as Encode
-import Markdown
-import Time
-import Todo.Body as Body exposing (Body)
-import Todo.Tag as Tag exposing (Tag)
-import Todo.UUID exposing (UUID)
-import Username as Username exposing (Username)
-import Viewer exposing (Viewer)
-
-
-
--- TYPES
-
-
-{-| An Todo, optionally with an Todo body.
-To see the difference between { extraInfo : a } and { extraInfo : Maybe Body },
-consider the difference between the "view individual Todo" page (which
-renders one Todo, including its body) and the "Todo feed" -
-which displays multiple Todos, but without bodies.
-This definition for `Todo` means we can write:
-viewTodo : Todo Full -> Html msg
-viewFeed : List (Todo Preview) -> Html msg
-This indicates that `viewTodo` requires an Todo _with a `body` present_,
-wereas `viewFeed` accepts Todos with no bodies. (We could also have written
-it as `List (Todo a)` to specify that feeds can accept either Todos that
-have `body` present or not. Either work, given that feeds do not attempt to
-read the `body` field from Todos.)
-This is an important distinction, because in Request.Todo, the `feed`
-function produces `List (Todo Preview)` because the API does not return bodies.
-Those Todos are useful to the feed, but not to the individual Todo view.
--}
-type Todo a
- = Todo Internals a
-
-
-{-| Metadata about the Todo - its title, description, and so on.
-Importantly, this module's public API exposes a way to read this metadata, but
-not to alter it. This is read-only information!
-If we find ourselves using any particular piece of metadata often,
-for example `title`, we could expose a convenience function like this:
-Todo.title : Todo a -> String
-If you like, it's totally reasonable to expose a function like that for every one
-of these fields!
-(Okay, to be completely honest, exposing one function per field is how I prefer
-to do it, and that's how I originally wrote this module. However, I'm aware that
-this code base has become a common reference point for beginners, and I think it
-is _extremely important_ that slapping some "getters and setters" on a record
-does not become a habit for anyone who is getting started with Elm. The whole
-point of making the Todo type opaque is to create guarantees through
-_selectively choosing boundaries_ around it. If you aren't selective about
-where those boundaries are, and instead expose a "getter and setter" for every
-field in the record, the result is an API with no more guarantees than if you'd
-exposed the entire record directly! It is so important to me that beginners not
-fall into the terrible "getters and setters" trap that I've exposed this
-Metadata record instead of exposing a single function for each of its fields,
-as I did originally. This record is not a bad way to do it, by any means,
-but if this seems at odds with - now you know why!
-)
--}
-type alias Metadata =
- { description : String
- , title : String
- , tags : List String
- , createdAt : Time.Posix
- , favorited : Bool
- , favoritesCount : Int
- }
-
-
-type alias Internals =
- { uuid : UUID
- , author : Author
- , metadata : Metadata
- }
-
-
-type Preview
- = Preview
-
-
-type Full
- = Full Body
-
-
-
--- INFO
-
-
-author : Todo a -> Author
-author (Todo internals _) =
- internals.author
-
-
-metadata : Todo a -> Metadata
-metadata (Todo internals _) =
- internals.metadata
-
-
-uuid : Todo a -> UUID
-uuid (Todo internals _) =
- internals.uuid
-
-
-body : Todo Full -> Body
-body (Todo _ (Full extraInfo)) =
- extraInfo
-
-
-
--- TRANSFORM
-
-
-{-| This is the only way you can transform an existing Todo:
-you can change its author (e.g. to follow or unfollow them).
-All other Todo data necessarily comes from the server!
-We can tell this for sure by looking at the types of the exposed functions
-in this module.
--}
-mapAuthor : (Author -> Author) -> Todo a -> Todo a
-mapAuthor transform (Todo info extras) =
- Todo { info | author = transform info.author } extras
-
-
-fromPreview : Body -> Todo Preview -> Todo Full
-fromPreview newBody (Todo info Preview) =
- Todo info (Full newBody)
-
-
-
--- SERIALIZATION
-
-
-previewDecoder : Maybe Cred -> Decoder (Todo Preview)
-previewDecoder maybeCred =
- Decode.succeed Todo
- |> custom (internalsDecoder maybeCred)
- |> hardcoded Preview
-
-
-fullDecoder : Maybe Cred -> Decoder (Todo Full)
-fullDecoder maybeCred =
- Decode.succeed Todo
- |> custom (internalsDecoder maybeCred)
- |> required "body" (Decode.map Full Body.decoder)
-
-
-internalsDecoder : Maybe Cred -> Decoder Internals
-internalsDecoder maybeCred =
- Decode.succeed Internals
- |> required "uuid" UUID.decoder
- |> required "author" (Author.decoder maybeCred)
- |> custom metadataDecoder
-
-
-metadataDecoder : Decoder Metadata
-metadataDecoder =
- Decode.succeed Metadata
- |> required "description" (Decode.map (Maybe.withDefault "") (Decode.nullable Decode.string))
- |> required "title" Decode.string
- |> required "tagList" (Decode.list Decode.string)
- |> required "createdAt" Iso8601.decoder
- |> required "favorited" Decode.bool
- |> required "favoritesCount" Decode.int
-
-
-
--- SINGLE
-
-
-fetch : Maybe Cred -> UUID -> Http.Request (Todo Full)
-fetch maybeCred uuid =
- Decode.field "Todo" (fullDecoder maybeCred)
- |> Api.get (Endpoint.Todo uuid) maybeCred
-
-
-
--- FAVORITE
-
-
-favorite : UUID -> Cred -> Http.Request (Todo Preview)
-favorite uuid cred =
- Api.post (Endpoint.favorite uuid) (Just cred) Http.emptyBody (faveDecoder cred)
-
-
-unfavorite : UUID -> Cred -> Http.Request (Todo Preview)
-unfavorite uuid cred =
- Api.delete (Endpoint.favorite uuid) cred Http.emptyBody (faveDecoder cred)
-
-
-faveDecoder : Cred -> Decoder (Todo Preview)
-faveDecoder cred =
- Decode.field "Todo" (previewDecoder (Just cred))
-
-
-{-| This is a "build your own element" API.
-You pass it some configuration, followed by a `List (Attribute msg)` and a
-`List (Html msg)`, just like any standard Html element.
--}
-favoriteButton :
- Cred
- -> msg
- -> List (Attribute msg)
- -> List (Element msg)
- -> Element msg
-favoriteButton _ msg attrs kids =
- toggleFavoriteButton "btn btn-sm btn-outline-primary" msg attrs kids
-
-
-unfavoriteButton :
- Cred
- -> msg
- -> List (Attribute msg)
- -> List (Element msg)
- -> Element msg
-unfavoriteButton _ msg attrs kids =
- toggleFavoriteButton "btn btn-sm btn-primary" msg attrs kids
-
-
-toggleFavoriteButton :
- String
- -> msg
- -> List (Attribute msg)
- -> List (Element msg)
- -> Element msg
-toggleFavoriteButton classStr msg attrs kids =
- Html.button
- (class classStr :: onClickStopPropagation msg :: attrs)
- (i [ class "ion-heart" ] [] :: kids)
-
-
-onClickStopPropagation : msg -> Attribute msg
-onClickStopPropagation msg =
- stopPropagationOn "click"
- (Decode.succeed ( msg, True ))
diff --git a/elm-deprecated/src/Todo/UUID.elm b/elm-deprecated/src/Todo/UUID.elm
deleted file mode 100644
index bd200f1..0000000
--- a/elm-deprecated/src/Todo/UUID.elm
+++ /dev/null
@@ -1,35 +0,0 @@
-module Todo.UUID exposing (UUID, decoder, toString, urlParser)
-
-import Json.Decode as Decode exposing (Decoder)
-import Url.Parser exposing (Parser)
-
-
-
--- TYPES
-
-
-type UUID
- = UUID String
-
-
-
--- CREATE
-
-
-urlParser : Parser (UUID -> a) a
-urlParser =
- Url.Parser.custom "UUID" (\str -> Just (UUID str))
-
-
-decoder : Decoder UUID
-decoder =
- Decode.map UUID Decode.string
-
-
-
--- TRANSFORM
-
-
-toString : UUID -> String
-toString (UUID str) =
- str
diff --git a/elm-deprecated/src/Username.elm b/elm-deprecated/src/Username.elm
deleted file mode 100644
index 781104d..0000000
--- a/elm-deprecated/src/Username.elm
+++ /dev/null
@@ -1,47 +0,0 @@
-module Username exposing (Username, decoder, encode, toHtml, toString, urlParser)
-
-import Element exposing (..)
-import Json.Decode as Decode exposing (Decoder)
-import Json.Encode as Encode exposing (Value)
-import Url.Parser
-
-
-
--- TYPES
-
-
-type Username
- = Username String
-
-
-
--- CREATE
-
-
-decoder : Decoder Username
-decoder =
- Decode.map Username Decode.string
-
-
-
--- TRANSFORM
-
-
-encode : Username -> Value
-encode (Username username) =
- Encode.string username
-
-
-toString : Username -> String
-toString (Username username) =
- username
-
-
-urlParser : Url.Parser.Parser (Username -> a) a
-urlParser =
- Url.Parser.custom "USERNAME" (\str -> Just (Username str))
-
-
-toHtml : Username -> Element msg
-toHtml (Username username) =
- text username
diff --git a/elm-deprecated/src/Viewer.elm b/elm-deprecated/src/Viewer.elm
deleted file mode 100644
index 58ec005..0000000
--- a/elm-deprecated/src/Viewer.elm
+++ /dev/null
@@ -1,66 +0,0 @@
-module Viewer exposing (Viewer, avatar, cred, decoder, minPasswordChars, store, username)
-
-{-| The logged-in user currently viewing this page. It stores enough data to
-be able to render the menu bar (username and avatar), along with Cred so it's
-impossible to have a Viewer if you aren't logged in.
--}
-
-import Api exposing (Cred)
-import Avatar exposing (Avatar)
-import Email exposing (Email)
-import Json.Decode as Decode exposing (Decoder)
-import Json.Decode.Pipeline exposing (custom, required)
-import Json.Encode as Encode exposing (Value)
-import Profile exposing (Profile)
-import Username exposing (Username)
-
-
-
--- TYPES
-
-
-type Viewer
- = Viewer Avatar Cred
-
-
-
--- INFO
-
-
-cred : Viewer -> Cred
-cred (Viewer _ val) =
- val
-
-
-username : Viewer -> Username
-username (Viewer _ val) =
- Api.username val
-
-
-avatar : Viewer -> Avatar
-avatar (Viewer val _) =
- val
-
-
-{-| Passwords must be at least this many characters long!
--}
-minPasswordChars : Int
-minPasswordChars =
- 6
-
-
-
--- SERIALIZATION
-
-
-decoder : Decoder (Cred -> Viewer)
-decoder =
- Decode.succeed Viewer
- |> custom (Decode.field "image" Avatar.decoder)
-
-
-store : Viewer -> Cmd msg
-store (Viewer avatarVal credVal) =
- Api.storeCredWith
- credVal
- avatarVal
diff --git a/todo/package.json b/package.json
similarity index 62%
rename from todo/package.json
rename to package.json
index fd35f15..2bcaeaf 100644
--- a/todo/package.json
+++ b/package.json
@@ -4,10 +4,6 @@
"description": "todo list app (because it hasnt been done before)",
"main": "src/index.js",
"scripts": {
- "who": "pwd",
- "build": "cd frontend; elm make src/Main.elm --output=../static/elm.js",
- "debug": "cd frontend; elm make src/Main.elm --debug --output=../static/elm.js",
- "prod": "cd frontend; elm make src/Main.elm --optimize --output=../static/elm.js",
"start": "node src/index.js"
},
"repository": {
@@ -18,9 +14,8 @@
"license": "SEE LICENSE IN LICENSE",
"dependencies": {
"cookie-parser": "^1.4.5",
- "cors": "^2.8.5",
"express": "^4.17.1",
"pg": "^8.6.0",
"sequelize": "^6.6.2"
}
-}
\ No newline at end of file
+}
diff --git a/todo/pnpm-lock.yaml b/pnpm-lock.yaml
similarity index 97%
rename from todo/pnpm-lock.yaml
rename to pnpm-lock.yaml
index 1b8148f..7711e79 100644
--- a/todo/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,6 +1,5 @@
dependencies:
cookie-parser: 1.4.5
- cors: 2.8.5
express: 4.17.1
pg: 8.6.0
sequelize: 6.6.2_pg@8.6.0
@@ -89,15 +88,6 @@ packages:
node: '>= 0.6'
resolution:
integrity: sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
- /cors/2.8.5:
- dependencies:
- object-assign: 4.1.1
- vary: 1.1.2
- dev: false
- engines:
- node: '>= 0.10'
- resolution:
- integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
/debug/2.6.9:
dependencies:
ms: 2.0.0
@@ -343,12 +333,6 @@ packages:
node: '>= 0.6'
resolution:
integrity: sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
- /object-assign/4.1.1:
- dev: false
- engines:
- node: '>=0.10.0'
- resolution:
- integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
/on-finished/2.3.0:
dependencies:
ee-first: 1.1.1
@@ -695,7 +679,6 @@ packages:
integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
specifiers:
cookie-parser: ^1.4.5
- cors: ^2.8.5
express: ^4.17.1
pg: ^8.6.0
sequelize: ^6.6.2
diff --git a/todo/src/config.js b/src/config.js
similarity index 100%
rename from todo/src/config.js
rename to src/config.js
diff --git a/todo/src/db_interface.js b/src/db_interface.js
similarity index 100%
rename from todo/src/db_interface.js
rename to src/db_interface.js
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..af37430
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,39 @@
+const express = require('express');
+const cookieParser = require('cookie-parser');
+const Config = require('./config.js');
+
+const UserInterface = require('./user.js');
+
+let app = express();
+
+app.use(cookieParser());
+
+// force https
+app.use((req, res, next) => {
+ if (Config.config.https) {
+ if (req.headers['x-forwarded-proto'] !== 'https') {
+ return res.redirect(`https://${req.headers.host}${req.url}`);
+ }
+ }
+ return next();
+});
+
+if (!Config.config.secret) {
+ console.error("No password secret found. please set `secret` in config.json");
+ process.exit();
+}
+else if(Config.config.https && Config.config.secret == "TEST_SECRET") {
+ console.error("please do not use the testing secret in production.");
+ process.exit();
+}
+
+app.use("/user", UserInterface.router);
+
+// serve static files last
+// app.use(express.static("./static"));
+// DISABLED: no longer needs to serve static files
+// due to frontend being employed in elm
+
+app.listen(Config.config.port || 8080, () => {
+ console.log(`listening on port ${Config.config.port || 8080}`);
+});
\ No newline at end of file
diff --git a/todo/src/user.js b/src/user.js
similarity index 59%
rename from todo/src/user.js
rename to src/user.js
index 980cb77..2563baf 100644
--- a/todo/src/user.js
+++ b/src/user.js
@@ -13,19 +13,16 @@ user_cache = {};
email_cache = {};
async function get_user_details(id) {
- if (!id) {
- return undefined;
- }
console.log(`search for user with id ${id}`);
if (!user_cache[id]) {
- let user = await Database.schemas.user.findOne({where: {id: id}});
+ let user = await Database.schemas.user.findOne({ where: { id: id } });
if (!user) {
return undefined;
}
user_cache[user.id] = {
id: user.id,
email: user.email,
- password_hash: user.password_hash,
+ password_hash: user.password_hash
};
email_cache[user.email] = user.id;
}
@@ -34,19 +31,16 @@ async function get_user_details(id) {
}
async function get_user_details_by_email(email) {
- if (!email) {
- return undefined;
- }
console.log(`search for user with email ${email}}`);
if (!email_cache[email] || !user_cache[email_cache[email]]) {
- let user = await Database.schemas.user.findOne({where: {email: email}});
+ let user = await Database.schemas.user.findOne({ where: { email: email } });
if (!user) {
return undefined;
}
user_cache[user.id] = {
id: user.id,
email: user.email,
- password_hash: user.password_hash,
+ password_hash: user.password_hash
};
email_cache[user.email] = user.id;
}
@@ -54,10 +48,10 @@ async function get_user_details_by_email(email) {
return user_cache[email_cache[email]];
}
-router.get('/byEmail/:email', async (req, res) => {
+router.get("/byEmail/:email", async (req, res) => {
if (!req.params?.email) {
res.status(400).json({
- error: 'email is a required parameter',
+ error: "email is a required parameter"
});
}
let user = get_user_details_by_email(req.params.email);
@@ -65,33 +59,30 @@ router.get('/byEmail/:email', async (req, res) => {
if (user != null) {
res.json({
id: user.id,
- email: user.email,
+ email: user.email
});
- } else {
+ }
+ else {
res.sendStatus(404);
}
});
function hash(secret, password) {
- let pw_hash = crypto.pbkdf2Sync(
- password,
+ let pw_hash = crypto.pbkdf2Sync(password,
secret,
Config.config.key?.iterations || 1000,
Config.config.key?.length || 64,
- 'sha512'
- );
+ "sha512");
return pw_hash.toString('base64');
}
function verify(secret, password, hash) {
- let pw_hash = crypto.pbkdf2Sync(
- password,
+ let pw_hash = crypto.pbkdf2Sync(password,
secret,
Config.config.key?.iterations || 1000,
Config.config.key?.length || 64,
- 'sha512'
- );
+ "sha512");
return hash === pw_hash.toString('base64');
}
@@ -112,7 +103,8 @@ function get_session_token(id, token) {
function verify_session_token(id, hash, token) {
if (session_entropy[id]) {
return verify(session_entropy[id], hash, token);
- } else {
+ }
+ else {
return false;
}
}
@@ -120,116 +112,106 @@ function verify_session_token(id, hash, token) {
async function enforce_session_login(req, res, next) {
let userid = req.cookies?.userid;
let session_token = req.cookies?._session;
- console.log('a', userid, session_token);
+ console.log("a", userid, session_token);
if (!userid || !session_token) {
- return res.sendStatus(401);
+ res.sendStatus(401);
}
let user = await get_user_details(userid);
if (!user) {
- return res.sendStatus(401);
+ res.sendStatus(401);
}
- let verified_session = verify_session_token(
- userid,
- user.password_hash,
- session_token
- );
+ let verified_session = verify_session_token(userid, user.password_hash, session_token);
if (!verified_session) {
- return res.sendStatus(401);
+ res.sendStatus(401);
}
return next();
}
-router.post('/new', async (req, res) => {
+router.post("/new", async (req, res) => {
if (!req.body?.email || !req.body?.password) {
- return res.status(400).json({
- error: 'must have email and password fields',
+ res.status(400).json({
+ error: "must have email and password fields"
});
}
let user = await get_user_details_by_email(req.body.email);
console.log(user);
if (user != null) {
- return res.status(403).json({
- error: `email ${req.body.email} is already in use.`,
+ res.status(403).json({
+ error: `email ${req.body.email} is already in use.`
});
- } else {
+ }
+ else {
let user = await Database.schemas.user.create({
email: String(req.body.email),
- password_hash: hash_password(req.body.password),
+ password_hash: hash_password(req.body.password)
});
- return res.json({
+ res.json({
id: user.id,
- email: user.email,
+ email: user.email
});
}
});
-router.post('/login', async (req, res) => {
+router.post("/login", async (req, res) => {
if (!req.body?.email || !req.body?.password) {
- return res.status(400).json({
- error: 'must have email and password fields',
+ res.status(400).json({
+ error: "must have email and password fields"
});
}
let user = await get_user_details_by_email(req.body.email);
if (!user) {
- return res.status(401).json({
- error: 'incorrect email or password',
+ res.status(401).json({
+ error: "incorrect email or password"
});
}
let verified = verify_password(req.body.password, user.password_hash);
if (!verified) {
- return res.status(401).json({
- error: 'incorrect email or password',
+ res.status(401).json({
+ error: "incorrect email or password"
});
}
- res.cookie('userid', user.id, {
- httpOnly: true,
- secure: true,
- });
- res.cookie('_session', get_session_token(user.id, user.password_hash), {
- httpOnly: true,
- secure: true,
- });
- return res.sendStatus(204);
+ res.cookie("userid", user.id);
+ res.cookie("_session", get_session_token(user.id, user.password_hash));
+ res.sendStatus(204);
});
-router.get('/:id([a-f0-9-]+)', async (req, res) => {
- console.log(req.params);
+router.get("/:id(([a-f0-9\-])+)", async (req, res) => {
if (!req.params?.id) {
- return res.status(400).json({
- error: 'must have id parameter',
+ res.status(400).json({
+ error: "must have id parameter"
});
}
- let id = req.params?.id;
- console.log(id);
- let user = await get_user_details(id);
+ let user = await get_user_details(req.body.id);
console.log(user);
if (user != null) {
- return res.json({
+ res.json({
id: user.id,
- email: user.email,
+ email: user.email
});
- } else {
- return res.sendStatus(404);
+ }
+ else {
+ res.sendStatus(404);
}
});
-router.use('/authorized', enforce_session_login);
-router.get('/authorized', async (req, res) => {
+router.use("/authorized", enforce_session_login);
+router.get("/authorized", async (req, res) => {
let userid = req.cookies?.userid;
- let user = await get_user_details(userid);
- return res.json({
+ let user = get_user_details(userid);
+ res.json({
authorized: true,
user: {
id: user.id,
- email: user.email,
- },
+ email: user.email
+ }
});
});
+
module.exports = {
router: router,
- enforce_session_login: enforce_session_login,
-};
+ enforce_session_login: enforce_session_login
+};
\ No newline at end of file
diff --git a/static/index.html b/static/index.html
new file mode 100644
index 0000000..ec57bad
--- /dev/null
+++ b/static/index.html
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/todo/src/index.js b/todo/src/index.js
deleted file mode 100644
index 8c86ac0..0000000
--- a/todo/src/index.js
+++ /dev/null
@@ -1,62 +0,0 @@
-const http = require('http');
-const https = require('https');
-const cors = require('cors');
-const express = require('express');
-const cookieParser = require('cookie-parser');
-const Config = require('./config.js');
-
-const UserInterface = require('./user.js');
-
-let credentials = {};
-
-if (Config.config.https) {
- if (
- fs.existsSync(Config.config.cert) &&
- fs.existsSync(Config.config.cert_key)
- ) {
- credentials.key = fs.readFileSync(Config.config.cert_key);
- credentials.cert = fs.readFileSync(Config.config.cert);
- }
-}
-
-let app = express();
-
-app.use(cors());
-app.use(cookieParser());
-
-// force https
-app.use((req, res, next) => {
- if (Config.config.https) {
- if (req.headers['x-forwarded-proto'] !== 'https') {
- return res.redirect(`https://${req.headers.host}${req.url}`);
- }
- }
- return next();
-});
-
-if (!Config.config.secret) {
- console.error('No password secret found. please set `secret` in config.json');
- process.exit();
-} else if (Config.config.https && Config.config.secret == 'TEST_SECRET') {
- console.error('please do not use the testing secret in production.');
- process.exit();
-}
-
-app.use('/api/user', UserInterface.router);
-
-// serve static files last
-app.use(express.static('./static'));
-// DISABLED: no longer needs to serve static files
-// due to frontend being employed in elm
-
-if (Config.config.https) {
- var server = https.createServer(credentials, app);
- server.listen(Config.config.port || 8080);
-} else {
- var server = http.createServer(app);
- server.listen(Config.config.port || 8080);
-}
-console.log(
- `listening on port ${Config.config.port || 8080}` +
- ` with https ${Config.config.https ? 'enabled' : 'disabled'}`
-);