diff --git a/.gitignore b/.gitignore index f2ec00d..e7770f8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,11 @@ dist/ # Test data and logs folders .data/ .idea/dataSources.xml + +# CDN link +cdn/ +cdn + +# Config Link +cnf/ +cnf diff --git a/Makefile b/Makefile index 2074b0c..f487c3b 100644 --- a/Makefile +++ b/Makefile @@ -34,4 +34,8 @@ clean: deploy: build sudo systemctl stop wappcityuni sudo cp "${BIN}" /usr/local/bin + sudo cp *.go.html cnf + sudo cp *.go.yml cnf + sudo cp *.css cdn + sudo cp *.js cdn sudo systemctl start wappcityuni diff --git a/base.css b/base.css new file mode 100644 index 0000000..01535cb --- /dev/null +++ b/base.css @@ -0,0 +1,207 @@ +*{ + margin: 0; + padding: 0; + box-sizing: border-box; +} +main{ + padding-top: 90px; + padding-left: 6px; +} +.no-dec{ + text-decoration: none; +} +.no-lst-style{ + list-style: none; +} +.centered{ + text-align: center; +} +.content > p{ + margin-top: 0.5em; + margin-bottom: 0.5em; +} +.header{ + position: fixed; + top: 0; + width: 100%; +} +.home-button, .sort-button{ + display: inline-block; + width: 64px; + height: 82px; + overflow: hidden; + text-align: center; +} +.sort-button{ + cursor: pointer; +} +.home-button > div, .sort-button > div{ + display: inline; + font-size: 60px; + padding: 10px; + vertical-align: middle; +} +.nav{ + width: 100%; + height: 100%; + overflow: hidden; + max-height: 0; +} +.nav-menu, .sort-menu, .duration-hold{ + display: none; +} +.menu a{ + display: block; + padding: 32px 18px; +} +.hmb{ + cursor: pointer; + float: right; + background-color: transparent; + padding: 40px 20px; +} +.hmb-line{ + display: block; + height: 2px; + position: relative; + width: 24px; +} +.hmb-line::before, .hmb-line::after{ + content: ''; + display: block; + height: 100%; + position: absolute; + transition: all .1s ease-out; + width: 100%; +} +.hmb-line::before{ + top: 5px; +} +.hmb-line::after{ + top: -5px; +} +.nav-menu:checked ~ nav{ + max-height: 100%; +} +.nav-menu:checked ~ .hmb .hmb-line{ + background: transparent; +} +.nav-menu:checked ~ .hmb .hmb-line::before{ + transform: rotate(-45deg); + top:0; +} +.nav-menu:checked ~ .hmb .hmb-line::after{ + transform: rotate(45deg); + top:0; +} +.main-box{ + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: -webkit-flex; + display: flex; + -webkit-box-orient: vertical; + -moz-box-orient: vertical; + -webkit-box-direction: normal; + -moz-box-direction: normal; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-align-content: center; + align-content: center; + width: 100%; +} +.main-box > div, footer > p{ + margin: 3px 0; + padding: 2px; + box-sizing: content-box; +} +.item-table{ + display: table; + width: 100%; + box-sizing: content-box; + background-color: transparent; +} +.item-table > div{ + display: table-row; + background-color: transparent; + box-sizing: inherit; +} +.item-table > div > div > div{ + padding: 2px; +} +.item-table-caption{ + display: table-caption !important; + caption-side: bottom; +} +.item-table-full, .item-table-360, .item-table-caption{ + border-style: solid; + border-width: 1px; + background-color: transparent; + box-sizing: inherit; +} +.item-table-full{ + display: table-cell; + vertical-align: top; + width: 100%; +} +.item-table-360{ + display: none; +} +.image-box{ + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: -webkit-flex; + display: flex; + -webkit-box-orient: horizontal; + -moz-box-orient: horizontal; + -webkit-box-direction: normal; + -moz-box-direction: normal; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -ms-flex-wrap: wrap; + -webkit-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-justify-content: center; + justify-content: center; +} +.image-box > a{ + border-style: solid; + border-width: 1px; + margin: 2px; +} +@media (min-width: 540px){ + .main-box > div, footer > p{ + margin: 5px; + } + .item-table-360{ + display: table-cell; + vertical-align: middle; + width: 360px; + overflow: hidden; + } + .image-box > a{ + border-width: 4px; + margin: 10px; + } +} +@media (min-width: 640px){ + .nav{ + max-height: none; + top: 0; + position: relative; + float: right; + width: auto; + } + .menu li{ + float: left; + } + .menu a:hover{ + background-color: transparent; + } + .hmb{ + display: none; + } +} \ No newline at end of file diff --git a/dark.css b/dark.css new file mode 100644 index 0000000..a0f44e5 --- /dev/null +++ b/dark.css @@ -0,0 +1,35 @@ +body{ + color: #f9f9f9; + background-color: #050506; + border-color: #696969; +} +a{ + color: #b0b0f0; +} +.header, .nav, footer{ + background-color: #1d1d1e; +} +.home-button > div, .sort-button > div, .menu a{ + color: #e0e0e0; +} +.home-button:hover, .menu a:hover, .hmb:hover, .sort-button:hover, .sort-menu:checked ~ .sort-button{ + background-color: #606061; +} +.hmb-line, .hmb-line::before, .hmb-line::after{ + background: #e0e0e0; +} +.main-box{ + background-color: #0f0f0f; +} +.item-table{ + background-color: #3f3f3f; +} +.item-table > div > div, .item-table-caption{ + border-color: #f5deb3; +} +.image-box{ + background-color: #4f4f4f; +} +.image-box > a{ + border-color: #b0b0f0; +} \ No newline at end of file diff --git a/goinfo.go.html b/goinfo.go.html index 5b299fb..4ab075d 100644 --- a/goinfo.go.html +++ b/goinfo.go.html @@ -13,7 +13,6 @@ text-align: center; background-color: mediumslateblue; } - table, th, td { margin: auto; text-align: left; @@ -21,20 +20,16 @@ border-collapse: collapse; word-break: break-word; } - table, td { background-color: lightgray; } - table { width: 80%; } - th { background-color: lightsteelblue; width: 25%; } - td { width: 75%; } diff --git a/index.go.html b/index.go.html new file mode 100644 index 0000000..64d8d01 --- /dev/null +++ b/index.go.html @@ -0,0 +1,114 @@ + + + + + + + City University Portfolio + + {{ if .Light }} + + {{ else }} + + {{ end }} + + + +
+ {{ if .Light }} +
+
+ {{ else }} +
+ {{ if eq .Parameters "" }} +
+ {{ else }} +
+ {{ end }} + {{ end }} + + + + + +
+
+
+
+
+
+

{{ .Data.About.Title }}

+
+ +
+
+
+
{{ .Data.About.GetContent }}
+
+
+
{{ .Data.About.ImageAltText }}
+
+
+
+
+ {{ range .GetEntries }} +
+
+
+
+

{{ .Name }}

+
+
+ {{ if eq .GetStartDate .GetEndDate }} +

{{ .GetStartDate }}

+ {{ else }} +

{{ .GetStartDate }} - {{ .GetEndDate }}

+ {{ end }} +
+
+
+
+
{{ .GetContent }}
+
+
+
+ {{ if eq .VideoLocation "" }} + No Video + {{ else }} + + {{ end }} +
+
+
+ {{ if not (eq .GetImageCount 0) }} +
+
+ {{ range .GetImages }} + {{ .ImageAltText }} + {{ end }} +
+
+ {{ end }} +
+
{{ .GetInt64Duration }}
+
+ {{ end }} +
+ + + \ No newline at end of file diff --git a/index.go.yml b/index.go.yml new file mode 100644 index 0000000..3583d8a --- /dev/null +++ b/index.go.yml @@ -0,0 +1,156 @@ +#This file is (C) Captain ALM +#Under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License +cssBaseURL: "https://cityuni.captainalm.com/resources/assets/base.css" +cssDarkURL: "https://cityuni.captainalm.com/resources/assets/dark.css" +cssLightURL: "https://cityuni.captainalm.com/resources/assets/light.css" +jScriptURL: "https://cityuni.captainalm.com/resources/assets/index.js" +noVideoImageLocation: "https://cityuni.captainalm.com/resources/assets/novideo.png" +headerLinks: + Main Portfolio: "https://portfolio.captainalm.com/" + Root Site Home: "https://www.captainalm.com/" + Github: "https://github.com/Captain-ALM/" +about: + title: "Alfred Manville (Captain ALM)" + content: > +

+ Hello, I'm Alfred Manville (#age# Years Old). + I'm a free and open-source developer who enjoys networking my laptops together, + writes network software to communicate between them and then tries to break said software. + I also have a Youtube Channel which is in the process of being resumed from a hiatus. +

+

+ On the programming side, I know Visual Basic .net, C# .net, C, Java, Go, Javascript (Circa. 2000), Processing and Microsoft Smallbasic + (I have also dabbled in C++, Python and Bash/Batch). + I am currently in the progress of writing infrastructure software in Go, in the past, I wrote a command console in VB .net for my own + pluggable libraries (I created a CMD emulator to get past the school disabling interactive CMD) and some network communication applications + (Including a peer-to-peer VOIP client using NAudio as the audio library and my own network wrapper library). +

+

+ On the cracking / hacking side, I've used virtual machines a lot (Mainly infrastructure testing + but I did at one point try creating and breaking into a test windows domain network I had setup). + I've also used VMs to pull perform a PXE boot off a network and analyse the partially deployed image + (And this is why the deployment servers should be switched off when unneeded, especially since PXE + auto-domain-join would store its credentials in the image). + I also played around with accessing the RDP servers when I was in secondary school (Turns out remote apps + is just a glorified application auto-launcher with window size detection). +

+

+ I also bake bread although this sub-site is still under construction (Mostly learnt from my grandma). + I also play video-games and am an expert at using lower-end hardware. +

+

+ I used to do Karate (Kyokushin Brown Belt) and I wish I could still fit my bike. + My GPG Key for my email address. +

+ thumbnailLocation: "https://cityuni.captainalm.com/resources/assets/imageofyou_t.jpg" + imageLocation: "https://cityuni.captainalm.com/resources/assets/imageofyou.jpg" + imageAltText: "Image of me." + birthYear: 2002 + contactEmail: "alfred@captainalm.com" +entries: + - name: "Bootcamp 2021: Ninjaformer GUI" + content: > +

+ My first programming task at City, concluding the 2 week 2021 Programming Bootcamp, + although I have only spent 3 days programming this and was a tad bit too ambitious. + (I could have started earlier though) +

+

+ This Processing project show that I can use arrays, loops, mouse and keyboard interaction and geometric transforms. + The project contains a GUI library that I made to create the menu system for what could have been the Ninjaformer game. +

+

+ Unfortunately, while the code for loading tile, sprite and level information exists (JSON, sprite sheet support); + I ran out of time before the submission to actually even start on the game. But you can play around with the main code and build your own GUIs too so... +

+

+ Here is the repo: https://github.com/Captain-ALM/Ninjaformer-Processing +

+ startDate: "01/10/2021" + endDate: "31/10/2021" + videoLocation: "https://cityuni.captainalm.com/resources/stream/vid1.mp4" + videoContentType: "video/mp4" + thumbnailLocations: + - "https://cityuni.captainalm.com/resources/assets/pic1_t.png" + - "https://cityuni.captainalm.com/resources/assets/pic2_t.png" + - "https://cityuni.captainalm.com/resources/assets/pic3_t.png" + imageLocations: + - "https://cityuni.captainalm.com/resources/assets/pic1.png" + - "https://cityuni.captainalm.com/resources/assets/pic2.png" + - "https://cityuni.captainalm.com/resources/assets/pic3.png" + imageAltTexts: + - "Level select screen." + - "Empty content interface (Gameplay)." + - "Level editor screen." + - name: "City Game Project 2022: Ninjaformer (Alpha, Beta)" + content: > +

+ My first major project at City (A Java Game), concluding 2.2 Months of programming. + This game uses the University provided game library (Which is just JBox2D extended). +

+

+ Looking back on this, I wish I started earlier (Like January) that way I could have implemented all the features I wanted. + This project allows for levels to be designed within the program and allows them to be edited as XML outside the program. +

+

+ The code is extensible and it is relatively straight forward to implement new features. There are a few bugs that can crop up + (Such as sticking to surfaces due to ground body updates) but I already know ways to fix them. + This project relies on part of a GUI library I built in it and I had to modify the CityGame library by extending it. + The audio and assets were also created by me, although they're a bit amateurish as I'm a computer scientist not an artist! +

+

+ This game is designed to be a story based game... The tutorial level at the beginning of the game is the dream in which the main + character dreams of being a ninja, this allows for the player to learn the controls and basic mechanics of the game. + The next level is the training level in which the ninja trains within a monastery. + The final level allows the ninja to "complete" the game while exploring a set of caves. +

+

+ Here is the repo: Not public due to university anti-plagiarism policy. +

+ startDate: "25/02/2022" + endDate: "08/05/2022" + videoLocation: "https://cityuni.captainalm.com/resources/stream/vid2.mp4" + videoContentType: "video/mp4" + thumbnailLocations: + - "https://cityuni.captainalm.com/resources/assets/pic4_t.png" + - "https://cityuni.captainalm.com/resources/assets/pic5_t.png" + - "https://cityuni.captainalm.com/resources/assets/pic6_t.png" + imageLocations: + - "https://cityuni.captainalm.com/resources/assets/pic4.png" + - "https://cityuni.captainalm.com/resources/assets/pic5.png" + - "https://cityuni.captainalm.com/resources/assets/pic6.png" + imageAltTexts: + - "Cave level." + - "Tutorial level." + - "Training level." + - name: "Global Game Jam January 2022 : Shadow work" + content: > +

+ I may have not done any programming for this (Even though I know C#) but I helped write a good chunk of the background story + (I also helped with the level design although it turns out 48 hours is hard to get polished stuff done in, so some stuff had to be axed). +

+

+ Read about and get the game files from: https://globalgamejam.org/2022/games/shadow-work-8 +

+

+ Download the windows executable from: https://cdn.captainalm.com/download/ShadowWorkExecutable.zip +

+ startDate: "20/01/2022" + endDate: "30/01/2022" + #videoLocation: "https://cityuni.captainalm.com/resources/stream/vid3.mp4" + #videoContentType: "video/mp4" + - name: "City-University Portfolio" + content: > +

+ This project is what's outputting this page at the moment! The backend is written in Go and there is both custom front-end Javascript and CSS! + The pages support theming and the entries can be sorted through Javascript or on the backend through GET parameters. +

+

+ This project is under the BSD-3-Clause License, so if reusing, you must scrub references to me, the yml file this is written in is under + Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International + License. +

+

+ Find the source code here: https://code.mrmelon54.xyz/alfred/cityuni-webserver +

+ startDate: "13/07/2022" diff --git a/index.js b/index.js new file mode 100644 index 0000000..e69de29 diff --git a/light.css b/light.css new file mode 100644 index 0000000..993aabe --- /dev/null +++ b/light.css @@ -0,0 +1,35 @@ +body{ + color: #060606; + background-color: #fafaf9; + border-color: #969696; +} +a{ + color: #4f4fff; +} +.header, .nav, footer{ + background-color: #e2e2e1; +} +.home-button > div, .sort-button > div, .menu a{ + color: #1f1f1f; +} +.home-button:hover, .menu a:hover, .hmb:hover, .sort-button:hover, .sort-menu:checked ~ .sort-button{ + background-color: #9f9f9e; +} +.hmb-line, .hmb-line::before, .hmb-line::after{ + background: #1f1f1f; +} +.main-box{ + background-color: #f0f0f0; +} +.item-table{ + background-color: #c0c0c0; +} +.item-table > div > div, .item-table-caption{ + border-color: #0a214c; +} +.image-box{ + background-color: #b0b0b0; +} +.image-box > a{ + border-color: #4f4f0f; +} \ No newline at end of file diff --git a/pageHandler/page-handler.go b/pageHandler/page-handler.go index 87f269a..c222118 100644 --- a/pageHandler/page-handler.go +++ b/pageHandler/page-handler.go @@ -7,13 +7,14 @@ import ( "mime/multipart" "net/http" "net/textproto" - "net/url" "strconv" "strings" "sync" "time" ) +const indexName = "index.go" + type PageHandler struct { PageContentsCache map[string]*CachedPage PageProviders map[string]PageProvider @@ -50,7 +51,16 @@ func NewPageHandler(config conf.ServeYaml) *PageHandler { } func (ph *PageHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { - actualPagePath := strings.TrimRight(request.URL.Path, "/") + actualPagePath := "" + if strings.HasSuffix(request.URL.Path, "/") { + if strings.HasSuffix(request.URL.Path, ".go/") { + actualPagePath = strings.TrimRight(request.URL.Path, "/") + } else { + actualPagePath = request.URL.Path + indexName + } + } else { + actualPagePath = request.URL.Path + } var currentProvider PageProvider canCache := false @@ -64,7 +74,7 @@ func (ph *PageHandler) ServeHTTP(writer http.ResponseWriter, request *http.Reque actualQueries = currentProvider.GetCacheIDExtension(queryValues) if ph.CacheSettings.EnableContentsCaching { - cached := ph.getPageFromCache(request.URL, actualQueries) + cached := ph.getPageFromCache(request.URL.Path, actualQueries) if cached != nil { pageContent = cached.Content pageContentType = cached.ContentType @@ -76,7 +86,7 @@ func (ph *PageHandler) ServeHTTP(writer http.ResponseWriter, request *http.Reque pageContentType, pageContent, canCache = currentProvider.GetContents(queryValues) lastMod = currentProvider.GetLastModified() if pageContentType != "" && canCache && ph.CacheSettings.EnableContentsCaching { - ph.setPageToCache(request.URL, actualQueries, &CachedPage{ + ph.setPageToCache(request.URL.Path, actualQueries, &CachedPage{ Content: pageContent, ContentType: pageContentType, LastMod: lastMod, @@ -143,7 +153,7 @@ func (ph *PageHandler) ServeHTTP(writer http.ResponseWriter, request *http.Reque } } case http.MethodDelete: - ph.PurgeTemplateCache(actualPagePath) + ph.PurgeTemplateCache(actualPagePath, request.URL.Path == "/") ph.PurgeContentsCache(request.URL.Path, actualQueries) utils.SetNeverCacheHeader(writer.Header()) utils.WriteResponseHeaderCanWriteBody(request.Method, writer, http.StatusOK, "") @@ -167,12 +177,12 @@ func (ph *PageHandler) ServeHTTP(writer http.ResponseWriter, request *http.Reque func (ph *PageHandler) PurgeContentsCache(path string, query string) { if ph.CacheSettings.EnableContentsCaching && ph.CacheSettings.EnableContentsCachePurge { - if path == "" { + if path == "/" { ph.pageContentsCacheRWMutex.Lock() ph.PageContentsCache = make(map[string]*CachedPage) ph.pageContentsCacheRWMutex.Unlock() } else { - if strings.HasSuffix(path, "/") { + if strings.HasSuffix(path, ".go/") { ph.pageContentsCacheRWMutex.RLock() toDelete := make([]string, len(ph.PageContentsCache)) theSize := 0 @@ -189,22 +199,24 @@ func (ph *PageHandler) PurgeContentsCache(path string, query string) { delete(ph.PageContentsCache, toDelete[i]) } ph.pageContentsCacheRWMutex.Unlock() - } else { - ph.pageContentsCacheRWMutex.Lock() - if query == "" { - delete(ph.PageContentsCache, path) - } else { - delete(ph.PageContentsCache, path+"?"+query) - } - ph.pageContentsCacheRWMutex.Unlock() + return + } else if strings.HasSuffix(path, "/") { + path += indexName } + ph.pageContentsCacheRWMutex.Lock() + if query == "" { + delete(ph.PageContentsCache, path) + } else { + delete(ph.PageContentsCache, path+"?"+query) + } + ph.pageContentsCacheRWMutex.Unlock() } } } -func (ph *PageHandler) PurgeTemplateCache(path string) { +func (ph *PageHandler) PurgeTemplateCache(path string, all bool) { if ph.CacheSettings.EnableTemplateCaching && ph.CacheSettings.EnableTemplateCachePurge { - if path == "" { + if all { for _, pageProvider := range ph.PageProviders { pageProvider.PurgeTemplate() } @@ -215,36 +227,39 @@ func (ph *PageHandler) PurgeTemplateCache(path string) { } } } -func (ph *PageHandler) getPageFromCache(urlIn *url.URL, cleanedQueries string) *CachedPage { +func (ph *PageHandler) getPageFromCache(pathIn string, cleanedQueries string) *CachedPage { ph.pageContentsCacheRWMutex.RLock() defer ph.pageContentsCacheRWMutex.RUnlock() - if strings.HasSuffix(urlIn.Path, "/") { - return ph.PageContentsCache[strings.TrimRight(urlIn.Path, "/")] + if strings.HasSuffix(pathIn, ".go/") { + return ph.PageContentsCache[strings.TrimRight(pathIn, "/")] + } else if strings.HasSuffix(pathIn, "/") { + pathIn += indexName + } + if cleanedQueries == "" { + return ph.PageContentsCache[pathIn] } else { - if cleanedQueries == "" { - return ph.PageContentsCache[urlIn.Path] - } else { - return ph.PageContentsCache[urlIn.Path+"?"+cleanedQueries] - } + return ph.PageContentsCache[pathIn+"?"+cleanedQueries] } } -func (ph *PageHandler) setPageToCache(urlIn *url.URL, cleanedQueries string, newPage *CachedPage) { +func (ph *PageHandler) setPageToCache(pathIn string, cleanedQueries string, newPage *CachedPage) { ph.pageContentsCacheRWMutex.Lock() defer ph.pageContentsCacheRWMutex.Unlock() - if strings.HasSuffix(urlIn.Path, "/") { - ph.PageContentsCache[strings.TrimRight(urlIn.Path, "/")] = newPage + if strings.HasSuffix(pathIn, ".go/") { + ph.PageContentsCache[strings.TrimRight(pathIn, "/")] = newPage + return + } else if strings.HasSuffix(pathIn, "/") { + pathIn += indexName + } + if cleanedQueries == "" { + ph.PageContentsCache[pathIn] = newPage } else { - if cleanedQueries == "" { - ph.PageContentsCache[urlIn.Path] = newPage - } else { - ph.PageContentsCache[urlIn.Path+"?"+cleanedQueries] = newPage - } + ph.PageContentsCache[pathIn+"?"+cleanedQueries] = newPage } } func (ph *PageHandler) getAllowedMethodsForPath(pathIn string) []string { - if strings.HasSuffix(pathIn, "/") { + if pathIn == "/" || strings.HasSuffix(pathIn, ".go/") { if (ph.CacheSettings.EnableTemplateCaching && ph.CacheSettings.EnableTemplateCachePurge) || (ph.CacheSettings.EnableContentsCaching && ph.CacheSettings.EnableContentsCachePurge) { return []string{http.MethodHead, http.MethodGet, http.MethodOptions, http.MethodDelete} diff --git a/pageHandler/pages/index/about.go b/pageHandler/pages/index/about.go index 4379299..0fe9070 100644 --- a/pageHandler/pages/index/about.go +++ b/pageHandler/pages/index/about.go @@ -2,20 +2,23 @@ package index import ( "html/template" + "strconv" + "strings" "time" ) type AboutYaml struct { - Title string `yaml:"title"` - Content string `yaml:"content"` - ThumbnailLocation string `yaml:"thumbnailLocation"` - ImageLocation string `yaml:"imageLocation"` - BirthYear int `yaml:"birthYear"` - ContactEmail string `yaml:"contactEmail"` + Title string `yaml:"title"` + Content string `yaml:"content"` + ThumbnailLocation template.URL `yaml:"thumbnailLocation"` + ImageLocation template.URL `yaml:"imageLocation"` + ImageAltText string `yaml:"imageAltText"` + BirthYear int `yaml:"birthYear"` + ContactEmail string `yaml:"contactEmail"` } func (ay AboutYaml) GetContent() template.HTML { - return template.HTML(ay.Content) + return template.HTML(strings.ReplaceAll(strings.ReplaceAll(ay.Content, "#age#", strconv.Itoa(ay.GetAge())), "#birth#", strconv.Itoa(ay.BirthYear))) } func (ay AboutYaml) GetAge() int { diff --git a/pageHandler/pages/index/data.go b/pageHandler/pages/index/data.go index 81afea0..f697ff2 100644 --- a/pageHandler/pages/index/data.go +++ b/pageHandler/pages/index/data.go @@ -1,12 +1,34 @@ package index +import "html/template" + type DataYaml struct { - HomeLink string `yaml:"homeLink"` - PortfolioLink string `yaml:"portfolioLink"` - CSSBaseURL string `yaml:"cssBaseURL"` - CSSLightURL string `yaml:"cssLightURL"` - CSSDarkURL string `yaml:"cssDarkURL"` - JScriptURL string `yaml:"jScriptURL"` - About AboutYaml `yaml:"about"` - Entries []EntryYaml `yaml:"entries"` + HeaderLinks map[string]template.URL `yaml:"headerLinks"` + CSSBaseURL template.URL `yaml:"cssBaseURL"` + CSSLightURL template.URL `yaml:"cssLightURL"` + CSSDarkURL template.URL `yaml:"cssDarkURL"` + JScriptURL template.URL `yaml:"jScriptURL"` + NoVideoImageLocation template.URL `yaml:"noVideoImageLocation"` + About AboutYaml `yaml:"about"` + Entries []EntryYaml `yaml:"entries"` +} + +func (dy DataYaml) GetHeaderLabels() []string { + if dy.HeaderLinks == nil { + return []string{} + } + toReturn := make([]string, len(dy.HeaderLinks)) + i := 0 + for key := range dy.HeaderLinks { + toReturn[i] = key + i++ + } + return toReturn +} + +func (dy DataYaml) GetHeaderLink(headerLabel string) template.URL { + if dy.HeaderLinks == nil { + return "" + } + return dy.HeaderLinks[headerLabel] } diff --git a/pageHandler/pages/index/entry.go b/pageHandler/pages/index/entry.go index 06c85bd..f129589 100644 --- a/pageHandler/pages/index/entry.go +++ b/pageHandler/pages/index/entry.go @@ -1,21 +1,30 @@ package index import ( + "golang.captainalm.com/cityuni-webserver/utils/yaml" "html/template" + "math" "time" ) -const dateFormat = "2006-01-02" +const dateFormat = "01-2006" type EntryYaml struct { - Name string `yaml:"name"` - Content string `yaml:"content"` - StartDate time.Time `yaml:"startDate"` - EndDate time.Time `yaml:"endDate"` - VideoLocation string `yaml:"videoLocation"` - VideoContentType string `yaml:"videoContentType"` - ThumbnailLocations []string `yaml:"thumbnailLocations"` - ImageLocations []string `yaml:"imageLocations"` + Name string `yaml:"name"` + Content string `yaml:"content"` + StartDate yaml.DateType `yaml:"startDate"` + EndDate yaml.DateType `yaml:"endDate"` + VideoLocation template.URL `yaml:"videoLocation"` + VideoContentType string `yaml:"videoContentType"` + ThumbnailLocations []template.URL `yaml:"thumbnailLocations"` + ImageLocations []template.URL `yaml:"imageLocations"` + ImageAltTexts []string `yaml:"imageAltTexts"` +} + +type ImageReference struct { + ThumbnailLocation template.URL + ImageLocation template.URL + ImageAltText string } func (ey EntryYaml) GetStartDate() string { @@ -34,7 +43,7 @@ func (ey EntryYaml) GetEndTime() time.Time { if ey.EndDate.IsZero() { return time.Now() } else { - return ey.EndDate + return ey.EndDate.Time } } @@ -43,5 +52,25 @@ func (ey EntryYaml) GetContent() template.HTML { } func (ey EntryYaml) GetDuration() time.Duration { - return ey.GetEndTime().Sub(ey.StartDate).Truncate(time.Second) + return ey.GetEndTime().Sub(ey.StartDate.Time).Truncate(time.Second) +} + +func (ey EntryYaml) GetInt64Duration() int64 { + return int64(ey.GetDuration()) +} + +func (ey EntryYaml) GetImageCount() int { + return int(math.Min(math.Min(float64(len(ey.ThumbnailLocations)), float64(len(ey.ImageLocations))), float64(len(ey.ImageAltTexts)))) +} + +func (ey EntryYaml) GetImages() []ImageReference { + toReturn := make([]ImageReference, ey.GetImageCount()) + for i := 0; i < len(ey.ThumbnailLocations) && i < len(ey.ImageLocations) && i < len(ey.ImageAltTexts); i++ { + toReturn[i] = ImageReference{ + ThumbnailLocation: ey.ThumbnailLocations[i], + ImageLocation: ey.ImageLocations[i], + ImageAltText: ey.ImageAltTexts[i], + } + } + return toReturn } diff --git a/pageHandler/pages/index/index-page.go b/pageHandler/pages/index/index-page.go index bb73fe4..878dd29 100644 --- a/pageHandler/pages/index/index-page.go +++ b/pageHandler/pages/index/index-page.go @@ -17,12 +17,14 @@ const yamlName = "index.go.yml" func NewPage(dataStore string, cacheTemplates bool) *Page { var ptm *sync.Mutex + var sdm *sync.Mutex if cacheTemplates { ptm = &sync.Mutex{} + sdm = &sync.Mutex{} } pageToReturn := &Page{ DataStore: dataStore, - StoredDataMutex: &sync.Mutex{}, + StoredDataMutex: sdm, PageTemplateMutex: ptm, } return pageToReturn @@ -51,6 +53,17 @@ func (p *Page) GetLastModified() time.Time { } func (p *Page) GetCacheIDExtension(urlParameters url.Values) string { + toReturn := p.getNonThemedCleanQuery(urlParameters) + if toReturn != "" { + toReturn += "&" + } + if urlParameters.Has("light") { + toReturn += "light" + } + return strings.TrimRight(toReturn, "&") +} + +func (p *Page) getNonThemedCleanQuery(urlParameters url.Values) string { toReturn := "" if urlParameters.Has("order") { if theParameter := strings.ToLower(urlParameters.Get("order")); theParameter == "start" || theParameter == "end" || theParameter == "name" || theParameter == "duration" { @@ -59,12 +72,9 @@ func (p *Page) GetCacheIDExtension(urlParameters url.Values) string { } if urlParameters.Has("sort") { if theParameter := strings.ToLower(urlParameters.Get("sort")); theParameter == "asc" || theParameter == "ascending" || theParameter == "desc" || theParameter == "descending" { - toReturn += "sort=" + theParameter + "&" + toReturn += "sort=" + theParameter } } - if urlParameters.Has("light") { - toReturn += "light" - } return strings.TrimRight(toReturn, "&") } @@ -78,8 +88,9 @@ func (p *Page) GetContents(urlParameters url.Values) (contentType string, conten return "text/plain", []byte("Cannot Get Data.\r\n" + err.Error()), false } theMarshal := &Marshal{ - Data: *theData, - Light: urlParameters.Has("light"), + Data: *theData, + Light: urlParameters.Has("light"), + Parameters: template.URL(p.getNonThemedCleanQuery(urlParameters)), } switch strings.ToLower(urlParameters.Get("order")) { case "end": diff --git a/pageHandler/pages/index/template-marshal.go b/pageHandler/pages/index/template-marshal.go index afc91a0..32cfdea 100644 --- a/pageHandler/pages/index/template-marshal.go +++ b/pageHandler/pages/index/template-marshal.go @@ -1,9 +1,13 @@ package index -import "sort" +import ( + "html/template" + "sort" +) type Marshal struct { Data DataYaml + Parameters template.URL OrderStartDate int8 OrderEndDate int8 OrderName int8 @@ -15,12 +19,12 @@ func (m Marshal) GetEntries() (toReturn []EntryYaml) { toReturn = m.Data.Entries if m.OrderStartDate > 0 { sort.Slice(toReturn, func(i, j int) bool { - return toReturn[i].StartDate.Before(toReturn[j].StartDate) + return toReturn[i].StartDate.Before(toReturn[j].StartDate.Time) }) } if m.OrderStartDate < 0 { sort.Slice(toReturn, func(i, j int) bool { - return toReturn[i].StartDate.After(toReturn[j].StartDate) + return toReturn[i].StartDate.After(toReturn[j].StartDate.Time) }) } if m.OrderEndDate > 0 { diff --git a/utils/yaml/date-type.go b/utils/yaml/date-type.go new file mode 100644 index 0000000..910cd12 --- /dev/null +++ b/utils/yaml/date-type.go @@ -0,0 +1,31 @@ +package yaml + +import ( + "gopkg.in/yaml.v3" + "strings" + "time" +) + +const dateFormat = "02/01/2006" + +type DateType struct { + time.Time +} + +func (dt *DateType) MarshalYAML() (interface{}, error) { + return dt.Time.Format(dateFormat), nil +} + +func (dt *DateType) UnmarshalYAML(value *yaml.Node) error { + var stringIn string + err := value.Decode(&stringIn) + if err != nil { + return nil + } + pt, err := time.Parse(dateFormat, strings.TrimSpace(stringIn)) + if err != nil { + return err + } + dt.Time = pt + return nil +}