Explorar el Código

Web UI: static assets served, including favicon. GET(short) success OK.

Frederic G. MARAND hace 5 años
padre
commit
c9ae5474d8

+ 1 - 1
Makefile

@@ -10,5 +10,5 @@ clean:
 cmd/kurz/kurz: $(wildcard cmd/kurz/*.go)
 	cd cmd/kurz && go build
 
-cmd/kurzd/kurzd: $(wildcard cmd/kurzd/*.go) $(wildcard migrations/*.go)
+cmd/kurzd/kurzd: $(wildcard api/*.go) $(wildcard cmd/kurzd/*.go) $(wildcard domain/*.go) $(wildcard infrastructure/*.go) $(wildcard migrations/*.go) $(wildcard web/*.go)
 	cd cmd/kurzd && go build

+ 0 - 1
api/api.go

@@ -59,7 +59,6 @@ const (
 )
 
 // SetupRoutes() configures Web API routes on the passed mux.Router.
-//
 func SetupRoutes(router *mux.Router) {
 	// BUG(fgm): improve Accept header matchers once https://github.com/golang/go/issues/19307 is completed.
 	router.HandleFunc("/{short}", handleGetShort).

+ 2 - 0
cmd/kurzd/dist.config.yml

@@ -7,3 +7,5 @@ api:
   address: ":8000"
 web:
   address: ":3000"
+  # May be absolute or relative to the "kurzd" binary.
+  assetsPath: ../../web/public

+ 5 - 1
cmd/kurzd/serve.go

@@ -64,9 +64,13 @@ func serveHandler(_ *cobra.Command, args []string) {
 	db = ensureInfrastructure(db)
 	defer db.Close()
 
+	// This default is the relative position of the assets from the kurzd binary during development.
+	viper.SetDefault("web.assetsPath", "../../web/public")
+	assetsPath := viper.Get("web.assetsPath").(string)
+
 	router := mux.NewRouter()
 	api.SetupRoutes(router)
-	web.SetupRoutes(router)
+	web.SetupRoutes(router, assetsPath)
 	http.Handle("/", router)
 
 	address := viper.Get("web.address").(string)

+ 43 - 2
web/get_short.go

@@ -1,8 +1,49 @@
 package web
 
-import "net/http"
+import (
+	"code.osinet.fr/fgm/kurz/domain"
+	"github.com/gorilla/mux"
+	"net/http"
+)
 
 // handleGetShort handles path /<short>
 func handleGetShort(w http.ResponseWriter, r *http.Request) {
-	w.Write([]byte("Hello short"))
+	short, ok := mux.Vars(r)["short"]
+	if !ok {
+		w.WriteHeader(http.StatusBadRequest)
+		return
+	}
+
+	target, err := domain.GetTargetURL(short)
+	// Happy path.
+	if err == nil {
+		w.Header().Set("Location", target)
+		w.WriteHeader(http.StatusTemporaryRedirect)
+		return
+	}
+
+	// Very sad path.
+	domainErr, ok := err.(domain.Error)
+	if !ok {
+		// All errors return by the API should be domain-specific errors.
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+
+	// Normal sad paths.
+	// TODO improve UX: build pages instead of just HTTP 4xx.
+	var status int
+	switch domainErr.Kind {
+	case domain.ShortNotFound:
+		status = http.StatusNotFound
+	case domain.TargetBlockedError:
+		status = http.StatusForbidden
+	case domain.TargetCensoredError:
+		status = http.StatusUnavailableForLegalReasons
+	default:
+		// TargetInvalid is not supposed to happen in this case, so it is an internal error too.
+		status = http.StatusInternalServerError
+	}
+
+	w.WriteHeader(status)
 }

+ 7 - 0
web/public/css/index.html

@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <title>Nothing to see here</title>
+  <meta http-equiv="Refresh" content="0; url=/" />
+</head>
+</html>

+ 152 - 0
web/public/css/plusvite.css

@@ -0,0 +1,152 @@
+/**
+ * Main style sheet for plusvite.net
+ *
+ * @copyright 2003-2014 Ouest Systèmes Informatiques SARL
+ * @author Frédéric G. MARAND
+ * @license Confidentiel industrie. Tous droits réservés
+ */
+
+/* CSS Document */
+
+/* Chrome 32: input does not inherit body font. */
+body,
+input {
+  font-family: 'Lato', verdana, arial, sans-serif;
+}
+
+code {
+  font-family: monospace;
+  padding: 0.2em 0.5em;
+}
+
+#pageFooter {
+  color: #aba4b3;
+  margin-left: auto;
+  margin-right: auto;
+  max-width: 92%;
+  opacity: 1;
+}
+/*
+.finePrint {
+  border-top: solid blue 1px;
+  color: blue;
+  font-family: 'Times New Roman',Times,serif;
+  left: 0;
+  margin: 1em;
+  position: absolute ;
+  top: 90%;
+}
+*/
+.finePrint .tdStats {
+  font-size: 60%;
+  white-space: normal;
+}
+
+table,
+tbody,
+td,
+tr {
+  border: none;
+}
+
+.tdStats.invisible {
+  display: none;
+}
+
+pre {
+  font-family: monospace;
+  margin: 0.2em 2.5em;
+  padding: 0.5em 1em;
+}
+
+form {
+  /*border: ridge blue 2px;*/
+  /*background-color: #80ffc0;*/
+  font-size: 120%;
+  margin: 0.8em 0;
+  padding: 9px 8px 9px 8px;
+  text-align: center;
+}
+
+form div {
+  margin: 0.3em 0 0 0;
+}
+
+form ul {
+  color: red;
+  margin: 0;
+}
+
+label,
+input {
+  display: block;
+}
+
+input {
+  height: 30px;
+  margin: 0.8em auto;
+}
+
+ul {
+  list-style-type: none;
+  margin: 0.5em auto auto auto;
+  padding: 0;
+}
+
+li {
+  line-height: 1.5em;
+}
+
+h1 {
+  margin-bottom: 0.3em;
+  margin-top: 0;
+  text-align: center;
+}
+
+body {
+  background: url(../images/lights.jpg) no-repeat right;
+  font-size: 1.2em;
+  height: 100%;
+  line-height: 1.5;
+}
+
+#box {
+  background-color: rgba(253,250,252,1);
+  -webkit-border-radius: 5px;
+  -moz-border-radius: 5px;
+  border-radius: 5px;
+  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
+  color: #1c1b1e;
+  margin: 3em auto;
+  max-width: 600px;
+  padding: 1.2em;
+  vertical-align: middle;
+}
+
+.notes {
+  font-size: 80%;
+}
+
+.notes td:first-of-type {
+  padding: 0 1em;
+}
+
+a {
+  border-bottom: 1px dotted #FF9900;
+  color: inherit;
+  text-decoration: none;
+}
+
+a:visited {
+  color: inherit;
+}
+
+a:hover {
+  color: #1c1b1e;
+}
+
+.original-url,
+.shortened-url {
+  max-width: 100%;
+  white-space:normal;
+}

+ 256 - 0
web/public/css/riff.css

@@ -0,0 +1,256 @@
+pre,
+code {
+  font-family: 'courier new', fixed, monospace;
+  background-color: transparent;
+  color: black;
+}
+
+code {
+  font-size: 100%;
+  padding: 0 0.3em;
+}
+
+pre {
+  border: ridge #FF9900 3px;
+  margin: 0.5em 1em 0 0;
+  padding: 0.5em;
+  font-size: small;
+}
+
+td {
+  vertical-align: top
+}
+
+p {
+  margin: 0.3em 0 0 0;
+}
+
+/* dt { margin: 0 }
+dd { margin: -1.2em 0 0 4em } */
+
+.origin {
+  color: #000066;
+  font-size: 8pt;
+  font-style: oblique;
+  text-align: right;
+  margin: 5px;
+}
+
+.quote {
+  font-size: 10pt;
+  font-style: oblique;
+  text-align: justify;
+  margin-left: 15px;
+  margin-right: 15px;
+}
+
+/* div.main { width:			600px ; 	margin-left:	0 ;	} */
+
+div.footer {
+  /*	width:			600px ; */
+  margin-left: 150px;
+  text-align: right;
+}
+
+div.lborder {
+  /*	width:			150px ; */
+  margin-left: 0;
+  text-align: left;
+}
+
+div.tborder {
+  /*	width:			600px ; */
+  margin-left: 150px;
+  text-align: left;
+}
+
+#panelNW, #panelN, #panelNE {
+  font-size: smaller;
+  height: 60px;
+  border-bottom: ridge gray 2px;
+}
+
+#panelNW, #panelNE {
+  color: #003399;
+  background-color: transparent;
+  margin: 0;
+  text-align: center;
+}
+
+#panelNW IMG, #panelN IMG, #panelNE IMG {
+  margin-left: auto;
+  margin-right: auto;
+  display: block;
+  text-align: center;
+}
+
+#panelN {
+  width: auto;
+}
+
+#panelW, #panelE {
+  font-size: smaller;
+  background-color: #eeeecc;
+  border: groove gray 2px;
+}
+
+#panelW img {
+  border: none; /* solid black 1px ; */
+  margin: 1px 2px;
+  background-color: white;
+}
+
+#panelW h1, #main h1, #panelE h1 {
+  background-color: #ff6633;
+  text-align: center;
+  color: white;
+  padding: 0.5em 1em 0.5em 1em;
+  margin: 0;
+  font-size: large;
+}
+
+#main h3, #panelW h3, #panelE h3 {
+  background-color: #a0a070;
+  text-align: center;
+  color: white;
+  font-size: medium;
+  margin-bottom: 0;
+}
+
+#panelW TD, #panelE TD {
+  font-size: smaller;
+}
+
+#main {
+  font-size: smaller;
+  border: groove gray 2px;
+  margin: 0;
+}
+
+#main h2, #panelW h2, #panelE h2 {
+  background-color: #005a9c;
+  text-align: center;
+  color: white;
+  margin-left: 0;
+  margin-right: 0;
+  font-size: medium;
+}
+
+#panelW ul, #panelE ul {
+  list-style-image: none;
+  list-style-type: none;
+  margin-left: 0.5em;
+  margin-bottom: 0.5em;
+  font-size: 0.9em;
+}
+
+#panelW ul ul, #panelE ul ul {
+  font-size: 1em;
+}
+
+#panelW li, #panelE li {
+  margin: 0 0.1em 0.1em 0.5em;
+}
+
+#main div {
+  /*	width: 100% ; */
+  padding: 0 0.5em 0 0.5em;
+  text-align: justify;
+  border-top: solid #005a9c 1px;
+  padding: 0 0.3em 0.1em 0.3em;
+  margin: 0;
+}
+
+#main h3 {
+  margin: 0.2em 0 0 0;
+}
+
+#main .headline {
+  font-size: small;
+  margin: 0;
+  border: none;
+}
+
+.headline h3 {
+  font-size: small;
+  color: #990000;
+  margin: 0;
+}
+
+.tdStats {
+  text-align: center;
+  vertical-align: top;
+  padding: 0;
+  margin: 0;
+  white-space: nowrap;
+}
+
+.vs {
+  font-size: xx-small;
+}
+
+.warning {
+  margin: 1em;
+  padding: 1em;
+  background-color: #ffcc88;
+  color: black;
+  border: solid red 1px;
+}
+
+td.tdStats P {
+  margin-top: 0;
+}
+
+.num {
+  text-align: right;
+  background-color: #66ffcc;
+  padding: 0 0.3em 0 0
+}
+
+/* a:link, a:link {background-color: #CCCCFF; } */
+a:hover {
+  background-color: #ff9900;
+  color: #006600;
+}
+
+/*-- Repris de glo.css sur osinet --*/
+/* Table.innerTable { border: ridge 3 #000066 ; } */
+table.innerTable td {
+  border-bottom: 1px #0066ff solid;
+  vertical-align: top
+}
+
+table.innerTable td.r {
+  text-align: right
+}
+
+table.innerTable td.framed {
+  border: 1px #0066ff solid;
+  vertical-align: top
+}
+
+table.innerTable th {
+  background-color: #ffeecc;
+  border: 1px #666666 solid
+}
+
+.color1 {
+  background-color: #eeeeff;
+  color: black
+}
+
+.color2 {
+  background-color: #eeffee;
+  color: black
+}
+
+.color3 {
+  background-color: #ffffee;
+  color: black
+}
+
+abbr[title], acronym[title], dfn[title] {
+  border-bottom: 1px dotted;
+  cursor: help;
+}
+

BIN
web/public/favicon.ico


+ 7 - 0
web/public/images/index.html

@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <title>Nothing to see here</title>
+  <meta http-equiv="Refresh" content="0; url=/" />
+</head>
+</html>

BIN
web/public/images/lights.jpg


BIN
web/public/images/valid-xhtml10.gif


BIN
web/public/images/vcss.gif


+ 7 - 0
web/public/index.html

@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <title>Nothing to see here</title>
+  <meta http-equiv="Refresh" content="0; url=/" />
+</head>
+</html>

+ 7 - 0
web/public/js/index.html

@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <title>Nothing to see here</title>
+  <meta http-equiv="Refresh" content="0; url=/" />
+</head>
+</html>

+ 25 - 0
web/templates/201.gohtml

@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html lang="fr" dir="ltr">
+<head>
+    <title>{{.SiteName}}: vers {{.FullyQualifiedTargetURL}}</title>
+    <link rel="shortcut icon" href="{{.FullyQualifiedAssetsBaseURL}}/favicon.ico?v=1">
+    <link rel="stylesheet" type="text/css" href="{{.FullyQualifiedAssetsBaseURL}}/css/plusvite.css?v={{.AssetsVersion}}" />
+    <link rel="stylesheet" type="text/css" href="{{.FullyQualifiedAssetsBaseURL}}/css/riff.css?v={{.AssetsVersion}}" />
+    {{template "analytics" .}}
+    {{template "inlinecss"}}
+</head>
+
+<body>
+    <div id="box">
+        <h1>Plus vite !</h1>
+        <p>Vous pouvez dorénavant aller plus vite vers</p>
+        <pre><a href="{{.FullyQualifiedTargetURL}}" class="url-source">{{.FullyQualifiedTargetURL}}</a></pre>
+        <p>en utilisant le nouvel URL</p>
+        <pre><a href=".FullyQualifiedShortURL">.FullyQualifiedShortURL</a></pre>
+        <p>pour y faire référence.</p>
+        <p><a href=".FullyQualifiedSiteURL">Retour</a></p>
+    </div>
+
+    {{template "footer"}}
+</body>
+</html>

+ 23 - 0
web/templates/404.gohtml

@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html lang="fr" dir="ltr">
+<head>
+    <title>{{.SiteName}}: {{.FullyQualifiedShortURL}}</title>
+    <link rel="shortcut icon" href="{{.FullyQualifiedAssetsBaseURL}}/favicon.ico?v=1">
+    <link rel="stylesheet" type="text/css" href="{{.FullyQualifiedAssetsBaseURL}}/css/plusvite.css?v={{.AssetsVersion}}" />
+    <link rel="stylesheet" type="text/css" href="{{.FullyQualifiedAssetsBaseURL}}/css/riff.css?v={{.AssetsVersion}}" />
+    {{template "analytics" .}}
+    {{template "inlinecss"}}
+    <meta http-equiv="Refresh" content="{{.RefreshDelay}};{{.FullyQualifiedSiteURL}}" />
+</head>
+
+<body>
+    <div id="box">
+        <h1>{{.FullyQualifiedShortURL}}</h1>
+        <p>Ceci n'est pas un URL <a href="{{.FullyQualifiedSiteURL}}">{{.FullyQualifiedSiteURL}}</a> valide.</p>
+        <p>Dans quelques secondes, vous serez redirigé vers la page d'accueil du site.</p>
+        <p><a href="{{.FullyQualifiedSiteURL}}">Retour</a></p>
+    </div>
+
+    {{template "footer"}}
+</body>
+</html>

+ 50 - 0
web/templates/home.gohtml

@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html lang="fr" dir="ltr">
+<head>
+    <title>Plus Vite sur le Net: service gratuit d'URLs raccourcis</title>
+    <link rel="shortcut icon" href="{{.FullyQualifiedAssetsBaseURL}}/favicon.ico?v=1">
+    <link rel="stylesheet" type="text/css" href="{{.FullyQualifiedAssetsBaseURL}}/css/plusvite.css?v={{.AssetsVersion}}" />
+    <link rel="stylesheet" type="text/css" href="{{.FullyQualifiedAssetsBaseURL}}/css/riff.css?v={{.AssetsVersion}}" />
+    {{template "analytics" .}}
+    {{template "inlinecss"}}
+</head>
+
+<body>
+    <div id="box">
+        <h1>Plus Vite !</h1>
+
+        <p>Pour dire adieu aux URLs kilométriques des sites dynamiques, simplifiez-vous
+            la vie avec des URLs courts, plus vite saisis, plus vite notés, plus
+            vite communiqués même au téléphone sans risque d'erreur.</p>
+
+        <form method="post">
+            <div id="form"><div><label accesskey="U" for="form_url" class="required">L&#039;URL à transformer :</label><input type="url" id="form_url" name="form[url]" required="required" placeholder="http://www.osinet.fr" size="60" maxlength="240" /></div></div>
+            <input type="submit" name="submit" value="Envoyer" />
+        </form>
+
+        <div class="notes">
+            <p>Attention ! Les URLs doivent toujours commencer par :</p>
+            <table>
+                <tr>
+                    <td>ftp://</td>
+                    <td>pour les accès FTP</td>
+                </tr>
+                <tr>
+                    <td>http://</td>
+                    <td>pour les accès Web classiques</td>
+                </tr>
+                <tr>
+                    <td>https://</td>
+                    <td>pour les accès sécurisés</td>
+                </tr>
+                <tr>
+                    <td>mailto: </td>
+                    <td>pour la redirection/masquage d'adresses e-mail</td>
+                </tr>
+            </table>
+        </div>
+    </div>
+
+    {{template "footer"}}
+</body>
+</html>

+ 12 - 0
web/templates/layout/_analytics.gohtml

@@ -0,0 +1,12 @@
+{{define "analytics"}}
+    <script>
+        (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+            (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+            m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+        })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+        ga('create', 'UA-4995285-23', 'plusvite.net');
+        ga('send', 'pageview');
+
+    </script>
+{{end}}

+ 24 - 0
web/templates/layout/_footer.gohtml

@@ -0,0 +1,24 @@
+{{define "footer"}}
+    <div id="pageFooter" class="finePrint">
+        <table width="100%" border="0">
+            <tr>
+                <td class="tdStats">&copy; 2003-2018
+                    <a href="http://www.osinet.fr/">Ouest Systèmes Informatiques</a>
+                    Tous droits réservés.<br />
+                    <a href="http://plusvite.net/">Plus Vite sur le Net</a> est un site Mediacore, soumis aux
+                    <a href="http://www.osinet.fr/osi/orders.htm">conditions d'utilisation OSInet</a>
+                    et réservée à un usage individuel.<br />
+                    Toute soumission ou interrogation en masse est interdite et pourra donner lieu à des poursuites légales.
+                    <!-- Version du 11/28/18 -->
+                </td>
+                <td class="tdStats invisible">
+                    <a href="http://validator.w3.org/check/referer"><img src="http://plusvite-cdn.osinet.eu/images/valid-xhtml10.gif?v=1" alt="Valid XHTML 1.0!" width="88" height="31" /></a>
+                    <a href="http://jigsaw.w3.org/css-validator/check/referer?warning=0"><img src="http://plusvite-cdn.osinet.eu/images/vcss.gif?v=1" alt="Valid CSS1!" width="88" height="31" /></a>
+                </td>
+                <td class="tdStats">
+                    <a href="http://ac.audean.com/sc.php" title="Compteur Audean"><img src="http://ac.audean.com/pc.php" alt="Compteur Audean" style="border-bottom: solid silver 1px" /></a>
+                </td>
+            </tr>
+        </table>
+    </div>
+{{end}}

+ 10 - 0
web/templates/layout/_inlinecss.gohtml

@@ -0,0 +1,10 @@
+{{define "inlinecss"}}
+    <style>
+        @font-face {
+            font-family: 'Lato';
+            font-style: normal;
+            font-weight: 300;
+            src: local('Lato Light'), local('Lato-Light'), url(http://plusvite-cdn.osinet.eu/fonts/lato300.woff?v=1) format('woff');
+        }
+    </style>
+{{end}}

+ 24 - 6
web/web.go

@@ -7,7 +7,12 @@ a complete application.
 */
 package web
 
-import "github.com/gorilla/mux"
+import (
+	"net/http"
+	"path/filepath"
+
+	"github.com/gorilla/mux"
+)
 
 // Route names.
 const (
@@ -25,12 +30,26 @@ const (
 	HtmlTypeRegex = HtmlType
 )
 
-// SetupRoutes treats s as UTF-8-encoded bytes and returns a copy with all Unicode letters that begin
-// words mapped to their title case.
-func SetupRoutes(router *mux.Router) {
+/**
+FullyQualifiedAssetsBaseURL	http://plusvite-cdn.osinet.eu
+FullyQualifiedShortURL		(short string)
+FullyQualifiedSiteURL		http://plusvite.net/
+FullyQualifiedTargetURL		(target string)
+RefreshDelay				(seconds int)
+SiteName					PlusVite
+*/
+// SetupRoutes() configures Web UI routes on the passed mux.Router.
+func SetupRoutes(router *mux.Router, configAssetsPath string) {
+	absAssetsDir, err := filepath.Abs(configAssetsPath)
+	if err != nil {
+		panic(err)
+	}
+	fs := http.FileServer(http.Dir(absAssetsDir))
+	router.PathPrefix("/public").Handler(http.StripPrefix("/public", fs))
+	router.Handle("/favicon.ico", fs)
+
 	// BUG(fgm): improve Accept header matchers once https://github.com/golang/go/issues/19307 is completed.
 	router.HandleFunc("/{short}", handleGetShort).
-		HeadersRegexp("Accept", HtmlTypeRegex).
 		Methods("GET", "HEAD").
 		Name(RouteGetShort)
 	router.HandleFunc("/", handlePostTarget).
@@ -39,7 +58,6 @@ func SetupRoutes(router *mux.Router) {
 		Methods("POST").
 		Name(RoutePostTarget)
 	router.HandleFunc("/", handleGetRoot).
-		HeadersRegexp("Accept", HtmlTypeRegex).
 		Methods("GET", "HEAD").
 		Name(RouteGetRoot)
 }