Initial commit
This commit is contained in:
commit
2514708522
19 changed files with 2332 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
# Project exclude paths
|
||||||
|
/tmp/
|
||||||
5
.idea/.gitignore
vendored
Normal file
5
.idea/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
18
.idea/dataSources.local.xml
Normal file
18
.idea/dataSources.local.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="dataSourceStorageLocal" created-in="GO-233.13135.104">
|
||||||
|
<data-source name="site.db" uuid="6b63d5bd-e451-4904-b659-21db5c54c16d">
|
||||||
|
<database-info product="SQLite" version="3.40.1" jdbc-version="4.2" driver-name="SQLite JDBC" driver-version="3.40.1.0" dbms="SQLITE" exact-version="3.40.1" exact-driver-version="3.40">
|
||||||
|
<identifier-quote-string>"</identifier-quote-string>
|
||||||
|
</database-info>
|
||||||
|
<case-sensitivity plain-identifiers="mixed" quoted-identifiers="mixed" />
|
||||||
|
<secret-storage>master_key</secret-storage>
|
||||||
|
<auth-provider>no-auth</auth-provider>
|
||||||
|
<schema-mapping>
|
||||||
|
<introspection-scope>
|
||||||
|
<node kind="schema" qname="@" />
|
||||||
|
</introspection-scope>
|
||||||
|
</schema-mapping>
|
||||||
|
</data-source>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
12
.idea/dataSources.xml
Normal file
12
.idea/dataSources.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||||
|
<data-source source="LOCAL" name="site.db" uuid="6b63d5bd-e451-4904-b659-21db5c54c16d">
|
||||||
|
<driver-ref>sqlite.xerial</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/site.db</jdbc-url>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
1475
.idea/dataSources/6b63d5bd-e451-4904-b659-21db5c54c16d.xml
Normal file
1475
.idea/dataSources/6b63d5bd-e451-4904-b659-21db5c54c16d.xml
Normal file
File diff suppressed because it is too large
Load diff
16
.idea/inspectionProfiles/Project_Default.xml
Normal file
16
.idea/inspectionProfiles/Project_Default.xml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="HtmlUnknownAttribute" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="myValues">
|
||||||
|
<value>
|
||||||
|
<list size="1">
|
||||||
|
<item index="0" class="java.lang.String" itemvalue="x:onclick" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
<option name="myCustomValuesEnabled" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="XmlUnboundNsPrefix" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
13
.idea/mhtml.iml
Normal file
13
.idea/mhtml.iml
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="Go" enabled="true" />
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/mhtml.iml" filepath="$PROJECT_DIR$/.idea/mhtml.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
4
.idea/watcherTasks.xml
Normal file
4
.idea/watcherTasks.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectTasksOptions" suppressed-tasks="Pug/Jade" />
|
||||||
|
</project>
|
||||||
91
database/database.go
Normal file
91
database/database.go
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
once sync.Once
|
||||||
|
rdb *redis.Client
|
||||||
|
)
|
||||||
|
|
||||||
|
func Connect() *redis.Client {
|
||||||
|
once.Do(func() {
|
||||||
|
var ctx = context.Background()
|
||||||
|
var err error
|
||||||
|
rdb = redis.NewClient(&redis.Options{
|
||||||
|
Addr: "localhost:6379",
|
||||||
|
Password: "", // no password set
|
||||||
|
DB: 0, // use default DB
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := rdb.Ping(ctx)
|
||||||
|
|
||||||
|
if cmd.Err() != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return rdb
|
||||||
|
}
|
||||||
|
|
||||||
|
func Set[T any](key string, value T) error {
|
||||||
|
db := Connect()
|
||||||
|
serialized, err := json.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
result := db.Set(context.Background(), key, serialized, time.Duration(0))
|
||||||
|
return result.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func HSet[T any](set string, key string, value T) error {
|
||||||
|
db := Connect()
|
||||||
|
serialized, err := json.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
result := db.HSet(context.Background(), set, key, serialized)
|
||||||
|
return result.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get[T any](key string) (*T, error) {
|
||||||
|
db := Connect()
|
||||||
|
val, err := db.Get(context.Background(), key).Result()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := new(T)
|
||||||
|
err = json.Unmarshal([]byte(val), result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func HList[T any](key string) ([]*T, error) {
|
||||||
|
db := Connect()
|
||||||
|
val, err := db.HGetAll(context.Background(), key).Result()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := make([]*T, len(val))
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
for _, t := range val {
|
||||||
|
item := new(T)
|
||||||
|
err = json.Unmarshal([]byte(t), item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result[count] = item
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
28
go.mod
Normal file
28
go.mod
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
module mhtml
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/gofiber/fiber/v2 v2.47.0 // indirect
|
||||||
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
|
github.com/klauspost/compress v1.16.3 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
||||||
|
github.com/philhofer/fwd v1.1.2 // indirect
|
||||||
|
github.com/redis/go-redis/v9 v9.0.5 // indirect
|
||||||
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
|
github.com/samber/lo v1.38.1 // indirect
|
||||||
|
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 // indirect
|
||||||
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
||||||
|
github.com/tinylib/msgp v1.1.8 // indirect
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/fasthttp v1.47.0 // indirect
|
||||||
|
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
|
||||||
|
golang.org/x/sys v0.9.0 // indirect
|
||||||
|
)
|
||||||
103
go.sum
Normal file
103
go.sum
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||||
|
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
|
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
||||||
|
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/gofiber/fiber/v2 v2.47.0 h1:EN5lHVCc+Pyqh5OEsk8fzRiifgwpbrP0rulQ4iNf3fs=
|
||||||
|
github.com/gofiber/fiber/v2 v2.47.0/go.mod h1:mbFMVN1lQuzziTkkakgtKKdjfsXSw9BKR5lmcNksUoU=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY=
|
||||||
|
github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||||
|
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
|
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||||
|
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
|
||||||
|
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
|
||||||
|
github.com/redis/go-redis/v9 v9.0.5 h1:CuQcn5HIEeK7BgElubPP8CGtE0KakrnbBSTLjathl5o=
|
||||||
|
github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
|
||||||
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
||||||
|
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||||
|
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 h1:rmMl4fXJhKMNWl+K+r/fq4FbbKI+Ia2m9hYBLm2h4G4=
|
||||||
|
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8=
|
||||||
|
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4=
|
||||||
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
||||||
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
||||||
|
github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=
|
||||||
|
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
|
||||||
|
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasthttp v1.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK9c=
|
||||||
|
github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
|
||||||
|
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||||
|
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||||
|
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=
|
||||||
|
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
|
||||||
|
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||||
|
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||||
|
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
|
||||||
|
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4=
|
||||||
|
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
89
h/render.go
Normal file
89
h/render.go
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
package h
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const FlagSkip = "skip"
|
||||||
|
|
||||||
|
type Builder struct {
|
||||||
|
builder *strings.Builder
|
||||||
|
root *Node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (page Builder) render() {
|
||||||
|
page.renderNode(page.root)
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertAttribute(node *Node, name string, value string) {
|
||||||
|
existing := node.attributes[name]
|
||||||
|
if existing != "" {
|
||||||
|
node.attributes[name] = existing + " " + value
|
||||||
|
} else {
|
||||||
|
node.attributes[name] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (page Builder) renderNode(node *Node) {
|
||||||
|
if node.tag != "" {
|
||||||
|
page.builder.WriteString(fmt.Sprintf("<%s", node.tag))
|
||||||
|
index := 0
|
||||||
|
|
||||||
|
if node.attributes == nil {
|
||||||
|
node.attributes = map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, child := range node.children {
|
||||||
|
if child.tag == "class" {
|
||||||
|
insertAttribute(node, "class", child.value)
|
||||||
|
child.tag = FlagSkip
|
||||||
|
}
|
||||||
|
|
||||||
|
if child.tag == "attribute" {
|
||||||
|
for key, value := range child.attributes {
|
||||||
|
insertAttribute(node, key, value)
|
||||||
|
}
|
||||||
|
child.tag = FlagSkip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range node.attributes {
|
||||||
|
if index == 0 {
|
||||||
|
page.builder.WriteString(" ")
|
||||||
|
}
|
||||||
|
page.builder.WriteString(key)
|
||||||
|
page.builder.WriteString("=")
|
||||||
|
page.builder.WriteRune('"')
|
||||||
|
page.builder.WriteString(value)
|
||||||
|
page.builder.WriteRune('"')
|
||||||
|
if index < len(node.attributes) {
|
||||||
|
page.builder.WriteRune(' ')
|
||||||
|
}
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
page.builder.WriteString(">")
|
||||||
|
if node.text != "" {
|
||||||
|
page.builder.WriteString(node.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, child := range node.children {
|
||||||
|
if child.tag != FlagSkip {
|
||||||
|
page.renderNode(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if node.tag != "" {
|
||||||
|
page.builder.WriteString(fmt.Sprintf("</%s>", node.tag))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Render(node *Node) string {
|
||||||
|
builder := strings.Builder{}
|
||||||
|
page := Builder{
|
||||||
|
builder: &builder,
|
||||||
|
root: node,
|
||||||
|
}
|
||||||
|
page.render()
|
||||||
|
d := page.builder.String()
|
||||||
|
return d
|
||||||
|
}
|
||||||
204
h/tag.go
Normal file
204
h/tag.go
Normal file
|
|
@ -0,0 +1,204 @@
|
||||||
|
package h
|
||||||
|
|
||||||
|
type Node struct {
|
||||||
|
tag string
|
||||||
|
attributes map[string]string
|
||||||
|
children []*Node
|
||||||
|
text string
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Class(value string) *Node {
|
||||||
|
return &Node{
|
||||||
|
tag: "class",
|
||||||
|
value: value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Id(value string) *Node {
|
||||||
|
return Attribute("id", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Attribute(key string, value string) *Node {
|
||||||
|
return &Node{
|
||||||
|
tag: "attribute",
|
||||||
|
attributes: map[string]string{
|
||||||
|
key: value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(url string) *Node {
|
||||||
|
return Attribute("hx-get", url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Post(url string) *Node {
|
||||||
|
return Attribute("hx-post", url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Trigger(trigger string) *Node {
|
||||||
|
return Attribute("hx-trigger", trigger)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Target(target string) *Node {
|
||||||
|
return Attribute("hx-target", target)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Name(name string) *Node {
|
||||||
|
return Attribute("name", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Confirm(message string) *Node {
|
||||||
|
return Attribute("hx-confirm", message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Href(path string) *Node {
|
||||||
|
return Attribute("href", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Type(name string) *Node {
|
||||||
|
return Attribute("type", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Placeholder(placeholder string) *Node {
|
||||||
|
return Attribute("placeholder", placeholder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Swap(swap string) *Node {
|
||||||
|
return Attribute("hx-swap", swap)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Click(value string) *Node {
|
||||||
|
return Attribute("onclick", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Page(children ...*Node) *Node {
|
||||||
|
return &Node{
|
||||||
|
tag: "html",
|
||||||
|
children: children,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Head(children ...*Node) *Node {
|
||||||
|
return &Node{
|
||||||
|
tag: "head",
|
||||||
|
children: children,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Body(children ...*Node) *Node {
|
||||||
|
return &Node{
|
||||||
|
tag: "body",
|
||||||
|
children: children,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Script(url string) *Node {
|
||||||
|
return &Node{
|
||||||
|
tag: "script",
|
||||||
|
attributes: map[string]string{
|
||||||
|
"src": url,
|
||||||
|
},
|
||||||
|
children: make([]*Node, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Div(children ...*Node) *Node {
|
||||||
|
return &Node{
|
||||||
|
tag: "div",
|
||||||
|
children: children,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Input(inputType string, children ...*Node) *Node {
|
||||||
|
return &Node{
|
||||||
|
tag: "input",
|
||||||
|
attributes: map[string]string{
|
||||||
|
"type": inputType,
|
||||||
|
},
|
||||||
|
children: children,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HStack(children ...*Node) *Node {
|
||||||
|
return &Node{
|
||||||
|
tag: "div",
|
||||||
|
attributes: map[string]string{
|
||||||
|
"class": "flex gap-2",
|
||||||
|
},
|
||||||
|
children: children,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func VStack(children ...*Node) *Node {
|
||||||
|
return &Node{
|
||||||
|
tag: "div",
|
||||||
|
attributes: map[string]string{
|
||||||
|
"class": "flex flex-col gap-2",
|
||||||
|
},
|
||||||
|
children: children,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func List[T any](items []T, mapper func(item T) *Node) *Node {
|
||||||
|
node := &Node{
|
||||||
|
tag: "",
|
||||||
|
children: make([]*Node, len(items)),
|
||||||
|
}
|
||||||
|
for index, value := range items {
|
||||||
|
node.children[index] = mapper(value)
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fragment(children ...*Node) *Node {
|
||||||
|
return &Node{
|
||||||
|
tag: "",
|
||||||
|
children: children,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Button(children ...*Node) *Node {
|
||||||
|
return &Node{
|
||||||
|
tag: "button",
|
||||||
|
children: children,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func P(text string, children ...*Node) *Node {
|
||||||
|
return &Node{
|
||||||
|
tag: "p",
|
||||||
|
children: children,
|
||||||
|
text: text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func A(text string, children ...*Node) *Node {
|
||||||
|
return &Node{
|
||||||
|
tag: "a",
|
||||||
|
children: children,
|
||||||
|
text: text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Empty() *Node {
|
||||||
|
return &Node{
|
||||||
|
tag: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func If(condition bool, node *Node) *Node {
|
||||||
|
if condition {
|
||||||
|
return node
|
||||||
|
} else {
|
||||||
|
return Empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IfElse(condition bool, node *Node, node2 *Node) *Node {
|
||||||
|
if condition {
|
||||||
|
return node
|
||||||
|
} else {
|
||||||
|
return node2
|
||||||
|
}
|
||||||
|
}
|
||||||
56
httpjson/http.go
Normal file
56
httpjson/http.go
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
package httpjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var client *http.Client
|
||||||
|
var once sync.Once
|
||||||
|
|
||||||
|
func getClient() *http.Client {
|
||||||
|
once.Do(func() {
|
||||||
|
tr := &http.Transport{
|
||||||
|
MaxIdleConns: 10,
|
||||||
|
IdleConnTimeout: 15 * time.Second,
|
||||||
|
ResponseHeaderTimeout: 15 * time.Second,
|
||||||
|
DisableKeepAlives: false,
|
||||||
|
}
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: tr,
|
||||||
|
// do not follow redirects
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client = httpClient
|
||||||
|
})
|
||||||
|
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get[T any](url string) (T, error) {
|
||||||
|
resp, err := getClient().Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return *new(T), err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
io.Copy(io.Discard, resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return *new(T), err
|
||||||
|
}
|
||||||
|
d := new(T)
|
||||||
|
err = json.Unmarshal(body, &d)
|
||||||
|
if err != nil {
|
||||||
|
return *new(T), err
|
||||||
|
}
|
||||||
|
return *d, nil
|
||||||
|
}
|
||||||
24
index.html
Normal file
24
index.html
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<script src="https://unpkg.com/htmx.org@1.9.2"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="flex flex-col gap-2 flex flex-col gap-2 bg-gray-100 h-full w-full items-center p-12">
|
||||||
|
<div>
|
||||||
|
<p>Log in
|
||||||
|
<button></button>
|
||||||
|
</p>
|
||||||
|
<div class="flex flex-col gap-2 items-center"><p>Sydne Anschutz</p>
|
||||||
|
<p>sanschutz0808@gmail.com</p>
|
||||||
|
<button class="bg-red-200 rounded p-2" onclick="alert(1)"><p>Delete</p></button>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2 items-center"><p>Maddy</p>
|
||||||
|
<p>jm@madev.me</p>
|
||||||
|
<button class="bg-red-200 rounded p-2" onclick="alert(1)"><p>Delete</p></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div><p>Enter name</p><input type="text" class="rounded p-4 border-gray-100"></input></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
86
main.go
Normal file
86
main.go
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"mhtml/database"
|
||||||
|
"mhtml/h"
|
||||||
|
"mhtml/news"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Name string
|
||||||
|
Email string
|
||||||
|
}
|
||||||
|
|
||||||
|
var Version = time.Now().Nanosecond()
|
||||||
|
|
||||||
|
func LiveReloadHandler(c *fiber.Ctx) error {
|
||||||
|
v := strconv.FormatInt(int64(Version), 10)
|
||||||
|
current := c.Cookies("version", v)
|
||||||
|
|
||||||
|
if current != v {
|
||||||
|
c.Set("HX-Refresh", "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Cookie(&fiber.Cookie{
|
||||||
|
Name: "version",
|
||||||
|
Value: v,
|
||||||
|
})
|
||||||
|
return c.SendString("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Page(children ...*h.Node) *h.Node {
|
||||||
|
return h.Page(
|
||||||
|
h.Head(
|
||||||
|
h.Script("https://cdn.tailwindcss.com"),
|
||||||
|
h.Script("https://unpkg.com/htmx.org@1.9.2"),
|
||||||
|
),
|
||||||
|
h.Body(
|
||||||
|
h.VStack(
|
||||||
|
h.Class("flex flex-col gap-2 bg-gray-100 h-full w-full items-center p-12"),
|
||||||
|
h.Fragment(children...),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IndexPage(c *fiber.Ctx) error {
|
||||||
|
page := Page(
|
||||||
|
h.Div(
|
||||||
|
h.P("Hacker News - Top Stories"),
|
||||||
|
),
|
||||||
|
news.StoryList(),
|
||||||
|
)
|
||||||
|
return HtmlView(c, page)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HtmlView(c *fiber.Ctx, child *h.Node) error {
|
||||||
|
c.Set(fiber.HeaderContentType, fiber.MIMETextHTML)
|
||||||
|
return c.SendString(
|
||||||
|
h.Render(
|
||||||
|
child,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
database.HSet("users", "sydne", User{
|
||||||
|
Name: "Sydne Anschutz",
|
||||||
|
Email: "sanschutz0808@gmail.com",
|
||||||
|
})
|
||||||
|
|
||||||
|
app := fiber.New()
|
||||||
|
|
||||||
|
app.Get("/", IndexPage)
|
||||||
|
app.Get("/news/:id", func(c *fiber.Ctx) error {
|
||||||
|
return HtmlView(c, Page(
|
||||||
|
news.StoryFull(c.Params("id")),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
app.Get("/livereload", LiveReloadHandler)
|
||||||
|
|
||||||
|
app.Listen(":3000")
|
||||||
|
}
|
||||||
58
news/posts.go
Normal file
58
news/posts.go
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
package news
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"mhtml/httpjson"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Post struct {
|
||||||
|
By string `json:"by"`
|
||||||
|
Descendants int `json:"descendants"`
|
||||||
|
Id int `json:"id"`
|
||||||
|
Kids []int `json:"kids"`
|
||||||
|
Score int `json:"score"`
|
||||||
|
Time int `json:"time"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func List() ([]Post, error) {
|
||||||
|
responseIds, err := httpjson.Get[[]int64]("https://hacker-news.firebaseio.com/v0/topstories.json")
|
||||||
|
responseIds = responseIds[0:50]
|
||||||
|
if err != nil {
|
||||||
|
return []Post{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
posts := make([]Post, len(responseIds))
|
||||||
|
|
||||||
|
for index, id := range responseIds {
|
||||||
|
wg.Add(1)
|
||||||
|
id := id
|
||||||
|
index := index
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
url := fmt.Sprintf("https://hacker-news.firebaseio.com/v0/item/%d.json", id)
|
||||||
|
post, err := httpjson.Get[Post](url)
|
||||||
|
if err != nil {
|
||||||
|
println(err.Error())
|
||||||
|
}
|
||||||
|
posts[index] = post
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
return posts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(id string) (Post, error) {
|
||||||
|
url := fmt.Sprintf("https://hacker-news.firebaseio.com/v0/item/%s.json", id)
|
||||||
|
post, err := httpjson.Get[Post](url)
|
||||||
|
if err != nil {
|
||||||
|
return Post{}, err
|
||||||
|
}
|
||||||
|
return post, nil
|
||||||
|
}
|
||||||
40
news/views.go
Normal file
40
news/views.go
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
package news
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"mhtml/h"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StoryList() *h.Node {
|
||||||
|
posts, err := List()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return h.P(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(posts) == 0 {
|
||||||
|
return h.P("No results found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.Fragment(
|
||||||
|
h.VStack(h.List(posts, func(item Post) *h.Node {
|
||||||
|
return StoryCard(item)
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func StoryCard(post Post) *h.Node {
|
||||||
|
url := fmt.Sprintf("/news/%d", post.Id)
|
||||||
|
return h.VStack(
|
||||||
|
h.Class("items-center bg-red-200 p-4 rounded"),
|
||||||
|
h.A(post.Title, h.Href(url)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func StoryFull(id string) *h.Node {
|
||||||
|
post, err := Get(id)
|
||||||
|
if err != nil {
|
||||||
|
return h.P(err.Error())
|
||||||
|
}
|
||||||
|
return StoryCard(post)
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue