Skip to content

Commit 56122ea

Browse files
- Redesigned the documentation page
- Removed 'run on postman' button from readme and doc page
1 parent aa45976 commit 56122ea

6 files changed

Lines changed: 289 additions & 32 deletions

File tree

README.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,6 @@ This API provides endpoints to retrieve information about the historic City-Poly
66

77
Version 2 introduces user accounts and API key authentication. All data endpoints require an API key passed via the `X-API-Key` header.
88

9-
## Try it in Postman
10-
------------
11-
[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/thrtn85/main/api/2451bd7d-f19d-4b66-9317-a605ced2ba0c/entity?action=share&creator=13028315)
12-
139
---
1410

1511
## Getting Started

frontend/documentation.go

Lines changed: 98 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,116 @@
11
package frontend
22

33
import (
4-
"html/template"
4+
"bufio"
55
"net/http"
66
"os"
7+
"strings"
78

89
"github.com/gin-gonic/gin"
9-
"github.com/microcosm-cc/bluemonday"
10-
"github.com/russross/blackfriday"
1110
)
1211

12+
type EndpointDoc struct {
13+
Name string
14+
Method string // GET, POST, DELETE
15+
MethodClass string // get, post, delete — for CSS
16+
URL string
17+
Auth string
18+
Description string
19+
}
20+
21+
type EndpointGroup struct {
22+
Title string
23+
IsData bool // true for "Data Endpoints" — controls blue accent
24+
Endpoints []EndpointDoc
25+
}
26+
27+
// parseEndpointGroups reads README.md and extracts endpoint sections into structured data.
28+
func parseEndpointGroups(path string) ([]EndpointGroup, error) {
29+
f, err := os.Open(path)
30+
if err != nil {
31+
return nil, err
32+
}
33+
defer f.Close()
34+
35+
var groups []EndpointGroup
36+
var currentGroup *EndpointGroup
37+
var currentEndpoint *EndpointDoc
38+
39+
flushEndpoint := func() {
40+
if currentEndpoint != nil && currentGroup != nil {
41+
currentGroup.Endpoints = append(currentGroup.Endpoints, *currentEndpoint)
42+
currentEndpoint = nil
43+
}
44+
}
45+
46+
scanner := bufio.NewScanner(f)
47+
for scanner.Scan() {
48+
line := scanner.Text()
49+
50+
switch {
51+
case strings.HasPrefix(line, "## "):
52+
flushEndpoint()
53+
if currentGroup != nil {
54+
groups = append(groups, *currentGroup)
55+
}
56+
if strings.Contains(line, "Endpoints") {
57+
title := strings.TrimPrefix(line, "## ")
58+
currentGroup = &EndpointGroup{
59+
Title: title,
60+
IsData: strings.Contains(title, "Data"),
61+
}
62+
} else {
63+
currentGroup = nil
64+
}
65+
66+
case strings.HasPrefix(line, "### ") && currentGroup != nil:
67+
flushEndpoint()
68+
name := strings.TrimPrefix(line, "### ")
69+
currentEndpoint = &EndpointDoc{Name: name}
70+
71+
case strings.HasPrefix(line, "- **URL**:") && currentEndpoint != nil:
72+
currentEndpoint.URL = extractValue(line)
73+
74+
case strings.HasPrefix(line, "- **Method**:") && currentEndpoint != nil:
75+
currentEndpoint.Method = extractValue(line)
76+
currentEndpoint.MethodClass = strings.ToLower(currentEndpoint.Method)
77+
78+
case strings.HasPrefix(line, "- **Auth**:") && currentEndpoint != nil:
79+
currentEndpoint.Auth = extractValue(line)
80+
81+
case strings.HasPrefix(line, "- **Description**:") && currentEndpoint != nil:
82+
currentEndpoint.Description = extractValue(line)
83+
}
84+
}
85+
86+
flushEndpoint()
87+
if currentGroup != nil {
88+
groups = append(groups, *currentGroup)
89+
}
90+
91+
return groups, scanner.Err()
92+
}
93+
94+
// extractValue pulls the value after the first colon on a markdown list line,
95+
// stripping surrounding backticks and whitespace.
96+
func extractValue(line string) string {
97+
idx := strings.Index(line, ":")
98+
if idx < 0 {
99+
return ""
100+
}
101+
val := strings.TrimSpace(line[idx+1:])
102+
val = strings.Trim(val, "`")
103+
return strings.TrimSpace(val)
104+
}
105+
13106
func DocumentationPageHandler(c *gin.Context) {
14-
mdContent, err := os.ReadFile("README.md")
107+
groups, err := parseEndpointGroups("README.md")
15108
if err != nil {
16109
c.String(http.StatusInternalServerError, "Failed to read documentation")
17110
return
18111
}
19112

20-
// Convert markdown to HTML
21-
unsafeHTML := blackfriday.MarkdownCommon(mdContent)
22-
23-
// Sanitize HTML to prevent XSS
24-
policy := bluemonday.UGCPolicy()
25-
safeHTML := policy.SanitizeBytes(unsafeHTML)
26-
27113
c.HTML(http.StatusOK, "documentation.html", gin.H{
28-
"Content": template.HTML(safeHTML),
114+
"Groups": groups,
29115
})
30116
}

frontend/static/styles.css

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,153 @@ pre code {
494494
margin: 0 .75rem;
495495
}
496496

497+
/* ── Docs intro ───────────────────────────────────── */
498+
.docs-intro {
499+
background: var(--surface);
500+
border: 1px solid var(--border);
501+
border-left: 4px solid var(--poly-blue);
502+
border-radius: 8px;
503+
padding: 1.5rem;
504+
}
505+
506+
.gs-steps {
507+
display: flex;
508+
flex-direction: column;
509+
gap: .6rem;
510+
}
511+
512+
.gs-step {
513+
display: flex;
514+
align-items: baseline;
515+
gap: .75rem;
516+
font-size: .9rem;
517+
}
518+
519+
.gs-num {
520+
display: inline-flex;
521+
align-items: center;
522+
justify-content: center;
523+
width: 1.4rem;
524+
height: 1.4rem;
525+
background: var(--poly-blue);
526+
color: #fff;
527+
border-radius: 50%;
528+
font-size: .7rem;
529+
font-weight: 700;
530+
flex-shrink: 0;
531+
}
532+
533+
/* ── Endpoint cards ───────────────────────────────── */
534+
.endpoint-grid {
535+
display: grid;
536+
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
537+
gap: 1rem;
538+
}
539+
540+
.endpoint-card {
541+
background: var(--surface);
542+
border: 1px solid var(--border);
543+
border-top: 3px solid var(--city-black);
544+
border-radius: 8px;
545+
padding: 1.25rem;
546+
transition: border-top-color .2s;
547+
}
548+
549+
.endpoint-card:hover {
550+
border-top-color: var(--orange);
551+
}
552+
553+
.endpoint-card-name {
554+
font-weight: 600;
555+
font-size: .9rem;
556+
}
557+
558+
.endpoint-card-path {
559+
display: inline-block;
560+
background: #f0f2f5;
561+
color: var(--poly-blue);
562+
font-family: "Source Code Pro", monospace;
563+
font-size: .78rem;
564+
padding: .2rem .5rem;
565+
border-radius: 4px;
566+
}
567+
568+
.endpoint-card-desc {
569+
font-size: .85rem;
570+
color: var(--muted);
571+
line-height: 1.5;
572+
margin: 0;
573+
}
574+
575+
.method-badge {
576+
display: inline-block;
577+
font-size: .65rem;
578+
font-weight: 700;
579+
letter-spacing: .06em;
580+
padding: .2rem .45rem;
581+
border-radius: 3px;
582+
text-transform: uppercase;
583+
flex-shrink: 0;
584+
}
585+
586+
.method-get { background: rgba(29,79,168,.12); color: var(--poly-blue); }
587+
.method-post { background: rgba(236,111,9,.12); color: var(--orange-dark); }
588+
.method-delete { background: rgba(180,30,30,.1); color: #b41e1e; }
589+
590+
.endpoint-auth-tag {
591+
display: inline-block;
592+
font-size: .7rem;
593+
color: var(--muted);
594+
background: var(--bg);
595+
border: 1px solid var(--border);
596+
border-radius: 100px;
597+
padding: .15rem .55rem;
598+
}
599+
600+
/* ── Docs schema ──────────────────────────────────── */
601+
.docs-schema {
602+
background: var(--surface);
603+
border: 1px solid var(--border);
604+
border-radius: 8px;
605+
overflow: hidden;
606+
}
607+
608+
.schema-row {
609+
display: flex;
610+
align-items: baseline;
611+
gap: 1rem;
612+
padding: .65rem 1.25rem;
613+
border-bottom: 1px solid var(--border);
614+
font-size: .875rem;
615+
}
616+
617+
.schema-row:last-child { border-bottom: none; }
618+
619+
.schema-key {
620+
min-width: 140px;
621+
color: var(--orange);
622+
background: #1e1e1e;
623+
font-size: .8rem;
624+
}
625+
626+
.schema-type {
627+
min-width: 60px;
628+
color: var(--poly-blue);
629+
font-size: .8rem;
630+
font-style: italic;
631+
}
632+
633+
.schema-desc { color: var(--muted); }
634+
635+
/* ── Docs note ────────────────────────────────────── */
636+
.docs-note {
637+
background: var(--surface);
638+
border: 1px solid var(--border);
639+
border-radius: 8px;
640+
padding: 1rem 1.25rem;
641+
font-size: .875rem;
642+
}
643+
497644
/* ── Utility ──────────────────────────────────────── */
498645
.text-orange { color: var(--orange) !important; }
499646
.text-poly-blue { color: var(--poly-blue) !important; }

frontend/templates/documentation.html

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,53 @@
1616
<h1>Documentation</h1>
1717
</div>
1818

19-
<div class="row">
20-
<div class="col-lg-9">
21-
<div class="docs-body">
22-
{{.Content}}
19+
<!-- Getting Started -->
20+
<div class="docs-intro mb-5">
21+
<p class="mb-3">This API provides endpoints to retrieve data about the historic City-Poly rivalry game. All data endpoints require an API key passed via the <code>X-API-Key</code> header.</p>
22+
<div class="gs-steps">
23+
<div class="gs-step"><span class="gs-num">1</span><span><strong>Register</strong> — Create an account with your email and password.</span></div>
24+
<div class="gs-step"><span class="gs-num">2</span><span><strong>Login</strong> — Receive a bearer token.</span></div>
25+
<div class="gs-step"><span class="gs-num">3</span><span><strong>Create API Key</strong> — Use the bearer token to generate an API key.</span></div>
26+
<div class="gs-step"><span class="gs-num">4</span><span><strong>Use the API Key</strong> — Pass it as <code>X-API-Key</code> on all data requests.</span></div>
27+
</div>
28+
</div>
29+
30+
<!-- Endpoint groups parsed from README -->
31+
{{range .Groups}}
32+
<div class="section-title{{if .IsData}} blue{{end}} mb-3">{{.Title}}</div>
33+
<div class="endpoint-grid mb-5">
34+
{{range .Endpoints}}
35+
<div class="endpoint-card">
36+
<div class="d-flex align-items-center gap-2 mb-2">
37+
<span class="method-badge method-{{.MethodClass}}">{{.Method}}</span>
38+
<span class="endpoint-card-name">{{.Name}}</span>
2339
</div>
40+
<div class="endpoint-card-path mb-2">{{.URL}}</div>
41+
{{if .Description}}<p class="endpoint-card-desc mb-2">{{.Description}}</p>{{end}}
42+
{{if .Auth}}<span class="endpoint-auth-tag">{{.Auth}}</span>{{end}}
2443
</div>
44+
{{end}}
45+
</div>
46+
{{end}}
47+
48+
<!-- Game Object -->
49+
<div class="section-title mb-3">Game Object</div>
50+
<div class="docs-schema mb-5">
51+
<div class="schema-row"><code class="schema-key">id</code><span class="schema-type">integer</span><span class="schema-desc">Unique identifier</span></div>
52+
<div class="schema-row"><code class="schema-key">home_team</code><span class="schema-type">string</span><span class="schema-desc">Name of the home team</span></div>
53+
<div class="schema-row"><code class="schema-key">away_team</code><span class="schema-type">string</span><span class="schema-desc">Name of the away team</span></div>
54+
<div class="schema-row"><code class="schema-key">date</code><span class="schema-type">string</span><span class="schema-desc">Date of the game — <code>YYYY-MM-DD</code></span></div>
55+
<div class="schema-row"><code class="schema-key">home_team_score</code><span class="schema-type">integer</span><span class="schema-desc">Home team score</span></div>
56+
<div class="schema-row"><code class="schema-key">away_team_score</code><span class="schema-type">integer</span><span class="schema-desc">Away team score</span></div>
57+
<div class="schema-row"><code class="schema-key">notes</code><span class="schema-type">string</span><span class="schema-desc">Additional notes about the game</span></div>
58+
</div>
59+
60+
<!-- Rate Limiting -->
61+
<div class="section-title mb-3">Rate Limiting</div>
62+
<div class="docs-note mb-5">
63+
Requests are limited to <strong>10 per second</strong> per IP with a burst allowance of 20. Exceeding this returns <code>429 Too Many Requests</code>.
2564
</div>
65+
2666
</div>
2767

2868
{{template "footer" .}}

go.mod

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,13 @@ require (
77
github.com/gin-gonic/gin v1.10.1
88
github.com/glebarez/sqlite v1.11.0
99
github.com/joho/godotenv v1.5.1
10-
github.com/microcosm-cc/bluemonday v1.0.27
11-
github.com/russross/blackfriday v1.6.0
1210
golang.org/x/crypto v0.45.0
1311
golang.org/x/time v0.14.0
1412
gorm.io/driver/postgres v1.6.0
1513
gorm.io/gorm v1.25.11
1614
)
1715

1816
require (
19-
github.com/aymerick/douceur v0.2.0 // indirect
2017
github.com/bytedance/sonic v1.13.3 // indirect
2118
github.com/bytedance/sonic/loader v0.2.4 // indirect
2219
github.com/cloudwego/base64x v0.1.5 // indirect
@@ -29,7 +26,6 @@ require (
2926
github.com/go-playground/validator/v10 v10.26.0 // indirect
3027
github.com/goccy/go-json v0.10.5 // indirect
3128
github.com/google/uuid v1.6.0 // indirect
32-
github.com/gorilla/css v1.0.1 // indirect
3329
github.com/jackc/pgpassfile v1.0.0 // indirect
3430
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
3531
github.com/jackc/pgx/v5 v5.6.0 // indirect

0 commit comments

Comments
 (0)