big work on templating, added middleware
108
assets/dk.css
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
In general, we don't want to stray too far from the original appearance.
|
||||||
|
We'll optimize the layout for modern CSS and use better styles and fonts
|
||||||
|
for legibility and design, but keep the soul of the original project.
|
||||||
|
*/
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
color: #222;
|
||||||
|
font-family: Cambria, Cochin, Georgia, Times, 'Times New Roman', serif;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
padding: 1rem;
|
||||||
|
background-image: url("/assets/images/backgrounds/snowstorm.jpg");
|
||||||
|
}
|
||||||
|
|
||||||
|
div#container {
|
||||||
|
width: 90vw;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 0px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
nav {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
section#game {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 180px 1fr 180px;
|
||||||
|
margin: 1rem 0;
|
||||||
|
border-top: 2px solid #000;
|
||||||
|
|
||||||
|
& > aside#left {
|
||||||
|
grid-column: 1;
|
||||||
|
border-right: 2px solid #000;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > main {
|
||||||
|
grid-column: 2;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > aside#right {
|
||||||
|
grid-column: 3;
|
||||||
|
border-left: 2px solid #000;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #663300;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #330000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.small {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light {
|
||||||
|
color: #999999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
border: solid 1px black;
|
||||||
|
background-color: #eeeeee;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 5px;
|
||||||
|
margin: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
|
||||||
|
& > div {
|
||||||
|
flex: 0 0 25%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
BIN
assets/images/backgrounds/background.gif
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
assets/images/backgrounds/classic.jpg
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
assets/images/backgrounds/snowstorm.jpg
Normal file
After Width: | Height: | Size: 386 KiB |
BIN
assets/images/bars_green.gif
Normal file
After Width: | Height: | Size: 94 B |
BIN
assets/images/bars_red.gif
Normal file
After Width: | Height: | Size: 94 B |
BIN
assets/images/bars_yellow.gif
Normal file
After Width: | Height: | Size: 94 B |
BIN
assets/images/button_character.gif
Normal file
After Width: | Height: | Size: 527 B |
BIN
assets/images/button_fastspells.gif
Normal file
After Width: | Height: | Size: 575 B |
BIN
assets/images/button_functions.gif
Normal file
After Width: | Height: | Size: 561 B |
BIN
assets/images/button_help.gif
Normal file
After Width: | Height: | Size: 402 B |
BIN
assets/images/button_inventory.gif
Normal file
After Width: | Height: | Size: 551 B |
BIN
assets/images/button_location.gif
Normal file
After Width: | Height: | Size: 486 B |
BIN
assets/images/button_login.gif
Normal file
After Width: | Height: | Size: 474 B |
BIN
assets/images/button_logout.gif
Normal file
After Width: | Height: | Size: 500 B |
BIN
assets/images/button_register.gif
Normal file
After Width: | Height: | Size: 523 B |
BIN
assets/images/button_shoutbox.gif
Normal file
After Width: | Height: | Size: 565 B |
BIN
assets/images/button_spells.gif
Normal file
After Width: | Height: | Size: 469 B |
BIN
assets/images/button_status.gif
Normal file
After Width: | Height: | Size: 469 B |
BIN
assets/images/button_towns.gif
Normal file
After Width: | Height: | Size: 461 B |
BIN
assets/images/compass.webp
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
assets/images/dkforumsbutton.gif
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
assets/images/help_exploring.jpg
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
assets/images/help_fighting.jpg
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
assets/images/icon_armor.gif
Normal file
After Width: | Height: | Size: 147 B |
BIN
assets/images/icon_shield.gif
Normal file
After Width: | Height: | Size: 121 B |
BIN
assets/images/icon_weapon.gif
Normal file
After Width: | Height: | Size: 112 B |
BIN
assets/images/logo.gif
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
assets/images/map.gif
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
assets/images/title_exploring.gif
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
assets/images/title_fighting.gif
Normal file
After Width: | Height: | Size: 1005 B |
BIN
assets/images/town_1.gif
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/images/town_2.gif
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/images/town_3.gif
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/images/town_4.gif
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/images/town_5.gif
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/images/town_6.gif
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/images/town_7.gif
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/images/town_8.gif
Normal file
After Width: | Height: | Size: 1.7 KiB |
4
internal/middleware/doc.go
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
// Package middleware provides reusable HTTP middleware for the Dragon Knight server.
|
||||||
|
// Middleware functions wrap request handlers to add cross-cutting functionality
|
||||||
|
// like timing, logging, authentication, and request processing.
|
||||||
|
package middleware
|
49
internal/middleware/timing.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"dk/internal/router"
|
||||||
|
)
|
||||||
|
|
||||||
|
const RequestTimerKey = "request_start_time"
|
||||||
|
|
||||||
|
// Timing adds request timing functionality
|
||||||
|
func Timing() router.Middleware {
|
||||||
|
return func(next router.Handler) router.Handler {
|
||||||
|
return func(ctx router.Ctx, params []string) {
|
||||||
|
startTime := time.Now()
|
||||||
|
ctx.SetUserValue(RequestTimerKey, startTime)
|
||||||
|
|
||||||
|
next(ctx, params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRequestTime returns the total request processing time in seconds (formatted)
|
||||||
|
func GetRequestTime(ctx router.Ctx) string {
|
||||||
|
startTime, ok := ctx.UserValue(RequestTimerKey).(time.Time)
|
||||||
|
if !ok {
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
duration := time.Since(startTime)
|
||||||
|
seconds := duration.Seconds()
|
||||||
|
|
||||||
|
if seconds < 0.001 {
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%.3f", seconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRequestDuration returns the raw duration
|
||||||
|
func GetRequestDuration(ctx router.Ctx) time.Duration {
|
||||||
|
startTime, ok := ctx.UserValue(RequestTimerKey).(time.Time)
|
||||||
|
if !ok {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Since(startTime)
|
||||||
|
}
|
@ -6,6 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"dk/internal/middleware"
|
||||||
"dk/internal/router"
|
"dk/internal/router"
|
||||||
"dk/internal/template"
|
"dk/internal/template"
|
||||||
|
|
||||||
@ -19,41 +20,48 @@ func Start(port string) error {
|
|||||||
return fmt.Errorf("failed to get current working directory: %w", err)
|
return fmt.Errorf("failed to get current working directory: %w", err)
|
||||||
}
|
}
|
||||||
templateCache := template.NewCache(cwd)
|
templateCache := template.NewCache(cwd)
|
||||||
|
|
||||||
// Initialize router
|
// Initialize router
|
||||||
r := router.New()
|
r := router.New()
|
||||||
|
|
||||||
|
// Add timing middleware
|
||||||
|
r.Use(middleware.Timing())
|
||||||
|
|
||||||
// Hello world endpoint
|
// Hello world endpoint
|
||||||
r.Get("/", func(ctx router.Ctx, params []string) {
|
r.Get("/", func(ctx router.Ctx, params []string) {
|
||||||
tmpl, err := templateCache.Load("hello.html")
|
tmpl, err := templateCache.Load("layout.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
||||||
fmt.Fprintf(ctx, "Template error: %v", err)
|
fmt.Fprintf(ctx, "Template error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data := map[string]any{
|
data := map[string]any{
|
||||||
"title": "Dragon Knight",
|
"title": "Dragon Knight",
|
||||||
"message": "Hello World!",
|
"content": "Hello World!",
|
||||||
|
"totaltime": middleware.GetRequestTime(ctx),
|
||||||
|
"numqueries": "0", // Placeholder for now
|
||||||
|
"version": "1.0.0",
|
||||||
|
"build": "dev",
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpl.WriteTo(ctx, data)
|
tmpl.WriteTo(ctx, data)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Use current working directory for static files
|
// Use current working directory for static files
|
||||||
assetsDir := filepath.Join(cwd, "assets")
|
assetsDir := filepath.Join(cwd, "assets")
|
||||||
|
|
||||||
// Static file server for /assets
|
// Static file server for /assets
|
||||||
fs := &fasthttp.FS{
|
fs := &fasthttp.FS{
|
||||||
Root: assetsDir,
|
Root: assetsDir,
|
||||||
Compress: true,
|
Compress: false,
|
||||||
}
|
}
|
||||||
assetsHandler := fs.NewRequestHandler()
|
assetsHandler := fs.NewRequestHandler()
|
||||||
|
|
||||||
// Combined handler
|
// Combined handler
|
||||||
requestHandler := func(ctx *fasthttp.RequestCtx) {
|
requestHandler := func(ctx *fasthttp.RequestCtx) {
|
||||||
path := string(ctx.Path())
|
path := string(ctx.Path())
|
||||||
|
|
||||||
// Handle static assets - strip /assets prefix
|
// Handle static assets - strip /assets prefix
|
||||||
if len(path) >= 7 && path[:7] == "/assets" {
|
if len(path) >= 7 && path[:7] == "/assets" {
|
||||||
// Strip the /assets prefix for the file system handler
|
// Strip the /assets prefix for the file system handler
|
||||||
@ -63,12 +71,12 @@ func Start(port string) error {
|
|||||||
ctx.Request.URI().SetPathBytes(originalPath) // Restore original path
|
ctx.Request.URI().SetPathBytes(originalPath) // Restore original path
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle routes
|
// Handle routes
|
||||||
r.ServeHTTP(ctx)
|
r.ServeHTTP(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := ":" + port
|
addr := ":" + port
|
||||||
log.Printf("Server starting on %s", addr)
|
log.Printf("Server starting on %s", addr)
|
||||||
return fasthttp.ListenAndServe(addr, requestHandler)
|
return fasthttp.ListenAndServe(addr, requestHandler)
|
||||||
}
|
}
|
||||||
|
43
templates/layout.html
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{title}</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/assets/dk.css">
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function open_char_popup()
|
||||||
|
{
|
||||||
|
window.open("/showchar", "", "width=210,height=500,scrollbars")
|
||||||
|
}
|
||||||
|
|
||||||
|
function open_map_popup()
|
||||||
|
{
|
||||||
|
window.open("/showmap", "", "width=520,height=520,scrollbars")
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="container">
|
||||||
|
<header>
|
||||||
|
<div><img src="/assets/images/logo.gif" alt="Dragon Knight" title="Dragon Knight"></div>
|
||||||
|
<nav>{topnav}</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section id="game">
|
||||||
|
<aside id="left">{leftside}</aside>
|
||||||
|
<main>{content}</main>
|
||||||
|
<aside id="right">{rightside}</aside>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<div>Powered by <a href="/">Dragon Knight</a></div>
|
||||||
|
<div>© 2025 Sharkk</div>
|
||||||
|
<div>{totaltime} Seconds, {numqueries} Queries</div>
|
||||||
|
<div>Version {version} {build}</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|