Initial commit

This commit is contained in:
maddalax 2024-01-22 09:22:16 -06:00
commit 2514708522
19 changed files with 2332 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
# Project exclude paths
/tmp/

5
.idea/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

View 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>&quot;</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
View 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>

File diff suppressed because it is too large Load diff

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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)
}