parent
854e37c476
commit
c11f9bbf3a
5 changed files with 201 additions and 0 deletions
@ -0,0 +1,30 @@ |
||||
Copyright Chris Allen (c) 2015 |
||||
|
||||
All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
* Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
* Redistributions in binary form must reproduce the above |
||||
copyright notice, this list of conditions and the following |
||||
disclaimer in the documentation and/or other materials provided |
||||
with the distribution. |
||||
|
||||
* Neither the name of Chris Allen nor the names of other |
||||
contributors may be used to endorse or promote products derived |
||||
from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,2 @@ |
||||
import Distribution.Simple |
||||
main = defaultMain |
@ -0,0 +1,104 @@ |
||||
{-# LANGUAGE OverloadedStrings #-} |
||||
|
||||
module Main where |
||||
|
||||
import Control.Monad (replicateM) |
||||
import Control.Monad.IO.Class (liftIO) |
||||
import qualified Data.ByteString.Char8 as BC |
||||
import Data.Text.Encoding (decodeUtf8, encodeUtf8) |
||||
import qualified Data.Text.Lazy as TL |
||||
import qualified Database.Redis as R |
||||
import Network.URI (URI, parseURI) |
||||
import qualified System.Random as SR |
||||
import Web.Scotty |
||||
|
||||
alphaNum :: String |
||||
alphaNum = ['A'..'Z'] ++ ['0'..'9'] |
||||
|
||||
randomElement :: String -> IO Char |
||||
randomElement xs = do |
||||
let maxIndex :: Int |
||||
maxIndex = length xs - 1 |
||||
-- Right of arrow is IO Int, so randomDigit is Int |
||||
randomDigit <- SR.randomRIO (0, maxIndex) :: IO Int |
||||
return (xs !! randomDigit) |
||||
|
||||
shortyGen :: IO String |
||||
shortyGen = |
||||
replicateM 7 (randomElement alphaNum) |
||||
|
||||
saveURI :: R.Connection |
||||
-> BC.ByteString |
||||
-> BC.ByteString |
||||
-> IO (Either R.Reply R.Status) |
||||
saveURI conn shortURI uri = |
||||
R.runRedis conn $ R.set shortURI uri |
||||
|
||||
getURI :: R.Connection |
||||
-> BC.ByteString |
||||
-> IO (Either R.Reply (Maybe BC.ByteString)) |
||||
getURI conn shortURI = R.runRedis conn $ R.get shortURI |
||||
|
||||
linkShorty :: String -> String |
||||
linkShorty shorty = |
||||
concat [ "<a href=\"" |
||||
, shorty |
||||
, "\">Copy and paste your short URL</a>" |
||||
] |
||||
|
||||
shortyCreated :: Show a => a -> String -> TL.Text |
||||
shortyCreated resp shawty = |
||||
TL.concat [ TL.pack (show resp) |
||||
, " shorty is: ", TL.pack (linkShorty shawty) |
||||
] |
||||
|
||||
shortyAintUri :: TL.Text -> TL.Text |
||||
shortyAintUri uri = |
||||
TL.concat [ uri |
||||
, " wasn't a url, did you forget http://?" |
||||
] |
||||
|
||||
shortyFound :: TL.Text -> TL.Text |
||||
shortyFound tbs = |
||||
TL.concat ["<a href=\"", tbs, "\">", tbs, "</a>"] |
||||
|
||||
app :: R.Connection |
||||
-> ScottyM () |
||||
app rConn = do |
||||
get "/" $ do |
||||
uri <- param "uri" |
||||
let parsedUri :: Maybe URI |
||||
parsedUri = parseURI (TL.unpack uri) |
||||
case parsedUri of |
||||
Just _ -> do |
||||
shawty <- liftIO shortyGen |
||||
let shorty = BC.pack shawty |
||||
uri' = encodeUtf8 (TL.toStrict uri) |
||||
-- **** Excercise **** |
||||
-- Check if it already exists |
||||
uri'' <- liftIO (getURI rConn shorty) |
||||
case uri'' of |
||||
Left reply -> text (TL.pack (show reply)) |
||||
Right mbBS -> case mbBS of |
||||
Nothing -> do |
||||
-- It doesn't so save it |
||||
resp <- liftIO (saveURI rConn shorty uri') |
||||
html (shortyCreated resp shawty) |
||||
Just _ -> text "Duplicate short uri created." |
||||
-- **** End of Exercise **** |
||||
Nothing -> text (shortyAintUri uri) |
||||
get "/:short" $ do |
||||
short <- param "short" |
||||
uri <- liftIO (getURI rConn short) |
||||
case uri of |
||||
Left reply -> text (TL.pack (show reply)) |
||||
Right mbBS -> case mbBS of |
||||
Nothing -> text "uri not found" |
||||
Just bs -> html (shortyFound tbs) |
||||
where tbs :: TL.Text |
||||
tbs = TL.fromStrict (decodeUtf8 bs) |
||||
|
||||
main :: IO () |
||||
main = do |
||||
rConn <- R.connect R.defaultConnectInfo |
||||
scotty 3000 (app rConn) |
@ -0,0 +1,33 @@ |
||||
name: shawty |
||||
version: 0.1.0.0 |
||||
synopsis: Initial project template from stack |
||||
description: Please see README.md |
||||
homepage: http://github.com/bitemyapp/shawty#readme |
||||
license: BSD3 |
||||
license-file: LICENSE |
||||
author: Chris Allen |
||||
maintainer: cma@bitemyapp.com |
||||
copyright: 2015, Chris Allen |
||||
category: Web |
||||
build-type: Simple |
||||
cabal-version: >=1.10 |
||||
|
||||
executable shawty |
||||
hs-source-dirs: app |
||||
main-is: Main.hs |
||||
ghc-options: -threaded -rtsopts -with-rtsopts=-N |
||||
build-depends: base |
||||
, bytestring |
||||
, hedis |
||||
, mtl |
||||
, network-uri |
||||
, random |
||||
, scotty |
||||
, semigroups |
||||
, text |
||||
, transformers |
||||
default-language: Haskell2010 |
||||
|
||||
source-repository head |
||||
type: git |
||||
location: https://github.com/bitemyapp/shawty-prime |
@ -0,0 +1,32 @@ |
||||
# For more information, see: https://github.com/commercialhaskell/stack/blob/release/doc/yaml_configuration.md |
||||
|
||||
# Specifies the GHC version and set of packages available (e.g., lts-3.5, nightly-2015-09-21, ghc-7.10.2) |
||||
resolver: lts-3.7 |
||||
|
||||
# Local packages, usually specified by relative directory name |
||||
packages: |
||||
- '.' |
||||
|
||||
# Packages to be pulled from upstream that are not in the resolver (e.g., acme-missiles-0.3) |
||||
extra-deps: [] |
||||
|
||||
# Override default flag values for local packages and extra-deps |
||||
flags: {} |
||||
|
||||
# Extra package databases containing global packages |
||||
extra-package-dbs: [] |
||||
|
||||
# Control whether we use the GHC we find on the path |
||||
# system-ghc: true |
||||
|
||||
# Require a specific version of stack, using version ranges |
||||
# require-stack-version: -any # Default |
||||
# require-stack-version: >= 0.1.4.0 |
||||
|
||||
# Override the architecture used by stack, especially useful on Windows |
||||
# arch: i386 |
||||
# arch: x86_64 |
||||
|
||||
# Extra directories used by stack for building |
||||
# extra-include-dirs: [/path/to/dir] |
||||
# extra-lib-dirs: [/path/to/dir] |
Loading…
Reference in new issue