From cd557d80445deabfbd7aec9d8c80b341991cbd8e Mon Sep 17 00:00:00 2001
From: Amel Abdic <Amel.Abdic@Student.Reutlingen-University.DE>
Date: Fri, 3 Jan 2025 12:56:32 +0100
Subject: [PATCH] =?UTF-8?q?Edit=20Funktionalit=C3=A4t=20final?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 go.mod                                        |   1 -
 go.sum                                        |   2 -
 requirements.json                             |   2 +-
 requirements.txt                              |  17 +-
 src/app/eiffel/service.go                     |   5 +-
 src/app/eiffel/web.go                         | 332 +++++--
 templates/eiffel/_form-elicitation.go.html    |  76 +-
 templates/eiffel/_project-collection.go.html  |  54 ++
 translations/de.json                          |   3 +
 translations/en.json                          |   3 +
 vendor/github.com/gorilla/mux/.editorconfig   |  20 -
 vendor/github.com/gorilla/mux/.gitignore      |   1 -
 vendor/github.com/gorilla/mux/LICENSE         |  27 -
 vendor/github.com/gorilla/mux/Makefile        |  34 -
 vendor/github.com/gorilla/mux/README.md       | 812 ------------------
 vendor/github.com/gorilla/mux/doc.go          | 305 -------
 vendor/github.com/gorilla/mux/middleware.go   |  74 --
 vendor/github.com/gorilla/mux/mux.go          | 608 -------------
 vendor/github.com/gorilla/mux/regexp.go       | 388 ---------
 vendor/github.com/gorilla/mux/route.go        | 765 -----------------
 vendor/github.com/gorilla/mux/test_helpers.go |  19 -
 vendor/modules.txt                            |   1 -
 22 files changed, 374 insertions(+), 3175 deletions(-)
 create mode 100644 templates/eiffel/_project-collection.go.html
 delete mode 100644 vendor/github.com/gorilla/mux/.editorconfig
 delete mode 100644 vendor/github.com/gorilla/mux/.gitignore
 delete mode 100644 vendor/github.com/gorilla/mux/LICENSE
 delete mode 100644 vendor/github.com/gorilla/mux/Makefile
 delete mode 100644 vendor/github.com/gorilla/mux/README.md
 delete mode 100644 vendor/github.com/gorilla/mux/doc.go
 delete mode 100644 vendor/github.com/gorilla/mux/middleware.go
 delete mode 100644 vendor/github.com/gorilla/mux/mux.go
 delete mode 100644 vendor/github.com/gorilla/mux/regexp.go
 delete mode 100644 vendor/github.com/gorilla/mux/route.go
 delete mode 100644 vendor/github.com/gorilla/mux/test_helpers.go

diff --git a/go.mod b/go.mod
index ad3b03c..3936dfe 100644
--- a/go.mod
+++ b/go.mod
@@ -16,7 +16,6 @@ require (
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/golang/snappy v0.0.4 // indirect
-	github.com/gorilla/mux v1.8.1 // indirect
 	github.com/jackc/pgpassfile v1.0.0 // indirect
 	github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
 	github.com/jackc/puddle/v2 v2.2.1 // indirect
diff --git a/go.sum b/go.sum
index 4d4b771..e68a1d3 100644
--- a/go.sum
+++ b/go.sum
@@ -15,8 +15,6 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
 github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
-github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
 github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
 github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
 github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
diff --git a/requirements.json b/requirements.json
index 2797bd8..bc2900c 100644
--- a/requirements.json
+++ b/requirements.json
@@ -1 +1 @@
-[{"_id":"673b9a543c547c9012cab78c","created_at":"2024-11-18T19:49:40.559Z","parsing_result":{"errors":null,"notices":null,"requirement":"test muss test als relevant betrachten, dass test test.","templateid":"kontext","templatename":"Kontext","templatetype":"ebt","templateversion":"0.1.0","variantname":"Kontext mit Bedingung","warnings":null},"segment_map":{"abgrenzung":"test","bedingung":"test","begruendung":"test","modalitaet":"muss","punkt":".","relevanz":"als relevant betrachten, dass","system":"test"},"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8","variant_key":"kontext-mit-bedingung"},{"_id":"673b9a9b3c547c9012cab78d","created_at":"2024-11-18T19:50:51.469Z","parsing_result":{"errors":null,"notices":null,"requirement":"test muss test als relevant betrachten, dass test test.","templateid":"kontext","templatename":"Kontext","templatetype":"ebt","templateversion":"0.1.0","variantname":"Kontext mit Bedingung","warnings":null},"segment_map":{"abgrenzung":"test","bedingung":"test","begruendung":"test","modalitaet":"muss","punkt":".","relevanz":"als relevant betrachten, dass","system":"test"},"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8","variant_key":"kontext-mit-bedingung"},{"_id":"673bcf662d6ca63eb5638dd2","created_at":"2024-11-18T23:36:06.836Z","parsing_result":{"errors":null,"notices":null,"requirement":"testing muss testing als relevant betrachten, dass testing testing.","templateid":"kontext","templatename":"Kontext","templatetype":"ebt","templateversion":"0.1.0","variantname":"Kontext mit Bedingung","warnings":null},"segment_map":{"abgrenzung":"testing","bedingung":"testing","begruendung":"testing","modalitaet":"muss","punkt":".","relevanz":"als relevant betrachten, dass","system":"testing"},"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8","variant_key":"kontext-mit-bedingung"},{"_id":"673bd0ed2d6ca63eb5638dd3","created_at":"2024-11-18T23:42:37.769Z","parsing_result":{"errors":null,"notices":null,"requirement":"testtt muss testtt als relevant betrachten, dass testtt testtt.","templateid":"kontext","templatename":"Kontext","templatetype":"ebt","templateversion":"0.1.0","variantname":"Kontext mit Bedingung","warnings":null},"segment_map":{"abgrenzung":"testtt","bedingung":"testtt","begruendung":"testtt","modalitaet":"muss","punkt":".","relevanz":"als relevant betrachten, dass","system":"testtt"},"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8","variant_key":"kontext-mit-bedingung"},{"_id":"673c370f67f12bdd37995229","created_at":"2024-11-19T06:58:23.88Z","parsing_result":{"errors":null,"notices":null,"requirement":"testttttttttt muss testttttttttt als relevant betrachten, dass testttttttttt.","templateid":"kontext","templatename":"Kontext","templatetype":"ebt","templateversion":"0.1.0","variantname":"Kontext mit Bedingung","warnings":null},"segment_map":{"abgrenzung":"testttttttttt","bedingung":"testttttttttt","begruendung":"","modalitaet":"muss","punkt":".","relevanz":"als relevant betrachten, dass","system":"testttttttttt"},"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8","variant_key":"kontext-mit-bedingung"},{"_id":"6746983f934773bedf28c1d2","created_at":"2024-11-27T03:55:43.267Z","parsing_result":{"errors":null,"notices":null,"requirement":"ameltest muss ameltest als relevant betrachten, dass ameltest ameltest.","templateid":"kontext","templatename":"Kontext","templatetype":"ebt","templateversion":"0.1.0","variantname":"Kontext mit Bedingung","warnings":null},"segment_map":{"abgrenzung":"ameltest","bedingung":"ameltest","begruendung":"ameltest","modalitaet":"muss","punkt":".","relevanz":"als relevant betrachten, dass","system":"ameltest"},"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8","variant_key":"kontext-mit-bedingung"},{"_id":"6746c7151a6cae65b5dde03e","created_at":"2024-11-27T07:15:33.204Z","parsing_result":{"errors":null,"notices":null,"requirement":"Falls das Smartphone als Outdoor-Gerät bezeichnet wird, muss die Komponente Außenhülle als relevant betrachten, dass das System Smartphone bei einer Umgebungstemperatur von -20°C bis 40°C betrieben wird.","templateid":"kontext","templatename":"Kontext","templatetype":"ebt","templateversion":"0.1.0","variantname":"Kontext mit Bedingung","warnings":null},"segment_map":{"abgrenzung":"das System Smartphone bei einer Umgebungstemperatur von -20°C bis 40°C betrieben wird","bedingung":"Falls das Smartphone als Outdoor-Gerät bezeichnet wird,","begruendung":"","modalitaet":"muss","punkt":".","relevanz":"als relevant betrachten, dass","system":"die Komponente Außenhülle"},"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8","variant_key":"kontext-mit-bedingung"},{"_id":"6746c7761a6cae65b5dde03f","created_at":"2024-11-27T07:17:10.769Z","parsing_result":{"errors":null,"notices":null,"requirement":"test muss test technisch die Eigenschaft haben, test test.","templateid":"technische-eigenschaft","templatename":"Technische Eigenschaft","templatetype":"ebt","templateversion":"0.1.0","variantname":"Technische Eigenschaft mit Bedingung","warnings":null},"segment_map":{"bedingung":"test","begruendung":"","modalitaet":"muss","name-der-eigenschaft":"test","objektbeschreibung":"test","punkt":".","system":"test","technisch-die-eigenschaft-haben":"technisch die Eigenschaft haben,"},"template_id":"59cda3e1-87cc-406f-b504-34059ec297e4","variant_key":"technische-eigenschaft-mit-bedingung"},{"_id":"674adbf103066ad475dee8f5","created_at":"2024-11-30T09:33:37.733Z","parsing_result":{"errors":[{"downgrade":false,"extra":null,"level":0,"message":"eiffel.parser.equals-any.error","segment":{"name":"modalitaet","value":"tset"},"translationargs":["expected","\"muss\", \"soll\", \"sollte\", \"kann\", \"wird\"","actual","tset"]}],"notices":null,"requirement":"test tset test als relevant betrachten, dass test test.","templateid":"kontext","templatename":"Kontext","templatetype":"ebt","templateversion":"0.1.0","variantname":"Kontext mit Bedingung","warnings":null},"segment_map":{"abgrenzung":"test","bedingung":"test","begruendung":"test","modalitaet":"tset","punkt":".","relevanz":"als relevant betrachten, dass","system":"test"},"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8","variant_key":"kontext-mit-bedingung"},{"_id":"674adbf703066ad475dee8f6","created_at":"2024-11-30T09:33:43.19Z","parsing_result":{"errors":null,"notices":null,"requirement":"test muss test als relevant betrachten, dass test test.","templateid":"kontext","templatename":"Kontext","templatetype":"ebt","templateversion":"0.1.0","variantname":"Kontext mit Bedingung","warnings":null},"segment_map":{"abgrenzung":"test","bedingung":"test","begruendung":"test","modalitaet":"muss","punkt":".","relevanz":"als relevant betrachten, dass","system":"test"},"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8","variant_key":"kontext-mit-bedingung"},{"_id":"674adc2f03066ad475dee8f7","created_at":"2024-11-30T09:34:39.271Z","parsing_result":{"errors":[{"downgrade":false,"extra":null,"level":0,"message":"eiffel.parser.equals-any.error","segment":{"name":"modalitaet","value":"test"},"translationargs":["expected","\"muss\", \"soll\", \"sollte\", \"kann\", \"wird\"","actual","test"]},{"downgrade":false,"extra":null,"level":0,"message":"eiffel.parser.equals-any.error","segment":{"name":"ein-kein","value":"test"},"translationargs":["expected","\"ein\", \"kein\"","actual","test"]},{"downgrade":false,"extra":null,"level":0,"message":"eiffel.parser.equals-any.error","segment":{"name":"des-der","value":"test"},"translationargs":["expected","\"des\", \"der\"","actual","test"]}],"notices":[{"downgrade":true,"extra":null,"level":2,"message":"eiffel.parser.error.missing-segment","segment":{"name":"eigenname","value":""},"translationargs":["name","Eigenname","technicalName","eigenname"]}],"requirement":"test test es test test test test sein, test test.","templateid":"ziel","templatename":"Ziel","templatetype":"ebt","templateversion":"0.1.0","variantname":"Ziel mit Bedingung","warnings":null},"segment_map":{"bedingung":"test","begruendung":"test","bezug":"test","des-der":"test","eigenname":"","ein-kein":"test","es-klein":"es","modalitaet":"test","punkt":".","sein":"sein,","zielart":"test","zu-erreichender-zustand":"test"},"template_id":"35b9e593-dc5a-4ad9-85f4-c13d0703f89c","variant_key":"ziel-mit-bedingung"},{"_id":"674adc3803066ad475dee8f8","created_at":"2024-11-30T09:34:48.828Z","parsing_result":{"errors":null,"notices":[{"downgrade":true,"extra":null,"level":2,"message":"eiffel.parser.error.missing-segment","segment":{"name":"eigenname","value":""},"translationargs":["name","Eigenname","technicalName","eigenname"]}],"requirement":"test muss es ein test des test sein, test test.","templateid":"ziel","templatename":"Ziel","templatetype":"ebt","templateversion":"0.1.0","variantname":"Ziel mit Bedingung","warnings":null},"segment_map":{"bedingung":"test","begruendung":"test","bezug":"test","des-der":"des","eigenname":"","ein-kein":"ein","es-klein":"es","modalitaet":"muss","punkt":".","sein":"sein,","zielart":"test","zu-erreichender-zustand":"test"},"template_id":"35b9e593-dc5a-4ad9-85f4-c13d0703f89c","variant_key":"ziel-mit-bedingung"},{"_id":"674adde903066ad475dee8f9","created_at":"2024-11-30T09:42:01.378Z","parsing_result":{"errors":null,"notices":null,"requirement":"Robin muss Robin als relevant betrachten, dass Robin Robin.","templateid":"kontext","templatename":"Kontext","templatetype":"ebt","templateversion":"0.1.0","variantname":"Kontext mit Bedingung","warnings":null},"segment_map":{"abgrenzung":"Robin","bedingung":"Robin","begruendung":"Robin","modalitaet":"muss","punkt":".","relevanz":"als relevant betrachten, dass","system":"Robin"},"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8","variant_key":"kontext-mit-bedingung"},{"_id":"674adfd903066ad475dee8fa","created_at":"2024-11-30T09:50:17.498Z","parsing_result":{"errors":null,"notices":null,"requirement":"Robin3 must possess the quality feature Robin3 Robin3.","templateid":"esqua","templatename":"Extended Template for Quality Requirements (ESQUA)","templatetype":"ebt","templateversion":"0.1.0","variantname":"Quality Feature without Condition","warnings":null},"segment_map":{"dot":".","justification":"Robin3","modality-medium":"must","possess-quality-feature-medium":"possess the quality feature","quality-feature":"Robin3","who-or-what-full":"Robin3"},"template_id":"55ae0045-7c80-48b4-ac54-d76e2a9da667","variant_key":"quality-feature-without-condition"}]
+[{"_id":"676ea16d54d3f2fbc0aff7eb","created_at":"2024-12-27T12:45:33.488Z","parsing_result":{"errors":null,"notices":null,"requirement":"test muss test als relevant betrachten, dass test test.","templateid":"kontext","templatename":"Kontext","templatetype":"ebt","templateversion":"0.1.0","variantname":"Kontext mit Bedingung","warnings":null},"project_id":"123a","segment_map":{"abgrenzung":"test","bedingung":"test","begruendung":"test","modalitaet":"muss","punkt":".","relevanz":"als relevant betrachten, dass","system":"test"},"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8","variant_key":"kontext-mit-bedingung"},{"_id":"676ea18f54d3f2fbc0aff7ed","created_at":"2024-12-27T12:46:07.869Z","parsing_result":{"errors":null,"notices":null,"requirement":"test2 muss test2 als relevant betrachten, dass test2 test2.","templateid":"kontext","templatename":"Kontext","templatetype":"ebt","templateversion":"0.1.0","variantname":"Kontext mit Bedingung","warnings":null},"project_id":"123atest2","segment_map":{"abgrenzung":"test2","bedingung":"test2","begruendung":"test2","modalitaet":"muss","punkt":".","relevanz":"als relevant betrachten, dass","system":"test2"},"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8","variant_key":"kontext-mit-bedingung"},{"_id":"676ea1a854d3f2fbc0aff7ee","created_at":"2024-12-27T12:46:32.021Z","parsing_result":{"errors":null,"notices":null,"requirement":"test2 edited muss test2 edited als relevant betrachten, dass test2edited test2 edited.","templateid":"kontext","templatename":"Kontext","templatetype":"ebt","templateversion":"0.1.0","variantname":"Kontext mit Bedingung","warnings":null},"project_id":"123a","segment_map":{"abgrenzung":"test2edited","bedingung":"test2 edited","begruendung":"test2 edited","modalitaet":"muss","punkt":".","relevanz":"als relevant betrachten, dass","system":"test2 edited"},"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8","variant_key":"kontext-mit-bedingung"}]
diff --git a/requirements.txt b/requirements.txt
index 4e2723f..7eed297 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,14 +1,3 @@
-{"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8","variant_key":"kontext-mit-bedingung","segment_map":{"modalitaet":"muss","system":"test","relevanz":"als relevant betrachten, dass","abgrenzung":"test","begruendung":"test","punkt":".","bedingung":"test"},"parsing_result":{"templateid":"kontext","templatetype":"ebt","templatename":"Kontext","requirement":"test muss test als relevant betrachten, dass test test.","errors":null,"notices":null,"templateversion":"0.1.0","variantname":"Kontext mit Bedingung","warnings":null},"created_at":{"$date":{"$numberLong":"1731959380559"}},"_id":{"$oid":"673b9a543c547c9012cab78c"}}
-{"variant_key":"kontext-mit-bedingung","_id":{"$oid":"673b9a9b3c547c9012cab78d"},"segment_map":{"abgrenzung":"test","begruendung":"test","punkt":".","bedingung":"test","modalitaet":"muss","system":"test","relevanz":"als relevant betrachten, dass"},"parsing_result":{"variantname":"Kontext mit Bedingung","notices":null,"errors":null,"warnings":null,"templateid":"kontext","templatetype":"ebt","templateversion":"0.1.0","templatename":"Kontext","requirement":"test muss test als relevant betrachten, dass test test."},"created_at":{"$date":{"$numberLong":"1731959451469"}},"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8"}
-{"segment_map":{"relevanz":"als relevant betrachten, dass","abgrenzung":"testing","begruendung":"testing","punkt":".","bedingung":"testing","modalitaet":"muss","system":"testing"},"parsing_result":{"templateid":"kontext","templatetype":"ebt","requirement":"testing muss testing als relevant betrachten, dass testing testing.","warnings":null,"templateversion":"0.1.0","templatename":"Kontext","variantname":"Kontext mit Bedingung","errors":null,"notices":null},"created_at":{"$date":{"$numberLong":"1731972966836"}},"_id":{"$oid":"673bcf662d6ca63eb5638dd2"},"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8","variant_key":"kontext-mit-bedingung"}
-{"_id":{"$oid":"673bd0ed2d6ca63eb5638dd3"},"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8","variant_key":"kontext-mit-bedingung","segment_map":{"punkt":".","bedingung":"testtt","modalitaet":"muss","system":"testtt","relevanz":"als relevant betrachten, dass","abgrenzung":"testtt","begruendung":"testtt"},"parsing_result":{"templateid":"kontext","templateversion":"0.1.0","variantname":"Kontext mit Bedingung","warnings":null,"notices":null,"templatetype":"ebt","templatename":"Kontext","requirement":"testtt muss testtt als relevant betrachten, dass testtt testtt.","errors":null},"created_at":{"$date":{"$numberLong":"1731973357769"}}}
-{"_id":{"$oid":"673c370f67f12bdd37995229"},"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8","variant_key":"kontext-mit-bedingung","segment_map":{"abgrenzung":"testttttttttt","begruendung":"","punkt":".","bedingung":"testttttttttt","modalitaet":"muss","system":"testttttttttt","relevanz":"als relevant betrachten, dass"},"parsing_result":{"variantname":"Kontext mit Bedingung","requirement":"testttttttttt muss testttttttttt als relevant betrachten, dass testttttttttt.","errors":null,"warnings":null,"templatetype":"ebt","templatename":"Kontext","notices":null,"templateid":"kontext","templateversion":"0.1.0"},"created_at":{"$date":{"$numberLong":"1731999503880"}}}
-{"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8","variant_key":"kontext-mit-bedingung","segment_map":{"modalitaet":"muss","system":"ameltest","relevanz":"als relevant betrachten, dass","abgrenzung":"ameltest","begruendung":"ameltest","punkt":".","bedingung":"ameltest"},"parsing_result":{"errors":null,"warnings":null,"requirement":"ameltest muss ameltest als relevant betrachten, dass ameltest ameltest.","templatetype":"ebt","templateversion":"0.1.0","templatename":"Kontext","variantname":"Kontext mit Bedingung","notices":null,"templateid":"kontext"},"_id":{"$oid":"6746983f934773bedf28c1d2"},"created_at":{"$date":{"$numberLong":"1732679743267"}}}
-{"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8","variant_key":"kontext-mit-bedingung","segment_map":{"relevanz":"als relevant betrachten, dass","abgrenzung":"das System Smartphone bei einer Umgebungstemperatur von -20°C bis 40°C betrieben wird","begruendung":"","punkt":".","bedingung":"Falls das Smartphone als Outdoor-Gerät bezeichnet wird,","modalitaet":"muss","system":"die Komponente Außenhülle"},"parsing_result":{"templateid":"kontext","templatename":"Kontext","warnings":null,"notices":null,"templatetype":"ebt","templateversion":"0.1.0","variantname":"Kontext mit Bedingung","requirement":"Falls das Smartphone als Outdoor-Gerät bezeichnet wird, muss die Komponente Außenhülle als relevant betrachten, dass das System Smartphone bei einer Umgebungstemperatur von -20°C bis 40°C betrieben wird.","errors":null},"created_at":{"$date":{"$numberLong":"1732691733204"}},"_id":{"$oid":"6746c7151a6cae65b5dde03e"}}
-{"_id":{"$oid":"6746c7761a6cae65b5dde03f"},"segment_map":{"technisch-die-eigenschaft-haben":"technisch die Eigenschaft haben,","objektbeschreibung":"test","name-der-eigenschaft":"test","begruendung":"","punkt":".","bedingung":"test","modalitaet":"muss","system":"test"},"parsing_result":{"templateid":"technische-eigenschaft","templatetype":"ebt","templatename":"Technische Eigenschaft","variantname":"Technische Eigenschaft mit Bedingung","warnings":null,"templateversion":"0.1.0","requirement":"test muss test technisch die Eigenschaft haben, test test.","errors":null,"notices":null},"created_at":{"$date":{"$numberLong":"1732691830769"}},"template_id":"59cda3e1-87cc-406f-b504-34059ec297e4","variant_key":"technische-eigenschaft-mit-bedingung"}
-{"_id":{"$oid":"674adbf103066ad475dee8f5"},"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8","variant_key":"kontext-mit-bedingung","segment_map":{"abgrenzung":"test","begruendung":"test","punkt":".","bedingung":"test","modalitaet":"tset","system":"test","relevanz":"als relevant betrachten, dass"},"parsing_result":{"templateversion":"0.1.0","templatename":"Kontext","variantname":"Kontext mit Bedingung","errors":[{"level":{"$numberInt":"0"},"message":"eiffel.parser.equals-any.error","translationargs":["expected","\"muss\", \"soll\", \"sollte\", \"kann\", \"wird\"","actual","tset"],"extra":null,"downgrade":false,"segment":{"name":"modalitaet","value":"tset"}}],"warnings":null,"notices":null,"templateid":"kontext","requirement":"test tset test als relevant betrachten, dass test test.","templatetype":"ebt"},"created_at":{"$date":{"$numberLong":"1732959217733"}}}
-{"_id":{"$oid":"674adbf703066ad475dee8f6"},"parsing_result":{"templateversion":"0.1.0","templatename":"Kontext","variantname":"Kontext mit Bedingung","errors":null,"notices":null,"templateid":"kontext","templatetype":"ebt","requirement":"test muss test als relevant betrachten, dass test test.","warnings":null},"created_at":{"$date":{"$numberLong":"1732959223190"}},"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8","variant_key":"kontext-mit-bedingung","segment_map":{"bedingung":"test","modalitaet":"muss","system":"test","relevanz":"als relevant betrachten, dass","abgrenzung":"test","begruendung":"test","punkt":"."}}
-{"segment_map":{"zielart":"test","bedingung":"test","zu-erreichender-zustand":"test","bezug":"test","eigenname":"","modalitaet":"test","punkt":".","des-der":"test","es-klein":"es","begruendung":"test","sein":"sein,","ein-kein":"test"},"parsing_result":{"requirement":"test test es test test test test sein, test test.","notices":[{"level":{"$numberInt":"2"},"message":"eiffel.parser.error.missing-segment","translationargs":["name","Eigenname","technicalName","eigenname"],"extra":null,"downgrade":true,"segment":{"name":"eigenname","value":""}}],"templatetype":"ebt","templatename":"Ziel","variantname":"Ziel mit Bedingung","errors":[{"level":{"$numberInt":"0"},"message":"eiffel.parser.equals-any.error","translationargs":["expected","\"muss\", \"soll\", \"sollte\", \"kann\", \"wird\"","actual","test"],"extra":null,"downgrade":false,"segment":{"name":"modalitaet","value":"test"}},{"segment":{"name":"ein-kein","value":"test"},"level":{"$numberInt":"0"},"message":"eiffel.parser.equals-any.error","translationargs":["expected","\"ein\", \"kein\"","actual","test"],"extra":null,"downgrade":false},{"segment":{"name":"des-der","value":"test"},"level":{"$numberInt":"0"},"message":"eiffel.parser.equals-any.error","translationargs":["expected","\"des\", \"der\"","actual","test"],"extra":null,"downgrade":false}],"warnings":null,"templateid":"ziel","templateversion":"0.1.0"},"created_at":{"$date":{"$numberLong":"1732959279271"}},"_id":{"$oid":"674adc2f03066ad475dee8f7"},"template_id":"35b9e593-dc5a-4ad9-85f4-c13d0703f89c","variant_key":"ziel-mit-bedingung"}
-{"parsing_result":{"templatename":"Ziel","variantname":"Ziel mit Bedingung","requirement":"test muss es ein test des test sein, test test.","notices":[{"segment":{"name":"eigenname","value":""},"level":{"$numberInt":"2"},"message":"eiffel.parser.error.missing-segment","translationargs":["name","Eigenname","technicalName","eigenname"],"extra":null,"downgrade":true}],"templateid":"ziel","templatetype":"ebt","templateversion":"0.1.0","errors":null,"warnings":null},"created_at":{"$date":{"$numberLong":"1732959288828"}},"_id":{"$oid":"674adc3803066ad475dee8f8"},"template_id":"35b9e593-dc5a-4ad9-85f4-c13d0703f89c","variant_key":"ziel-mit-bedingung","segment_map":{"zu-erreichender-zustand":"test","des-der":"des","punkt":".","sein":"sein,","es-klein":"es","bezug":"test","bedingung":"test","begruendung":"test","modalitaet":"muss","zielart":"test","eigenname":"","ein-kein":"ein"}}
-{"parsing_result":{"errors":null,"notices":null,"templatetype":"ebt","templateversion":"0.1.0","variantname":"Kontext mit Bedingung","requirement":"Robin muss Robin als relevant betrachten, dass Robin Robin.","warnings":null,"templateid":"kontext","templatename":"Kontext"},"created_at":{"$date":{"$numberLong":"1732959721378"}},"_id":{"$oid":"674adde903066ad475dee8f9"},"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8","variant_key":"kontext-mit-bedingung","segment_map":{"begruendung":"Robin","punkt":".","bedingung":"Robin","modalitaet":"muss","system":"Robin","relevanz":"als relevant betrachten, dass","abgrenzung":"Robin"}}
-{"parsing_result":{"templateversion":"0.1.0","templatename":"Extended Template for Quality Requirements (ESQUA)","errors":null,"templateid":"esqua","templatetype":"ebt","warnings":null,"notices":null,"variantname":"Quality Feature without Condition","requirement":"Robin3 must possess the quality feature Robin3 Robin3."},"created_at":{"$date":{"$numberLong":"1732960217498"}},"template_id":"55ae0045-7c80-48b4-ac54-d76e2a9da667","variant_key":"quality-feature-without-condition","segment_map":{"dot":".","who-or-what-full":"Robin3","modality-medium":"must","possess-quality-feature-medium":"possess the quality feature","quality-feature":"Robin3","justification":"Robin3"},"_id":{"$oid":"674adfd903066ad475dee8fa"}}
+{"created_at":{"$date":{"$numberLong":"1735303533488"}},"_id":{"$oid":"676ea16d54d3f2fbc0aff7eb"},"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8","variant_key":"kontext-mit-bedingung","segment_map":{"relevanz":"als relevant betrachten, dass","abgrenzung":"test","system":"test","begruendung":"test","punkt":".","modalitaet":"muss","bedingung":"test"},"parsing_result":{"templateid":"kontext","templatename":"Kontext","warnings":null,"templatetype":"ebt","templateversion":"0.1.0","variantname":"Kontext mit Bedingung","requirement":"test muss test als relevant betrachten, dass test test.","errors":null,"notices":null},"project_id":"123a"}
+{"_id":{"$oid":"676ea18f54d3f2fbc0aff7ed"},"project_id":"123atest2","created_at":{"$date":{"$numberLong":"1735303567869"}},"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8","variant_key":"kontext-mit-bedingung","segment_map":{"bedingung":"test2","system":"test2","abgrenzung":"test2","modalitaet":"muss","relevanz":"als relevant betrachten, dass","begruendung":"test2","punkt":"."},"parsing_result":{"templateversion":"0.1.0","templatename":"Kontext","warnings":null,"templateid":"kontext","templatetype":"ebt","variantname":"Kontext mit Bedingung","requirement":"test2 muss test2 als relevant betrachten, dass test2 test2.","errors":null,"notices":null}}
+{"parsing_result":{"templateversion":"0.1.0","templateid":"kontext","templatetype":"ebt","requirement":"test2 edited muss test2 edited als relevant betrachten, dass test2edited test2 edited.","errors":null,"warnings":null,"notices":null,"templatename":"Kontext","variantname":"Kontext mit Bedingung"},"project_id":"123a","created_at":{"$date":{"$numberLong":"1735303592021"}},"_id":{"$oid":"676ea1a854d3f2fbc0aff7ee"},"template_id":"f9c0636d-e8a4-46e9-be24-89d48d4762f8","variant_key":"kontext-mit-bedingung","segment_map":{"bedingung":"test2 edited","system":"test2 edited","begruendung":"test2 edited","abgrenzung":"test2edited","punkt":".","modalitaet":"muss","relevanz":"als relevant betrachten, dass"}}
diff --git a/src/app/eiffel/service.go b/src/app/eiffel/service.go
index 0164c37..febfa2e 100644
--- a/src/app/eiffel/service.go
+++ b/src/app/eiffel/service.go
@@ -3,13 +3,14 @@ package eiffel
 import (
 	"context"
 	"encoding/json"
+	"net/http"
+	"strings"
+
 	"github.com/google/uuid"
 	"github.com/org-harmony/harmony/src/app/template"
 	"github.com/org-harmony/harmony/src/app/template/parser"
 	"github.com/org-harmony/harmony/src/app/user"
 	"github.com/org-harmony/harmony/src/core/validation"
-	"net/http"
-	"strings"
 )
 
 // TemplateDisplayTypes returns a map of rule names to display types. The rule names are the keys of the BasicTemplate.Rules map.
diff --git a/src/app/eiffel/web.go b/src/app/eiffel/web.go
index 938aa89..c427c6c 100644
--- a/src/app/eiffel/web.go
+++ b/src/app/eiffel/web.go
@@ -23,6 +23,7 @@ import (
 	"github.com/org-harmony/harmony/src/core/util"
 	"github.com/org-harmony/harmony/src/core/web"
 	"go.mongodb.org/mongo-driver/bson"
+	"go.mongodb.org/mongo-driver/bson/primitive"
 	"go.mongodb.org/mongo-driver/mongo"
 	"go.mongodb.org/mongo-driver/mongo/options"
 )
@@ -71,6 +72,8 @@ type TemplateFormData struct {
 	SegmentMap map[string]string
 	// NeglectOptional is a flag indicating if optional rules (inputs) should be displayed different from non-optional rules.
 	NeglectOptional bool
+	ProjectID       string
+	EditMode        bool
 }
 
 // SearchTemplateData contains templates to render as search results and a flag indicating if the query was too short.
@@ -199,113 +202,107 @@ func exportTXTHandler(cfg Cfg) http.Handler {
 	})
 }
 
-func createRequirementHandler() http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		log.Println("POST /eiffel/requirements called")
-		var requirement bson.M
-		if err := json.NewDecoder(r.Body).Decode(&requirement); err != nil {
-			log.Printf("Failed to decode request body: %v", err)
-			http.Error(w, "Invalid request body", http.StatusBadRequest)
-			return
-		}
-		log.Printf("Requirement received: %+v", requirement)
-
+func deleteProjectHandler(appCtx *hctx.AppCtx, webCtx *web.Ctx) http.Handler {
+	return web.NewController(appCtx, webCtx, func(io web.IO) error {
+		id := web.URLParam(io.Request(), "id")
 		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 		defer cancel()
 
-		result, err := mongoCollection.InsertOne(ctx, requirement)
-		if err != nil {
-			log.Printf("Failed to insert requirement: %v", err)
-			http.Error(w, fmt.Sprintf("Failed to create requirement: %v", err), http.StatusInternalServerError)
-			return
+		var filter bson.M
+		if len(id) == 24 {
+			if objectID, err := primitive.ObjectIDFromHex(id); err == nil {
+				filter = bson.M{"_id": objectID}
+			} else {
+				filter = bson.M{"_id": id}
+			}
+		} else {
+			filter = bson.M{"_id": id}
 		}
 
-		log.Printf("Requirement created with ID: %v", result.InsertedID)
-		w.WriteHeader(http.StatusCreated)
-		json.NewEncoder(w).Encode(requirement)
-	})
-}
-
-func getAllRequirementsHandler() http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
-		defer cancel()
-
-		cursor, err := mongoCollection.Find(ctx, bson.M{})
-		if err != nil {
-			http.Error(w, fmt.Sprintf("Failed to fetch requirements: %v", err), http.StatusInternalServerError)
-			return
+		if _, err := mongoCollection.DeleteMany(ctx, bson.M{"project_id": id}); err != nil {
+			return fmt.Errorf("Fehler beim Löschen der zugehörigen Requirements: %w", err)
 		}
-		defer cursor.Close(ctx)
 
-		var requirements []bson.M
-		if err := cursor.All(ctx, &requirements); err != nil {
-			http.Error(w, fmt.Sprintf("Failed to parse requirements: %v", err), http.StatusInternalServerError)
-			return
+		if _, err := mongoCollection.DeleteOne(ctx, filter); err != nil {
+			return fmt.Errorf("Fehler beim Löschen des Projekts: %w", err)
 		}
 
-		w.Header().Set("Content-Type", "application/json")
-		json.NewEncoder(w).Encode(requirements)
+		io.Response().Header().Set("Location", "/eiffel/projects")
+		io.Response().WriteHeader(http.StatusSeeOther) // 303 Redirect
+		return nil
 	})
 }
 
-func getRequirementByIDHandler() http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		id := web.URLParam(r, "id")
+func deleteRequirementHandler(appCtx *hctx.AppCtx, webCtx *web.Ctx) http.Handler {
+	return web.NewController(appCtx, webCtx, func(io web.IO) error {
+		id := web.URLParam(io.Request(), "id")
+		id = strings.TrimPrefix(id, "ObjectID(\"")
+		id = strings.TrimSuffix(id, "\")")
+
+		if len(id) != 24 {
+			return fmt.Errorf("ungültige ID-Länge: %s", id)
+		}
+
+		objectID, err := primitive.ObjectIDFromHex(id)
+		if err != nil {
+			return fmt.Errorf("ungültige ID: %w", err)
+		}
 
 		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 		defer cancel()
 
-		var requirement bson.M
-		err := mongoCollection.FindOne(ctx, bson.M{"_id": id}).Decode(&requirement)
-		if err != nil {
-			http.Error(w, fmt.Sprintf("Requirement not found: %v", err), http.StatusNotFound)
-			return
+		if _, err := mongoCollection.DeleteOne(ctx, bson.M{"_id": objectID}); err != nil {
+			return fmt.Errorf("Fehler beim Löschen des Requirements: %w", err)
 		}
 
-		w.Header().Set("Content-Type", "application/json")
-		json.NewEncoder(w).Encode(requirement)
+		io.Response().Header().Set("Location", "/eiffel/projects")
+		io.Response().WriteHeader(http.StatusSeeOther) // 303 Redirect
+		return nil
 	})
 }
 
-func updateRequirementHandler() http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		id := web.URLParam(r, "id")
+func updateRequirementHandler(appCtx *hctx.AppCtx, webCtx *web.Ctx) http.Handler {
+	return web.NewController(appCtx, webCtx, func(io web.IO) error {
+		id := web.URLParam(io.Request(), "id")
 
-		var update bson.M
-		if err := json.NewDecoder(r.Body).Decode(&update); err != nil {
-			http.Error(w, "Invalid request body", http.StatusBadRequest)
-			return
-		}
+		// Bereinige die ID
+		id = strings.TrimPrefix(id, "ObjectID(\"")
+		id = strings.TrimSuffix(id, "\")")
 
-		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
-		defer cancel()
+		if len(id) != 24 {
+			return fmt.Errorf("ungültige ID: %s", id)
+		}
 
-		_, err := mongoCollection.UpdateOne(ctx, bson.M{"_id": id}, bson.M{"$set": update})
+		objectID, err := primitive.ObjectIDFromHex(id)
 		if err != nil {
-			http.Error(w, fmt.Sprintf("Failed to update requirement: %v", err), http.StatusInternalServerError)
-			return
+			return fmt.Errorf("ungültige ID: %w", err)
 		}
 
-		w.WriteHeader(http.StatusOK)
-		json.NewEncoder(w).Encode(update)
-	})
-}
-
-func deleteRequirementHandler() http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		id := web.URLParam(r, "id")
+		// Neue Daten aus dem Formular abrufen
+		var formData struct {
+			Requirement string `json:"requirement"`
+		}
+		if err := io.Request().ParseForm(); err != nil {
+			return fmt.Errorf("Fehler beim Parsen des Formulars: %w", err)
+		}
+		formData.Requirement = io.Request().FormValue("requirement")
 
+		// Requirement in der Datenbank aktualisieren
 		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 		defer cancel()
 
-		_, err := mongoCollection.DeleteOne(ctx, bson.M{"_id": id})
+		_, err = mongoCollection.UpdateOne(
+			ctx,
+			bson.M{"_id": objectID},
+			bson.M{"$set": bson.M{"parsing_result.requirement": formData.Requirement}},
+		)
 		if err != nil {
-			http.Error(w, fmt.Sprintf("Failed to delete requirement: %v", err), http.StatusInternalServerError)
-			return
+			return fmt.Errorf("Fehler beim Aktualisieren des Requirements: %w", err)
 		}
 
-		w.WriteHeader(http.StatusNoContent)
+		io.Response().Header().Set("Location", "/eiffel/projects") // Zurück zur Projektseite
+		io.Response().WriteHeader(http.StatusSeeOther)
+		return nil
 	})
 }
 
@@ -324,6 +321,8 @@ func RegisterController(appCtx *hctx.AppCtx, webCtx *web.Ctx) {
 	router.Get("/eiffel", eiffelElicitationPage(cfg, appCtx, webCtx).ServeHTTP)
 	router.Get("/eiffel/{templateID}", eiffelElicitationPage(cfg, appCtx, webCtx).ServeHTTP)
 	router.Get("/eiffel/{templateID}/{variant}", eiffelElicitationPage(cfg, appCtx, webCtx).ServeHTTP)
+	router.Post("/eiffel/{templateID}/{variant}", eiffelElicitationPage(cfg, appCtx, webCtx).ServeHTTP)
+
 	router.Get("/eiffel/elicitation/templates/search/modal", searchModal(appCtx, webCtx).ServeHTTP)
 	router.Post("/eiffel/elicitation/templates/search", searchTemplate(appCtx, webCtx).ServeHTTP)
 	router.Get("/eiffel/elicitation/{templateID}", elicitationTemplate(cfg, appCtx, webCtx, true).ServeHTTP)
@@ -333,11 +332,12 @@ func RegisterController(appCtx *hctx.AppCtx, webCtx *web.Ctx) {
 	router.Get("/eiffel/export/json", exportJSONHandler(cfg).ServeHTTP)
 	router.Get("/eiffel/export/txt", exportTXTHandler(cfg).ServeHTTP)
 	// TO DO CRUD-Endpunkte müssen noch überarbeitet werden
-	router.Post("/eiffel/requirements", createRequirementHandler().ServeHTTP)
-	router.Get("/eiffel/requirements", getAllRequirementsHandler().ServeHTTP)
-	router.Get("/eiffel/requirements/{id}", getRequirementByIDHandler().ServeHTTP)
-	router.Put("/eiffel/requirements/{id}", updateRequirementHandler().ServeHTTP)
-	router.Delete("/eiffel/requirements/{id}", deleteRequirementHandler().ServeHTTP)
+	router.Post("/eiffel/requirements/{id}/update", updateRequirementHandler(appCtx, webCtx).ServeHTTP)
+
+	router.Post("/eiffel/projects/{id}/delete", deleteProjectHandler(appCtx, webCtx).ServeHTTP)
+	router.Post("/eiffel/requirements/{id}/delete", deleteRequirementHandler(appCtx, webCtx).ServeHTTP)
+	//Projekt Endpunkt
+	router.Get("/eiffel/projects", getProjectPackagesHandler(appCtx, webCtx).ServeHTTP)
 }
 
 func subscribeEvents(appCtx *hctx.AppCtx) {
@@ -382,6 +382,67 @@ func registerNavigation(appCtx *hctx.AppCtx, webCtx *web.Ctx) {
 		},
 		Position: 100,
 	})
+
+	// Projekt-Sammlung Button
+	webCtx.Navigation.Add("eiffel.project.collection", web.NavItem{
+		URL:  "/eiffel/projects", // Die URL zu deiner Projekt-Sammlung-Seite
+		Name: "Projekt-Sammlung", // Der Name, der angezeigt wird
+		Display: func(io web.IO) (bool, error) {
+			return true, nil
+		},
+		Position: 110, // Position in der Navigation; erscheint direkt nach dem EIFFEL-Button
+	})
+}
+
+type ProjectCollectionData struct {
+	Projects []bson.M
+}
+
+func getProjectPackagesHandler(appCtx *hctx.AppCtx, webCtx *web.Ctx) http.Handler {
+	return web.NewController(appCtx, webCtx, func(io web.IO) error {
+		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+		defer cancel()
+
+		// MongoDB Aggregation: Gruppieren nach project_id
+		pipeline := mongo.Pipeline{
+			bson.D{{
+				"$group", bson.D{
+					{"_id", "$project_id"},
+					{"requirements", bson.D{{"$push", "$$ROOT"}}},
+				},
+			}},
+		}
+
+		cursor, err := mongoCollection.Aggregate(ctx, pipeline)
+		if err != nil {
+			log.Printf("Error fetching project packages: %v", err)
+			return io.InlineError(web.ErrInternal, err)
+		}
+		defer cursor.Close(ctx)
+
+		var results []bson.M
+		if err := cursor.All(ctx, &results); err != nil {
+			log.Printf("Error parsing project packages: %v", err)
+			return io.InlineError(web.ErrInternal, err)
+		}
+
+		// Verpacke die Ergebnisse in die Struktur
+		data := ProjectCollectionData{
+			Projects: results,
+		}
+
+		//log.Printf("Fetched projects: %+v", data)
+
+		// Erfolgsnachricht für das Template vorbereiten
+		successMessage := []string{"Projects fetched successfully"}
+
+		// Template rendern und Daten korrekt übergeben
+		return io.Render(
+			web.NewFormData(data, successMessage, err),
+			"project-collection",
+			"eiffel/_project-collection.go.html",
+		)
+	})
 }
 
 func eiffelElicitationPage(cfg Cfg, appCtx *hctx.AppCtx, webCtx *web.Ctx) http.Handler {
@@ -391,10 +452,21 @@ func eiffelElicitationPage(cfg Cfg, appCtx *hctx.AppCtx, webCtx *web.Ctx) http.H
 	return web.NewController(appCtx, webCtx, func(io web.IO) error {
 		templateID := web.URLParam(io.Request(), "templateID")
 		variantKey := web.URLParam(io.Request(), "variant")
+		requirementID := io.Request().PostFormValue("requirementID") // POST-Wert abrufen
+		editMode := io.Request().PostFormValue("edit") == "true"
+		// Prüfe, ob `templateID` leer ist, und rendere ein leeres Formular
 		if templateID == "" {
+			log.Println("templateID ist leer, rendere leeres Formular.")
 			return renderElicitationPage(io, TemplateFormData{NeglectOptional: cfg.NeglectOptional}, nil, nil)
 		}
+		// RequirementID bereinigen
+		requirementID = strings.TrimPrefix(requirementID, "ObjectID(\"")
+		requirementID = strings.TrimSuffix(requirementID, "\")")
 
+		// Debugging: Log requirementID und editMode
+		log.Printf("Received requirementID: %s, editMode: %t", requirementID, editMode)
+
+		// Grundlegende Formulardaten laden
 		formData, err := TemplateFormFromRequest(
 			io.Context(),
 			templateID,
@@ -408,11 +480,76 @@ func eiffelElicitationPage(cfg Cfg, appCtx *hctx.AppCtx, webCtx *web.Ctx) http.H
 		formData.NeglectOptional = cfg.NeglectOptional
 		formData.CopyAfterParse = CopyAfterParseSetting(io.Request(), sessionStore, true)
 
+		// Wenn "Edit-Modus" aktiv ist, lade Segment-Daten aus der Datenbank
+		if editMode && requirementID != "" {
+			ctx := io.Context()
+
+			// Validate requirementID as ObjectID
+			filter := bson.M{}
+			if len(requirementID) == 24 {
+				objectID, parseErr := primitive.ObjectIDFromHex(requirementID)
+				if parseErr == nil {
+					filter["_id"] = objectID
+				} else {
+					log.Printf("Invalid ObjectID format: %v", parseErr)
+					return fmt.Errorf("Invalid Requirement ID: %s", requirementID)
+				}
+			} else {
+				log.Printf("RequirementID is not a valid ObjectID: %s", requirementID)
+				return fmt.Errorf("RequirementID is not valid: %s", requirementID)
+			}
+
+			// Query database for requirement
+			var requirement bson.M
+			dbErr := mongoCollection.FindOne(ctx, filter).Decode(&requirement)
+			if dbErr != nil {
+				log.Printf("Requirement not found: %v", dbErr)
+				return fmt.Errorf("Requirement not found: %w", dbErr)
+			}
+
+			log.Printf("Fetched Requirement: %+v", requirement)
+
+			// Extract SegmentMap from the database result
+			segmentMap := make(map[string]string)
+			if rawSegmentMap, ok := requirement["segment_map"].(bson.M); ok {
+				for key, value := range rawSegmentMap {
+					if strValue, ok := value.(string); ok {
+						segmentMap[key] = strValue
+					}
+				}
+			}
+
+			if projectIDValue, ok := requirement["project_id"].(string); ok {
+				formData.ProjectID = projectIDValue
+				log.Printf("Loaded Project ID: %s", formData.ProjectID)
+			} else {
+				log.Printf("Project ID not found in requirement")
+			}
+			log.Printf("Loaded SegmentMap: %+v", segmentMap)
+
+			// Assign SegmentMap to formData
+			formData.SegmentMap = segmentMap
+			formData.EditMode = editMode
+			//Lösche das Requirement wieder
+			if _, err := mongoCollection.DeleteMany(ctx, bson.M{"project_id": requirementID}); err != nil {
+				return fmt.Errorf("Fehler beim Löschen der zugehörigen Requirements: %w", err)
+			}
+			if _, err := mongoCollection.DeleteOne(ctx, filter); err != nil {
+				return fmt.Errorf("Fehler beim Löschen des Requirements: %w", err)
+			}
+		}
+
+		// Debugging: Log the final FormData before rendering
+		log.Printf("Final FormData: %+v", formData)
+		log.Printf("FormData: %+v", formData)
+		// Render the form
 		return renderElicitationPage(io, formData, nil, []error{err})
 	})
 }
 
 func renderElicitationPage(io web.IO, data TemplateFormData, success []string, errs []error) error {
+	log.Printf("Render context: %+v", web.NewFormData(data, success, errs...))
+
 	return io.Render(
 		web.NewFormData(data, success, errs...),
 		"eiffel.elicitation.page",
@@ -535,6 +672,7 @@ func saveToMongoDB(formData *TemplateFormData) error {
 		"variant_key":    formData.VariantKey,
 		"segment_map":    formData.SegmentMap,
 		"parsing_result": formData.ParsingResult,
+		"project_id":     formData.ProjectID,
 		"created_at":     time.Now(),
 	}
 
@@ -552,9 +690,24 @@ func parseRequirement(cfg Cfg, appCtx *hctx.AppCtx, webCtx *web.Ctx) http.Handle
 		ctx := request.Context()
 		parsers := RuleParsers()
 
+		// **Formulardaten explizit parsen**
+		if err := request.ParseForm(); err != nil {
+			log.Printf("Error parsing form data: %v", err)
+			return io.InlineError(web.ErrInternal, err)
+		}
+
+		// **Action prüfen: validate oder save**
+		action := request.FormValue("action")
+		log.Printf("Form Action: %s", action)
+
+		// **ProjectID aus Formulardaten extrahieren**
+		projectID := request.FormValue("project_id")
+		log.Printf("Captured Project ID: %s", projectID)
+
 		templateID := web.URLParam(request, "templateID")
 		variant := web.URLParam(request, "variant")
 
+		// Template-Daten lesen
 		formData, err := TemplateFormFromRequest(
 			ctx,
 			templateID,
@@ -567,6 +720,7 @@ func parseRequirement(cfg Cfg, appCtx *hctx.AppCtx, webCtx *web.Ctx) http.Handle
 		if err != nil {
 			return io.InlineError(err)
 		}
+		formData.ProjectID = projectID
 
 		segmentMap, err := SegmentMapFromRequest(request, len(formData.Variant.Rules))
 		if err != nil {
@@ -574,16 +728,11 @@ func parseRequirement(cfg Cfg, appCtx *hctx.AppCtx, webCtx *web.Ctx) http.Handle
 		}
 		formData.SegmentMap = segmentMap
 
+		// **Anforderung prüfen (validate)**
 		parsingResult, err := formData.Template.Parse(ctx, parsers, formData.VariantKey, SegmentMapToSegments(segmentMap)...)
 		formData.ParsingResult = &parsingResult
 		log.Printf("Captured formData: %+v\n", formData)
 
-		// Save to MongoDB
-		err = saveToMongoDB(&formData)
-		if err != nil {
-			log.Printf("Failed to save formData to MongoDB: %v\n", err)
-		}
-
 		var s []string
 		if parsingResult.Flawless() {
 			s = []string{"eiffel.elicitation.parse.flawless-success"}
@@ -591,6 +740,7 @@ func parseRequirement(cfg Cfg, appCtx *hctx.AppCtx, webCtx *web.Ctx) http.Handle
 			s = []string{"eiffel.elicitation.parse.success"}
 		}
 
+		// **HX-Trigger wird für Validierungsereignisse genutzt**
 		if parsingResult.Ok() {
 			triggerEvent := &HTMXTriggerParsingSuccessEvent{ParsingSuccessEvent: &parsingResult}
 			triggerEventJSON, err := json.Marshal(triggerEvent)
@@ -603,6 +753,20 @@ func parseRequirement(cfg Cfg, appCtx *hctx.AppCtx, webCtx *web.Ctx) http.Handle
 			io.Response().Header().Set("ParsingSuccessEvent", base64.URLEncoding.EncodeToString(triggerEventJSON))
 		}
 
+		// **Speichern nur bei Aktion "save"**
+		if action == "save" {
+			log.Println("Saving requirement to MongoDB...")
+			err = saveToMongoDB(&formData)
+			if err != nil {
+				log.Printf("Failed to save formData to MongoDB: %v\n", err)
+				return io.InlineError(web.ErrInternal, err)
+			}
+			log.Println("Form data successfully saved to MongoDB")
+			s = append(s, "eiffel.elicitation.save.success")
+		} else {
+			log.Println("Validation complete, no data saved to MongoDB")
+		}
+
 		formData.NeglectOptional = cfg.NeglectOptional
 		formData.CopyAfterParse = CopyAfterParseSetting(request, sessionStore, false)
 
diff --git a/templates/eiffel/_form-elicitation.go.html b/templates/eiffel/_form-elicitation.go.html
index e4b903e..9af53cf 100644
--- a/templates/eiffel/_form-elicitation.go.html
+++ b/templates/eiffel/_form-elicitation.go.html
@@ -68,32 +68,44 @@
                                         <input type="hidden" name="{{ $inputName }}" value="{{ $rule.Value }}" />
                                     {{ end }}
 
+                                    {{ if or (not $segments) (not (index $segments $ruleName)) }}
                                     <input type="text"
                                         id="eiffelFormInput-{{ $ruleName }}"
                                         class="form-control {{ if $violations }}is-invalid{{ end }}"
                                         name="{{ $inputName }}"
                                         aria-label="{{ $displayName }}"
                                         aria-description="{{ $rule.Hint }}"
-
-                                        {{ if $nonOptionalText }} {{/* show fixed text in input */}}
+                                        {{ if $nonOptionalText }}
                                             value="{{ $rule.Value }}"
                                             disabled
-                                        {{ else if $parsingResult }} {{/* show content from last submit for optional text show value as placeholder */}}
+                                        {{ else if $parsingResult }}
                                             value="{{ index $segments $ruleName }}"
-                                        {{ else if $optionalText }} {{/* show value that's suggested by the rule */}}
+                                        {{ else if $optionalText }}
                                             value="{{ $rule.Value }}"
                                         {{ end }}
-
                                         {{ if $optionalText }}
                                             placeholder="{{ $rule.Value }}"
                                         {{ else }}
                                             placeholder="{{ $displayName }}"
                                         {{ end }}
-
                                         {{ if eq $displayType "input-single-select" }}list="eiffelFormInput-{{ $ruleName }}-datalist"{{ end }}
                                         {{ if not $rule.Optional }}required{{ end }}
                                         {{ if $first }}autofocus{{ end }}
                                     />
+                                {{ else }}
+                                    <input type="text"
+                                        id="eiffelFormInput-{{ $ruleName }}"
+                                        class="form-control {{ if $violations }}is-invalid{{ end }}"
+                                        name="{{ $inputName }}"
+                                        aria-label="{{ $displayName }}"
+                                        aria-description="{{ $rule.Hint }}"
+                                        value="{{ index $segments $ruleName }}"
+                                        placeholder="{{ $displayName }}"
+                                        {{ if eq $displayType "input-single-select" }}list="eiffelFormInput-{{ $ruleName }}-datalist"{{ end }}
+                                        {{ if not $rule.Optional }}required{{ end }}
+                                        {{ if $first }}autofocus{{ end }}
+                                    />
+                                {{ end }}
 
                                     {{ if $violations }}
                                         <div id="eiffelFormInput-{{ $ruleName }}-error" class="invalid-feedback">
@@ -109,16 +121,29 @@
                                 <div class="input-group {{ if $violations }}has-validation{{ end }}">
                                     <span data-bs-target="#eiffelRule-{{ $ruleName }}-info" class="input-group-text" role="button" data-bs-toggle="modal">i</span>
 
-                                    <textarea id="eiffelFormInput-{{ $ruleName }}"
-                                        class="form-control {{ if $violations }}is-invalid{{ end }}"
-                                        name="{{ $inputName }}"
-                                        placeholder="{{ $displayName }}"
-                                        aria-label="{{ $displayName }}"
-                                        aria-description="{{ $rule.Hint }}"
-                                        {{ if not $rule.Optional }}required{{ end }}
-                                        {{ if $first }}autofocus{{ end }}
-                                        data-eiffel-auto-resize {{/* see eiffel.js */}}
-                                        rows="1">{{ if not $parsingResult }}{{ $rule.Value }}{{ else }}{{ index $segments $ruleName }}{{ end }}</textarea>
+                                    {{ if or (not $segments) (not (index $segments $ruleName)) }}
+                                        <textarea id="eiffelFormInput-{{ $ruleName }}"
+                                            class="form-control {{ if $violations }}is-invalid{{ end }}"
+                                            name="{{ $inputName }}"
+                                            placeholder="{{ $displayName }}"
+                                            aria-label="{{ $displayName }}"
+                                            aria-description="{{ $rule.Hint }}"
+                                            {{ if not $rule.Optional }}required{{ end }}
+                                            {{ if $first }}autofocus{{ end }}
+                                            data-eiffel-auto-resize
+                                            rows="1">{{ if not $parsingResult }}{{ $rule.Value }}{{ else }}{{ index $segments $ruleName }}{{ end }}</textarea>
+                                    {{ else }}
+                                        <textarea id="eiffelFormInput-{{ $ruleName }}"
+                                            class="form-control {{ if $violations }}is-invalid{{ end }}"
+                                            name="{{ $inputName }}"
+                                            placeholder="{{ $displayName }}"
+                                            aria-label="{{ $displayName }}"
+                                            aria-description="{{ $rule.Hint }}"
+                                            {{ if not $rule.Optional }}required{{ end }}
+                                            {{ if $first }}autofocus{{ end }}
+                                            data-eiffel-auto-resize
+                                            rows="1">{{ index $segments $ruleName }}</textarea>
+                                    {{ end }}
 
                                     {{ if $violations }}
                                         <div id="eiffelFormInput-{{ $ruleName }}-error" class="invalid-feedback">
@@ -186,8 +211,25 @@
                     </div>
                     {{ $first = false}}
                 {{ end }}
+                
+                <div class="col-12 mb-3">
+                    <label for="eiffelFormInput-project-id" class="form-label">
+                        {{ t "eiffel.elicitation.form.project-id" }}
+                    </label>
+                    <input type="text"
+                           id="eiffelFormInput-project-id"
+                           class="form-control"
+                           name="project_id"
+                           placeholder='{{ t "eiffel.elicitation.form.placeholder" }}'
+                           value="{{ if .Data.Form.ProjectID }}{{ .Data.Form.ProjectID }}{{ end }}"
+                           required />
+                </div>
+
+                <div class="col-12">
+                    <button type="submit" name="action" value="validate" class="btn btn-primary w-100">{{ t "eiffel.elicitation.form.submit" }}</button>
+                </div>
                 <div class="col-12">
-                    <button type="submit" class="btn btn-primary w-100">{{ t "eiffel.elicitation.form.submit" }}</button>
+                    <button type="submit" name="action" value="save" class="btn btn-success w-100">{{ t "eiffel.elicitation.form.save-requirement" }}</button>
                 </div>
             </div>
             <div class="row mt-2">
diff --git a/templates/eiffel/_project-collection.go.html b/templates/eiffel/_project-collection.go.html
new file mode 100644
index 0000000..d41c6d5
--- /dev/null
+++ b/templates/eiffel/_project-collection.go.html
@@ -0,0 +1,54 @@
+{{ define "project-collection" }}
+<h1>Projekt-Sammlung</h1>
+
+<!-- Export-Buttons -->
+<div style="margin-bottom: 20px;">
+    <form method="GET" action="/eiffel/export/json" style="display: inline;">
+        <button type="submit" style="padding: 10px; margin-right: 10px; background-color: #4CAF50; color: white; border: none; cursor: pointer;">
+            Export als JSON
+        </button>
+    </form>
+    <form method="GET" action="/eiffel/export/txt" style="display: inline;">
+        <button type="submit" style="padding: 10px; background-color: #2196F3; color: white; border: none; cursor: pointer;">
+            Export als TXT
+        </button>
+    </form>
+</div>
+
+{{ with .Data.Form }}
+    {{ range .Projects }}
+    <div class="project-box" style="border: 1px solid #ccc; margin: 10px; padding: 10px;">
+        <h2>Projekt ID: {{ ._id }}</h2>
+        <ul>
+            {{ range .requirements }}
+                <li>
+                    <!-- Input-Box -->
+                    <form method="POST" action="/eiffel/{{ .template_id }}/{{ .variant_key }}" style="display: inline;">
+                        <p style="display: inline; margin-right: 10px; max-width: 1000px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
+                            {{ .parsing_result.requirement }}
+                        </p>
+                        <input type="hidden" name="requirementID" value="{{ ._id }}">
+                        <input type="hidden" name="edit" value="true">
+                        <input type="hidden" name="project_id" value="{{ .project_id }}"> 
+                        <button type="submit" style="padding: 5px; background-color: #4CAF50; color: white; border: none; cursor: pointer;">
+                            Edit
+                        </button>
+                    </form>
+                    
+                    
+                    
+                    <!-- Löschen-Knopf -->
+                    <form method="POST" action="/eiffel/requirements/{{ index . "_id" }}/delete" style="display: inline;">
+                        <button type="submit" style="margin-left: 5px; padding: 5px; background-color: #F44336; color: white; border: none; cursor: pointer;">
+                            Delete
+                        </button>
+                    </form>
+                </li>
+            {{ end }}
+        </ul>
+    </div>
+    {{ end }}
+{{ else }}
+    <p>Keine Projektdaten vorhanden</p>
+{{ end }}
+{{ end }}
diff --git a/translations/de.json b/translations/de.json
index 71c89a9..7eb8f1d 100644
--- a/translations/de.json
+++ b/translations/de.json
@@ -133,6 +133,9 @@
       "form": {
         "title": "Anforderung erfassen (Alt + P)",
         "submit": "Anforderung prüfen (Alt + Enter)",
+        "save-requirement": "Anforderung speichern",
+        "project-id": "Projekt-ID",
+        "placeholder": "Projekt-ID hier eingeben",
         "parsing-error": "Die Anforderung entspricht nicht der Schablone.",
         "rule-description": "{{ .rule }}",
         "rule-description.optional-flag": "(Optional)",
diff --git a/translations/en.json b/translations/en.json
index 9722f71..4f6fe0e 100644
--- a/translations/en.json
+++ b/translations/en.json
@@ -133,6 +133,9 @@
       "form": {
         "title": "Capture Requirement (Alt + P)",
         "submit": "Check Requirement (Alt + Enter)",
+        "save-requirement": "Save requirement",
+        "project-id": "Project ID",
+        "placeholder": "Enter Project ID here",
         "parsing-error": "The requirement does not conform to the template.",
         "rule-description": "{{ .rule }}",
         "rule-description.optional-flag": "(Optional)",
diff --git a/vendor/github.com/gorilla/mux/.editorconfig b/vendor/github.com/gorilla/mux/.editorconfig
deleted file mode 100644
index c6b74c3..0000000
--- a/vendor/github.com/gorilla/mux/.editorconfig
+++ /dev/null
@@ -1,20 +0,0 @@
-; https://editorconfig.org/
-
-root = true
-
-[*]
-insert_final_newline = true
-charset = utf-8
-trim_trailing_whitespace = true
-indent_style = space
-indent_size = 2
-
-[{Makefile,go.mod,go.sum,*.go,.gitmodules}]
-indent_style = tab
-indent_size = 4
-
-[*.md]
-indent_size = 4
-trim_trailing_whitespace = false
-
-eclint_indent_style = unset
\ No newline at end of file
diff --git a/vendor/github.com/gorilla/mux/.gitignore b/vendor/github.com/gorilla/mux/.gitignore
deleted file mode 100644
index 84039fe..0000000
--- a/vendor/github.com/gorilla/mux/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-coverage.coverprofile
diff --git a/vendor/github.com/gorilla/mux/LICENSE b/vendor/github.com/gorilla/mux/LICENSE
deleted file mode 100644
index bb9d80b..0000000
--- a/vendor/github.com/gorilla/mux/LICENSE
+++ /dev/null
@@ -1,27 +0,0 @@
-Copyright (c) 2023 The Gorilla Authors. 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 Google Inc. nor the names of its
-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.
diff --git a/vendor/github.com/gorilla/mux/Makefile b/vendor/github.com/gorilla/mux/Makefile
deleted file mode 100644
index 98f5ab7..0000000
--- a/vendor/github.com/gorilla/mux/Makefile
+++ /dev/null
@@ -1,34 +0,0 @@
-GO_LINT=$(shell which golangci-lint 2> /dev/null || echo '')
-GO_LINT_URI=github.com/golangci/golangci-lint/cmd/golangci-lint@latest
-
-GO_SEC=$(shell which gosec 2> /dev/null || echo '')
-GO_SEC_URI=github.com/securego/gosec/v2/cmd/gosec@latest
-
-GO_VULNCHECK=$(shell which govulncheck 2> /dev/null || echo '')
-GO_VULNCHECK_URI=golang.org/x/vuln/cmd/govulncheck@latest
-
-.PHONY: golangci-lint
-golangci-lint:
-	$(if $(GO_LINT), ,go install $(GO_LINT_URI))
-	@echo "##### Running golangci-lint"
-	golangci-lint run -v
-	
-.PHONY: gosec
-gosec:
-	$(if $(GO_SEC), ,go install $(GO_SEC_URI))
-	@echo "##### Running gosec"
-	gosec ./...
-
-.PHONY: govulncheck
-govulncheck:
-	$(if $(GO_VULNCHECK), ,go install $(GO_VULNCHECK_URI))
-	@echo "##### Running govulncheck"
-	govulncheck ./...
-
-.PHONY: verify
-verify: golangci-lint gosec govulncheck
-
-.PHONY: test
-test:
-	@echo "##### Running tests"
-	go test -race -cover -coverprofile=coverage.coverprofile -covermode=atomic -v ./...
\ No newline at end of file
diff --git a/vendor/github.com/gorilla/mux/README.md b/vendor/github.com/gorilla/mux/README.md
deleted file mode 100644
index 382513d..0000000
--- a/vendor/github.com/gorilla/mux/README.md
+++ /dev/null
@@ -1,812 +0,0 @@
-# gorilla/mux
-
-![testing](https://github.com/gorilla/mux/actions/workflows/test.yml/badge.svg)
-[![codecov](https://codecov.io/github/gorilla/mux/branch/main/graph/badge.svg)](https://codecov.io/github/gorilla/mux)
-[![godoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux)
-[![sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge)
-
-
-![Gorilla Logo](https://github.com/gorilla/.github/assets/53367916/d92caabf-98e0-473e-bfbf-ab554ba435e5)
-
-Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to
-their respective handler.
-
-The name mux stands for "HTTP request multiplexer". Like the standard `http.ServeMux`, `mux.Router` matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are:
-
-* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`.
-* Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers.
-* URL hosts, paths and query values can have variables with an optional regular expression.
-* Registered URLs can be built, or "reversed", which helps maintaining references to resources.
-* Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching.
-
----
-
-* [Install](#install)
-* [Examples](#examples)
-* [Matching Routes](#matching-routes)
-* [Static Files](#static-files)
-* [Serving Single Page Applications](#serving-single-page-applications) (e.g. React, Vue, Ember.js, etc.)
-* [Registered URLs](#registered-urls)
-* [Walking Routes](#walking-routes)
-* [Graceful Shutdown](#graceful-shutdown)
-* [Middleware](#middleware)
-* [Handling CORS Requests](#handling-cors-requests)
-* [Testing Handlers](#testing-handlers)
-* [Full Example](#full-example)
-
----
-
-## Install
-
-With a [correctly configured](https://golang.org/doc/install#testing) Go toolchain:
-
-```sh
-go get -u github.com/gorilla/mux
-```
-
-## Examples
-
-Let's start registering a couple of URL paths and handlers:
-
-```go
-func main() {
-    r := mux.NewRouter()
-    r.HandleFunc("/", HomeHandler)
-    r.HandleFunc("/products", ProductsHandler)
-    r.HandleFunc("/articles", ArticlesHandler)
-    http.Handle("/", r)
-}
-```
-
-Here we register three routes mapping URL paths to handlers. This is equivalent to how `http.HandleFunc()` works: if an incoming request URL matches one of the paths, the corresponding handler is called passing (`http.ResponseWriter`, `*http.Request`) as parameters.
-
-Paths can have variables. They are defined using the format `{name}` or `{name:pattern}`. If a regular expression pattern is not defined, the matched variable will be anything until the next slash. For example:
-
-```go
-r := mux.NewRouter()
-r.HandleFunc("/products/{key}", ProductHandler)
-r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
-r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
-```
-
-The names are used to create a map of route variables which can be retrieved calling `mux.Vars()`:
-
-```go
-func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) {
-    vars := mux.Vars(r)
-    w.WriteHeader(http.StatusOK)
-    fmt.Fprintf(w, "Category: %v\n", vars["category"])
-}
-```
-
-And this is all you need to know about the basic usage. More advanced options are explained below.
-
-### Matching Routes
-
-Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables:
-
-```go
-r := mux.NewRouter()
-// Only matches if domain is "www.example.com".
-r.Host("www.example.com")
-// Matches a dynamic subdomain.
-r.Host("{subdomain:[a-z]+}.example.com")
-```
-
-There are several other matchers that can be added. To match path prefixes:
-
-```go
-r.PathPrefix("/products/")
-```
-
-...or HTTP methods:
-
-```go
-r.Methods("GET", "POST")
-```
-
-...or URL schemes:
-
-```go
-r.Schemes("https")
-```
-
-...or header values:
-
-```go
-r.Headers("X-Requested-With", "XMLHttpRequest")
-```
-
-...or query values:
-
-```go
-r.Queries("key", "value")
-```
-
-...or to use a custom matcher function:
-
-```go
-r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
-    return r.ProtoMajor == 0
-})
-```
-
-...and finally, it is possible to combine several matchers in a single route:
-
-```go
-r.HandleFunc("/products", ProductsHandler).
-  Host("www.example.com").
-  Methods("GET").
-  Schemes("http")
-```
-
-Routes are tested in the order they were added to the router. If two routes match, the first one wins:
-
-```go
-r := mux.NewRouter()
-r.HandleFunc("/specific", specificHandler)
-r.PathPrefix("/").Handler(catchAllHandler)
-```
-
-Setting the same matching conditions again and again can be boring, so we have a way to group several routes that share the same requirements. We call it "subrouting".
-
-For example, let's say we have several URLs that should only match when the host is `www.example.com`. Create a route for that host and get a "subrouter" from it:
-
-```go
-r := mux.NewRouter()
-s := r.Host("www.example.com").Subrouter()
-```
-
-Then register routes in the subrouter:
-
-```go
-s.HandleFunc("/products/", ProductsHandler)
-s.HandleFunc("/products/{key}", ProductHandler)
-s.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
-```
-
-The three URL paths we registered above will only be tested if the domain is `www.example.com`, because the subrouter is tested first. This is not only convenient, but also optimizes request matching. You can create subrouters combining any attribute matchers accepted by a route.
-
-Subrouters can be used to create domain or path "namespaces": you define subrouters in a central place and then parts of the app can register its paths relatively to a given subrouter.
-
-There's one more thing about subroutes. When a subrouter has a path prefix, the inner routes use it as base for their paths:
-
-```go
-r := mux.NewRouter()
-s := r.PathPrefix("/products").Subrouter()
-// "/products/"
-s.HandleFunc("/", ProductsHandler)
-// "/products/{key}/"
-s.HandleFunc("/{key}/", ProductHandler)
-// "/products/{key}/details"
-s.HandleFunc("/{key}/details", ProductDetailsHandler)
-```
-
-
-### Static Files
-
-Note that the path provided to `PathPrefix()` represents a "wildcard": calling
-`PathPrefix("/static/").Handler(...)` means that the handler will be passed any
-request that matches "/static/\*". This makes it easy to serve static files with mux:
-
-```go
-func main() {
-    var dir string
-
-    flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
-    flag.Parse()
-    r := mux.NewRouter()
-
-    // This will serve files under http://localhost:8000/static/<filename>
-    r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))
-
-    srv := &http.Server{
-        Handler:      r,
-        Addr:         "127.0.0.1:8000",
-        // Good practice: enforce timeouts for servers you create!
-        WriteTimeout: 15 * time.Second,
-        ReadTimeout:  15 * time.Second,
-    }
-
-    log.Fatal(srv.ListenAndServe())
-}
-```
-
-### Serving Single Page Applications
-
-Most of the time it makes sense to serve your SPA on a separate web server from your API,
-but sometimes it's desirable to serve them both from one place. It's possible to write a simple
-handler for serving your SPA (for use with React Router's [BrowserRouter](https://reacttraining.com/react-router/web/api/BrowserRouter) for example), and leverage
-mux's powerful routing for your API endpoints.
-
-```go
-package main
-
-import (
-	"encoding/json"
-	"log"
-	"net/http"
-	"os"
-	"path/filepath"
-	"time"
-
-	"github.com/gorilla/mux"
-)
-
-// spaHandler implements the http.Handler interface, so we can use it
-// to respond to HTTP requests. The path to the static directory and
-// path to the index file within that static directory are used to
-// serve the SPA in the given static directory.
-type spaHandler struct {
-	staticPath string
-	indexPath  string
-}
-
-// ServeHTTP inspects the URL path to locate a file within the static dir
-// on the SPA handler. If a file is found, it will be served. If not, the
-// file located at the index path on the SPA handler will be served. This
-// is suitable behavior for serving an SPA (single page application).
-func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	// Join internally call path.Clean to prevent directory traversal
-	path := filepath.Join(h.staticPath, r.URL.Path)
-
-	// check whether a file exists or is a directory at the given path
-	fi, err := os.Stat(path)
-	if os.IsNotExist(err) || fi.IsDir() {
-		// file does not exist or path is a directory, serve index.html
-		http.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath))
-		return
-	}
-
-	if err != nil {
-		// if we got an error (that wasn't that the file doesn't exist) stating the
-		// file, return a 500 internal server error and stop
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-        return
-	}
-
-	// otherwise, use http.FileServer to serve the static file
-	http.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r)
-}
-
-func main() {
-	router := mux.NewRouter()
-
-	router.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) {
-		// an example API handler
-		json.NewEncoder(w).Encode(map[string]bool{"ok": true})
-	})
-
-	spa := spaHandler{staticPath: "build", indexPath: "index.html"}
-	router.PathPrefix("/").Handler(spa)
-
-	srv := &http.Server{
-		Handler: router,
-		Addr:    "127.0.0.1:8000",
-		// Good practice: enforce timeouts for servers you create!
-		WriteTimeout: 15 * time.Second,
-		ReadTimeout:  15 * time.Second,
-	}
-
-	log.Fatal(srv.ListenAndServe())
-}
-```
-
-### Registered URLs
-
-Now let's see how to build registered URLs.
-
-Routes can be named. All routes that define a name can have their URLs built, or "reversed". We define a name calling `Name()` on a route. For example:
-
-```go
-r := mux.NewRouter()
-r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
-  Name("article")
-```
-
-To build a URL, get the route and call the `URL()` method, passing a sequence of key/value pairs for the route variables. For the previous route, we would do:
-
-```go
-url, err := r.Get("article").URL("category", "technology", "id", "42")
-```
-
-...and the result will be a `url.URL` with the following path:
-
-```
-"/articles/technology/42"
-```
-
-This also works for host and query value variables:
-
-```go
-r := mux.NewRouter()
-r.Host("{subdomain}.example.com").
-  Path("/articles/{category}/{id:[0-9]+}").
-  Queries("filter", "{filter}").
-  HandlerFunc(ArticleHandler).
-  Name("article")
-
-// url.String() will be "http://news.example.com/articles/technology/42?filter=gorilla"
-url, err := r.Get("article").URL("subdomain", "news",
-                                 "category", "technology",
-                                 "id", "42",
-                                 "filter", "gorilla")
-```
-
-All variables defined in the route are required, and their values must conform to the corresponding patterns. These requirements guarantee that a generated URL will always match a registered route -- the only exception is for explicitly defined "build-only" routes which never match.
-
-Regex support also exists for matching Headers within a route. For example, we could do:
-
-```go
-r.HeadersRegexp("Content-Type", "application/(text|json)")
-```
-
-...and the route will match both requests with a Content-Type of `application/json` as well as `application/text`
-
-There's also a way to build only the URL host or path for a route: use the methods `URLHost()` or `URLPath()` instead. For the previous route, we would do:
-
-```go
-// "http://news.example.com/"
-host, err := r.Get("article").URLHost("subdomain", "news")
-
-// "/articles/technology/42"
-path, err := r.Get("article").URLPath("category", "technology", "id", "42")
-```
-
-And if you use subrouters, host and path defined separately can be built as well:
-
-```go
-r := mux.NewRouter()
-s := r.Host("{subdomain}.example.com").Subrouter()
-s.Path("/articles/{category}/{id:[0-9]+}").
-  HandlerFunc(ArticleHandler).
-  Name("article")
-
-// "http://news.example.com/articles/technology/42"
-url, err := r.Get("article").URL("subdomain", "news",
-                                 "category", "technology",
-                                 "id", "42")
-```
-
-To find all the required variables for a given route when calling `URL()`, the method `GetVarNames()` is available:
-```go
-r := mux.NewRouter()
-r.Host("{domain}").
-    Path("/{group}/{item_id}").
-    Queries("some_data1", "{some_data1}").
-    Queries("some_data2", "{some_data2}").
-    Name("article")
-
-// Will print [domain group item_id some_data1 some_data2] <nil>
-fmt.Println(r.Get("article").GetVarNames())
-
-```
-### Walking Routes
-
-The `Walk` function on `mux.Router` can be used to visit all of the routes that are registered on a router. For example,
-the following prints all of the registered routes:
-
-```go
-package main
-
-import (
-	"fmt"
-	"net/http"
-	"strings"
-
-	"github.com/gorilla/mux"
-)
-
-func handler(w http.ResponseWriter, r *http.Request) {
-	return
-}
-
-func main() {
-	r := mux.NewRouter()
-	r.HandleFunc("/", handler)
-	r.HandleFunc("/products", handler).Methods("POST")
-	r.HandleFunc("/articles", handler).Methods("GET")
-	r.HandleFunc("/articles/{id}", handler).Methods("GET", "PUT")
-	r.HandleFunc("/authors", handler).Queries("surname", "{surname}")
-	err := r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
-		pathTemplate, err := route.GetPathTemplate()
-		if err == nil {
-			fmt.Println("ROUTE:", pathTemplate)
-		}
-		pathRegexp, err := route.GetPathRegexp()
-		if err == nil {
-			fmt.Println("Path regexp:", pathRegexp)
-		}
-		queriesTemplates, err := route.GetQueriesTemplates()
-		if err == nil {
-			fmt.Println("Queries templates:", strings.Join(queriesTemplates, ","))
-		}
-		queriesRegexps, err := route.GetQueriesRegexp()
-		if err == nil {
-			fmt.Println("Queries regexps:", strings.Join(queriesRegexps, ","))
-		}
-		methods, err := route.GetMethods()
-		if err == nil {
-			fmt.Println("Methods:", strings.Join(methods, ","))
-		}
-		fmt.Println()
-		return nil
-	})
-
-	if err != nil {
-		fmt.Println(err)
-	}
-
-	http.Handle("/", r)
-}
-```
-
-### Graceful Shutdown
-
-Go 1.8 introduced the ability to [gracefully shutdown](https://golang.org/doc/go1.8#http_shutdown) a `*http.Server`. Here's how to do that alongside `mux`:
-
-```go
-package main
-
-import (
-    "context"
-    "flag"
-    "log"
-    "net/http"
-    "os"
-    "os/signal"
-    "time"
-
-    "github.com/gorilla/mux"
-)
-
-func main() {
-    var wait time.Duration
-    flag.DurationVar(&wait, "graceful-timeout", time.Second * 15, "the duration for which the server gracefully wait for existing connections to finish - e.g. 15s or 1m")
-    flag.Parse()
-
-    r := mux.NewRouter()
-    // Add your routes as needed
-
-    srv := &http.Server{
-        Addr:         "0.0.0.0:8080",
-        // Good practice to set timeouts to avoid Slowloris attacks.
-        WriteTimeout: time.Second * 15,
-        ReadTimeout:  time.Second * 15,
-        IdleTimeout:  time.Second * 60,
-        Handler: r, // Pass our instance of gorilla/mux in.
-    }
-
-    // Run our server in a goroutine so that it doesn't block.
-    go func() {
-        if err := srv.ListenAndServe(); err != nil {
-            log.Println(err)
-        }
-    }()
-
-    c := make(chan os.Signal, 1)
-    // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C)
-    // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught.
-    signal.Notify(c, os.Interrupt)
-
-    // Block until we receive our signal.
-    <-c
-
-    // Create a deadline to wait for.
-    ctx, cancel := context.WithTimeout(context.Background(), wait)
-    defer cancel()
-    // Doesn't block if no connections, but will otherwise wait
-    // until the timeout deadline.
-    srv.Shutdown(ctx)
-    // Optionally, you could run srv.Shutdown in a goroutine and block on
-    // <-ctx.Done() if your application should wait for other services
-    // to finalize based on context cancellation.
-    log.Println("shutting down")
-    os.Exit(0)
-}
-```
-
-### Middleware
-
-Mux supports the addition of middlewares to a [Router](https://godoc.org/github.com/gorilla/mux#Router), which are executed in the order they are added if a match is found, including its subrouters.
-Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or `ResponseWriter` hijacking.
-
-Mux middlewares are defined using the de facto standard type:
-
-```go
-type MiddlewareFunc func(http.Handler) http.Handler
-```
-
-Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc. This takes advantage of closures being able access variables from the context where they are created, while retaining the signature enforced by the receivers.
-
-A very basic middleware which logs the URI of the request being handled could be written as:
-
-```go
-func loggingMiddleware(next http.Handler) http.Handler {
-    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-        // Do stuff here
-        log.Println(r.RequestURI)
-        // Call the next handler, which can be another middleware in the chain, or the final handler.
-        next.ServeHTTP(w, r)
-    })
-}
-```
-
-Middlewares can be added to a router using `Router.Use()`:
-
-```go
-r := mux.NewRouter()
-r.HandleFunc("/", handler)
-r.Use(loggingMiddleware)
-```
-
-A more complex authentication middleware, which maps session token to users, could be written as:
-
-```go
-// Define our struct
-type authenticationMiddleware struct {
-	tokenUsers map[string]string
-}
-
-// Initialize it somewhere
-func (amw *authenticationMiddleware) Populate() {
-	amw.tokenUsers["00000000"] = "user0"
-	amw.tokenUsers["aaaaaaaa"] = "userA"
-	amw.tokenUsers["05f717e5"] = "randomUser"
-	amw.tokenUsers["deadbeef"] = "user0"
-}
-
-// Middleware function, which will be called for each request
-func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
-    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-        token := r.Header.Get("X-Session-Token")
-
-        if user, found := amw.tokenUsers[token]; found {
-        	// We found the token in our map
-        	log.Printf("Authenticated user %s\n", user)
-        	// Pass down the request to the next middleware (or final handler)
-        	next.ServeHTTP(w, r)
-        } else {
-        	// Write an error and stop the handler chain
-        	http.Error(w, "Forbidden", http.StatusForbidden)
-        }
-    })
-}
-```
-
-```go
-r := mux.NewRouter()
-r.HandleFunc("/", handler)
-
-amw := authenticationMiddleware{tokenUsers: make(map[string]string)}
-amw.Populate()
-
-r.Use(amw.Middleware)
-```
-
-Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. Middlewares _should_ write to `ResponseWriter` if they _are_ going to terminate the request, and they _should not_ write to `ResponseWriter` if they _are not_ going to terminate it.
-
-### Handling CORS Requests
-
-[CORSMethodMiddleware](https://godoc.org/github.com/gorilla/mux#CORSMethodMiddleware) intends to make it easier to strictly set the `Access-Control-Allow-Methods` response header.
-
-* You will still need to use your own CORS handler to set the other CORS headers such as `Access-Control-Allow-Origin`
-* The middleware will set the `Access-Control-Allow-Methods` header to all the method matchers (e.g. `r.Methods(http.MethodGet, http.MethodPut, http.MethodOptions)` -> `Access-Control-Allow-Methods: GET,PUT,OPTIONS`) on a route
-* If you do not specify any methods, then:
-> _Important_: there must be an `OPTIONS` method matcher for the middleware to set the headers.
-
-Here is an example of using `CORSMethodMiddleware` along with a custom `OPTIONS` handler to set all the required CORS headers:
-
-```go
-package main
-
-import (
-	"net/http"
-	"github.com/gorilla/mux"
-)
-
-func main() {
-    r := mux.NewRouter()
-
-    // IMPORTANT: you must specify an OPTIONS method matcher for the middleware to set CORS headers
-    r.HandleFunc("/foo", fooHandler).Methods(http.MethodGet, http.MethodPut, http.MethodPatch, http.MethodOptions)
-    r.Use(mux.CORSMethodMiddleware(r))
-    
-    http.ListenAndServe(":8080", r)
-}
-
-func fooHandler(w http.ResponseWriter, r *http.Request) {
-    w.Header().Set("Access-Control-Allow-Origin", "*")
-    if r.Method == http.MethodOptions {
-        return
-    }
-
-    w.Write([]byte("foo"))
-}
-```
-
-And an request to `/foo` using something like:
-
-```bash
-curl localhost:8080/foo -v
-```
-
-Would look like:
-
-```bash
-*   Trying ::1...
-* TCP_NODELAY set
-* Connected to localhost (::1) port 8080 (#0)
-> GET /foo HTTP/1.1
-> Host: localhost:8080
-> User-Agent: curl/7.59.0
-> Accept: */*
-> 
-< HTTP/1.1 200 OK
-< Access-Control-Allow-Methods: GET,PUT,PATCH,OPTIONS
-< Access-Control-Allow-Origin: *
-< Date: Fri, 28 Jun 2019 20:13:30 GMT
-< Content-Length: 3
-< Content-Type: text/plain; charset=utf-8
-< 
-* Connection #0 to host localhost left intact
-foo
-```
-
-### Testing Handlers
-
-Testing handlers in a Go web application is straightforward, and _mux_ doesn't complicate this any further. Given two files: `endpoints.go` and `endpoints_test.go`, here's how we'd test an application using _mux_.
-
-First, our simple HTTP handler:
-
-```go
-// endpoints.go
-package main
-
-func HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
-    // A very simple health check.
-    w.Header().Set("Content-Type", "application/json")
-    w.WriteHeader(http.StatusOK)
-
-    // In the future we could report back on the status of our DB, or our cache
-    // (e.g. Redis) by performing a simple PING, and include them in the response.
-    io.WriteString(w, `{"alive": true}`)
-}
-
-func main() {
-    r := mux.NewRouter()
-    r.HandleFunc("/health", HealthCheckHandler)
-
-    log.Fatal(http.ListenAndServe("localhost:8080", r))
-}
-```
-
-Our test code:
-
-```go
-// endpoints_test.go
-package main
-
-import (
-    "net/http"
-    "net/http/httptest"
-    "testing"
-)
-
-func TestHealthCheckHandler(t *testing.T) {
-    // Create a request to pass to our handler. We don't have any query parameters for now, so we'll
-    // pass 'nil' as the third parameter.
-    req, err := http.NewRequest("GET", "/health", nil)
-    if err != nil {
-        t.Fatal(err)
-    }
-
-    // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
-    rr := httptest.NewRecorder()
-    handler := http.HandlerFunc(HealthCheckHandler)
-
-    // Our handlers satisfy http.Handler, so we can call their ServeHTTP method
-    // directly and pass in our Request and ResponseRecorder.
-    handler.ServeHTTP(rr, req)
-
-    // Check the status code is what we expect.
-    if status := rr.Code; status != http.StatusOK {
-        t.Errorf("handler returned wrong status code: got %v want %v",
-            status, http.StatusOK)
-    }
-
-    // Check the response body is what we expect.
-    expected := `{"alive": true}`
-    if rr.Body.String() != expected {
-        t.Errorf("handler returned unexpected body: got %v want %v",
-            rr.Body.String(), expected)
-    }
-}
-```
-
-In the case that our routes have [variables](#examples), we can pass those in the request. We could write
-[table-driven tests](https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go) to test multiple
-possible route variables as needed.
-
-```go
-// endpoints.go
-func main() {
-    r := mux.NewRouter()
-    // A route with a route variable:
-    r.HandleFunc("/metrics/{type}", MetricsHandler)
-
-    log.Fatal(http.ListenAndServe("localhost:8080", r))
-}
-```
-
-Our test file, with a table-driven test of `routeVariables`:
-
-```go
-// endpoints_test.go
-func TestMetricsHandler(t *testing.T) {
-    tt := []struct{
-        routeVariable string
-        shouldPass bool
-    }{
-        {"goroutines", true},
-        {"heap", true},
-        {"counters", true},
-        {"queries", true},
-        {"adhadaeqm3k", false},
-    }
-
-    for _, tc := range tt {
-        path := fmt.Sprintf("/metrics/%s", tc.routeVariable)
-        req, err := http.NewRequest("GET", path, nil)
-        if err != nil {
-            t.Fatal(err)
-        }
-
-        rr := httptest.NewRecorder()
-	
-	// To add the vars to the context, 
-	// we need to create a router through which we can pass the request.
-	router := mux.NewRouter()
-        router.HandleFunc("/metrics/{type}", MetricsHandler)
-        router.ServeHTTP(rr, req)
-
-        // In this case, our MetricsHandler returns a non-200 response
-        // for a route variable it doesn't know about.
-        if rr.Code == http.StatusOK && !tc.shouldPass {
-            t.Errorf("handler should have failed on routeVariable %s: got %v want %v",
-                tc.routeVariable, rr.Code, http.StatusOK)
-        }
-    }
-}
-```
-
-## Full Example
-
-Here's a complete, runnable example of a small `mux` based server:
-
-```go
-package main
-
-import (
-    "net/http"
-    "log"
-    "github.com/gorilla/mux"
-)
-
-func YourHandler(w http.ResponseWriter, r *http.Request) {
-    w.Write([]byte("Gorilla!\n"))
-}
-
-func main() {
-    r := mux.NewRouter()
-    // Routes consist of a path and a handler function.
-    r.HandleFunc("/", YourHandler)
-
-    // Bind to a port and pass our router in
-    log.Fatal(http.ListenAndServe(":8000", r))
-}
-```
-
-## License
-
-BSD licensed. See the LICENSE file for details.
diff --git a/vendor/github.com/gorilla/mux/doc.go b/vendor/github.com/gorilla/mux/doc.go
deleted file mode 100644
index 8060135..0000000
--- a/vendor/github.com/gorilla/mux/doc.go
+++ /dev/null
@@ -1,305 +0,0 @@
-// Copyright 2012 The Gorilla Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-/*
-Package mux implements a request router and dispatcher.
-
-The name mux stands for "HTTP request multiplexer". Like the standard
-http.ServeMux, mux.Router matches incoming requests against a list of
-registered routes and calls a handler for the route that matches the URL
-or other conditions. The main features are:
-
-  - Requests can be matched based on URL host, path, path prefix, schemes,
-    header and query values, HTTP methods or using custom matchers.
-  - URL hosts, paths and query values can have variables with an optional
-    regular expression.
-  - Registered URLs can be built, or "reversed", which helps maintaining
-    references to resources.
-  - Routes can be used as subrouters: nested routes are only tested if the
-    parent route matches. This is useful to define groups of routes that
-    share common conditions like a host, a path prefix or other repeated
-    attributes. As a bonus, this optimizes request matching.
-  - It implements the http.Handler interface so it is compatible with the
-    standard http.ServeMux.
-
-Let's start registering a couple of URL paths and handlers:
-
-	func main() {
-		r := mux.NewRouter()
-		r.HandleFunc("/", HomeHandler)
-		r.HandleFunc("/products", ProductsHandler)
-		r.HandleFunc("/articles", ArticlesHandler)
-		http.Handle("/", r)
-	}
-
-Here we register three routes mapping URL paths to handlers. This is
-equivalent to how http.HandleFunc() works: if an incoming request URL matches
-one of the paths, the corresponding handler is called passing
-(http.ResponseWriter, *http.Request) as parameters.
-
-Paths can have variables. They are defined using the format {name} or
-{name:pattern}. If a regular expression pattern is not defined, the matched
-variable will be anything until the next slash. For example:
-
-	r := mux.NewRouter()
-	r.HandleFunc("/products/{key}", ProductHandler)
-	r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
-	r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
-
-Groups can be used inside patterns, as long as they are non-capturing (?:re). For example:
-
-	r.HandleFunc("/articles/{category}/{sort:(?:asc|desc|new)}", ArticlesCategoryHandler)
-
-The names are used to create a map of route variables which can be retrieved
-calling mux.Vars():
-
-	vars := mux.Vars(request)
-	category := vars["category"]
-
-Note that if any capturing groups are present, mux will panic() during parsing. To prevent
-this, convert any capturing groups to non-capturing, e.g. change "/{sort:(asc|desc)}" to
-"/{sort:(?:asc|desc)}". This is a change from prior versions which behaved unpredictably
-when capturing groups were present.
-
-And this is all you need to know about the basic usage. More advanced options
-are explained below.
-
-Routes can also be restricted to a domain or subdomain. Just define a host
-pattern to be matched. They can also have variables:
-
-	r := mux.NewRouter()
-	// Only matches if domain is "www.example.com".
-	r.Host("www.example.com")
-	// Matches a dynamic subdomain.
-	r.Host("{subdomain:[a-z]+}.domain.com")
-
-There are several other matchers that can be added. To match path prefixes:
-
-	r.PathPrefix("/products/")
-
-...or HTTP methods:
-
-	r.Methods("GET", "POST")
-
-...or URL schemes:
-
-	r.Schemes("https")
-
-...or header values:
-
-	r.Headers("X-Requested-With", "XMLHttpRequest")
-
-...or query values:
-
-	r.Queries("key", "value")
-
-...or to use a custom matcher function:
-
-	r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
-		return r.ProtoMajor == 0
-	})
-
-...and finally, it is possible to combine several matchers in a single route:
-
-	r.HandleFunc("/products", ProductsHandler).
-	  Host("www.example.com").
-	  Methods("GET").
-	  Schemes("http")
-
-Setting the same matching conditions again and again can be boring, so we have
-a way to group several routes that share the same requirements.
-We call it "subrouting".
-
-For example, let's say we have several URLs that should only match when the
-host is "www.example.com". Create a route for that host and get a "subrouter"
-from it:
-
-	r := mux.NewRouter()
-	s := r.Host("www.example.com").Subrouter()
-
-Then register routes in the subrouter:
-
-	s.HandleFunc("/products/", ProductsHandler)
-	s.HandleFunc("/products/{key}", ProductHandler)
-	s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
-
-The three URL paths we registered above will only be tested if the domain is
-"www.example.com", because the subrouter is tested first. This is not
-only convenient, but also optimizes request matching. You can create
-subrouters combining any attribute matchers accepted by a route.
-
-Subrouters can be used to create domain or path "namespaces": you define
-subrouters in a central place and then parts of the app can register its
-paths relatively to a given subrouter.
-
-There's one more thing about subroutes. When a subrouter has a path prefix,
-the inner routes use it as base for their paths:
-
-	r := mux.NewRouter()
-	s := r.PathPrefix("/products").Subrouter()
-	// "/products/"
-	s.HandleFunc("/", ProductsHandler)
-	// "/products/{key}/"
-	s.HandleFunc("/{key}/", ProductHandler)
-	// "/products/{key}/details"
-	s.HandleFunc("/{key}/details", ProductDetailsHandler)
-
-Note that the path provided to PathPrefix() represents a "wildcard": calling
-PathPrefix("/static/").Handler(...) means that the handler will be passed any
-request that matches "/static/*". This makes it easy to serve static files with mux:
-
-	func main() {
-		var dir string
-
-		flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
-		flag.Parse()
-		r := mux.NewRouter()
-
-		// This will serve files under http://localhost:8000/static/<filename>
-		r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))
-
-		srv := &http.Server{
-			Handler:      r,
-			Addr:         "127.0.0.1:8000",
-			// Good practice: enforce timeouts for servers you create!
-			WriteTimeout: 15 * time.Second,
-			ReadTimeout:  15 * time.Second,
-		}
-
-		log.Fatal(srv.ListenAndServe())
-	}
-
-Now let's see how to build registered URLs.
-
-Routes can be named. All routes that define a name can have their URLs built,
-or "reversed". We define a name calling Name() on a route. For example:
-
-	r := mux.NewRouter()
-	r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
-	  Name("article")
-
-To build a URL, get the route and call the URL() method, passing a sequence of
-key/value pairs for the route variables. For the previous route, we would do:
-
-	url, err := r.Get("article").URL("category", "technology", "id", "42")
-
-...and the result will be a url.URL with the following path:
-
-	"/articles/technology/42"
-
-This also works for host and query value variables:
-
-	r := mux.NewRouter()
-	r.Host("{subdomain}.domain.com").
-	  Path("/articles/{category}/{id:[0-9]+}").
-	  Queries("filter", "{filter}").
-	  HandlerFunc(ArticleHandler).
-	  Name("article")
-
-	// url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla"
-	url, err := r.Get("article").URL("subdomain", "news",
-	                                 "category", "technology",
-	                                 "id", "42",
-	                                 "filter", "gorilla")
-
-All variables defined in the route are required, and their values must
-conform to the corresponding patterns. These requirements guarantee that a
-generated URL will always match a registered route -- the only exception is
-for explicitly defined "build-only" routes which never match.
-
-Regex support also exists for matching Headers within a route. For example, we could do:
-
-	r.HeadersRegexp("Content-Type", "application/(text|json)")
-
-...and the route will match both requests with a Content-Type of `application/json` as well as
-`application/text`
-
-There's also a way to build only the URL host or path for a route:
-use the methods URLHost() or URLPath() instead. For the previous route,
-we would do:
-
-	// "http://news.domain.com/"
-	host, err := r.Get("article").URLHost("subdomain", "news")
-
-	// "/articles/technology/42"
-	path, err := r.Get("article").URLPath("category", "technology", "id", "42")
-
-And if you use subrouters, host and path defined separately can be built
-as well:
-
-	r := mux.NewRouter()
-	s := r.Host("{subdomain}.domain.com").Subrouter()
-	s.Path("/articles/{category}/{id:[0-9]+}").
-	  HandlerFunc(ArticleHandler).
-	  Name("article")
-
-	// "http://news.domain.com/articles/technology/42"
-	url, err := r.Get("article").URL("subdomain", "news",
-	                                 "category", "technology",
-	                                 "id", "42")
-
-Mux supports the addition of middlewares to a Router, which are executed in the order they are added if a match is found, including its subrouters. Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or ResponseWriter hijacking.
-
-	type MiddlewareFunc func(http.Handler) http.Handler
-
-Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc (closures can access variables from the context where they are created).
-
-A very basic middleware which logs the URI of the request being handled could be written as:
-
-	func simpleMw(next http.Handler) http.Handler {
-		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-			// Do stuff here
-			log.Println(r.RequestURI)
-			// Call the next handler, which can be another middleware in the chain, or the final handler.
-			next.ServeHTTP(w, r)
-		})
-	}
-
-Middlewares can be added to a router using `Router.Use()`:
-
-	r := mux.NewRouter()
-	r.HandleFunc("/", handler)
-	r.Use(simpleMw)
-
-A more complex authentication middleware, which maps session token to users, could be written as:
-
-	// Define our struct
-	type authenticationMiddleware struct {
-		tokenUsers map[string]string
-	}
-
-	// Initialize it somewhere
-	func (amw *authenticationMiddleware) Populate() {
-		amw.tokenUsers["00000000"] = "user0"
-		amw.tokenUsers["aaaaaaaa"] = "userA"
-		amw.tokenUsers["05f717e5"] = "randomUser"
-		amw.tokenUsers["deadbeef"] = "user0"
-	}
-
-	// Middleware function, which will be called for each request
-	func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
-		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-			token := r.Header.Get("X-Session-Token")
-
-			if user, found := amw.tokenUsers[token]; found {
-				// We found the token in our map
-				log.Printf("Authenticated user %s\n", user)
-				next.ServeHTTP(w, r)
-			} else {
-				http.Error(w, "Forbidden", http.StatusForbidden)
-			}
-		})
-	}
-
-	r := mux.NewRouter()
-	r.HandleFunc("/", handler)
-
-	amw := authenticationMiddleware{tokenUsers: make(map[string]string)}
-	amw.Populate()
-
-	r.Use(amw.Middleware)
-
-Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to.
-*/
-package mux
diff --git a/vendor/github.com/gorilla/mux/middleware.go b/vendor/github.com/gorilla/mux/middleware.go
deleted file mode 100644
index cb51c56..0000000
--- a/vendor/github.com/gorilla/mux/middleware.go
+++ /dev/null
@@ -1,74 +0,0 @@
-package mux
-
-import (
-	"net/http"
-	"strings"
-)
-
-// MiddlewareFunc is a function which receives an http.Handler and returns another http.Handler.
-// Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed
-// to it, and then calls the handler passed as parameter to the MiddlewareFunc.
-type MiddlewareFunc func(http.Handler) http.Handler
-
-// middleware interface is anything which implements a MiddlewareFunc named Middleware.
-type middleware interface {
-	Middleware(handler http.Handler) http.Handler
-}
-
-// Middleware allows MiddlewareFunc to implement the middleware interface.
-func (mw MiddlewareFunc) Middleware(handler http.Handler) http.Handler {
-	return mw(handler)
-}
-
-// Use appends a MiddlewareFunc to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router.
-func (r *Router) Use(mwf ...MiddlewareFunc) {
-	for _, fn := range mwf {
-		r.middlewares = append(r.middlewares, fn)
-	}
-}
-
-// useInterface appends a middleware to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router.
-func (r *Router) useInterface(mw middleware) {
-	r.middlewares = append(r.middlewares, mw)
-}
-
-// CORSMethodMiddleware automatically sets the Access-Control-Allow-Methods response header
-// on requests for routes that have an OPTIONS method matcher to all the method matchers on
-// the route. Routes that do not explicitly handle OPTIONS requests will not be processed
-// by the middleware. See examples for usage.
-func CORSMethodMiddleware(r *Router) MiddlewareFunc {
-	return func(next http.Handler) http.Handler {
-		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
-			allMethods, err := getAllMethodsForRoute(r, req)
-			if err == nil {
-				for _, v := range allMethods {
-					if v == http.MethodOptions {
-						w.Header().Set("Access-Control-Allow-Methods", strings.Join(allMethods, ","))
-					}
-				}
-			}
-
-			next.ServeHTTP(w, req)
-		})
-	}
-}
-
-// getAllMethodsForRoute returns all the methods from method matchers matching a given
-// request.
-func getAllMethodsForRoute(r *Router, req *http.Request) ([]string, error) {
-	var allMethods []string
-
-	for _, route := range r.routes {
-		var match RouteMatch
-		if route.Match(req, &match) || match.MatchErr == ErrMethodMismatch {
-			methods, err := route.GetMethods()
-			if err != nil {
-				return nil, err
-			}
-
-			allMethods = append(allMethods, methods...)
-		}
-	}
-
-	return allMethods, nil
-}
diff --git a/vendor/github.com/gorilla/mux/mux.go b/vendor/github.com/gorilla/mux/mux.go
deleted file mode 100644
index 1e08990..0000000
--- a/vendor/github.com/gorilla/mux/mux.go
+++ /dev/null
@@ -1,608 +0,0 @@
-// Copyright 2012 The Gorilla Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package mux
-
-import (
-	"context"
-	"errors"
-	"fmt"
-	"net/http"
-	"path"
-	"regexp"
-)
-
-var (
-	// ErrMethodMismatch is returned when the method in the request does not match
-	// the method defined against the route.
-	ErrMethodMismatch = errors.New("method is not allowed")
-	// ErrNotFound is returned when no route match is found.
-	ErrNotFound = errors.New("no matching route was found")
-)
-
-// NewRouter returns a new router instance.
-func NewRouter() *Router {
-	return &Router{namedRoutes: make(map[string]*Route)}
-}
-
-// Router registers routes to be matched and dispatches a handler.
-//
-// It implements the http.Handler interface, so it can be registered to serve
-// requests:
-//
-//	var router = mux.NewRouter()
-//
-//	func main() {
-//	    http.Handle("/", router)
-//	}
-//
-// Or, for Google App Engine, register it in a init() function:
-//
-//	func init() {
-//	    http.Handle("/", router)
-//	}
-//
-// This will send all incoming requests to the router.
-type Router struct {
-	// Configurable Handler to be used when no route matches.
-	// This can be used to render your own 404 Not Found errors.
-	NotFoundHandler http.Handler
-
-	// Configurable Handler to be used when the request method does not match the route.
-	// This can be used to render your own 405 Method Not Allowed errors.
-	MethodNotAllowedHandler http.Handler
-
-	// Routes to be matched, in order.
-	routes []*Route
-
-	// Routes by name for URL building.
-	namedRoutes map[string]*Route
-
-	// If true, do not clear the request context after handling the request.
-	//
-	// Deprecated: No effect, since the context is stored on the request itself.
-	KeepContext bool
-
-	// Slice of middlewares to be called after a match is found
-	middlewares []middleware
-
-	// configuration shared with `Route`
-	routeConf
-}
-
-// common route configuration shared between `Router` and `Route`
-type routeConf struct {
-	// If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to"
-	useEncodedPath bool
-
-	// If true, when the path pattern is "/path/", accessing "/path" will
-	// redirect to the former and vice versa.
-	strictSlash bool
-
-	// If true, when the path pattern is "/path//to", accessing "/path//to"
-	// will not redirect
-	skipClean bool
-
-	// Manager for the variables from host and path.
-	regexp routeRegexpGroup
-
-	// List of matchers.
-	matchers []matcher
-
-	// The scheme used when building URLs.
-	buildScheme string
-
-	buildVarsFunc BuildVarsFunc
-}
-
-// returns an effective deep copy of `routeConf`
-func copyRouteConf(r routeConf) routeConf {
-	c := r
-
-	if r.regexp.path != nil {
-		c.regexp.path = copyRouteRegexp(r.regexp.path)
-	}
-
-	if r.regexp.host != nil {
-		c.regexp.host = copyRouteRegexp(r.regexp.host)
-	}
-
-	c.regexp.queries = make([]*routeRegexp, 0, len(r.regexp.queries))
-	for _, q := range r.regexp.queries {
-		c.regexp.queries = append(c.regexp.queries, copyRouteRegexp(q))
-	}
-
-	c.matchers = make([]matcher, len(r.matchers))
-	copy(c.matchers, r.matchers)
-
-	return c
-}
-
-func copyRouteRegexp(r *routeRegexp) *routeRegexp {
-	c := *r
-	return &c
-}
-
-// Match attempts to match the given request against the router's registered routes.
-//
-// If the request matches a route of this router or one of its subrouters the Route,
-// Handler, and Vars fields of the the match argument are filled and this function
-// returns true.
-//
-// If the request does not match any of this router's or its subrouters' routes
-// then this function returns false. If available, a reason for the match failure
-// will be filled in the match argument's MatchErr field. If the match failure type
-// (eg: not found) has a registered handler, the handler is assigned to the Handler
-// field of the match argument.
-func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
-	for _, route := range r.routes {
-		if route.Match(req, match) {
-			// Build middleware chain if no error was found
-			if match.MatchErr == nil {
-				for i := len(r.middlewares) - 1; i >= 0; i-- {
-					match.Handler = r.middlewares[i].Middleware(match.Handler)
-				}
-			}
-			return true
-		}
-	}
-
-	if match.MatchErr == ErrMethodMismatch {
-		if r.MethodNotAllowedHandler != nil {
-			match.Handler = r.MethodNotAllowedHandler
-			return true
-		}
-
-		return false
-	}
-
-	// Closest match for a router (includes sub-routers)
-	if r.NotFoundHandler != nil {
-		match.Handler = r.NotFoundHandler
-		match.MatchErr = ErrNotFound
-		return true
-	}
-
-	match.MatchErr = ErrNotFound
-	return false
-}
-
-// ServeHTTP dispatches the handler registered in the matched route.
-//
-// When there is a match, the route variables can be retrieved calling
-// mux.Vars(request).
-func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
-	if !r.skipClean {
-		path := req.URL.Path
-		if r.useEncodedPath {
-			path = req.URL.EscapedPath()
-		}
-		// Clean path to canonical form and redirect.
-		if p := cleanPath(path); p != path {
-
-			// Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query.
-			// This matches with fix in go 1.2 r.c. 4 for same problem.  Go Issue:
-			// http://code.google.com/p/go/issues/detail?id=5252
-			url := *req.URL
-			url.Path = p
-			p = url.String()
-
-			w.Header().Set("Location", p)
-			w.WriteHeader(http.StatusMovedPermanently)
-			return
-		}
-	}
-	var match RouteMatch
-	var handler http.Handler
-	if r.Match(req, &match) {
-		handler = match.Handler
-		req = requestWithVars(req, match.Vars)
-		req = requestWithRoute(req, match.Route)
-	}
-
-	if handler == nil && match.MatchErr == ErrMethodMismatch {
-		handler = methodNotAllowedHandler()
-	}
-
-	if handler == nil {
-		handler = http.NotFoundHandler()
-	}
-
-	handler.ServeHTTP(w, req)
-}
-
-// Get returns a route registered with the given name.
-func (r *Router) Get(name string) *Route {
-	return r.namedRoutes[name]
-}
-
-// GetRoute returns a route registered with the given name. This method
-// was renamed to Get() and remains here for backwards compatibility.
-func (r *Router) GetRoute(name string) *Route {
-	return r.namedRoutes[name]
-}
-
-// StrictSlash defines the trailing slash behavior for new routes. The initial
-// value is false.
-//
-// When true, if the route path is "/path/", accessing "/path" will perform a redirect
-// to the former and vice versa. In other words, your application will always
-// see the path as specified in the route.
-//
-// When false, if the route path is "/path", accessing "/path/" will not match
-// this route and vice versa.
-//
-// The re-direct is a HTTP 301 (Moved Permanently). Note that when this is set for
-// routes with a non-idempotent method (e.g. POST, PUT), the subsequent re-directed
-// request will be made as a GET by most clients. Use middleware or client settings
-// to modify this behaviour as needed.
-//
-// Special case: when a route sets a path prefix using the PathPrefix() method,
-// strict slash is ignored for that route because the redirect behavior can't
-// be determined from a prefix alone. However, any subrouters created from that
-// route inherit the original StrictSlash setting.
-func (r *Router) StrictSlash(value bool) *Router {
-	r.strictSlash = value
-	return r
-}
-
-// SkipClean defines the path cleaning behaviour for new routes. The initial
-// value is false. Users should be careful about which routes are not cleaned
-//
-// When true, if the route path is "/path//to", it will remain with the double
-// slash. This is helpful if you have a route like: /fetch/http://xkcd.com/534/
-//
-// When false, the path will be cleaned, so /fetch/http://xkcd.com/534/ will
-// become /fetch/http/xkcd.com/534
-func (r *Router) SkipClean(value bool) *Router {
-	r.skipClean = value
-	return r
-}
-
-// UseEncodedPath tells the router to match the encoded original path
-// to the routes.
-// For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to".
-//
-// If not called, the router will match the unencoded path to the routes.
-// For eg. "/path/foo%2Fbar/to" will match the path "/path/foo/bar/to"
-func (r *Router) UseEncodedPath() *Router {
-	r.useEncodedPath = true
-	return r
-}
-
-// ----------------------------------------------------------------------------
-// Route factories
-// ----------------------------------------------------------------------------
-
-// NewRoute registers an empty route.
-func (r *Router) NewRoute() *Route {
-	// initialize a route with a copy of the parent router's configuration
-	route := &Route{routeConf: copyRouteConf(r.routeConf), namedRoutes: r.namedRoutes}
-	r.routes = append(r.routes, route)
-	return route
-}
-
-// Name registers a new route with a name.
-// See Route.Name().
-func (r *Router) Name(name string) *Route {
-	return r.NewRoute().Name(name)
-}
-
-// Handle registers a new route with a matcher for the URL path.
-// See Route.Path() and Route.Handler().
-func (r *Router) Handle(path string, handler http.Handler) *Route {
-	return r.NewRoute().Path(path).Handler(handler)
-}
-
-// HandleFunc registers a new route with a matcher for the URL path.
-// See Route.Path() and Route.HandlerFunc().
-func (r *Router) HandleFunc(path string, f func(http.ResponseWriter,
-	*http.Request)) *Route {
-	return r.NewRoute().Path(path).HandlerFunc(f)
-}
-
-// Headers registers a new route with a matcher for request header values.
-// See Route.Headers().
-func (r *Router) Headers(pairs ...string) *Route {
-	return r.NewRoute().Headers(pairs...)
-}
-
-// Host registers a new route with a matcher for the URL host.
-// See Route.Host().
-func (r *Router) Host(tpl string) *Route {
-	return r.NewRoute().Host(tpl)
-}
-
-// MatcherFunc registers a new route with a custom matcher function.
-// See Route.MatcherFunc().
-func (r *Router) MatcherFunc(f MatcherFunc) *Route {
-	return r.NewRoute().MatcherFunc(f)
-}
-
-// Methods registers a new route with a matcher for HTTP methods.
-// See Route.Methods().
-func (r *Router) Methods(methods ...string) *Route {
-	return r.NewRoute().Methods(methods...)
-}
-
-// Path registers a new route with a matcher for the URL path.
-// See Route.Path().
-func (r *Router) Path(tpl string) *Route {
-	return r.NewRoute().Path(tpl)
-}
-
-// PathPrefix registers a new route with a matcher for the URL path prefix.
-// See Route.PathPrefix().
-func (r *Router) PathPrefix(tpl string) *Route {
-	return r.NewRoute().PathPrefix(tpl)
-}
-
-// Queries registers a new route with a matcher for URL query values.
-// See Route.Queries().
-func (r *Router) Queries(pairs ...string) *Route {
-	return r.NewRoute().Queries(pairs...)
-}
-
-// Schemes registers a new route with a matcher for URL schemes.
-// See Route.Schemes().
-func (r *Router) Schemes(schemes ...string) *Route {
-	return r.NewRoute().Schemes(schemes...)
-}
-
-// BuildVarsFunc registers a new route with a custom function for modifying
-// route variables before building a URL.
-func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route {
-	return r.NewRoute().BuildVarsFunc(f)
-}
-
-// Walk walks the router and all its sub-routers, calling walkFn for each route
-// in the tree. The routes are walked in the order they were added. Sub-routers
-// are explored depth-first.
-func (r *Router) Walk(walkFn WalkFunc) error {
-	return r.walk(walkFn, []*Route{})
-}
-
-// SkipRouter is used as a return value from WalkFuncs to indicate that the
-// router that walk is about to descend down to should be skipped.
-var SkipRouter = errors.New("skip this router")
-
-// WalkFunc is the type of the function called for each route visited by Walk.
-// At every invocation, it is given the current route, and the current router,
-// and a list of ancestor routes that lead to the current route.
-type WalkFunc func(route *Route, router *Router, ancestors []*Route) error
-
-func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error {
-	for _, t := range r.routes {
-		err := walkFn(t, r, ancestors)
-		if err == SkipRouter {
-			continue
-		}
-		if err != nil {
-			return err
-		}
-		for _, sr := range t.matchers {
-			if h, ok := sr.(*Router); ok {
-				ancestors = append(ancestors, t)
-				err := h.walk(walkFn, ancestors)
-				if err != nil {
-					return err
-				}
-				ancestors = ancestors[:len(ancestors)-1]
-			}
-		}
-		if h, ok := t.handler.(*Router); ok {
-			ancestors = append(ancestors, t)
-			err := h.walk(walkFn, ancestors)
-			if err != nil {
-				return err
-			}
-			ancestors = ancestors[:len(ancestors)-1]
-		}
-	}
-	return nil
-}
-
-// ----------------------------------------------------------------------------
-// Context
-// ----------------------------------------------------------------------------
-
-// RouteMatch stores information about a matched route.
-type RouteMatch struct {
-	Route   *Route
-	Handler http.Handler
-	Vars    map[string]string
-
-	// MatchErr is set to appropriate matching error
-	// It is set to ErrMethodMismatch if there is a mismatch in
-	// the request method and route method
-	MatchErr error
-}
-
-type contextKey int
-
-const (
-	varsKey contextKey = iota
-	routeKey
-)
-
-// Vars returns the route variables for the current request, if any.
-func Vars(r *http.Request) map[string]string {
-	if rv := r.Context().Value(varsKey); rv != nil {
-		return rv.(map[string]string)
-	}
-	return nil
-}
-
-// CurrentRoute returns the matched route for the current request, if any.
-// This only works when called inside the handler of the matched route
-// because the matched route is stored in the request context which is cleared
-// after the handler returns.
-func CurrentRoute(r *http.Request) *Route {
-	if rv := r.Context().Value(routeKey); rv != nil {
-		return rv.(*Route)
-	}
-	return nil
-}
-
-func requestWithVars(r *http.Request, vars map[string]string) *http.Request {
-	ctx := context.WithValue(r.Context(), varsKey, vars)
-	return r.WithContext(ctx)
-}
-
-func requestWithRoute(r *http.Request, route *Route) *http.Request {
-	ctx := context.WithValue(r.Context(), routeKey, route)
-	return r.WithContext(ctx)
-}
-
-// ----------------------------------------------------------------------------
-// Helpers
-// ----------------------------------------------------------------------------
-
-// cleanPath returns the canonical path for p, eliminating . and .. elements.
-// Borrowed from the net/http package.
-func cleanPath(p string) string {
-	if p == "" {
-		return "/"
-	}
-	if p[0] != '/' {
-		p = "/" + p
-	}
-	np := path.Clean(p)
-	// path.Clean removes trailing slash except for root;
-	// put the trailing slash back if necessary.
-	if p[len(p)-1] == '/' && np != "/" {
-		np += "/"
-	}
-
-	return np
-}
-
-// uniqueVars returns an error if two slices contain duplicated strings.
-func uniqueVars(s1, s2 []string) error {
-	for _, v1 := range s1 {
-		for _, v2 := range s2 {
-			if v1 == v2 {
-				return fmt.Errorf("mux: duplicated route variable %q", v2)
-			}
-		}
-	}
-	return nil
-}
-
-// checkPairs returns the count of strings passed in, and an error if
-// the count is not an even number.
-func checkPairs(pairs ...string) (int, error) {
-	length := len(pairs)
-	if length%2 != 0 {
-		return length, fmt.Errorf(
-			"mux: number of parameters must be multiple of 2, got %v", pairs)
-	}
-	return length, nil
-}
-
-// mapFromPairsToString converts variadic string parameters to a
-// string to string map.
-func mapFromPairsToString(pairs ...string) (map[string]string, error) {
-	length, err := checkPairs(pairs...)
-	if err != nil {
-		return nil, err
-	}
-	m := make(map[string]string, length/2)
-	for i := 0; i < length; i += 2 {
-		m[pairs[i]] = pairs[i+1]
-	}
-	return m, nil
-}
-
-// mapFromPairsToRegex converts variadic string parameters to a
-// string to regex map.
-func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) {
-	length, err := checkPairs(pairs...)
-	if err != nil {
-		return nil, err
-	}
-	m := make(map[string]*regexp.Regexp, length/2)
-	for i := 0; i < length; i += 2 {
-		regex, err := regexp.Compile(pairs[i+1])
-		if err != nil {
-			return nil, err
-		}
-		m[pairs[i]] = regex
-	}
-	return m, nil
-}
-
-// matchInArray returns true if the given string value is in the array.
-func matchInArray(arr []string, value string) bool {
-	for _, v := range arr {
-		if v == value {
-			return true
-		}
-	}
-	return false
-}
-
-// matchMapWithString returns true if the given key/value pairs exist in a given map.
-func matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool {
-	for k, v := range toCheck {
-		// Check if key exists.
-		if canonicalKey {
-			k = http.CanonicalHeaderKey(k)
-		}
-		if values := toMatch[k]; values == nil {
-			return false
-		} else if v != "" {
-			// If value was defined as an empty string we only check that the
-			// key exists. Otherwise we also check for equality.
-			valueExists := false
-			for _, value := range values {
-				if v == value {
-					valueExists = true
-					break
-				}
-			}
-			if !valueExists {
-				return false
-			}
-		}
-	}
-	return true
-}
-
-// matchMapWithRegex returns true if the given key/value pairs exist in a given map compiled against
-// the given regex
-func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]string, canonicalKey bool) bool {
-	for k, v := range toCheck {
-		// Check if key exists.
-		if canonicalKey {
-			k = http.CanonicalHeaderKey(k)
-		}
-		if values := toMatch[k]; values == nil {
-			return false
-		} else if v != nil {
-			// If value was defined as an empty string we only check that the
-			// key exists. Otherwise we also check for equality.
-			valueExists := false
-			for _, value := range values {
-				if v.MatchString(value) {
-					valueExists = true
-					break
-				}
-			}
-			if !valueExists {
-				return false
-			}
-		}
-	}
-	return true
-}
-
-// methodNotAllowed replies to the request with an HTTP status code 405.
-func methodNotAllowed(w http.ResponseWriter, r *http.Request) {
-	w.WriteHeader(http.StatusMethodNotAllowed)
-}
-
-// methodNotAllowedHandler returns a simple request handler
-// that replies to each request with a status code 405.
-func methodNotAllowedHandler() http.Handler { return http.HandlerFunc(methodNotAllowed) }
diff --git a/vendor/github.com/gorilla/mux/regexp.go b/vendor/github.com/gorilla/mux/regexp.go
deleted file mode 100644
index 5d05cfa..0000000
--- a/vendor/github.com/gorilla/mux/regexp.go
+++ /dev/null
@@ -1,388 +0,0 @@
-// Copyright 2012 The Gorilla Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package mux
-
-import (
-	"bytes"
-	"fmt"
-	"net/http"
-	"net/url"
-	"regexp"
-	"strconv"
-	"strings"
-)
-
-type routeRegexpOptions struct {
-	strictSlash    bool
-	useEncodedPath bool
-}
-
-type regexpType int
-
-const (
-	regexpTypePath regexpType = iota
-	regexpTypeHost
-	regexpTypePrefix
-	regexpTypeQuery
-)
-
-// newRouteRegexp parses a route template and returns a routeRegexp,
-// used to match a host, a path or a query string.
-//
-// It will extract named variables, assemble a regexp to be matched, create
-// a "reverse" template to build URLs and compile regexps to validate variable
-// values used in URL building.
-//
-// Previously we accepted only Python-like identifiers for variable
-// names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that
-// name and pattern can't be empty, and names can't contain a colon.
-func newRouteRegexp(tpl string, typ regexpType, options routeRegexpOptions) (*routeRegexp, error) {
-	// Check if it is well-formed.
-	idxs, errBraces := braceIndices(tpl)
-	if errBraces != nil {
-		return nil, errBraces
-	}
-	// Backup the original.
-	template := tpl
-	// Now let's parse it.
-	defaultPattern := "[^/]+"
-	if typ == regexpTypeQuery {
-		defaultPattern = ".*"
-	} else if typ == regexpTypeHost {
-		defaultPattern = "[^.]+"
-	}
-	// Only match strict slash if not matching
-	if typ != regexpTypePath {
-		options.strictSlash = false
-	}
-	// Set a flag for strictSlash.
-	endSlash := false
-	if options.strictSlash && strings.HasSuffix(tpl, "/") {
-		tpl = tpl[:len(tpl)-1]
-		endSlash = true
-	}
-	varsN := make([]string, len(idxs)/2)
-	varsR := make([]*regexp.Regexp, len(idxs)/2)
-	pattern := bytes.NewBufferString("")
-	pattern.WriteByte('^')
-	reverse := bytes.NewBufferString("")
-	var end int
-	var err error
-	for i := 0; i < len(idxs); i += 2 {
-		// Set all values we are interested in.
-		raw := tpl[end:idxs[i]]
-		end = idxs[i+1]
-		parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2)
-		name := parts[0]
-		patt := defaultPattern
-		if len(parts) == 2 {
-			patt = parts[1]
-		}
-		// Name or pattern can't be empty.
-		if name == "" || patt == "" {
-			return nil, fmt.Errorf("mux: missing name or pattern in %q",
-				tpl[idxs[i]:end])
-		}
-		// Build the regexp pattern.
-		fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt)
-
-		// Build the reverse template.
-		fmt.Fprintf(reverse, "%s%%s", raw)
-
-		// Append variable name and compiled pattern.
-		varsN[i/2] = name
-		varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
-		if err != nil {
-			return nil, err
-		}
-	}
-	// Add the remaining.
-	raw := tpl[end:]
-	pattern.WriteString(regexp.QuoteMeta(raw))
-	if options.strictSlash {
-		pattern.WriteString("[/]?")
-	}
-	if typ == regexpTypeQuery {
-		// Add the default pattern if the query value is empty
-		if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" {
-			pattern.WriteString(defaultPattern)
-		}
-	}
-	if typ != regexpTypePrefix {
-		pattern.WriteByte('$')
-	}
-
-	var wildcardHostPort bool
-	if typ == regexpTypeHost {
-		if !strings.Contains(pattern.String(), ":") {
-			wildcardHostPort = true
-		}
-	}
-	reverse.WriteString(raw)
-	if endSlash {
-		reverse.WriteByte('/')
-	}
-	// Compile full regexp.
-	reg, errCompile := regexp.Compile(pattern.String())
-	if errCompile != nil {
-		return nil, errCompile
-	}
-
-	// Check for capturing groups which used to work in older versions
-	if reg.NumSubexp() != len(idxs)/2 {
-		panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) +
-			"Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)")
-	}
-
-	// Done!
-	return &routeRegexp{
-		template:         template,
-		regexpType:       typ,
-		options:          options,
-		regexp:           reg,
-		reverse:          reverse.String(),
-		varsN:            varsN,
-		varsR:            varsR,
-		wildcardHostPort: wildcardHostPort,
-	}, nil
-}
-
-// routeRegexp stores a regexp to match a host or path and information to
-// collect and validate route variables.
-type routeRegexp struct {
-	// The unmodified template.
-	template string
-	// The type of match
-	regexpType regexpType
-	// Options for matching
-	options routeRegexpOptions
-	// Expanded regexp.
-	regexp *regexp.Regexp
-	// Reverse template.
-	reverse string
-	// Variable names.
-	varsN []string
-	// Variable regexps (validators).
-	varsR []*regexp.Regexp
-	// Wildcard host-port (no strict port match in hostname)
-	wildcardHostPort bool
-}
-
-// Match matches the regexp against the URL host or path.
-func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
-	if r.regexpType == regexpTypeHost {
-		host := getHost(req)
-		if r.wildcardHostPort {
-			// Don't be strict on the port match
-			if i := strings.Index(host, ":"); i != -1 {
-				host = host[:i]
-			}
-		}
-		return r.regexp.MatchString(host)
-	}
-
-	if r.regexpType == regexpTypeQuery {
-		return r.matchQueryString(req)
-	}
-	path := req.URL.Path
-	if r.options.useEncodedPath {
-		path = req.URL.EscapedPath()
-	}
-	return r.regexp.MatchString(path)
-}
-
-// url builds a URL part using the given values.
-func (r *routeRegexp) url(values map[string]string) (string, error) {
-	urlValues := make([]interface{}, len(r.varsN))
-	for k, v := range r.varsN {
-		value, ok := values[v]
-		if !ok {
-			return "", fmt.Errorf("mux: missing route variable %q", v)
-		}
-		if r.regexpType == regexpTypeQuery {
-			value = url.QueryEscape(value)
-		}
-		urlValues[k] = value
-	}
-	rv := fmt.Sprintf(r.reverse, urlValues...)
-	if !r.regexp.MatchString(rv) {
-		// The URL is checked against the full regexp, instead of checking
-		// individual variables. This is faster but to provide a good error
-		// message, we check individual regexps if the URL doesn't match.
-		for k, v := range r.varsN {
-			if !r.varsR[k].MatchString(values[v]) {
-				return "", fmt.Errorf(
-					"mux: variable %q doesn't match, expected %q", values[v],
-					r.varsR[k].String())
-			}
-		}
-	}
-	return rv, nil
-}
-
-// getURLQuery returns a single query parameter from a request URL.
-// For a URL with foo=bar&baz=ding, we return only the relevant key
-// value pair for the routeRegexp.
-func (r *routeRegexp) getURLQuery(req *http.Request) string {
-	if r.regexpType != regexpTypeQuery {
-		return ""
-	}
-	templateKey := strings.SplitN(r.template, "=", 2)[0]
-	val, ok := findFirstQueryKey(req.URL.RawQuery, templateKey)
-	if ok {
-		return templateKey + "=" + val
-	}
-	return ""
-}
-
-// findFirstQueryKey returns the same result as (*url.URL).Query()[key][0].
-// If key was not found, empty string and false is returned.
-func findFirstQueryKey(rawQuery, key string) (value string, ok bool) {
-	query := []byte(rawQuery)
-	for len(query) > 0 {
-		foundKey := query
-		if i := bytes.IndexAny(foundKey, "&;"); i >= 0 {
-			foundKey, query = foundKey[:i], foundKey[i+1:]
-		} else {
-			query = query[:0]
-		}
-		if len(foundKey) == 0 {
-			continue
-		}
-		var value []byte
-		if i := bytes.IndexByte(foundKey, '='); i >= 0 {
-			foundKey, value = foundKey[:i], foundKey[i+1:]
-		}
-		if len(foundKey) < len(key) {
-			// Cannot possibly be key.
-			continue
-		}
-		keyString, err := url.QueryUnescape(string(foundKey))
-		if err != nil {
-			continue
-		}
-		if keyString != key {
-			continue
-		}
-		valueString, err := url.QueryUnescape(string(value))
-		if err != nil {
-			continue
-		}
-		return valueString, true
-	}
-	return "", false
-}
-
-func (r *routeRegexp) matchQueryString(req *http.Request) bool {
-	return r.regexp.MatchString(r.getURLQuery(req))
-}
-
-// braceIndices returns the first level curly brace indices from a string.
-// It returns an error in case of unbalanced braces.
-func braceIndices(s string) ([]int, error) {
-	var level, idx int
-	var idxs []int
-	for i := 0; i < len(s); i++ {
-		switch s[i] {
-		case '{':
-			if level++; level == 1 {
-				idx = i
-			}
-		case '}':
-			if level--; level == 0 {
-				idxs = append(idxs, idx, i+1)
-			} else if level < 0 {
-				return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
-			}
-		}
-	}
-	if level != 0 {
-		return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
-	}
-	return idxs, nil
-}
-
-// varGroupName builds a capturing group name for the indexed variable.
-func varGroupName(idx int) string {
-	return "v" + strconv.Itoa(idx)
-}
-
-// ----------------------------------------------------------------------------
-// routeRegexpGroup
-// ----------------------------------------------------------------------------
-
-// routeRegexpGroup groups the route matchers that carry variables.
-type routeRegexpGroup struct {
-	host    *routeRegexp
-	path    *routeRegexp
-	queries []*routeRegexp
-}
-
-// setMatch extracts the variables from the URL once a route matches.
-func (v routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) {
-	// Store host variables.
-	if v.host != nil {
-		host := getHost(req)
-		if v.host.wildcardHostPort {
-			// Don't be strict on the port match
-			if i := strings.Index(host, ":"); i != -1 {
-				host = host[:i]
-			}
-		}
-		matches := v.host.regexp.FindStringSubmatchIndex(host)
-		if len(matches) > 0 {
-			extractVars(host, matches, v.host.varsN, m.Vars)
-		}
-	}
-	path := req.URL.Path
-	if r.useEncodedPath {
-		path = req.URL.EscapedPath()
-	}
-	// Store path variables.
-	if v.path != nil {
-		matches := v.path.regexp.FindStringSubmatchIndex(path)
-		if len(matches) > 0 {
-			extractVars(path, matches, v.path.varsN, m.Vars)
-			// Check if we should redirect.
-			if v.path.options.strictSlash {
-				p1 := strings.HasSuffix(path, "/")
-				p2 := strings.HasSuffix(v.path.template, "/")
-				if p1 != p2 {
-					u, _ := url.Parse(req.URL.String())
-					if p1 {
-						u.Path = u.Path[:len(u.Path)-1]
-					} else {
-						u.Path += "/"
-					}
-					m.Handler = http.RedirectHandler(u.String(), http.StatusMovedPermanently)
-				}
-			}
-		}
-	}
-	// Store query string variables.
-	for _, q := range v.queries {
-		queryURL := q.getURLQuery(req)
-		matches := q.regexp.FindStringSubmatchIndex(queryURL)
-		if len(matches) > 0 {
-			extractVars(queryURL, matches, q.varsN, m.Vars)
-		}
-	}
-}
-
-// getHost tries its best to return the request host.
-// According to section 14.23 of RFC 2616 the Host header
-// can include the port number if the default value of 80 is not used.
-func getHost(r *http.Request) string {
-	if r.URL.IsAbs() {
-		return r.URL.Host
-	}
-	return r.Host
-}
-
-func extractVars(input string, matches []int, names []string, output map[string]string) {
-	for i, name := range names {
-		output[name] = input[matches[2*i+2]:matches[2*i+3]]
-	}
-}
diff --git a/vendor/github.com/gorilla/mux/route.go b/vendor/github.com/gorilla/mux/route.go
deleted file mode 100644
index e8f11df..0000000
--- a/vendor/github.com/gorilla/mux/route.go
+++ /dev/null
@@ -1,765 +0,0 @@
-// Copyright 2012 The Gorilla Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package mux
-
-import (
-	"errors"
-	"fmt"
-	"net/http"
-	"net/url"
-	"regexp"
-	"strings"
-)
-
-// Route stores information to match a request and build URLs.
-type Route struct {
-	// Request handler for the route.
-	handler http.Handler
-	// If true, this route never matches: it is only used to build URLs.
-	buildOnly bool
-	// The name used to build URLs.
-	name string
-	// Error resulted from building a route.
-	err error
-
-	// "global" reference to all named routes
-	namedRoutes map[string]*Route
-
-	// config possibly passed in from `Router`
-	routeConf
-}
-
-// SkipClean reports whether path cleaning is enabled for this route via
-// Router.SkipClean.
-func (r *Route) SkipClean() bool {
-	return r.skipClean
-}
-
-// Match matches the route against the request.
-func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
-	if r.buildOnly || r.err != nil {
-		return false
-	}
-
-	var matchErr error
-
-	// Match everything.
-	for _, m := range r.matchers {
-		if matched := m.Match(req, match); !matched {
-			if _, ok := m.(methodMatcher); ok {
-				matchErr = ErrMethodMismatch
-				continue
-			}
-
-			// Ignore ErrNotFound errors. These errors arise from match call
-			// to Subrouters.
-			//
-			// This prevents subsequent matching subrouters from failing to
-			// run middleware. If not ignored, the middleware would see a
-			// non-nil MatchErr and be skipped, even when there was a
-			// matching route.
-			if match.MatchErr == ErrNotFound {
-				match.MatchErr = nil
-			}
-
-			matchErr = nil // nolint:ineffassign
-			return false
-		} else {
-			// Multiple routes may share the same path but use different HTTP methods. For instance:
-			// Route 1: POST "/users/{id}".
-			// Route 2: GET "/users/{id}", parameters: "id": "[0-9]+".
-			//
-			// The router must handle these cases correctly. For a GET request to "/users/abc" with "id" as "-2",
-			// The router should return a "Not Found" error as no route fully matches this request.
-			if match.MatchErr == ErrMethodMismatch {
-				match.MatchErr = nil
-			}
-		}
-	}
-
-	if matchErr != nil {
-		match.MatchErr = matchErr
-		return false
-	}
-
-	if match.MatchErr == ErrMethodMismatch && r.handler != nil {
-		// We found a route which matches request method, clear MatchErr
-		match.MatchErr = nil
-		// Then override the mis-matched handler
-		match.Handler = r.handler
-	}
-
-	// Yay, we have a match. Let's collect some info about it.
-	if match.Route == nil {
-		match.Route = r
-	}
-	if match.Handler == nil {
-		match.Handler = r.handler
-	}
-	if match.Vars == nil {
-		match.Vars = make(map[string]string)
-	}
-
-	// Set variables.
-	r.regexp.setMatch(req, match, r)
-	return true
-}
-
-// ----------------------------------------------------------------------------
-// Route attributes
-// ----------------------------------------------------------------------------
-
-// GetError returns an error resulted from building the route, if any.
-func (r *Route) GetError() error {
-	return r.err
-}
-
-// BuildOnly sets the route to never match: it is only used to build URLs.
-func (r *Route) BuildOnly() *Route {
-	r.buildOnly = true
-	return r
-}
-
-// Handler --------------------------------------------------------------------
-
-// Handler sets a handler for the route.
-func (r *Route) Handler(handler http.Handler) *Route {
-	if r.err == nil {
-		r.handler = handler
-	}
-	return r
-}
-
-// HandlerFunc sets a handler function for the route.
-func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route {
-	return r.Handler(http.HandlerFunc(f))
-}
-
-// GetHandler returns the handler for the route, if any.
-func (r *Route) GetHandler() http.Handler {
-	return r.handler
-}
-
-// Name -----------------------------------------------------------------------
-
-// Name sets the name for the route, used to build URLs.
-// It is an error to call Name more than once on a route.
-func (r *Route) Name(name string) *Route {
-	if r.name != "" {
-		r.err = fmt.Errorf("mux: route already has name %q, can't set %q",
-			r.name, name)
-	}
-	if r.err == nil {
-		r.name = name
-		r.namedRoutes[name] = r
-	}
-	return r
-}
-
-// GetName returns the name for the route, if any.
-func (r *Route) GetName() string {
-	return r.name
-}
-
-// ----------------------------------------------------------------------------
-// Matchers
-// ----------------------------------------------------------------------------
-
-// matcher types try to match a request.
-type matcher interface {
-	Match(*http.Request, *RouteMatch) bool
-}
-
-// addMatcher adds a matcher to the route.
-func (r *Route) addMatcher(m matcher) *Route {
-	if r.err == nil {
-		r.matchers = append(r.matchers, m)
-	}
-	return r
-}
-
-// addRegexpMatcher adds a host or path matcher and builder to a route.
-func (r *Route) addRegexpMatcher(tpl string, typ regexpType) error {
-	if r.err != nil {
-		return r.err
-	}
-	if typ == regexpTypePath || typ == regexpTypePrefix {
-		if len(tpl) > 0 && tpl[0] != '/' {
-			return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
-		}
-		if r.regexp.path != nil {
-			tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
-		}
-	}
-	rr, err := newRouteRegexp(tpl, typ, routeRegexpOptions{
-		strictSlash:    r.strictSlash,
-		useEncodedPath: r.useEncodedPath,
-	})
-	if err != nil {
-		return err
-	}
-	for _, q := range r.regexp.queries {
-		if err = uniqueVars(rr.varsN, q.varsN); err != nil {
-			return err
-		}
-	}
-	if typ == regexpTypeHost {
-		if r.regexp.path != nil {
-			if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil {
-				return err
-			}
-		}
-		r.regexp.host = rr
-	} else {
-		if r.regexp.host != nil {
-			if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil {
-				return err
-			}
-		}
-		if typ == regexpTypeQuery {
-			r.regexp.queries = append(r.regexp.queries, rr)
-		} else {
-			r.regexp.path = rr
-		}
-	}
-	r.addMatcher(rr)
-	return nil
-}
-
-// Headers --------------------------------------------------------------------
-
-// headerMatcher matches the request against header values.
-type headerMatcher map[string]string
-
-func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
-	return matchMapWithString(m, r.Header, true)
-}
-
-// Headers adds a matcher for request header values.
-// It accepts a sequence of key/value pairs to be matched. For example:
-//
-//	r := mux.NewRouter().NewRoute()
-//	r.Headers("Content-Type", "application/json",
-//	          "X-Requested-With", "XMLHttpRequest")
-//
-// The above route will only match if both request header values match.
-// If the value is an empty string, it will match any value if the key is set.
-func (r *Route) Headers(pairs ...string) *Route {
-	if r.err == nil {
-		var headers map[string]string
-		headers, r.err = mapFromPairsToString(pairs...)
-		return r.addMatcher(headerMatcher(headers))
-	}
-	return r
-}
-
-// headerRegexMatcher matches the request against the route given a regex for the header
-type headerRegexMatcher map[string]*regexp.Regexp
-
-func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool {
-	return matchMapWithRegex(m, r.Header, true)
-}
-
-// HeadersRegexp accepts a sequence of key/value pairs, where the value has regex
-// support. For example:
-//
-//	r := mux.NewRouter().NewRoute()
-//	r.HeadersRegexp("Content-Type", "application/(text|json)",
-//	          "X-Requested-With", "XMLHttpRequest")
-//
-// The above route will only match if both the request header matches both regular expressions.
-// If the value is an empty string, it will match any value if the key is set.
-// Use the start and end of string anchors (^ and $) to match an exact value.
-func (r *Route) HeadersRegexp(pairs ...string) *Route {
-	if r.err == nil {
-		var headers map[string]*regexp.Regexp
-		headers, r.err = mapFromPairsToRegex(pairs...)
-		return r.addMatcher(headerRegexMatcher(headers))
-	}
-	return r
-}
-
-// Host -----------------------------------------------------------------------
-
-// Host adds a matcher for the URL host.
-// It accepts a template with zero or more URL variables enclosed by {}.
-// Variables can define an optional regexp pattern to be matched:
-//
-// - {name} matches anything until the next dot.
-//
-// - {name:pattern} matches the given regexp pattern.
-//
-// For example:
-//
-//	r := mux.NewRouter().NewRoute()
-//	r.Host("www.example.com")
-//	r.Host("{subdomain}.domain.com")
-//	r.Host("{subdomain:[a-z]+}.domain.com")
-//
-// Variable names must be unique in a given route. They can be retrieved
-// calling mux.Vars(request).
-func (r *Route) Host(tpl string) *Route {
-	r.err = r.addRegexpMatcher(tpl, regexpTypeHost)
-	return r
-}
-
-// MatcherFunc ----------------------------------------------------------------
-
-// MatcherFunc is the function signature used by custom matchers.
-type MatcherFunc func(*http.Request, *RouteMatch) bool
-
-// Match returns the match for a given request.
-func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool {
-	return m(r, match)
-}
-
-// MatcherFunc adds a custom function to be used as request matcher.
-func (r *Route) MatcherFunc(f MatcherFunc) *Route {
-	return r.addMatcher(f)
-}
-
-// Methods --------------------------------------------------------------------
-
-// methodMatcher matches the request against HTTP methods.
-type methodMatcher []string
-
-func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool {
-	return matchInArray(m, r.Method)
-}
-
-// Methods adds a matcher for HTTP methods.
-// It accepts a sequence of one or more methods to be matched, e.g.:
-// "GET", "POST", "PUT".
-func (r *Route) Methods(methods ...string) *Route {
-	for k, v := range methods {
-		methods[k] = strings.ToUpper(v)
-	}
-	return r.addMatcher(methodMatcher(methods))
-}
-
-// Path -----------------------------------------------------------------------
-
-// Path adds a matcher for the URL path.
-// It accepts a template with zero or more URL variables enclosed by {}. The
-// template must start with a "/".
-// Variables can define an optional regexp pattern to be matched:
-//
-// - {name} matches anything until the next slash.
-//
-// - {name:pattern} matches the given regexp pattern.
-//
-// For example:
-//
-//	r := mux.NewRouter().NewRoute()
-//	r.Path("/products/").Handler(ProductsHandler)
-//	r.Path("/products/{key}").Handler(ProductsHandler)
-//	r.Path("/articles/{category}/{id:[0-9]+}").
-//	  Handler(ArticleHandler)
-//
-// Variable names must be unique in a given route. They can be retrieved
-// calling mux.Vars(request).
-func (r *Route) Path(tpl string) *Route {
-	r.err = r.addRegexpMatcher(tpl, regexpTypePath)
-	return r
-}
-
-// PathPrefix -----------------------------------------------------------------
-
-// PathPrefix adds a matcher for the URL path prefix. This matches if the given
-// template is a prefix of the full URL path. See Route.Path() for details on
-// the tpl argument.
-//
-// Note that it does not treat slashes specially ("/foobar/" will be matched by
-// the prefix "/foo") so you may want to use a trailing slash here.
-//
-// Also note that the setting of Router.StrictSlash() has no effect on routes
-// with a PathPrefix matcher.
-func (r *Route) PathPrefix(tpl string) *Route {
-	r.err = r.addRegexpMatcher(tpl, regexpTypePrefix)
-	return r
-}
-
-// Query ----------------------------------------------------------------------
-
-// Queries adds a matcher for URL query values.
-// It accepts a sequence of key/value pairs. Values may define variables.
-// For example:
-//
-//	r := mux.NewRouter().NewRoute()
-//	r.Queries("foo", "bar", "id", "{id:[0-9]+}")
-//
-// The above route will only match if the URL contains the defined queries
-// values, e.g.: ?foo=bar&id=42.
-//
-// If the value is an empty string, it will match any value if the key is set.
-//
-// Variables can define an optional regexp pattern to be matched:
-//
-// - {name} matches anything until the next slash.
-//
-// - {name:pattern} matches the given regexp pattern.
-func (r *Route) Queries(pairs ...string) *Route {
-	length := len(pairs)
-	if length%2 != 0 {
-		r.err = fmt.Errorf(
-			"mux: number of parameters must be multiple of 2, got %v", pairs)
-		return nil
-	}
-	for i := 0; i < length; i += 2 {
-		if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], regexpTypeQuery); r.err != nil {
-			return r
-		}
-	}
-
-	return r
-}
-
-// Schemes --------------------------------------------------------------------
-
-// schemeMatcher matches the request against URL schemes.
-type schemeMatcher []string
-
-func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool {
-	scheme := r.URL.Scheme
-	// https://golang.org/pkg/net/http/#Request
-	// "For [most] server requests, fields other than Path and RawQuery will be
-	// empty."
-	// Since we're an http muxer, the scheme is either going to be http or https
-	// though, so we can just set it based on the tls termination state.
-	if scheme == "" {
-		if r.TLS == nil {
-			scheme = "http"
-		} else {
-			scheme = "https"
-		}
-	}
-	return matchInArray(m, scheme)
-}
-
-// Schemes adds a matcher for URL schemes.
-// It accepts a sequence of schemes to be matched, e.g.: "http", "https".
-// If the request's URL has a scheme set, it will be matched against.
-// Generally, the URL scheme will only be set if a previous handler set it,
-// such as the ProxyHeaders handler from gorilla/handlers.
-// If unset, the scheme will be determined based on the request's TLS
-// termination state.
-// The first argument to Schemes will be used when constructing a route URL.
-func (r *Route) Schemes(schemes ...string) *Route {
-	for k, v := range schemes {
-		schemes[k] = strings.ToLower(v)
-	}
-	if len(schemes) > 0 {
-		r.buildScheme = schemes[0]
-	}
-	return r.addMatcher(schemeMatcher(schemes))
-}
-
-// BuildVarsFunc --------------------------------------------------------------
-
-// BuildVarsFunc is the function signature used by custom build variable
-// functions (which can modify route variables before a route's URL is built).
-type BuildVarsFunc func(map[string]string) map[string]string
-
-// BuildVarsFunc adds a custom function to be used to modify build variables
-// before a route's URL is built.
-func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route {
-	if r.buildVarsFunc != nil {
-		// compose the old and new functions
-		old := r.buildVarsFunc
-		r.buildVarsFunc = func(m map[string]string) map[string]string {
-			return f(old(m))
-		}
-	} else {
-		r.buildVarsFunc = f
-	}
-	return r
-}
-
-// Subrouter ------------------------------------------------------------------
-
-// Subrouter creates a subrouter for the route.
-//
-// It will test the inner routes only if the parent route matched. For example:
-//
-//	r := mux.NewRouter().NewRoute()
-//	s := r.Host("www.example.com").Subrouter()
-//	s.HandleFunc("/products/", ProductsHandler)
-//	s.HandleFunc("/products/{key}", ProductHandler)
-//	s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
-//
-// Here, the routes registered in the subrouter won't be tested if the host
-// doesn't match.
-func (r *Route) Subrouter() *Router {
-	// initialize a subrouter with a copy of the parent route's configuration
-	router := &Router{routeConf: copyRouteConf(r.routeConf), namedRoutes: r.namedRoutes}
-	r.addMatcher(router)
-	return router
-}
-
-// ----------------------------------------------------------------------------
-// URL building
-// ----------------------------------------------------------------------------
-
-// URL builds a URL for the route.
-//
-// It accepts a sequence of key/value pairs for the route variables. For
-// example, given this route:
-//
-//	r := mux.NewRouter()
-//	r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
-//	  Name("article")
-//
-// ...a URL for it can be built using:
-//
-//	url, err := r.Get("article").URL("category", "technology", "id", "42")
-//
-// ...which will return an url.URL with the following path:
-//
-//	"/articles/technology/42"
-//
-// This also works for host variables:
-//
-//	r := mux.NewRouter()
-//	r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
-//	  Host("{subdomain}.domain.com").
-//	  Name("article")
-//
-//	// url.String() will be "http://news.domain.com/articles/technology/42"
-//	url, err := r.Get("article").URL("subdomain", "news",
-//	                                 "category", "technology",
-//	                                 "id", "42")
-//
-// The scheme of the resulting url will be the first argument that was passed to Schemes:
-//
-//	// url.String() will be "https://example.com"
-//	r := mux.NewRouter().NewRoute()
-//	url, err := r.Host("example.com")
-//	             .Schemes("https", "http").URL()
-//
-// All variables defined in the route are required, and their values must
-// conform to the corresponding patterns.
-func (r *Route) URL(pairs ...string) (*url.URL, error) {
-	if r.err != nil {
-		return nil, r.err
-	}
-	values, err := r.prepareVars(pairs...)
-	if err != nil {
-		return nil, err
-	}
-	var scheme, host, path string
-	queries := make([]string, 0, len(r.regexp.queries))
-	if r.regexp.host != nil {
-		if host, err = r.regexp.host.url(values); err != nil {
-			return nil, err
-		}
-		scheme = "http"
-		if r.buildScheme != "" {
-			scheme = r.buildScheme
-		}
-	}
-	if r.regexp.path != nil {
-		if path, err = r.regexp.path.url(values); err != nil {
-			return nil, err
-		}
-	}
-	for _, q := range r.regexp.queries {
-		var query string
-		if query, err = q.url(values); err != nil {
-			return nil, err
-		}
-		queries = append(queries, query)
-	}
-	return &url.URL{
-		Scheme:   scheme,
-		Host:     host,
-		Path:     path,
-		RawQuery: strings.Join(queries, "&"),
-	}, nil
-}
-
-// URLHost builds the host part of the URL for a route. See Route.URL().
-//
-// The route must have a host defined.
-func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
-	if r.err != nil {
-		return nil, r.err
-	}
-	if r.regexp.host == nil {
-		return nil, errors.New("mux: route doesn't have a host")
-	}
-	values, err := r.prepareVars(pairs...)
-	if err != nil {
-		return nil, err
-	}
-	host, err := r.regexp.host.url(values)
-	if err != nil {
-		return nil, err
-	}
-	u := &url.URL{
-		Scheme: "http",
-		Host:   host,
-	}
-	if r.buildScheme != "" {
-		u.Scheme = r.buildScheme
-	}
-	return u, nil
-}
-
-// URLPath builds the path part of the URL for a route. See Route.URL().
-//
-// The route must have a path defined.
-func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
-	if r.err != nil {
-		return nil, r.err
-	}
-	if r.regexp.path == nil {
-		return nil, errors.New("mux: route doesn't have a path")
-	}
-	values, err := r.prepareVars(pairs...)
-	if err != nil {
-		return nil, err
-	}
-	path, err := r.regexp.path.url(values)
-	if err != nil {
-		return nil, err
-	}
-	return &url.URL{
-		Path: path,
-	}, nil
-}
-
-// GetPathTemplate returns the template used to build the
-// route match.
-// This is useful for building simple REST API documentation and for instrumentation
-// against third-party services.
-// An error will be returned if the route does not define a path.
-func (r *Route) GetPathTemplate() (string, error) {
-	if r.err != nil {
-		return "", r.err
-	}
-	if r.regexp.path == nil {
-		return "", errors.New("mux: route doesn't have a path")
-	}
-	return r.regexp.path.template, nil
-}
-
-// GetPathRegexp returns the expanded regular expression used to match route path.
-// This is useful for building simple REST API documentation and for instrumentation
-// against third-party services.
-// An error will be returned if the route does not define a path.
-func (r *Route) GetPathRegexp() (string, error) {
-	if r.err != nil {
-		return "", r.err
-	}
-	if r.regexp.path == nil {
-		return "", errors.New("mux: route does not have a path")
-	}
-	return r.regexp.path.regexp.String(), nil
-}
-
-// GetQueriesRegexp returns the expanded regular expressions used to match the
-// route queries.
-// This is useful for building simple REST API documentation and for instrumentation
-// against third-party services.
-// An error will be returned if the route does not have queries.
-func (r *Route) GetQueriesRegexp() ([]string, error) {
-	if r.err != nil {
-		return nil, r.err
-	}
-	if r.regexp.queries == nil {
-		return nil, errors.New("mux: route doesn't have queries")
-	}
-	queries := make([]string, 0, len(r.regexp.queries))
-	for _, query := range r.regexp.queries {
-		queries = append(queries, query.regexp.String())
-	}
-	return queries, nil
-}
-
-// GetQueriesTemplates returns the templates used to build the
-// query matching.
-// This is useful for building simple REST API documentation and for instrumentation
-// against third-party services.
-// An error will be returned if the route does not define queries.
-func (r *Route) GetQueriesTemplates() ([]string, error) {
-	if r.err != nil {
-		return nil, r.err
-	}
-	if r.regexp.queries == nil {
-		return nil, errors.New("mux: route doesn't have queries")
-	}
-	queries := make([]string, 0, len(r.regexp.queries))
-	for _, query := range r.regexp.queries {
-		queries = append(queries, query.template)
-	}
-	return queries, nil
-}
-
-// GetMethods returns the methods the route matches against
-// This is useful for building simple REST API documentation and for instrumentation
-// against third-party services.
-// An error will be returned if route does not have methods.
-func (r *Route) GetMethods() ([]string, error) {
-	if r.err != nil {
-		return nil, r.err
-	}
-	for _, m := range r.matchers {
-		if methods, ok := m.(methodMatcher); ok {
-			return []string(methods), nil
-		}
-	}
-	return nil, errors.New("mux: route doesn't have methods")
-}
-
-// GetHostTemplate returns the template used to build the
-// route match.
-// This is useful for building simple REST API documentation and for instrumentation
-// against third-party services.
-// An error will be returned if the route does not define a host.
-func (r *Route) GetHostTemplate() (string, error) {
-	if r.err != nil {
-		return "", r.err
-	}
-	if r.regexp.host == nil {
-		return "", errors.New("mux: route doesn't have a host")
-	}
-	return r.regexp.host.template, nil
-}
-
-// GetVarNames returns the names of all variables added by regexp matchers
-// These can be used to know which route variables should be passed into r.URL()
-func (r *Route) GetVarNames() ([]string, error) {
-	if r.err != nil {
-		return nil, r.err
-	}
-	var varNames []string
-	if r.regexp.host != nil {
-		varNames = append(varNames, r.regexp.host.varsN...)
-	}
-	if r.regexp.path != nil {
-		varNames = append(varNames, r.regexp.path.varsN...)
-	}
-	for _, regx := range r.regexp.queries {
-		varNames = append(varNames, regx.varsN...)
-	}
-	return varNames, nil
-}
-
-// prepareVars converts the route variable pairs into a map. If the route has a
-// BuildVarsFunc, it is invoked.
-func (r *Route) prepareVars(pairs ...string) (map[string]string, error) {
-	m, err := mapFromPairsToString(pairs...)
-	if err != nil {
-		return nil, err
-	}
-	return r.buildVars(m), nil
-}
-
-func (r *Route) buildVars(m map[string]string) map[string]string {
-	if r.buildVarsFunc != nil {
-		m = r.buildVarsFunc(m)
-	}
-	return m
-}
diff --git a/vendor/github.com/gorilla/mux/test_helpers.go b/vendor/github.com/gorilla/mux/test_helpers.go
deleted file mode 100644
index 5f5c496..0000000
--- a/vendor/github.com/gorilla/mux/test_helpers.go
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2012 The Gorilla Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package mux
-
-import "net/http"
-
-// SetURLVars sets the URL variables for the given request, to be accessed via
-// mux.Vars for testing route behaviour. Arguments are not modified, a shallow
-// copy is returned.
-//
-// This API should only be used for testing purposes; it provides a way to
-// inject variables into the request context. Alternatively, URL variables
-// can be set by making a route that captures the required variables,
-// starting a server and sending the request to that server.
-func SetURLVars(r *http.Request, val map[string]string) *http.Request {
-	return requestWithVars(r, val)
-}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index c824999..9999c71 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -16,7 +16,6 @@ github.com/golang/snappy
 github.com/google/uuid
 # github.com/gorilla/mux v1.8.1
 ## explicit; go 1.20
-github.com/gorilla/mux
 # github.com/jackc/pgpassfile v1.0.0
 ## explicit; go 1.12
 github.com/jackc/pgpassfile
-- 
GitLab