├── .gitignore ├── CQRSGui ├── Content │ └── Site.css ├── main.go └── pages │ ├── add.html │ ├── base.html │ ├── changename.html │ ├── checkin.html │ ├── deactivate.html │ ├── details.html │ ├── index.html │ ├── remove.html │ └── view.html ├── LICENSE ├── README.md ├── SimpleCQRS ├── CommandHandlers.go ├── Domain.go ├── EventStore.go ├── Events.go ├── FakeBus.go ├── ReadModel.go ├── commands.go └── guid.go ├── go.mod └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | #ignore thumbnails created by windows 3 | Thumbs.db 4 | #Ignore files build by Visual Studio 5 | *.obj 6 | *.exe 7 | *.pdb 8 | *.user 9 | *.aps 10 | *.pch 11 | *.vspscc 12 | *_i.c 13 | *_p.c 14 | *.ncb 15 | *.suo 16 | *.tlb 17 | *.tlh 18 | *.bak 19 | *.cache 20 | *.ilk 21 | *.log 22 | [Bb]in 23 | [Dd]ebug*/ 24 | *.lib 25 | *.sbr 26 | obj/ 27 | [Rr]elease*/ 28 | _ReSharper*/ 29 | [Tt]est[Rr]esult* 30 | packages/ 31 | .vs/ 32 | -------------------------------------------------------------------------------- /CQRSGui/Content/Site.css: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------- 2 | The base color for this template is #5c87b2. If you'd like 3 | to use a different color start by replacing all instances of 4 | #5c87b2 with your new color. 5 | ----------------------------------------------------------*/ 6 | body 7 | { 8 | background-color: #5c87b2; 9 | font-size: .75em; 10 | font-family: Verdana, Helvetica, Sans-Serif; 11 | margin: 0; 12 | padding: 0; 13 | color: #696969; 14 | } 15 | 16 | a:link 17 | { 18 | color: #034af3; 19 | text-decoration: underline; 20 | } 21 | a:visited 22 | { 23 | color: #505abc; 24 | } 25 | a:hover 26 | { 27 | color: #1d60ff; 28 | text-decoration: none; 29 | } 30 | a:active 31 | { 32 | color: #12eb87; 33 | } 34 | 35 | p, ul 36 | { 37 | margin-bottom: 20px; 38 | line-height: 1.6em; 39 | } 40 | 41 | /* HEADINGS 42 | ----------------------------------------------------------*/ 43 | h1, h2, h3, h4, h5, h6 44 | { 45 | font-size: 1.5em; 46 | color: #000; 47 | font-family: Arial, Helvetica, sans-serif; 48 | } 49 | 50 | h1 51 | { 52 | font-size: 2em; 53 | padding-bottom: 0; 54 | margin-bottom: 0; 55 | } 56 | h2 57 | { 58 | padding: 0 0 10px 0; 59 | } 60 | h3 61 | { 62 | font-size: 1.2em; 63 | } 64 | h4 65 | { 66 | font-size: 1.1em; 67 | } 68 | h5, h6 69 | { 70 | font-size: 1em; 71 | } 72 | 73 | /* this rule styles

tags that are the 74 | first child of the left and right table columns */ 75 | .rightColumn > h1, .rightColumn > h2, .leftColumn > h1, .leftColumn > h2 76 | { 77 | margin-top: 0; 78 | } 79 | 80 | /* PRIMARY LAYOUT ELEMENTS 81 | ----------------------------------------------------------*/ 82 | 83 | /* you can specify a greater or lesser percentage for the 84 | page width. Or, you can specify an exact pixel width. */ 85 | .page 86 | { 87 | width: 90%; 88 | margin-left: auto; 89 | margin-right: auto; 90 | } 91 | 92 | #header 93 | { 94 | position: relative; 95 | margin-bottom: 0px; 96 | color: #000; 97 | padding: 0; 98 | } 99 | 100 | #header h1 101 | { 102 | font-weight: bold; 103 | padding: 5px 0; 104 | margin: 0; 105 | color: #fff; 106 | border: none; 107 | line-height: 2em; 108 | font-family: Arial, Helvetica, sans-serif; 109 | font-size: 32px !important; 110 | } 111 | 112 | #main 113 | { 114 | padding: 30px 30px 15px 30px; 115 | background-color: #fff; 116 | margin-bottom: 30px; 117 | _height: 1px; /* only IE6 applies CSS properties starting with an underscore */ 118 | } 119 | 120 | #footer 121 | { 122 | color: #999; 123 | padding: 10px 0; 124 | text-align: center; 125 | line-height: normal; 126 | margin: 0; 127 | font-size: .9em; 128 | } 129 | 130 | /* TAB MENU 131 | ----------------------------------------------------------*/ 132 | ul#menu 133 | { 134 | border-bottom: 1px #5C87B2 solid; 135 | padding: 0 0 2px; 136 | position: relative; 137 | margin: 0; 138 | text-align: right; 139 | } 140 | 141 | ul#menu li 142 | { 143 | display: inline; 144 | list-style: none; 145 | } 146 | 147 | ul#menu li#greeting 148 | { 149 | padding: 10px 20px; 150 | font-weight: bold; 151 | text-decoration: none; 152 | line-height: 2.8em; 153 | color: #fff; 154 | } 155 | 156 | ul#menu li a 157 | { 158 | padding: 10px 20px; 159 | font-weight: bold; 160 | text-decoration: none; 161 | line-height: 2.8em; 162 | background-color: #e8eef4; 163 | color: #034af3; 164 | } 165 | 166 | ul#menu li a:hover 167 | { 168 | background-color: #fff; 169 | text-decoration: none; 170 | } 171 | 172 | ul#menu li a:active 173 | { 174 | background-color: #a6e2a6; 175 | text-decoration: none; 176 | } 177 | 178 | ul#menu li.selected a 179 | { 180 | background-color: #fff; 181 | color: #000; 182 | } 183 | 184 | /* FORM LAYOUT ELEMENTS 185 | ----------------------------------------------------------*/ 186 | 187 | fieldset 188 | { 189 | margin: 1em 0; 190 | padding: 1em; 191 | border: 1px solid #CCC; 192 | } 193 | 194 | fieldset p 195 | { 196 | margin: 2px 12px 10px 10px; 197 | } 198 | 199 | legend 200 | { 201 | font-size: 1.1em; 202 | font-weight: 600; 203 | padding: 2px 4px 8px 4px; 204 | } 205 | 206 | input[type="text"] 207 | { 208 | width: 200px; 209 | border: 1px solid #CCC; 210 | } 211 | 212 | input[type="password"] 213 | { 214 | width: 200px; 215 | border: 1px solid #CCC; 216 | } 217 | 218 | /* TABLE 219 | ----------------------------------------------------------*/ 220 | 221 | table 222 | { 223 | border: solid 1px #e8eef4; 224 | border-collapse: collapse; 225 | } 226 | 227 | table td 228 | { 229 | padding: 5px; 230 | border: solid 1px #e8eef4; 231 | } 232 | 233 | table th 234 | { 235 | padding: 6px 5px; 236 | text-align: left; 237 | background-color: #e8eef4; 238 | border: solid 1px #e8eef4; 239 | } 240 | 241 | /* MISC 242 | ----------------------------------------------------------*/ 243 | .clear 244 | { 245 | clear: both; 246 | } 247 | 248 | .error 249 | { 250 | color:Red; 251 | } 252 | 253 | #menucontainer 254 | { 255 | margin-top:40px; 256 | } 257 | 258 | div#title 259 | { 260 | margin-top: -40px; 261 | display:block; 262 | float:left; 263 | text-align:left; 264 | } 265 | 266 | #logindisplay 267 | { 268 | font-size:1.1em; 269 | display:block; 270 | text-align:right; 271 | margin:10px; 272 | color:White; 273 | } 274 | 275 | #logindisplay a:link 276 | { 277 | color: white; 278 | text-decoration: underline; 279 | } 280 | 281 | #logindisplay a:visited 282 | { 283 | color: white; 284 | text-decoration: underline; 285 | } 286 | 287 | #logindisplay a:hover 288 | { 289 | color: white; 290 | text-decoration: none; 291 | } 292 | 293 | /* Styles for validation helpers 294 | -----------------------------------------------------------*/ 295 | .field-validation-error 296 | { 297 | color: #ff0000; 298 | } 299 | 300 | .field-validation-valid 301 | { 302 | display: none; 303 | } 304 | 305 | .input-validation-error 306 | { 307 | border: 1px solid #ff0000; 308 | background-color: #ffeeee; 309 | } 310 | 311 | .validation-summary-errors 312 | { 313 | font-weight: bold; 314 | color: #ff0000; 315 | } 316 | 317 | .validation-summary-valid 318 | { 319 | display: none; 320 | } 321 | 322 | /* Styles for editor and display helpers 323 | ----------------------------------------------------------*/ 324 | .display-label, 325 | .editor-label, 326 | .display-field, 327 | .editor-field 328 | { 329 | margin: 0.5em 0; 330 | } 331 | 332 | .text-box 333 | { 334 | width: 30em; 335 | } 336 | 337 | .text-box.multi-line 338 | { 339 | height: 6.5em; 340 | } 341 | 342 | .tri-state 343 | { 344 | width: 6em; 345 | } 346 | -------------------------------------------------------------------------------- /CQRSGui/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | s "SimpleCQRS/SimpleCQRS" 5 | "context" 6 | "fmt" 7 | "github.com/gorilla/mux" 8 | "html/template" 9 | "net/http" 10 | "reflect" 11 | "strconv" 12 | ) 13 | 14 | func addTemplates(templates map[string]*template.Template) func(next http.Handler) http.Handler { 15 | return func(next http.Handler) http.Handler { 16 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 17 | ctx := context.WithValue(r.Context(), "templates", templates) 18 | next.ServeHTTP(w, r.WithContext(ctx)) 19 | }) 20 | } 21 | } 22 | 23 | func getTemplates(r *http.Request) map[string]*template.Template { 24 | return r.Context().Value("templates").(map[string]*template.Template) 25 | } 26 | 27 | func addReadModel(rm s.ReadModel) func(next http.Handler) http.Handler { 28 | return func(next http.Handler) http.Handler { 29 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 30 | ctx := context.WithValue(r.Context(), "readmodel", rm) 31 | next.ServeHTTP(w, r.WithContext(ctx)) 32 | }) 33 | } 34 | } 35 | 36 | func getReadModel(r *http.Request) s.ReadModel { 37 | return r.Context().Value("readmodel").(s.ReadModel) 38 | } 39 | 40 | func addBus(bus s.CommandDispatcher) func(next http.Handler) http.Handler { 41 | return func(next http.Handler) http.Handler { 42 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 43 | ctx := context.WithValue(r.Context(), "bus", bus) 44 | next.ServeHTTP(w, r.WithContext(ctx)) 45 | }) 46 | } 47 | } 48 | 49 | func getBus(r *http.Request) s.CommandDispatcher { 50 | return r.Context().Value("bus").(s.CommandDispatcher) 51 | } 52 | 53 | func indexHandler(w http.ResponseWriter, r *http.Request) { 54 | template := getTemplates(r)["index"] 55 | readmodel := getReadModel(r) 56 | data := make(map[string][]s.InventoryItemListDto) 57 | data["InventoryItems"] = readmodel.GetInventoryItems() 58 | err := template.ExecuteTemplate(w, "base", data) 59 | if err != nil { 60 | http.Error(w, err.Error(), http.StatusInternalServerError) 61 | } 62 | } 63 | 64 | func addHandler(w http.ResponseWriter, r *http.Request) { 65 | switch r.Method { 66 | case "GET": 67 | template := getTemplates(r)["add"] 68 | err := template.ExecuteTemplate(w, "base", nil) 69 | if err != nil { 70 | http.Error(w, err.Error(), http.StatusInternalServerError) 71 | } 72 | case "POST": 73 | // Call ParseForm() to parse the raw query and update r.PostForm and r.Form. 74 | if err := r.ParseForm(); err != nil { 75 | fmt.Fprintf(w, "ParseForm() err: %v", err) 76 | return 77 | } 78 | //fmt.Fprintf(w, "Post from website! r.PostFrom = %v\n", r.PostForm) 79 | name := r.FormValue("name") 80 | bus := getBus(r) 81 | err := bus.Dispatch(s.CreateInventoryItem{InventoryItemId: s.NewGuid(), Name: name}, nil) 82 | if err != nil { 83 | http.Error(w, err.Error(), http.StatusInternalServerError) 84 | return 85 | } 86 | http.Redirect(w, r, "/", http.StatusFound) 87 | default: 88 | fmt.Fprintf(w, "Sorry, only GET and POST methods are supported.") 89 | } 90 | } 91 | 92 | func changeNameHandler(w http.ResponseWriter, r *http.Request) { 93 | vars := mux.Vars(r) 94 | id := vars["id"] 95 | readmodel := getReadModel(r) 96 | ii, err := readmodel.GetInventoryItemDetails(s.Guid(id)) 97 | if err != nil { 98 | http.Error(w, err.Error(), http.StatusInternalServerError) 99 | return 100 | } 101 | 102 | switch r.Method { 103 | case "GET": 104 | template := getTemplates(r)["changename"] 105 | err := template.ExecuteTemplate(w, "base", ii) 106 | if err != nil { 107 | http.Error(w, err.Error(), http.StatusInternalServerError) 108 | } 109 | case "POST": 110 | // Call ParseForm() to parse the raw query and update r.PostForm and r.Form. 111 | if err := r.ParseForm(); err != nil { 112 | fmt.Fprintf(w, "ParseForm() err: %v", err) 113 | return 114 | } 115 | //fmt.Fprintf(w, "Post from website! r.PostFrom = %v\n", r.PostForm) 116 | name := r.FormValue("name") 117 | v, err := strconv.ParseInt(r.FormValue("version"), 10, 64) 118 | if err != nil { 119 | http.Error(w, err.Error(), http.StatusInternalServerError) 120 | return 121 | } 122 | version := int(v) 123 | 124 | wait_for_success := r.FormValue("wait_for_success") 125 | var waitForSuccess chan s.CommandProcessingError = nil 126 | if wait_for_success != "" { 127 | waitForSuccess = make(chan s.CommandProcessingError) 128 | } 129 | 130 | bus := getBus(r) 131 | err = bus.Dispatch(s.RenameInventoryItem{ii.Id, version, name}, waitForSuccess) 132 | if err != nil { 133 | http.Error(w, err.Error(), http.StatusInternalServerError) 134 | return 135 | } 136 | 137 | if waitForSuccess != nil { 138 | err = <-waitForSuccess 139 | if err != nil { 140 | http.Error(w, err.Error(), http.StatusInternalServerError) 141 | return 142 | } 143 | } else { 144 | fmt.Println("Not waiting around!") 145 | } 146 | http.Redirect(w, r, "/", http.StatusFound) 147 | default: 148 | fmt.Fprintf(w, "Sorry, only GET and POST methods are supported.") 149 | } 150 | } 151 | 152 | func checkinHandler(w http.ResponseWriter, r *http.Request) { 153 | vars := mux.Vars(r) 154 | id := vars["id"] 155 | readmodel := getReadModel(r) 156 | ii, err := readmodel.GetInventoryItemDetails(s.Guid(id)) 157 | if err != nil { 158 | http.Error(w, err.Error(), http.StatusInternalServerError) 159 | return 160 | } 161 | 162 | switch r.Method { 163 | case "GET": 164 | template := getTemplates(r)["checkin"] 165 | err := template.ExecuteTemplate(w, "base", ii) 166 | if err != nil { 167 | http.Error(w, err.Error(), http.StatusInternalServerError) 168 | } 169 | case "POST": 170 | // Call ParseForm() to parse the raw query and update r.PostForm and r.Form. 171 | if err := r.ParseForm(); err != nil { 172 | fmt.Fprintf(w, "ParseForm() err: %v", err) 173 | return 174 | } 175 | //fmt.Fprintf(w, "Post from website! r.PostFrom = %v\n", r.PostForm) 176 | number, err := strconv.Atoi(r.FormValue("number")) 177 | if err != nil { 178 | http.Error(w, err.Error(), http.StatusInternalServerError) 179 | return 180 | } 181 | version, err := strconv.Atoi(r.FormValue("version")) 182 | if err != nil { 183 | http.Error(w, err.Error(), http.StatusInternalServerError) 184 | return 185 | } 186 | bus := getBus(r) 187 | err = bus.Dispatch(s.CheckInItemsToInventory{ii.Id, version, number}, nil) 188 | if err != nil { 189 | http.Error(w, err.Error(), http.StatusInternalServerError) 190 | return 191 | } 192 | http.Redirect(w, r, "/", http.StatusFound) 193 | default: 194 | fmt.Fprintf(w, "Sorry, only GET and POST methods are supported.") 195 | } 196 | } 197 | 198 | func removeHandler(w http.ResponseWriter, r *http.Request) { 199 | vars := mux.Vars(r) 200 | id := vars["id"] 201 | readmodel := getReadModel(r) 202 | ii, err := readmodel.GetInventoryItemDetails(s.Guid(id)) 203 | if err != nil { 204 | http.Error(w, err.Error(), http.StatusInternalServerError) 205 | return 206 | } 207 | 208 | switch r.Method { 209 | case "GET": 210 | template := getTemplates(r)["remove"] 211 | err := template.ExecuteTemplate(w, "base", ii) 212 | if err != nil { 213 | http.Error(w, err.Error(), http.StatusInternalServerError) 214 | } 215 | case "POST": 216 | // Call ParseForm() to parse the raw query and update r.PostForm and r.Form. 217 | if err := r.ParseForm(); err != nil { 218 | fmt.Fprintf(w, "ParseForm() err: %v", err) 219 | return 220 | } 221 | //fmt.Fprintf(w, "Post from website! r.PostFrom = %v\n", r.PostForm) 222 | number, err := strconv.Atoi(r.FormValue("number")) 223 | if err != nil { 224 | http.Error(w, err.Error(), http.StatusInternalServerError) 225 | return 226 | } 227 | version, err := strconv.Atoi(r.FormValue("version")) 228 | if err != nil { 229 | http.Error(w, err.Error(), http.StatusInternalServerError) 230 | return 231 | } 232 | bus := getBus(r) 233 | err = bus.Dispatch(s.RemoveItemsFromInventory{ii.Id, version, number}, nil) 234 | if err != nil { 235 | http.Error(w, err.Error(), http.StatusInternalServerError) 236 | return 237 | } 238 | http.Redirect(w, r, "/", http.StatusFound) 239 | default: 240 | fmt.Fprintf(w, "Sorry, only GET and POST methods are supported.") 241 | } 242 | } 243 | 244 | func deactivateHandler(w http.ResponseWriter, r *http.Request) { 245 | vars := mux.Vars(r) 246 | id := vars["id"] 247 | readmodel := getReadModel(r) 248 | ii, err := readmodel.GetInventoryItemDetails(s.Guid(id)) 249 | if err != nil { 250 | http.Error(w, err.Error(), http.StatusInternalServerError) 251 | return 252 | } 253 | 254 | switch r.Method { 255 | case "GET": 256 | template := getTemplates(r)["deactivate"] 257 | err := template.ExecuteTemplate(w, "base", ii) 258 | if err != nil { 259 | http.Error(w, err.Error(), http.StatusInternalServerError) 260 | } 261 | case "POST": 262 | // Call ParseForm() to parse the raw query and update r.PostForm and r.Form. 263 | if err := r.ParseForm(); err != nil { 264 | fmt.Fprintf(w, "ParseForm() err: %v", err) 265 | return 266 | } 267 | id = r.FormValue("id") 268 | version, err := strconv.Atoi(r.FormValue("version")) 269 | if err != nil { 270 | http.Error(w, err.Error(), http.StatusInternalServerError) 271 | return 272 | } 273 | bus := getBus(r) 274 | err = bus.Dispatch(s.DeactivateInventoryItem{s.Guid(id), version}, nil) 275 | if err != nil { 276 | http.Error(w, err.Error(), http.StatusInternalServerError) 277 | return 278 | } 279 | http.Redirect(w, r, "/", http.StatusFound) 280 | default: 281 | fmt.Fprintf(w, "Sorry, only GET and POST methods are supported.") 282 | } 283 | } 284 | 285 | func detailsHandler(w http.ResponseWriter, r *http.Request) { 286 | template := getTemplates(r)["details"] 287 | readmodel := getReadModel(r) 288 | vars := mux.Vars(r) 289 | id := vars["id"] 290 | 291 | guid := s.Guid(id) 292 | data := make(map[string]s.InventoryItemDetailsDto) 293 | model, _ := readmodel.GetInventoryItemDetails(guid) 294 | data["Model"] = model 295 | err := template.ExecuteTemplate(w, "base", data) 296 | if err != nil { 297 | http.Error(w, err.Error(), http.StatusInternalServerError) 298 | } 299 | } 300 | 301 | func setupCQRS(mimicEventualConsistency bool) (s.ReadModel, s.CommandDispatcher) { 302 | 303 | bus := s.NewFakeBus(mimicEventualConsistency) 304 | storage := s.NewEventStore(bus) 305 | rep := s.InventoryItemRepository{storage} 306 | commands := s.NewInventoryCommandHandlers(rep) 307 | bus.SetCommandHandler(reflect.TypeOf(s.CheckInItemsToInventory{}), commands.HandleCheckInItemsToInventory) 308 | bus.SetCommandHandler(reflect.TypeOf(s.CreateInventoryItem{}), commands.HandleCreateInventoryItem) 309 | bus.SetCommandHandler(reflect.TypeOf(s.DeactivateInventoryItem{}), commands.HandleDeactivateInventoryItem) 310 | bus.SetCommandHandler(reflect.TypeOf(s.RemoveItemsFromInventory{}), commands.HandleRemoveItemsFromInventory) 311 | bus.SetCommandHandler(reflect.TypeOf(s.RenameInventoryItem{}), commands.HandleRenameInventoryItem) 312 | 313 | bsdb := s.NewBSDB() 314 | 315 | detail := s.NewInventoryItemDetailView(&bsdb) 316 | bus.AddEventProcessor(reflect.TypeOf(s.InventoryItemCreated{}), detail.ProcessInventoryItemCreated) 317 | bus.AddEventProcessor(reflect.TypeOf(s.InventoryItemDeactivated{}), detail.ProcessInventoryItemDeactivated) 318 | bus.AddEventProcessor(reflect.TypeOf(s.InventoryItemRenamed{}), detail.ProcessInventoryItemRenamed) 319 | bus.AddEventProcessor(reflect.TypeOf(s.ItemsCheckedInToInventory{}), detail.ProcessItemsCheckedInToInventory) 320 | bus.AddEventProcessor(reflect.TypeOf(s.ItemsRemovedFromInventory{}), detail.ProcessItemsRemovedFromInventory) 321 | 322 | list := s.NewInventoryListView(&bsdb) 323 | bus.AddEventProcessor(reflect.TypeOf(s.InventoryItemCreated{}), list.ProcessInventoryItemCreated) 324 | bus.AddEventProcessor(reflect.TypeOf(s.InventoryItemRenamed{}), list.ProcessInventoryItemRenamed) 325 | bus.AddEventProcessor(reflect.TypeOf(s.InventoryItemDeactivated{}), list.ProcessInventoryItemDeactivated) 326 | 327 | id := s.NewGuid() 328 | bus.Dispatch(s.CreateInventoryItem{id, "The self-seed inventory item"}, nil) 329 | rmf := s.NewReadModelFacade(&bsdb) 330 | fmt.Println("Returning facade") 331 | return &rmf, bus 332 | } 333 | 334 | func buildTemplates() map[string]*template.Template { 335 | t := make(map[string]*template.Template) 336 | 337 | for _, name := range []string{"index", "details", "add", "changename", "checkin", "remove", "deactivate"} { 338 | t[name] = template.Must( 339 | template.ParseFiles( 340 | fmt.Sprintf("./CQRSGui/pages/%v.html", name), 341 | "./CQRSGui/pages/base.html")) 342 | } 343 | 344 | return t 345 | } 346 | 347 | func main() { 348 | fmt.Println("Starting") 349 | templates := buildTemplates() 350 | 351 | fmt.Println("Starting CQRS") 352 | readmodel, bus := setupCQRS(false) // true to introduce delays 353 | 354 | fmt.Println("Starting Router") 355 | rtr := mux.NewRouter() 356 | rtr.Use(addReadModel(readmodel)) 357 | rtr.Use(addTemplates(templates)) 358 | rtr.Use(addBus(bus)) 359 | 360 | rtr.HandleFunc("/", indexHandler).Methods("GET") 361 | rtr.HandleFunc("/add", addHandler).Methods("GET", "POST") 362 | 363 | ii := rtr.PathPrefix("/details").Subrouter() 364 | ii.HandleFunc("/{id}", detailsHandler).Methods("GET") 365 | ii.HandleFunc("/{id}/changename", changeNameHandler).Methods("GET", "POST") 366 | ii.HandleFunc("/{id}/checkin", checkinHandler).Methods("GET", "POST") 367 | ii.HandleFunc("/{id}/remove", removeHandler).Methods("GET", "POST") 368 | ii.HandleFunc("/{id}/deactivate", deactivateHandler).Methods("GET", "POST") 369 | 370 | rtr.PathPrefix("/Content/").Handler( 371 | http.StripPrefix("/Content/", 372 | http.FileServer(http.Dir("./CQRSGui/Content/")))) 373 | 374 | http.Handle("/", rtr) 375 | fmt.Println("Starting ListenAndServe") 376 | http.ListenAndServe(":8080", nil) 377 | } 378 | -------------------------------------------------------------------------------- /CQRSGui/pages/add.html: -------------------------------------------------------------------------------- 1 | {{define "title"}}Add{{end}} 2 | 3 | {{define "mainContent"}} 4 |

Add

5 |
6 |
7 |
8 | Submit 9 |
10 | {{end}} 11 | -------------------------------------------------------------------------------- /CQRSGui/pages/base.html: -------------------------------------------------------------------------------- 1 | {{define "base"}} 2 | 3 | 4 | {{template "title" .}} 5 | 6 | 7 |
8 |