initial commit
This commit is contained in:
5
.env
Normal file
5
.env
Normal file
@@ -0,0 +1,5 @@
|
||||
PSQL_HOST=127.0.0.1
|
||||
PSQL_NAME=hyst2
|
||||
PSQL_USER=hyst2
|
||||
PSQL_PASS=123456
|
||||
JWT_SECRET=CHANGEME
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.idea/
|
||||
provider.json
|
||||
14
Dockerfile
Normal file
14
Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
||||
FROM golang:1.26-alpine AS builder
|
||||
WORKDIR /src
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY ./app ./app/
|
||||
COPY main.go .
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg/mod go build -v -o api .
|
||||
|
||||
FROM alpine:3.23 AS runner
|
||||
WORKDIR /app
|
||||
COPY --from=builder /src/api /app/api
|
||||
EXPOSE 8080
|
||||
CMD ["/app/api"]
|
||||
USER nobody
|
||||
3
Makefile
Normal file
3
Makefile
Normal file
@@ -0,0 +1,3 @@
|
||||
build:
|
||||
docker build -t git.nix13.pw/scuroneko/h2master:latest .
|
||||
docker push git.nix13.pw/scuroneko/h2master:latest
|
||||
79
app/api.go
Normal file
79
app/api.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
Ok bool `json:"ok"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
func WriteError(w http.ResponseWriter, err error) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
e := json.NewEncoder(w).Encode(Error{
|
||||
Ok: false,
|
||||
Error: err.Error(),
|
||||
})
|
||||
if e != nil {
|
||||
log.Println(e)
|
||||
}
|
||||
}
|
||||
|
||||
type Response[T any] struct {
|
||||
Ok bool `json:"ok"`
|
||||
Data T `json:"data"`
|
||||
}
|
||||
|
||||
func WriteResponse[T any](w http.ResponseWriter, data T) {
|
||||
WriteResponseCode(w, data, 200)
|
||||
}
|
||||
func WriteResponseCode[T any](w http.ResponseWriter, data T, code int) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(code)
|
||||
enc := json.NewEncoder(w)
|
||||
enc.SetEscapeHTML(false)
|
||||
e := enc.Encode(Response[T]{
|
||||
Ok: true,
|
||||
Data: data,
|
||||
})
|
||||
if e != nil {
|
||||
log.Println(e)
|
||||
}
|
||||
}
|
||||
|
||||
func WriteResponseOrError[T any](w http.ResponseWriter, data T, err error) {
|
||||
if err != nil {
|
||||
WriteError(w, err)
|
||||
} else {
|
||||
WriteResponse(w, data)
|
||||
}
|
||||
}
|
||||
func ReadBody[T any](r io.ReadCloser) (T, error) {
|
||||
dst := new(T)
|
||||
data, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return *dst, err
|
||||
}
|
||||
err = json.Unmarshal(data, dst)
|
||||
return *dst, err
|
||||
}
|
||||
func ReadResponse[T any](r io.ReadCloser) (T, error) {
|
||||
var zero T
|
||||
resp, err := ReadBody[Response[T]](r)
|
||||
if err != nil {
|
||||
return zero, err
|
||||
}
|
||||
return resp.Data, nil
|
||||
}
|
||||
func CheckToken(r *http.Request) bool {
|
||||
auth := r.Header.Get("Authorization")
|
||||
if auth != cfg.JWTSecret {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
13
app/config.go
Normal file
13
app/config.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package app
|
||||
|
||||
import "os"
|
||||
|
||||
type Config struct {
|
||||
JWTSecret string
|
||||
}
|
||||
|
||||
var cfg *Config
|
||||
|
||||
func LoadConfig() {
|
||||
cfg = &Config{JWTSecret: os.Getenv("JWT_SECRET")}
|
||||
}
|
||||
44
app/database.go
Normal file
44
app/database.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/vinovest/sqlx"
|
||||
)
|
||||
|
||||
var db *sqlx.DB = nil
|
||||
|
||||
func init() {
|
||||
if err := godotenv.Load(); err != nil {
|
||||
log.Println("Error loading .env file. If you use docker, then you can ignore this.")
|
||||
}
|
||||
|
||||
if db == nil {
|
||||
connectPsql()
|
||||
}
|
||||
}
|
||||
|
||||
func getDSN() string {
|
||||
user := os.Getenv("PSQL_USER")
|
||||
password := os.Getenv("PSQL_PASS")
|
||||
database := os.Getenv("PSQL_NAME")
|
||||
host, exists := os.LookupEnv("PSQL_HOST")
|
||||
if !exists {
|
||||
host = "localhost"
|
||||
}
|
||||
return fmt.Sprintf("postgresql://%s:%s@%s/%s?sslmode=disable", user, password, host, database)
|
||||
}
|
||||
func connectPsql() {
|
||||
var err error
|
||||
db, err = sqlx.Connect("postgres", getDSN())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
func ClosePsql() error {
|
||||
return db.Close()
|
||||
}
|
||||
81
app/manager.go
Normal file
81
app/manager.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type NodeManager struct {
|
||||
client *http.Client
|
||||
token string
|
||||
url string
|
||||
}
|
||||
|
||||
func NewNodeManager(node Node) NodeManager {
|
||||
client := &http.Client{Timeout: time.Second * 15}
|
||||
return NodeManager{
|
||||
client, node.Token, node.URL,
|
||||
}
|
||||
}
|
||||
func (m *NodeManager) NewRequest(method, path string, body io.Reader) (*http.Request, error) {
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("%s%s", m.url, path), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Add("Authorization", m.token)
|
||||
return req, nil
|
||||
}
|
||||
func (m *NodeManager) Do(req *http.Request) (*http.Response, error) {
|
||||
return m.client.Do(req)
|
||||
}
|
||||
func (m *NodeManager) Request(method, path string, body io.Reader) (*http.Response, error) {
|
||||
req, err := m.NewRequest(method, path, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m.Do(req)
|
||||
}
|
||||
func (m *NodeManager) LoadConfig() (NodeConfig, HysteriaConfig, error) {
|
||||
var _cfg NodeConfig
|
||||
var hysteriaCfg HysteriaConfig
|
||||
|
||||
resp, err := m.Request("GET", "/config", nil)
|
||||
if err != nil {
|
||||
return _cfg, hysteriaCfg, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data, err := ReadResponse[NodeConfigRsp](resp.Body)
|
||||
if err != nil {
|
||||
return _cfg, hysteriaCfg, err
|
||||
}
|
||||
return data.Config, data.HysteriaConfig, nil
|
||||
}
|
||||
func (m *NodeManager) AddUser(username, password string) (User, error) {
|
||||
body := strings.NewReader(fmt.Sprintf(`{"username":"%s","password":"%s"}`, username, password))
|
||||
resp, err := m.Request("POST", "/users", body)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return ReadResponse[User](resp.Body)
|
||||
}
|
||||
func (m *NodeManager) GetUser(id uint64) (User, error) {
|
||||
var user User
|
||||
resp, err := m.Request("GET", fmt.Sprintf("/users/%d", id), nil)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return ReadResponse[User](resp.Body)
|
||||
}
|
||||
func (m *NodeManager) GetUserKey(id uint64) (string, error) {
|
||||
resp, err := m.Request("GET", fmt.Sprintf("/users/%d/key", id), nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return ReadResponse[string](resp.Body)
|
||||
}
|
||||
32
app/nodes.go
Normal file
32
app/nodes.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/vinovest/sqlx"
|
||||
)
|
||||
|
||||
type NodeRepository struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
func NewNodeRepository(db *sqlx.DB) *NodeRepository { return &NodeRepository{db} }
|
||||
|
||||
func (r *NodeRepository) All() ([]Node, error) {
|
||||
nodes := make([]Node, 0)
|
||||
sql := "SELECT * FROM nodes;"
|
||||
err := r.db.Select(&nodes, sql)
|
||||
return nodes, err
|
||||
}
|
||||
func (r *NodeRepository) Find(id int) (Node, error) {
|
||||
var node Node
|
||||
sql := "SELECT * FROM nodes WHERE id=?;"
|
||||
sql = r.db.Rebind(sql)
|
||||
err := r.db.Select(&node, sql, id)
|
||||
return node, err
|
||||
}
|
||||
func (r *NodeRepository) Create(token, url string) (Node, error) {
|
||||
sql := "INSERT INTO nodes (token, url) VALUES (?, ?) RETURNING *;"
|
||||
sql = r.db.Rebind(sql)
|
||||
var node Node
|
||||
err := r.db.Get(&node, sql, token, url)
|
||||
return node, err
|
||||
}
|
||||
125
app/routes.go
Normal file
125
app/routes.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
var hosts = []string{
|
||||
"185.231.245.25",
|
||||
"46.243.6.125",
|
||||
}
|
||||
|
||||
func AddNode(w http.ResponseWriter, r *http.Request) {
|
||||
type AddNodeReq struct {
|
||||
Token string `json:"token"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
req, err := ReadBody[AddNodeReq](r.Body)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
rep := NewNodeRepository(db)
|
||||
node, err := rep.Create(req.Token, req.URL)
|
||||
WriteResponseOrError(w, node, err)
|
||||
}
|
||||
func GetNodes(w http.ResponseWriter, r *http.Request) {
|
||||
rep := NewNodeRepository(db)
|
||||
nodes, err := rep.All()
|
||||
WriteResponseOrError(w, nodes, err)
|
||||
}
|
||||
func GetNodeById(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
nodeId, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
WriteError(w, err)
|
||||
return
|
||||
}
|
||||
rep := NewNodeRepository(db)
|
||||
node, err := rep.Find(nodeId)
|
||||
WriteResponseOrError(w, node, err)
|
||||
}
|
||||
|
||||
func AddUser(w http.ResponseWriter, r *http.Request) {
|
||||
rep := NewNodeRepository(db)
|
||||
nodes, err := rep.All()
|
||||
if err != nil {
|
||||
WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
type AddUserReq struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
req, err := ReadBody[AddUserReq](r.Body)
|
||||
if err != nil {
|
||||
WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var user User
|
||||
for _, node := range nodes {
|
||||
manager := NewNodeManager(node)
|
||||
user, err = manager.AddUser(req.Username, req.Password)
|
||||
if err != nil {
|
||||
WriteError(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
WriteResponseOrError(w, user, err)
|
||||
}
|
||||
func Sub(w http.ResponseWriter, r *http.Request) {
|
||||
idS := chi.URLParam(r, "id")
|
||||
userId, err := strconv.ParseUint(idS, 10, 32)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
rep := NewNodeRepository(db)
|
||||
nodes, err := rep.All()
|
||||
if err != nil {
|
||||
WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
subs := make([]string, 0)
|
||||
for _, node := range nodes {
|
||||
manager := NewNodeManager(node)
|
||||
conf, hystConf, err := manager.LoadConfig()
|
||||
if err != nil {
|
||||
WriteError(w, err)
|
||||
continue
|
||||
}
|
||||
key, err := manager.GetUserKey(userId)
|
||||
if err != nil {
|
||||
WriteError(w, err)
|
||||
return
|
||||
}
|
||||
user, err := manager.GetUser(userId)
|
||||
if err != nil {
|
||||
WriteError(w, err)
|
||||
return
|
||||
}
|
||||
hystUrl := buildSubUrl(conf, hystConf, key, user)
|
||||
subs = append(subs, hystUrl)
|
||||
}
|
||||
|
||||
sub := []byte(base64.StdEncoding.EncodeToString([]byte(strings.Join(subs, "\n"))))
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(sub)))
|
||||
w.Header().Set("Content-Disposition", `attachment; filename="ScuroNeko"`)
|
||||
w.Header().Set("profile-web-page-url", "http://185.231.245.25:9997/sub/1")
|
||||
w.Header().Set("support-url", "https://t.me/scuroneko")
|
||||
w.Header().Set("profile-title", "base64:YmFuLm5peDEzLnB3")
|
||||
w.Header().Set("profile-update-interval", "12")
|
||||
w.Header().Set("subscription-userinfo", "upload=0; download=197782510450; total=0; expire=0")
|
||||
w.WriteHeader(200)
|
||||
w.Write(sub)
|
||||
}
|
||||
133
app/types.go
Normal file
133
app/types.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package app
|
||||
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
ID int `json:"id"`
|
||||
Token string `json:"token"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type NodeConfig struct {
|
||||
JWTSecret string
|
||||
Host string
|
||||
SNI string
|
||||
NameFormat string
|
||||
}
|
||||
|
||||
type HysteriaTLSConfig struct {
|
||||
Cert string `yaml:"cert"`
|
||||
Key string `yaml:"key"`
|
||||
}
|
||||
|
||||
type HysteriaObfsConfig struct {
|
||||
Type string `yaml:"type"`
|
||||
Salamander struct {
|
||||
Password string `yaml:"password"`
|
||||
} `yaml:"salamander"`
|
||||
}
|
||||
|
||||
type HysteriaQuicConfig struct {
|
||||
InitStreamReceiveWindow *int64 `yaml:"initStreamReceiveWindow"`
|
||||
MaxStreamReceiveWindow *int64 `yaml:"maxStreamReceiveWindow"`
|
||||
InitConnReceiveWindow *int64 `yaml:"initConnReceiveWindow"`
|
||||
MaxConnReceiveWindow *int64 `yaml:"maxConnReceiveWindow"`
|
||||
MaxIdleTimeout *string `yaml:"maxIdleTimeout"`
|
||||
MaxIncomingStream *int64 `yaml:"maxIncomingStream"`
|
||||
DisablePathMTUDiscovery *bool `yaml:"disablePathMTUDiscovery"`
|
||||
}
|
||||
|
||||
type HysteriaBandwidthConfig struct {
|
||||
UP *string `yaml:"up"`
|
||||
Down *string `yaml:"down"`
|
||||
IgnoreClientBandwidth *bool `yaml:"ignoreClientBandwidth"`
|
||||
}
|
||||
|
||||
type HysteriaAuthType string
|
||||
type HysteriaAuthConfig struct {
|
||||
Type HysteriaAuthType `yaml:"type"`
|
||||
Password string `yaml:"password,omitempty"`
|
||||
Userpass map[string]string `yaml:"userpass,omitempty"`
|
||||
Http struct {
|
||||
URL string `yaml:"url"`
|
||||
Insecure bool `yaml:"insecure"`
|
||||
} `yaml:"http,omitempty"`
|
||||
Command string `yaml:"command,omitempty"`
|
||||
}
|
||||
|
||||
type HysteriaTCPUDPResolverConfig struct {
|
||||
Address string `yaml:"addr"`
|
||||
Timeout string `yaml:"timeout"`
|
||||
}
|
||||
type HysteriaTLSResolverConfig struct {
|
||||
Address string `yaml:"addr"`
|
||||
Timeout string `yaml:"timeout"`
|
||||
SNI string `yaml:"sni"`
|
||||
Insecure bool `yaml:"insecure"`
|
||||
}
|
||||
type HysteriaResolverType string
|
||||
type HysteriaResolverConfig struct {
|
||||
Type HysteriaResolverType `yaml:"type"`
|
||||
TCP *HysteriaTCPUDPResolverConfig `yaml:"tcp,omitempty"`
|
||||
UDP *HysteriaTCPUDPResolverConfig `yaml:"udp,omitempty"`
|
||||
TLS *HysteriaTLSResolverConfig `yaml:"tls,omitempty"`
|
||||
HTTPS *HysteriaTLSResolverConfig `yaml:"https,omitempty"`
|
||||
}
|
||||
|
||||
type HysteriaSniffConfig struct {
|
||||
Enable bool `yaml:"enable"`
|
||||
}
|
||||
|
||||
type HysteriaACLConfig struct{}
|
||||
type HysteriaOutboundConfig struct {
|
||||
Name string `yaml:"name"`
|
||||
Type string `yaml:"type"`
|
||||
}
|
||||
|
||||
type HysteriaTrafficStatsConfig struct {
|
||||
Listen string `yaml:"listen"`
|
||||
Secret string `yaml:"secret"`
|
||||
}
|
||||
|
||||
type HysteriaMasqueradeType string
|
||||
type HysteriaMasqueradeFile struct {
|
||||
Dir string `yaml:"dir"`
|
||||
}
|
||||
type HysteriaMasqueradeProxy struct {
|
||||
URL string `yaml:"url"`
|
||||
RewriteHost *bool `yaml:"rewriteHost,omitempty"`
|
||||
Insecure *bool `yaml:"insecure,omitempty"`
|
||||
}
|
||||
type HysteriaMasqueradeString struct {
|
||||
Content string `yaml:"content"`
|
||||
Headers map[string]string `yaml:"headers,omitempty"`
|
||||
StatusCode int `yaml:"statusCode"`
|
||||
}
|
||||
type HysteriaMasqueradeConfig struct {
|
||||
Type HysteriaMasqueradeType `yaml:"type"`
|
||||
File *HysteriaMasqueradeFile `yaml:"file,omitempty"`
|
||||
Proxy *HysteriaMasqueradeProxy `yaml:"proxy,omitempty"`
|
||||
String *HysteriaMasqueradeString `yaml:"string,omitempty"`
|
||||
|
||||
ListenHTTP *string `yaml:"listenHTTP,omitempty"`
|
||||
ListenHTTPS *string `yaml:"listenHTTPS,omitempty"`
|
||||
ForceHTTPS *bool `yaml:"forceHTTPS,omitempty"`
|
||||
}
|
||||
|
||||
type HysteriaConfig struct {
|
||||
Listen string `yaml:"listen"`
|
||||
TLS HysteriaTLSConfig `yaml:"tls"`
|
||||
Obfs *HysteriaObfsConfig `yaml:"obfs,omitempty"`
|
||||
Quic *HysteriaQuicConfig `yaml:"quic,omitempty"`
|
||||
SpeedTest *bool `yaml:"speedTest,omitempty"`
|
||||
Auth HysteriaAuthConfig `yaml:"auth"`
|
||||
Resolver *HysteriaResolverConfig `yaml:"resolver,omitempty"`
|
||||
Sniff *HysteriaSniffConfig `yaml:"sniff,omitempty"`
|
||||
ACL *HysteriaACLConfig `yaml:"acl,omitempty"`
|
||||
Outbounds []HysteriaOutboundConfig `yaml:"outbounds,omitempty"`
|
||||
TrafficStats *HysteriaTrafficStatsConfig `yaml:"trafficStats,omitempty"`
|
||||
}
|
||||
31
app/utils.go
Normal file
31
app/utils.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type NodeConfigRsp struct {
|
||||
Config NodeConfig `json:"config"`
|
||||
HysteriaConfig HysteriaConfig `json:"hysteria"`
|
||||
}
|
||||
|
||||
func buildSubUrl(conf NodeConfig, hystConf HysteriaConfig, key string, user User) string {
|
||||
values := url.Values{}
|
||||
values.Add("type", "hysteria")
|
||||
values.Add("security", "tls")
|
||||
values.Add("alpn", "h3,h2")
|
||||
values.Add("fp", "chrome")
|
||||
values.Add("allowInsecure", "0")
|
||||
values.Add("sni", conf.SNI)
|
||||
if hystConf.Obfs != nil {
|
||||
values.Add("obfs", hystConf.Obfs.Type)
|
||||
values.Add("obfs-password", hystConf.Obfs.Salamander.Password)
|
||||
}
|
||||
|
||||
host := fmt.Sprintf("%s@%s%s", key, conf.Host, hystConf.Listen)
|
||||
subName := strings.ReplaceAll(conf.NameFormat, "{username}", user.Username)
|
||||
return fmt.Sprintf("hysteria2://%s?%s#%s", host, values.Encode(), subName)
|
||||
// hysteria2://pass@185.231.245.25:51052?&type=hysteria&mport&security=tls&sni=nix13.pw&alpn=h3&fp=chrome&allowInsecure=0#🇷🇺ssia (user1)
|
||||
}
|
||||
12
go.mod
Normal file
12
go.mod
Normal file
@@ -0,0 +1,12 @@
|
||||
module Hyst2API
|
||||
|
||||
go 1.26
|
||||
|
||||
require (
|
||||
github.com/go-chi/chi/v5 v5.2.5
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/lib/pq v1.11.2
|
||||
github.com/vinovest/sqlx v1.7.2
|
||||
)
|
||||
|
||||
require github.com/muir/sqltoken v0.1.0 // indirect
|
||||
24
go.sum
Normal file
24
go.sum
Normal file
@@ -0,0 +1,24 @@
|
||||
filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw=
|
||||
filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
|
||||
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
||||
github.com/go-sql-driver/mysql v1.9.0 h1:Y0zIbQXhQKmQgTp44Y1dp3wTXcn804QoTptLZT1vtvo=
|
||||
github.com/go-sql-driver/mysql v1.9.0/go.mod h1:pDetrLJeA3oMujJuvXc8RJoasr589B6A9fwzD3QMrqw=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs=
|
||||
github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/muir/sqltoken v0.1.0 h1:edosEGsOClOZNfgGQNQSgxR9O6LiVefm2rDRqp2InuI=
|
||||
github.com/muir/sqltoken v0.1.0/go.mod h1:lgOIORnKekMsuc/ZwdPOfwz/PtWLPCke43cEbT3uDuY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/vinovest/sqlx v1.7.2 h1:t/IahCJqO71GJYnhOcACiUXlMiiMomMHtxtUthdcBfo=
|
||||
github.com/vinovest/sqlx v1.7.2/go.mod h1:o49uG4W/ZYZompljKx5GZ7qx6OFklPjSHXP63nSmND8=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
19
main.go
Normal file
19
main.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"Hyst2API/app"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := chi.NewRouter()
|
||||
r.Post("/nodes", app.AddNode)
|
||||
r.Get("/nodes", app.GetNodes)
|
||||
r.Get("/nodes/{id}", app.GetNodeById)
|
||||
r.Post("/users", app.AddUser)
|
||||
r.Get("/sub/{id}", app.Sub)
|
||||
log.Fatal(http.ListenAndServe(":9997", r))
|
||||
}
|
||||
6
scripts/00-init.sql
Normal file
6
scripts/00-init.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
CREATE TABLE nodes(
|
||||
id SERIAL PRIMARY KEY,
|
||||
token TEXT NOT NULL,
|
||||
url TEXT NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX nodes_id ON nodes(id);
|
||||
Reference in New Issue
Block a user