mirror of
https://github.com/ChronosX88/yans.git
synced 2024-11-08 06:41:00 +00:00
Implement attachments for article
This commit is contained in:
parent
537c3abe82
commit
56205ffdd1
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
*.db
|
*.db
|
||||||
.config.toml
|
.config.toml
|
||||||
.idea
|
.idea
|
||||||
|
.upload
|
10
README.md
10
README.md
@ -9,9 +9,9 @@
|
|||||||
- :heavy_check_mark: Wildmat support
|
- :heavy_check_mark: Wildmat support
|
||||||
- :heavy_check_mark: Database (SQLite)
|
- :heavy_check_mark: Database (SQLite)
|
||||||
- :heavy_check_mark: Basic article posting
|
- :heavy_check_mark: Basic article posting
|
||||||
- :construction: Article retrieving
|
- :heavy_check_mark: Article retrieving
|
||||||
- :construction: Multipart article support
|
- :heavy_check_mark: Multipart article support
|
||||||
- :x: Transit mode
|
- :construction: Transit mode
|
||||||
- :x: Authentication
|
- :x: Authentication
|
||||||
|
|
||||||
#### Commands
|
#### Commands
|
||||||
@ -21,8 +21,8 @@
|
|||||||
- :heavy_check_mark: `CAPABILITIES`
|
- :heavy_check_mark: `CAPABILITIES`
|
||||||
- :heavy_check_mark: `QUIT`
|
- :heavy_check_mark: `QUIT`
|
||||||
- :construction: Article posting
|
- :construction: Article posting
|
||||||
- :construction: `POST`
|
- :heavy_check_mark: `POST`
|
||||||
- :x: `IHAVE`
|
- :construction: `IHAVE`
|
||||||
- :heavy_check_mark: Article retrieving
|
- :heavy_check_mark: Article retrieving
|
||||||
- :heavy_check_mark: `ARTICLE`
|
- :heavy_check_mark: `ARTICLE`
|
||||||
- :heavy_check_mark: `HEAD`
|
- :heavy_check_mark: `HEAD`
|
||||||
|
11
internal/backend/sqlite/migrations/002_attachments.sql
Normal file
11
internal/backend/sqlite/migrations/002_attachments.sql
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
-- +goose Up
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS attachments_articles_mapping (
|
||||||
|
article_id INTEGER REFERENCES articles(id),
|
||||||
|
content_type TEXT NOT NULL,
|
||||||
|
attachment_id TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS attachments_articles_mapping;
|
@ -124,6 +124,15 @@ func (sb *SQLiteBackend) SaveArticle(a models.Article, groups []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// save attachments into db
|
||||||
|
for _, v := range a.Attachments {
|
||||||
|
_, err = sb.db.Exec("INSERT INTO attachments_articles_mapping (article_id, content_type, attachment_id) VALUES (?, ?, ?)", articleID, v.ContentType, v.FileName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,6 +144,9 @@ func (sb *SQLiteBackend) GetArticle(messageID string) (models.Article, error) {
|
|||||||
if err := sb.db.Get(&a.ArticleNumber, "SELECT article_number FROM articles_to_groups WHERE article_id = ?", a.ID); err != nil {
|
if err := sb.db.Get(&a.ArticleNumber, "SELECT article_number FROM articles_to_groups WHERE article_id = ?", a.ID); err != nil {
|
||||||
return a, err
|
return a, err
|
||||||
}
|
}
|
||||||
|
if err := sb.db.Select(&a.Attachments, "SELECT content_type, attachment_id FROM attachments_articles_mapping WHERE article_id = ?", a.ID); err != nil {
|
||||||
|
return a, err
|
||||||
|
}
|
||||||
return a, json.Unmarshal([]byte(a.HeaderRaw), &a.Header)
|
return a, json.Unmarshal([]byte(a.HeaderRaw), &a.Header)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,6 +156,9 @@ func (sb *SQLiteBackend) GetArticleByNumber(g *models.Group, num int) (models.Ar
|
|||||||
return a, err
|
return a, err
|
||||||
}
|
}
|
||||||
a.ArticleNumber = num
|
a.ArticleNumber = num
|
||||||
|
if err := sb.db.Select(&a.Attachments, "SELECT content_type, attachment_id FROM attachments_articles_mapping WHERE article_id = ?", a.ID); err != nil {
|
||||||
|
return a, err
|
||||||
|
}
|
||||||
return a, json.Unmarshal([]byte(a.HeaderRaw), &a.Header)
|
return a, json.Unmarshal([]byte(a.HeaderRaw), &a.Header)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ type Config struct {
|
|||||||
BackendType string `toml:"backend_type"`
|
BackendType string `toml:"backend_type"`
|
||||||
Domain string `toml:"domain"`
|
Domain string `toml:"domain"`
|
||||||
SQLite SQLiteBackendConfig `toml:"sqlite"`
|
SQLite SQLiteBackendConfig `toml:"sqlite"`
|
||||||
|
UploadPath string `toml:"upload_path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SQLiteBackendConfig struct {
|
type SQLiteBackendConfig struct {
|
||||||
|
@ -17,4 +17,10 @@ type Article struct {
|
|||||||
Header textproto.MIMEHeader `db:"-"`
|
Header textproto.MIMEHeader `db:"-"`
|
||||||
Envelope *enmime.Envelope `db:"-"`
|
Envelope *enmime.Envelope `db:"-"`
|
||||||
ArticleNumber int `db:"-"`
|
ArticleNumber int `db:"-"`
|
||||||
|
Attachments []Attachment
|
||||||
|
}
|
||||||
|
|
||||||
|
type Attachment struct {
|
||||||
|
ContentType string `db:"content_type"`
|
||||||
|
FileName string `db:"attachment_id"`
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,9 @@ import (
|
|||||||
"github.com/ChronosX88/yans/internal/utils"
|
"github.com/ChronosX88/yans/internal/utils"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jhillyerd/enmime"
|
"github.com/jhillyerd/enmime"
|
||||||
|
"io/ioutil"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -22,9 +24,10 @@ type Handler struct {
|
|||||||
handlers map[string]func(s *Session, command string, arguments []string, id uint) error
|
handlers map[string]func(s *Session, command string, arguments []string, id uint) error
|
||||||
backend backend.StorageBackend
|
backend backend.StorageBackend
|
||||||
serverDomain string
|
serverDomain string
|
||||||
|
uploadPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHandler(b backend.StorageBackend, serverDomain string) *Handler {
|
func NewHandler(b backend.StorageBackend, serverDomain, uploadPath string) *Handler {
|
||||||
h := &Handler{}
|
h := &Handler{}
|
||||||
h.backend = b
|
h.backend = b
|
||||||
h.handlers = map[string]func(s *Session, command string, arguments []string, id uint) error{
|
h.handlers = map[string]func(s *Session, command string, arguments []string, id uint) error{
|
||||||
@ -49,6 +52,7 @@ func NewHandler(b backend.StorageBackend, serverDomain string) *Handler {
|
|||||||
protocol.CommandXover: h.handleOver,
|
protocol.CommandXover: h.handleOver,
|
||||||
}
|
}
|
||||||
h.serverDomain = serverDomain
|
h.serverDomain = serverDomain
|
||||||
|
h.uploadPath = uploadPath
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,13 +351,29 @@ func (h *Handler) handlePost(s *Session, command string, arguments []string, id
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !parentMessage.Thread.Valid {
|
|
||||||
var parentHeader mail.Header
|
var parentHeader mail.Header
|
||||||
err = json.Unmarshal([]byte(parentMessage.HeaderRaw), &parentHeader)
|
err = json.Unmarshal([]byte(parentMessage.HeaderRaw), &parentHeader)
|
||||||
parentMessageID := parentHeader.Get("Message-ID")
|
parentMessageID := parentHeader.Get("Message-ID")
|
||||||
a.Thread = sql.NullString{String: parentMessageID, Valid: true}
|
a.Thread = sql.NullString{String: parentMessageID, Valid: true}
|
||||||
} else {
|
}
|
||||||
a.Thread = parentMessage.Thread
|
|
||||||
|
if len(envelope.Attachments) > 0 {
|
||||||
|
// save attachments
|
||||||
|
for _, v := range envelope.Attachments {
|
||||||
|
if v.ContentType != "image/jpeg" && v.ContentType != "image/png" && v.ContentType != "image/gif" {
|
||||||
|
return s.tconn.PrintfLine(protocol.NNTPResponse{Code: 441, Message: "disallowed attachment type"}.String())
|
||||||
|
}
|
||||||
|
ext_ := strings.Split(v.FileName, ".")
|
||||||
|
ext := ext_[len(ext_)-1]
|
||||||
|
fileName := uuid.New().String() + "." + ext
|
||||||
|
err = ioutil.WriteFile(path.Join(h.uploadPath, fileName), v.Content, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.Attachments = append(a.Attachments, models.Attachment{
|
||||||
|
ContentType: v.ContentType,
|
||||||
|
FileName: fileName,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -493,7 +513,10 @@ func (h *Handler) handleArticle(s *Session, command string, arguments []string,
|
|||||||
builder = builder.Header(k, j)
|
builder = builder.Header(k, j)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
builder = builder.Text([]byte(a.Body)) // FIXME currently only plain text is supported
|
builder = builder.Text([]byte(a.Body))
|
||||||
|
for _, v := range a.Attachments {
|
||||||
|
builder = builder.AddFileAttachment(path.Join(h.uploadPath, v.FileName))
|
||||||
|
}
|
||||||
p, err := builder.Build()
|
p, err := builder.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -97,7 +97,7 @@ func (ns *NNTPServer) Start() error {
|
|||||||
|
|
||||||
id, _ := uuid.NewUUID()
|
id, _ := uuid.NewUUID()
|
||||||
closed := make(chan bool)
|
closed := make(chan bool)
|
||||||
session, err := NewSession(ctx, conn, Capabilities, id.String(), closed, NewHandler(ns.backend, ns.cfg.Domain))
|
session, err := NewSession(ctx, conn, Capabilities, id.String(), closed, NewHandler(ns.backend, ns.cfg.Domain, ns.cfg.UploadPath))
|
||||||
ns.sessionPoolMutex.Lock()
|
ns.sessionPoolMutex.Lock()
|
||||||
ns.sessionPool[id.String()] = session
|
ns.sessionPool[id.String()] = session
|
||||||
ns.sessionPoolMutex.Unlock()
|
ns.sessionPoolMutex.Unlock()
|
||||||
|
Loading…
Reference in New Issue
Block a user