working file sharing
This commit is contained in:
@@ -2,7 +2,7 @@ version: "3.8"
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
minio:
|
minio:
|
||||||
image: minio/minio:RELEASE.2024-03-19T00-00-00Z
|
image: minio/minio:latest
|
||||||
container_name: minio
|
container_name: minio
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "5432:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/var/lib/postgresql/dat
|
- ./data:/var/lib/postgresql/data
|
||||||
|
|||||||
@@ -86,6 +86,8 @@
|
|||||||
<button onclick="showForm('get-connection-messages')">POST /get/connection/messages</button>
|
<button onclick="showForm('get-connection-messages')">POST /get/connection/messages</button>
|
||||||
<button onclick="showForm('msg-user')">POST /msg/user</button>
|
<button onclick="showForm('msg-user')">POST /msg/user</button>
|
||||||
<button onclick="showForm('msg-group')">POST /msg/group</button>
|
<button onclick="showForm('msg-group')">POST /msg/group</button>
|
||||||
|
<button onclick="showForm('file-upload')">POST /new/file</button>
|
||||||
|
<button onclick="showForm('file-download')">POST /get/file</button>
|
||||||
<button onclick="showForm('websocket')">WS /ws</button>
|
<button onclick="showForm('websocket')">WS /ws</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -260,9 +262,10 @@
|
|||||||
fields: [
|
fields: [
|
||||||
{ id: 'mu-token', label: 'token', ph: '' },
|
{ id: 'mu-token', label: 'token', ph: '' },
|
||||||
{ id: 'mu-connectionid', label: 'connectionid', ph: 'UUID' },
|
{ id: 'mu-connectionid', label: 'connectionid', ph: 'UUID' },
|
||||||
{ id: 'mu-msgContent', label: 'msgContent', ph: 'message text' },
|
{ id: 'mu-msgContent', label: 'msgContent', ph: 'message text (optional if mediaKey set)' },
|
||||||
|
{ id: 'mu-attachedFile', label: 'attachedFile', ph: 'key returned by /new/file (optional)' },
|
||||||
],
|
],
|
||||||
submit: () => httpPost('/msg/user', { token:'mu-token', connectionid:'mu-connectionid', msgContent:'mu-msgContent' })
|
submit: () => httpPost('/msg/user', { token:'mu-token', connectionid:'mu-connectionid', msgContent:'mu-msgContent', attachedFile:'mu-attachedFile' })
|
||||||
},
|
},
|
||||||
'msg-group': {
|
'msg-group': {
|
||||||
title: 'POST /msg/group — send message to group',
|
title: 'POST /msg/group — send message to group',
|
||||||
@@ -273,6 +276,28 @@
|
|||||||
],
|
],
|
||||||
submit: () => httpPost('/msg/group', { token:'mg-token', groupid:'mg-groupid', content:'mg-content' })
|
submit: () => httpPost('/msg/group', { token:'mg-token', groupid:'mg-groupid', content:'mg-content' })
|
||||||
},
|
},
|
||||||
|
'file-upload': {
|
||||||
|
title: 'POST /new/file — upload file (multipart, token in header)',
|
||||||
|
renderCustom: () => `
|
||||||
|
<div class="field"><label>token</label><input id="fu-token" placeholder=""></div>
|
||||||
|
<div class="field"><label>connectionid</label><input id="fu-connectionid" placeholder="UUID"></div>
|
||||||
|
<div class="field"><label>file</label><input id="fu-file" type="file"></div>
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="send" onclick="submitFileUpload()">Send</button>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
},
|
||||||
|
'file-download': {
|
||||||
|
title: 'POST /get/file — get presigned download URL (token in header)',
|
||||||
|
renderCustom: () => `
|
||||||
|
<div class="field"><label>token</label><input id="fd-token" placeholder=""></div>
|
||||||
|
<div class="field"><label>connectionid</label><input id="fd-connectionid" placeholder="UUID"></div>
|
||||||
|
<div class="field"><label>key</label><input id="fd-key" placeholder="returned by upload"></div>
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="send" onclick="submitFileDownload()">Send</button>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
},
|
||||||
'websocket': {
|
'websocket': {
|
||||||
title: 'WS /ws — WebSocket connection',
|
title: 'WS /ws — WebSocket connection',
|
||||||
renderCustom: () => {
|
renderCustom: () => {
|
||||||
@@ -324,6 +349,7 @@
|
|||||||
|
|
||||||
if (def.renderCustom) {
|
if (def.renderCustom) {
|
||||||
fieldsEl.innerHTML = def.renderCustom();
|
fieldsEl.innerHTML = def.renderCustom();
|
||||||
|
autofillTokens();
|
||||||
} else {
|
} else {
|
||||||
let html = '';
|
let html = '';
|
||||||
for (const f of def.fields) {
|
for (const f of def.fields) {
|
||||||
@@ -420,6 +446,45 @@
|
|||||||
log('WS →', msg, 'log-info');
|
log('WS →', msg, 'log-info');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function submitFileUpload() {
|
||||||
|
const token = document.getElementById('fu-token').value;
|
||||||
|
const connectionid = document.getElementById('fu-connectionid').value;
|
||||||
|
const fileInput = document.getElementById('fu-file');
|
||||||
|
if (!fileInput.files.length) { log('HTTP ERR', 'no file selected', 'log-err'); return; }
|
||||||
|
const form = new FormData();
|
||||||
|
form.append('connectionid', connectionid);
|
||||||
|
form.append('file', fileInput.files[0]);
|
||||||
|
log('HTTP /new/file', `→ connectionid=${connectionid} file=${fileInput.files[0].name}`, 'log-info');
|
||||||
|
try {
|
||||||
|
const resp = await fetch(baseUrl() + '/new/file', { method: 'POST', headers: { token }, body: form });
|
||||||
|
const text = await resp.text();
|
||||||
|
log(`HTTP ${resp.status}`, text, resp.ok ? 'log-http' : 'log-err');
|
||||||
|
if (resp.ok) {
|
||||||
|
const keyEl = document.getElementById('fd-key');
|
||||||
|
if (keyEl) keyEl.value = text;
|
||||||
|
const dmKeyEl = document.getElementById('mu-attachedFile');
|
||||||
|
if (dmKeyEl) dmKeyEl.value = text;
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
log('HTTP ERR', e.message, 'log-err');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitFileDownload() {
|
||||||
|
const token = document.getElementById('fd-token').value;
|
||||||
|
const connectionid = document.getElementById('fd-connectionid').value;
|
||||||
|
const key = document.getElementById('fd-key').value;
|
||||||
|
const params = new URLSearchParams({ connectionid, key });
|
||||||
|
log('HTTP /get/file', `→ ${params.toString()}`, 'log-info');
|
||||||
|
try {
|
||||||
|
const resp = await fetch(baseUrl() + '/get/file', { method: 'POST', headers: { token }, body: params });
|
||||||
|
const text = await resp.text();
|
||||||
|
log(`HTTP ${resp.status}`, text, resp.ok ? 'log-http' : 'log-err');
|
||||||
|
} catch(e) {
|
||||||
|
log('HTTP ERR', e.message, 'log-err');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function setWsStatus(connected) {
|
function setWsStatus(connected) {
|
||||||
const el = document.getElementById('ws-status');
|
const el = document.getElementById('ws-status');
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
http2 "go-socket/packages/http"
|
"go-socket/packages/httpRequest"
|
||||||
|
"go-socket/packages/minio"
|
||||||
"go-socket/packages/postgresql"
|
"go-socket/packages/postgresql"
|
||||||
"go-socket/packages/wsServer"
|
"go-socket/packages/wsServer"
|
||||||
)
|
)
|
||||||
@@ -13,6 +14,11 @@ import (
|
|||||||
func withCORS(h http.HandlerFunc) http.HandlerFunc {
|
func withCORS(h http.HandlerFunc) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
w.Header().Set("Access-Control-Allow-Headers", "token, Content-Type")
|
||||||
|
if r.Method == http.MethodOptions {
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
return
|
||||||
|
}
|
||||||
h(w, r)
|
h(w, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,21 +26,24 @@ func withCORS(h http.HandlerFunc) http.HandlerFunc {
|
|||||||
func main() {
|
func main() {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
postgresql.Init(ctx)
|
postgresql.Init(ctx)
|
||||||
|
minio.Init(ctx)
|
||||||
|
|
||||||
http.HandleFunc("/new/user", withCORS(http2.HandleUserNew))
|
http.HandleFunc("/new/user", withCORS(httpRequest.HandleUserNew))
|
||||||
http.HandleFunc("/new/connection", withCORS(http2.HandleUserNewConnection))
|
http.HandleFunc("/new/connection", withCORS(httpRequest.HandleUserNewConnection))
|
||||||
http.HandleFunc("/new/token", withCORS(http2.HandleUserNewToken))
|
http.HandleFunc("/new/token", withCORS(httpRequest.HandleUserNewToken))
|
||||||
http.HandleFunc("/mod/user/appearence", withCORS(http2.HandleUserModifyAppearance))
|
http.HandleFunc("/new/file", withCORS(httpRequest.HandleFileUpload))
|
||||||
http.HandleFunc("/mod/user/about", withCORS(http2.HandleUserModifyAbout))
|
http.HandleFunc("/mod/user/appearence", withCORS(httpRequest.HandleUserModifyAppearance))
|
||||||
http.HandleFunc("/mod/connection/accept", withCORS(http2.HandleUserElevateConnection))
|
http.HandleFunc("/mod/user/about", withCORS(httpRequest.HandleUserModifyAbout))
|
||||||
|
http.HandleFunc("/mod/connection/accept", withCORS(httpRequest.HandleUserElevateConnection))
|
||||||
|
|
||||||
http.HandleFunc("/get/connections", withCORS(http2.HandleUserGetConnections))
|
http.HandleFunc("/get/connections", withCORS(httpRequest.HandleUserGetConnections))
|
||||||
http.HandleFunc("/get/connection/messages", withCORS(http2.HandleUserGetConnectionMessages))
|
http.HandleFunc("/get/connection/messages", withCORS(httpRequest.HandleUserGetConnectionMessages))
|
||||||
|
http.HandleFunc("/get/file", withCORS(httpRequest.HandleFileDownload))
|
||||||
|
|
||||||
http.HandleFunc("/del/user", withCORS(http2.HandleUserDelete))
|
http.HandleFunc("/del/user", withCORS(httpRequest.HandleUserDelete))
|
||||||
http.HandleFunc("/del/connection", withCORS(http2.HandleUserDeleteConnection))
|
http.HandleFunc("/del/connection", withCORS(httpRequest.HandleUserDeleteConnection))
|
||||||
|
|
||||||
http.HandleFunc("/msg/user", withCORS(http2.HandleDm))
|
http.HandleFunc("/msg/user", withCORS(httpRequest.HandleDm))
|
||||||
http.HandleFunc("/ws", wsServer.ServeWsConnection)
|
http.HandleFunc("/ws", wsServer.ServeWsConnection)
|
||||||
|
|
||||||
log.Println("listening on :8080")
|
log.Println("listening on :8080")
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const (
|
|||||||
FileStorageBucketName string = "communicator"
|
FileStorageBucketName string = "communicator"
|
||||||
MaxPostBytes uint32 = 4 << 10
|
MaxPostBytes uint32 = 4 << 10
|
||||||
MaxPostWithFileBytes uint32 = 1 << 30
|
MaxPostWithFileBytes uint32 = 1 << 30
|
||||||
FileProcessingPartSize uint64 = 24 << 10
|
FileProcessingPartSize uint64 = 12 << 20
|
||||||
FileProcessingThreads uint = 3
|
FileProcessingThreads uint = 3
|
||||||
FileDownloadLinkTtl time.Duration = 24 * time.Hour
|
FileDownloadLinkTtl time.Duration = 24 * time.Hour
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
package http
|
package httpRequest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
json2 "encoding/json"
|
json2 "encoding/json"
|
||||||
"maps"
|
"maps"
|
||||||
"net/http"
|
"net/http"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go-socket/packages/Enums/ConnectionState"
|
"go-socket/packages/Enums/ConnectionState"
|
||||||
@@ -43,11 +44,18 @@ func HandleDm(response http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
msgContent := request.FormValue("msgContent")
|
msgContent := request.FormValue("msgContent")
|
||||||
if msgContent == "" {
|
attachedFile := request.FormValue("attachedFile")
|
||||||
|
|
||||||
|
if msgContent == "" && attachedFile == "" {
|
||||||
http.Error(response, "empty msgContent", http.StatusBadRequest)
|
http.Error(response, "empty msgContent", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if attachedFile != "" && !strings.HasPrefix(attachedFile, targetConnection.String()+"/") {
|
||||||
|
http.Error(response, "invalid attachedFile", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var target *types.User
|
var target *types.User
|
||||||
|
|
||||||
if user.Id == conn.RequestorId {
|
if user.Id == conn.RequestorId {
|
||||||
@@ -66,6 +74,7 @@ func HandleDm(response http.ResponseWriter, request *http.Request) {
|
|||||||
message := &types.Message{
|
message := &types.Message{
|
||||||
Id: uuid.New(),
|
Id: uuid.New(),
|
||||||
Content: msgContent,
|
Content: msgContent,
|
||||||
|
AttachedFile: attachedFile,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
Sender: user.Id,
|
Sender: user.Id,
|
||||||
Receiver: conn.Id,
|
Receiver: conn.Id,
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
package http
|
package httpRequest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"go-socket/packages/convertions"
|
"go-socket/packages/convertions"
|
||||||
"go-socket/packages/globals"
|
"go-socket/packages/globals"
|
||||||
"go-socket/packages/minio"
|
"go-socket/packages/minio"
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func HandleFileUpload(response http.ResponseWriter, request *http.Request) {
|
func HandleFileUpload(response http.ResponseWriter, request *http.Request) {
|
||||||
@@ -47,17 +47,54 @@ func HandleFileUpload(response http.ResponseWriter, request *http.Request) {
|
|||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
contentType := header.Header.Get("Content-Type")
|
contentType := header.Header.Get("Content-Type")
|
||||||
|
key := minio.GetKey(connectionId, contentType)
|
||||||
|
|
||||||
key :=
|
if err = minio.Upload(ctx, key, file, header.Size, contentType, map[string]string{
|
||||||
|
"originalName": header.Filename,
|
||||||
if err = minio.Upload(ctx, fileId.String(), file, header.Size, contentType, map[string]string{
|
|
||||||
"orginalName": header.Filename,
|
|
||||||
"uploaderId": user.Id.String(),
|
"uploaderId": user.Id.String(),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
http.Error(response, "upload failed", http.StatusInternalServerError)
|
http.Error(response, "upload failed", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response.WriteHeader(http.StatusAccepted)
|
response.WriteHeader(http.StatusCreated)
|
||||||
response.Write([]byte(fileId.String()))
|
response.Write([]byte(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleFileDownload(response http.ResponseWriter, request *http.Request) {
|
||||||
|
if !postValidCheckWithResponseOnFail(&response, request, false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx := request.Context()
|
||||||
|
|
||||||
|
user, err := getUserByToken(ctx, request.Header.Get("token"))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(response, "invalid token", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionId, err := convertions.ConvertStringUuid(request.FormValue("connectionid"))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(response, "invalid connectionid", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := user.Connections[connectionId]; !ok {
|
||||||
|
http.Error(response, "no such connection", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
key := request.FormValue("key")
|
||||||
|
if !strings.HasPrefix(key, connectionId.String()+"/") {
|
||||||
|
http.Error(response, "no such file", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
url, err := minio.GetDownloadUrl(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(response, "no such file", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.WriteHeader(http.StatusOK)
|
||||||
|
response.Write([]byte(url.String()))
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package http
|
package httpRequest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package http
|
package httpRequest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package http
|
package httpRequest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
json2 "encoding/json"
|
json2 "encoding/json"
|
||||||
@@ -26,7 +26,7 @@ func HandleUserNewToken(response http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
password := request.FormValue("passwords")
|
password := request.FormValue("password")
|
||||||
|
|
||||||
if len(password) < 8 {
|
if len(password) < 8 {
|
||||||
http.Error(response, "no or short passwords", http.StatusBadRequest)
|
http.Error(response, "no or short passwords", http.StatusBadRequest)
|
||||||
@@ -85,7 +85,7 @@ func HandleUserNew(response http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
password := request.FormValue("passwords")
|
password := request.FormValue("password")
|
||||||
if len(password) < 8 {
|
if len(password) < 8 {
|
||||||
http.Error(response, "no or short passwords", http.StatusBadRequest)
|
http.Error(response, "no or short passwords", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
+14
-26
@@ -5,42 +5,27 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"mime"
|
"mime"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"go-socket/packages/globals"
|
"go-socket/packages/globals"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
)
|
)
|
||||||
|
|
||||||
var minClient *minio.Client
|
var minClient *minio.Client
|
||||||
|
|
||||||
var canonicalExt = map[string]string{
|
func GetKey(connectionId uuid.UUID, mimeType string) string {
|
||||||
"image/jpeg": ".jpg",
|
extensions, err := mime.ExtensionsByType(mimeType)
|
||||||
"image/png": ".png",
|
if err != nil || len(extensions) == 0 {
|
||||||
"image/gif": ".gif",
|
extensions = []string{".unknown"}
|
||||||
"image/webp": ".webp",
|
}
|
||||||
"video/mp4": ".mp4",
|
return connectionId.String() + "/" + strconv.FormatInt(time.Now().UnixMilli(), 10) + extensions[0]
|
||||||
"application/pdf": ".pdf",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func extensionFromContentType(ct string) string {
|
func Init(ctx context.Context) {
|
||||||
if ext, ok := canonicalExt[ct]; ok {
|
|
||||||
return ext
|
|
||||||
}
|
|
||||||
exts, err := mime.ExtensionsByType(ct)
|
|
||||||
if err != nil || len(exts) == 0 {
|
|
||||||
return ".unk"
|
|
||||||
}
|
|
||||||
return exts[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetKey(hash string, contentType string) string {
|
|
||||||
return hash + extensionFromContentType(contentType)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Init() {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
minClient, err = minio.New("localhost:9000", &minio.Options{
|
minClient, err = minio.New("localhost:9000", &minio.Options{
|
||||||
Creds: credentials.NewStaticV4("root", "change_to_env", ""),
|
Creds: credentials.NewStaticV4("root", "change_to_env", ""),
|
||||||
@@ -58,7 +43,10 @@ func Init() {
|
|||||||
if !exists {
|
if !exists {
|
||||||
err = minClient.MakeBucket(ctx, globals.FileStorageBucketName, minio.MakeBucketOptions{})
|
err = minClient.MakeBucket(ctx, globals.FileStorageBucketName, minio.MakeBucketOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
exists, checkErr := minClient.BucketExists(ctx, globals.FileStorageBucketName)
|
||||||
|
if checkErr != nil || !exists {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,8 @@ func Init(ctx context.Context) {
|
|||||||
sender_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
sender_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
receiver_id UUID NOT NULL REFERENCES user_connections(id) ON DELETE CASCADE,
|
receiver_id UUID NOT NULL REFERENCES user_connections(id) ON DELETE CASCADE,
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
content TEXT NOT NULL
|
content TEXT NOT NULL,
|
||||||
|
attached_file TEXT NOT NULL DEFAULT ''
|
||||||
)
|
)
|
||||||
`)
|
`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -182,21 +183,21 @@ func ConnectionUpdateState(ctx context.Context, conn *types.Connection) error {
|
|||||||
func ConnectionMessageSave(ctx context.Context, message *types.Message) error {
|
func ConnectionMessageSave(ctx context.Context, message *types.Message) error {
|
||||||
if message.Id != (uuid.UUID{}) {
|
if message.Id != (uuid.UUID{}) {
|
||||||
_, err := dbConn.Exec(ctx, `
|
_, err := dbConn.Exec(ctx, `
|
||||||
INSERT INTO direct_messages (id, sender_id, receiver_id, created_at, content) VALUES ($1, $2, $3, $4, $5)
|
INSERT INTO direct_messages (id, sender_id, receiver_id, created_at, content, attached_file) VALUES ($1, $2, $3, $4, $5, $6)
|
||||||
`, message.Id, message.Sender, message.Receiver, message.CreatedAt, message.Content)
|
`, message.Id, message.Sender, message.Receiver, message.CreatedAt, message.Content, message.AttachedFile)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return dbConn.QueryRow(ctx, `
|
return dbConn.QueryRow(ctx, `
|
||||||
INSERT INTO direct_messages (sender_id, receiver_id, created_at, content) VALUES ($1, $2, $3, $4)
|
INSERT INTO direct_messages (sender_id, receiver_id, created_at, content, attached_file) VALUES ($1, $2, $3, $4, $5)
|
||||||
RETURNING id
|
RETURNING id
|
||||||
`, message.Sender, message.Receiver, message.CreatedAt, message.Content).Scan(&message.Id)
|
`, message.Sender, message.Receiver, message.CreatedAt, message.Content, message.AttachedFile).Scan(&message.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConnectionGetMessagesBefore(ctx context.Context, before time.Time, connection uuid.UUID, cap uint32) ([]*types.Message, error) {
|
func ConnectionGetMessagesBefore(ctx context.Context, before time.Time, connection uuid.UUID, cap uint32) ([]*types.Message, error) {
|
||||||
rows, err := dbConn.Query(ctx, `
|
rows, err := dbConn.Query(ctx, `
|
||||||
SELECT id, sender_id, receiver_id, created_at, content
|
SELECT id, sender_id, receiver_id, created_at, content, attached_file
|
||||||
FROM (
|
FROM (
|
||||||
SELECT id, sender_id, receiver_id, created_at, content
|
SELECT id, sender_id, receiver_id, created_at, content, attached_file
|
||||||
FROM direct_messages
|
FROM direct_messages
|
||||||
WHERE receiver_id = $1
|
WHERE receiver_id = $1
|
||||||
AND created_at < $2
|
AND created_at < $2
|
||||||
@@ -213,7 +214,7 @@ func ConnectionGetMessagesBefore(ctx context.Context, before time.Time, connecti
|
|||||||
messages := make([]*types.Message, 0, cap)
|
messages := make([]*types.Message, 0, cap)
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
msg := &types.Message{}
|
msg := &types.Message{}
|
||||||
if err = rows.Scan(&msg.Id, &msg.Sender, &msg.Receiver, &msg.CreatedAt, &msg.Content); err != nil {
|
if err = rows.Scan(&msg.Id, &msg.Sender, &msg.Receiver, &msg.CreatedAt, &msg.Content, &msg.AttachedFile); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
messages = append(messages, msg)
|
messages = append(messages, msg)
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ type ConnectionElevationData struct {
|
|||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
Id uuid.UUID `json:"id"`
|
Id uuid.UUID `json:"id"`
|
||||||
AttachedMedia string `json:"attachedMedia"`
|
AttachedFile string `json:"attachedFile"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
Sender uuid.UUID `json:"sender"`
|
Sender uuid.UUID `json:"sender"`
|
||||||
@@ -96,7 +96,7 @@ type LoginReturn struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type WsEventMessage struct {
|
type WsEventMessage struct {
|
||||||
Type WsEventType.WsEventType `json:"types"`
|
Type WsEventType.WsEventType `json:"type"`
|
||||||
Event any `json:"event"`
|
Event any `json:"event"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user