🎉 Initial Commit

This commit is contained in:
2024-10-19 22:36:33 +08:00
commit 71b8607e32
46 changed files with 4166 additions and 0 deletions

15
pkg/internal/gap/net.go Normal file
View File

@ -0,0 +1,15 @@
package gap
import "net"
func GetOutboundIP() (net.IP, error) {
conn, err := net.Dial("udp", "1.1.1.1:80")
if err != nil {
return nil, err
} else {
defer conn.Close()
}
localAddr := conn.LocalAddr().(*net.UDPAddr)
return localAddr.IP, nil
}

View File

@ -0,0 +1,27 @@
package grpc
import (
"context"
"time"
health "google.golang.org/grpc/health/grpc_health_v1"
)
func (v *GrpcServer) Check(ctx context.Context, request *health.HealthCheckRequest) (*health.HealthCheckResponse, error) {
return &health.HealthCheckResponse{
Status: health.HealthCheckResponse_SERVING,
}, nil
}
func (v *GrpcServer) Watch(request *health.HealthCheckRequest, server health.Health_WatchServer) error {
for {
if server.Send(&health.HealthCheckResponse{
Status: health.HealthCheckResponse_SERVING,
}) != nil {
break
}
time.Sleep(1000 * time.Millisecond)
}
return nil
}

View File

@ -0,0 +1,44 @@
package grpc
import (
"net"
"git.solsynth.dev/hypernet/nexus/pkg/directory"
"git.solsynth.dev/hypernet/nexus/pkg/proto"
"google.golang.org/grpc/reflection"
"github.com/spf13/viper"
"google.golang.org/grpc"
health "google.golang.org/grpc/health/grpc_health_v1"
)
type GrpcServer struct {
proto.UnimplementedStreamControllerServer
srv *grpc.Server
}
func NewServer() *GrpcServer {
server := &GrpcServer{
srv: grpc.NewServer(),
}
proto.RegisterServiceDirectoryServer(server.srv, &directory.DirectoryRpcServer{})
proto.RegisterStreamControllerServer(server.srv, server)
health.RegisterHealthServer(server.srv, server)
reflection.Register(server.srv)
return server
}
func (v *GrpcServer) Listen() error {
listener, err := net.Listen("tcp", viper.GetString("grpc_bind"))
if err != nil {
return err
}
return v.srv.Serve(listener)
}

View File

@ -0,0 +1,86 @@
package grpc
import (
"context"
"fmt"
"git.solsynth.dev/hypernet/nexus/pkg/internal/services"
"git.solsynth.dev/hypernet/nexus/pkg/proto"
"github.com/samber/lo"
)
func (v *GrpcServer) CountStreamConnection(ctx context.Context, request *proto.CountConnectionRequest) (*proto.CountConnectionResponse, error) {
out := services.ClientCount(uint(request.GetUserId()))
return &proto.CountConnectionResponse{
Count: int64(out),
}, nil
}
func (v *GrpcServer) PushStream(ctx context.Context, request *proto.PushStreamRequest) (*proto.PushStreamResponse, error) {
var cnt int
var success int
var errs []error
if request.UserId != nil {
cnt, success, errs = services.WebsocketPush(uint(request.GetUserId()), request.GetBody())
} else if request.ClientId != nil {
cnt, success, errs = services.WebsocketPushDirect(request.GetClientId(), request.GetBody())
} else {
return nil, fmt.Errorf("you must give one of the user id or client id")
}
if len(errs) > 0 {
// Partial fail
return &proto.PushStreamResponse{
IsAllSuccess: false,
AffectedCount: int64(success),
FailedCount: int64(cnt - success),
}, nil
} else if cnt > 0 && success == 0 {
// All fail
return nil, fmt.Errorf("all push request failed: %v", errs)
}
return &proto.PushStreamResponse{
IsAllSuccess: true,
AffectedCount: int64(success),
FailedCount: int64(cnt - success),
}, nil
}
func (v *GrpcServer) PushStreamBatch(ctx context.Context, request *proto.PushStreamBatchRequest) (*proto.PushStreamResponse, error) {
var cnt int
var success int
var errs []error
if len(request.UserId) != 0 {
cnt, success, errs = services.WebsocketPushBatch(
lo.Map(request.GetUserId(), func(item uint64, idx int) uint {
return uint(item)
},
), request.GetBody(),
)
}
if len(request.ClientId) != 0 {
cCnt, cSuccess, cErrs := services.WebsocketPushBatchDirect(request.GetClientId(), request.GetBody())
cnt += cCnt
success += cSuccess
errs = append(errs, cErrs...)
}
if len(errs) > 0 {
// Partial fail
return &proto.PushStreamResponse{
IsAllSuccess: false,
AffectedCount: int64(success),
FailedCount: int64(cnt - success),
}, nil
} else if cnt > 0 && success == 0 {
// All fail
return nil, fmt.Errorf("all push request failed: %v", errs)
}
return &proto.PushStreamResponse{
IsAllSuccess: true,
AffectedCount: int64(success),
FailedCount: int64(cnt - success),
}, nil
}

5
pkg/internal/meta.go Normal file
View File

@ -0,0 +1,5 @@
package pkg
const (
AppVersion = "1.0.0"
)

View File

@ -0,0 +1,21 @@
package models
import (
"time"
"gorm.io/datatypes"
)
type Account struct {
BaseModel
Name string `json:"name" gorm:"uniqueIndex"`
Nick string `json:"nick"`
Description string `json:"description"`
Avatar string `json:"avatar"`
Banner string `json:"banner"`
EmailAddress string `json:"email"`
ConfirmedAt *time.Time `json:"confirmed_at"`
SuspendedAt *time.Time `json:"suspended_at"`
PermNodes datatypes.JSONMap `json:"perm_nodes"`
}

View File

@ -0,0 +1,17 @@
package models
import (
"time"
"gorm.io/datatypes"
"gorm.io/gorm"
)
type JSONMap = datatypes.JSONType[map[string]any]
type BaseModel struct {
ID uint `json:"id" gorm:"primaryKey"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
}

View File

@ -0,0 +1,95 @@
package services
import (
"math/rand"
"sync"
"git.solsynth.dev/hypernet/nexus/pkg/internal/models"
"github.com/gofiber/contrib/websocket"
)
var (
wsMutex sync.Mutex
wsConn = make(map[uint]map[uint64]*websocket.Conn)
)
func ClientRegister(user models.Account, conn *websocket.Conn) uint64 {
wsMutex.Lock()
if wsConn[user.ID] == nil {
wsConn[user.ID] = make(map[uint64]*websocket.Conn)
}
clientId := rand.Uint64()
wsConn[user.ID][clientId] = conn
wsMutex.Unlock()
return clientId
}
func ClientUnregister(user models.Account, id uint64) {
wsMutex.Lock()
if wsConn[user.ID] == nil {
wsConn[user.ID] = make(map[uint64]*websocket.Conn)
}
delete(wsConn[user.ID], id)
wsMutex.Unlock()
}
func ClientCount(uid uint) int {
return len(wsConn[uid])
}
func WebsocketPush(uid uint, body []byte) (count int, success int, errs []error) {
for _, conn := range wsConn[uid] {
if err := conn.WriteMessage(1, body); err != nil {
errs = append(errs, err)
} else {
success++
}
count++
}
return
}
func WebsocketPushDirect(clientId uint64, body []byte) (count int, success int, errs []error) {
for _, m := range wsConn {
if conn, ok := m[clientId]; ok {
if err := conn.WriteMessage(1, body); err != nil {
errs = append(errs, err)
} else {
success++
}
count++
}
}
return
}
func WebsocketPushBatch(uidList []uint, body []byte) (count int, success int, errs []error) {
for _, uid := range uidList {
for _, conn := range wsConn[uid] {
if err := conn.WriteMessage(1, body); err != nil {
errs = append(errs, err)
} else {
success++
}
count++
}
}
return
}
func WebsocketPushBatchDirect(clientIdList []uint64, body []byte) (count int, success int, errs []error) {
for _, clientId := range clientIdList {
for _, m := range wsConn {
if conn, ok := m[clientId]; ok {
if err := conn.WriteMessage(1, body); err != nil {
errs = append(errs, err)
} else {
success++
}
count++
}
}
}
return
}

View File

@ -0,0 +1,43 @@
package services
import (
"context"
firebase "firebase.google.com/go"
"github.com/sideshow/apns2"
"github.com/sideshow/apns2/token"
"github.com/spf13/viper"
"google.golang.org/api/option"
)
// ExtFire is a Firebase App client
var ExtFire *firebase.App
func SetupFirebase() error {
opt := option.WithCredentialsFile(viper.GetString("firebase_credentials"))
app, err := firebase.NewApp(context.Background(), nil, opt)
if err != nil {
return err
} else {
ExtFire = app
}
return nil
}
// ExtAPNS is an Apple Push Notification Services client
var ExtAPNS *apns2.Client
func SetupAPNS() error {
authKey, err := token.AuthKeyFromFile(viper.GetString("apns_credentials"))
if err != nil {
return err
}
ExtAPNS = apns2.NewTokenClient(&token.Token{
AuthKey: authKey,
KeyID: viper.GetString("apns_credentials_key"),
TeamID: viper.GetString("apns_credentials_team"),
}).Production()
return nil
}