mirror of
https://github.com/documize/community.git
synced 2025-07-20 05:39:42 +02:00
Merge branch 'master' into github-tidy-up
This commit is contained in:
commit
0316d6b2eb
9 changed files with 88 additions and 65 deletions
|
@ -15,7 +15,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/documize/community/core/api/request"
|
|
||||||
"github.com/documize/community/core/api/store"
|
"github.com/documize/community/core/api/store"
|
||||||
"github.com/documize/community/core/log"
|
"github.com/documize/community/core/log"
|
||||||
)
|
)
|
||||||
|
@ -26,18 +25,6 @@ func init() {
|
||||||
storageProvider = new(store.LocalStorageProvider)
|
storageProvider = new(store.LocalStorageProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
//getAppURL returns full HTTP url for the app
|
|
||||||
func getAppURL(c request.Context, endpoint string) string {
|
|
||||||
|
|
||||||
scheme := "http://"
|
|
||||||
|
|
||||||
if c.SSL {
|
|
||||||
scheme = "https://"
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%s%s/%s", scheme, c.AppURL, endpoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writePayloadError(w http.ResponseWriter, method string, err error) {
|
func writePayloadError(w http.ResponseWriter, method string, err error) {
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
|
|
@ -407,7 +407,7 @@ func SetFolderPermissions(w http.ResponseWriter, r *http.Request) {
|
||||||
hasEveryoneRole := false
|
hasEveryoneRole := false
|
||||||
roleCount := 0
|
roleCount := 0
|
||||||
|
|
||||||
url := getAppURL(p.Context, fmt.Sprintf("s/%s/%s", label.RefID, utility.MakeSlug(label.Name)))
|
url := p.Context.GetAppURL(fmt.Sprintf("s/%s/%s", label.RefID, utility.MakeSlug(label.Name)))
|
||||||
|
|
||||||
for _, role := range model.Roles {
|
for _, role := range model.Roles {
|
||||||
role.OrgID = p.Context.OrgID
|
role.OrgID = p.Context.OrgID
|
||||||
|
@ -742,13 +742,13 @@ func InviteToFolder(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
url := getAppURL(p.Context, fmt.Sprintf("s/%s/%s", label.RefID, utility.MakeSlug(label.Name)))
|
url := p.Context.GetAppURL(fmt.Sprintf("s/%s/%s", label.RefID, utility.MakeSlug(label.Name)))
|
||||||
go mail.ShareFolderExistingUser(email, inviter.Fullname(), url, label.Name, model.Message)
|
go mail.ShareFolderExistingUser(email, inviter.Fullname(), url, label.Name, model.Message)
|
||||||
log.Info(fmt.Sprintf("%s is sharing space %s with existing user %s", inviter.Email, label.Name, email))
|
log.Info(fmt.Sprintf("%s is sharing space %s with existing user %s", inviter.Email, label.Name, email))
|
||||||
} else {
|
} else {
|
||||||
// On-board new user
|
// On-board new user
|
||||||
if strings.Contains(email, "@") {
|
if strings.Contains(email, "@") {
|
||||||
url := getAppURL(p.Context, fmt.Sprintf("auth/share/%s/%s", label.RefID, utility.MakeSlug(label.Name)))
|
url := p.Context.GetAppURL(fmt.Sprintf("auth/share/%s/%s", label.RefID, utility.MakeSlug(label.Name)))
|
||||||
err = inviteNewUserToSharedFolder(p, email, inviter, url, label, model.Message)
|
err = inviteNewUserToSharedFolder(p, email, inviter, url, label, model.Message)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -80,7 +80,7 @@ Disallow: /
|
||||||
|
|
||||||
// Anonymous access would mean we allow bots to crawl.
|
// Anonymous access would mean we allow bots to crawl.
|
||||||
if org.AllowAnonymousAccess {
|
if org.AllowAnonymousAccess {
|
||||||
sitemap := getAppURL(p.Context, "sitemap.xml")
|
sitemap := p.Context.GetAppURL("sitemap.xml")
|
||||||
robots = fmt.Sprintf(
|
robots = fmt.Sprintf(
|
||||||
`User-agent: *
|
`User-agent: *
|
||||||
Disallow: /settings/
|
Disallow: /settings/
|
||||||
|
@ -138,7 +138,7 @@ func GetSitemap(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
for _, folder := range folders {
|
for _, folder := range folders {
|
||||||
var item sitemapItem
|
var item sitemapItem
|
||||||
item.URL = getAppURL(p.Context, fmt.Sprintf("s/%s/%s", folder.RefID, utility.MakeSlug(folder.Name)))
|
item.URL = p.Context.GetAppURL(fmt.Sprintf("s/%s/%s", folder.RefID, utility.MakeSlug(folder.Name)))
|
||||||
item.Date = folder.Revised.Format("2006-01-02T15:04:05.999999-07:00")
|
item.Date = folder.Revised.Format("2006-01-02T15:04:05.999999-07:00")
|
||||||
items = append(items, item)
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
|
@ -152,7 +152,7 @@ func GetSitemap(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
for _, document := range documents {
|
for _, document := range documents {
|
||||||
var item sitemapItem
|
var item sitemapItem
|
||||||
item.URL = getAppURL(p.Context, fmt.Sprintf("s/%s/%s/d/%s/%s",
|
item.URL = p.Context.GetAppURL(fmt.Sprintf("s/%s/%s/d/%s/%s",
|
||||||
document.FolderID, utility.MakeSlug(document.Folder), document.DocumentID, utility.MakeSlug(document.Document)))
|
document.FolderID, utility.MakeSlug(document.Folder), document.DocumentID, utility.MakeSlug(document.Document)))
|
||||||
item.Date = document.Revised.Format("2006-01-02T15:04:05.999999-07:00")
|
item.Date = document.Revised.Format("2006-01-02T15:04:05.999999-07:00")
|
||||||
items = append(items, item)
|
items = append(items, item)
|
||||||
|
|
|
@ -174,13 +174,13 @@ func AddUser(w http.ResponseWriter, r *http.Request) {
|
||||||
auth := fmt.Sprintf("%s:%s:%s", p.Context.AppURL, userModel.Email, requestedPassword[:size])
|
auth := fmt.Sprintf("%s:%s:%s", p.Context.AppURL, userModel.Email, requestedPassword[:size])
|
||||||
encrypted := utility.EncodeBase64([]byte(auth))
|
encrypted := utility.EncodeBase64([]byte(auth))
|
||||||
|
|
||||||
url := fmt.Sprintf("%s/%s", getAppURL(p.Context, "auth/sso"), url.QueryEscape(string(encrypted)))
|
url := fmt.Sprintf("%s/%s", p.Context.GetAppURL("auth/sso"), url.QueryEscape(string(encrypted)))
|
||||||
go mail.InviteNewUser(userModel.Email, inviter.Fullname(), url, userModel.Email, requestedPassword)
|
go mail.InviteNewUser(userModel.Email, inviter.Fullname(), url, userModel.Email, requestedPassword)
|
||||||
|
|
||||||
log.Info(fmt.Sprintf("%s invited by %s on %s", userModel.Email, inviter.Email, p.Context.AppURL))
|
log.Info(fmt.Sprintf("%s invited by %s on %s", userModel.Email, inviter.Email, p.Context.AppURL))
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
go mail.InviteExistingUser(userModel.Email, inviter.Fullname(), getAppURL(p.Context, ""))
|
go mail.InviteExistingUser(userModel.Email, inviter.Fullname(), p.Context.GetAppURL(""))
|
||||||
|
|
||||||
log.Info(fmt.Sprintf("%s is giving access to an existing user %s", inviter.Email, userModel.Email))
|
log.Info(fmt.Sprintf("%s is giving access to an existing user %s", inviter.Email, userModel.Email))
|
||||||
}
|
}
|
||||||
|
@ -605,9 +605,7 @@ func ForgotUserPassword(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
log.IfErr(tx.Commit())
|
log.IfErr(tx.Commit())
|
||||||
|
|
||||||
appURL := getAppURL(p.Context, fmt.Sprintf("auth/reset/%s", token))
|
appURL := p.Context.GetAppURL(fmt.Sprintf("auth/reset/%s", token))
|
||||||
|
|
||||||
fmt.Println(appURL)
|
|
||||||
|
|
||||||
go mail.PasswordReset(user.Email, appURL)
|
go mail.PasswordReset(user.Email, appURL)
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,8 @@ import (
|
||||||
"net/smtp"
|
"net/smtp"
|
||||||
|
|
||||||
"github.com/documize/community/core/api/request"
|
"github.com/documize/community/core/api/request"
|
||||||
"github.com/documize/community/core/web"
|
|
||||||
"github.com/documize/community/core/log"
|
"github.com/documize/community/core/log"
|
||||||
|
"github.com/documize/community/core/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InviteNewUser invites someone new providing credentials, explaining the product and stating who is inviting them.
|
// InviteNewUser invites someone new providing credentials, explaining the product and stating who is inviting them.
|
||||||
|
@ -44,8 +44,8 @@ func InviteNewUser(recipient, inviter, url, username, password string) {
|
||||||
|
|
||||||
subject := fmt.Sprintf("%s has invited you to Documize", inviter)
|
subject := fmt.Sprintf("%s has invited you to Documize", inviter)
|
||||||
|
|
||||||
e := newEmail()
|
e := NewEmail()
|
||||||
e.From = creds.SMTPsender()
|
e.From = SMTPCreds.SMTPsender()
|
||||||
e.To = []string{recipient}
|
e.To = []string{recipient}
|
||||||
e.Subject = subject
|
e.Subject = subject
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ func InviteNewUser(recipient, inviter, url, username, password string) {
|
||||||
log.IfErr(t.Execute(buffer, ¶meters))
|
log.IfErr(t.Execute(buffer, ¶meters))
|
||||||
e.HTML = buffer.Bytes()
|
e.HTML = buffer.Bytes()
|
||||||
|
|
||||||
err = e.Send(getHost(), getAuth())
|
err = e.Send(GetHost(), GetAuth())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(fmt.Sprintf("%s - unable to send email", method), err)
|
log.Error(fmt.Sprintf("%s - unable to send email", method), err)
|
||||||
|
@ -95,8 +95,8 @@ func InviteExistingUser(recipient, inviter, url string) {
|
||||||
|
|
||||||
subject := fmt.Sprintf("%s has invited you to their Documize account", inviter)
|
subject := fmt.Sprintf("%s has invited you to their Documize account", inviter)
|
||||||
|
|
||||||
e := newEmail()
|
e := NewEmail()
|
||||||
e.From = creds.SMTPsender()
|
e.From = SMTPCreds.SMTPsender()
|
||||||
e.To = []string{recipient}
|
e.To = []string{recipient}
|
||||||
e.Subject = subject
|
e.Subject = subject
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ func InviteExistingUser(recipient, inviter, url string) {
|
||||||
log.IfErr(t.Execute(buffer, ¶meters))
|
log.IfErr(t.Execute(buffer, ¶meters))
|
||||||
e.HTML = buffer.Bytes()
|
e.HTML = buffer.Bytes()
|
||||||
|
|
||||||
err = e.Send(getHost(), getAuth())
|
err = e.Send(GetHost(), GetAuth())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(fmt.Sprintf("%s - unable to send email", method), err)
|
log.Error(fmt.Sprintf("%s - unable to send email", method), err)
|
||||||
|
@ -137,8 +137,8 @@ func PasswordReset(recipient, url string) {
|
||||||
|
|
||||||
subject := "Documize password reset request"
|
subject := "Documize password reset request"
|
||||||
|
|
||||||
e := newEmail()
|
e := NewEmail()
|
||||||
e.From = creds.SMTPsender() //e.g. "Documize <hello@documize.com>"
|
e.From = SMTPCreds.SMTPsender() //e.g. "Documize <hello@documize.com>"
|
||||||
e.To = []string{recipient}
|
e.To = []string{recipient}
|
||||||
e.Subject = subject
|
e.Subject = subject
|
||||||
|
|
||||||
|
@ -155,7 +155,7 @@ func PasswordReset(recipient, url string) {
|
||||||
log.IfErr(t.Execute(buffer, ¶meters))
|
log.IfErr(t.Execute(buffer, ¶meters))
|
||||||
e.HTML = buffer.Bytes()
|
e.HTML = buffer.Bytes()
|
||||||
|
|
||||||
err = e.Send(getHost(), getAuth())
|
err = e.Send(GetHost(), GetAuth())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(fmt.Sprintf("%s - unable to send email", method), err)
|
log.Error(fmt.Sprintf("%s - unable to send email", method), err)
|
||||||
|
@ -182,8 +182,8 @@ func ShareFolderExistingUser(recipient, inviter, url, folder, intro string) {
|
||||||
|
|
||||||
subject := fmt.Sprintf("%s has shared %s with you", inviter, folder)
|
subject := fmt.Sprintf("%s has shared %s with you", inviter, folder)
|
||||||
|
|
||||||
e := newEmail()
|
e := NewEmail()
|
||||||
e.From = creds.SMTPsender()
|
e.From = SMTPCreds.SMTPsender()
|
||||||
e.To = []string{recipient}
|
e.To = []string{recipient}
|
||||||
e.Subject = subject
|
e.Subject = subject
|
||||||
|
|
||||||
|
@ -206,7 +206,7 @@ func ShareFolderExistingUser(recipient, inviter, url, folder, intro string) {
|
||||||
log.IfErr(t.Execute(buffer, ¶meters))
|
log.IfErr(t.Execute(buffer, ¶meters))
|
||||||
e.HTML = buffer.Bytes()
|
e.HTML = buffer.Bytes()
|
||||||
|
|
||||||
err = e.Send(getHost(), getAuth())
|
err = e.Send(GetHost(), GetAuth())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(fmt.Sprintf("%s - unable to send email", method), err)
|
log.Error(fmt.Sprintf("%s - unable to send email", method), err)
|
||||||
|
@ -233,8 +233,8 @@ func ShareFolderNewUser(recipient, inviter, url, folder, invitationMessage strin
|
||||||
|
|
||||||
subject := fmt.Sprintf("%s has shared %s with you on Documize", inviter, folder)
|
subject := fmt.Sprintf("%s has shared %s with you on Documize", inviter, folder)
|
||||||
|
|
||||||
e := newEmail()
|
e := NewEmail()
|
||||||
e.From = creds.SMTPsender()
|
e.From = SMTPCreds.SMTPsender()
|
||||||
e.To = []string{recipient}
|
e.To = []string{recipient}
|
||||||
e.Subject = subject
|
e.Subject = subject
|
||||||
|
|
||||||
|
@ -257,14 +257,15 @@ func ShareFolderNewUser(recipient, inviter, url, folder, invitationMessage strin
|
||||||
log.IfErr(t.Execute(buffer, ¶meters))
|
log.IfErr(t.Execute(buffer, ¶meters))
|
||||||
e.HTML = buffer.Bytes()
|
e.HTML = buffer.Bytes()
|
||||||
|
|
||||||
err = e.Send(getHost(), getAuth())
|
err = e.Send(GetHost(), GetAuth())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(fmt.Sprintf("%s - unable to send email", method), err)
|
log.Error(fmt.Sprintf("%s - unable to send email", method), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var creds = struct{ SMTPuserid, SMTPpassword, SMTPhost, SMTPport, SMTPsender func() string }{
|
// SMTPCreds return SMTP configuration.
|
||||||
|
var SMTPCreds = struct{ SMTPuserid, SMTPpassword, SMTPhost, SMTPport, SMTPsender func() string }{
|
||||||
func() string { return request.ConfigString("SMTP", "userid") },
|
func() string { return request.ConfigString("SMTP", "userid") },
|
||||||
func() string { return request.ConfigString("SMTP", "password") },
|
func() string { return request.ConfigString("SMTP", "password") },
|
||||||
func() string { return request.ConfigString("SMTP", "host") },
|
func() string { return request.ConfigString("SMTP", "host") },
|
||||||
|
@ -278,16 +279,16 @@ var creds = struct{ SMTPuserid, SMTPpassword, SMTPhost, SMTPport, SMTPsender fun
|
||||||
func() string { return request.ConfigString("SMTP", "sender") },
|
func() string { return request.ConfigString("SMTP", "sender") },
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to return SMTP credentials
|
// GetAuth to return SMTP credentials
|
||||||
func getAuth() smtp.Auth {
|
func GetAuth() smtp.Auth {
|
||||||
a := smtp.PlainAuth("", creds.SMTPuserid(), creds.SMTPpassword(), creds.SMTPhost())
|
a := smtp.PlainAuth("", SMTPCreds.SMTPuserid(), SMTPCreds.SMTPpassword(), SMTPCreds.SMTPhost())
|
||||||
//fmt.Printf("DEBUG getAuth() = %#v\n", a)
|
//fmt.Printf("DEBUG GetAuth() = %#v\n", a)
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to return SMTP host details
|
// GetHost to return SMTP host details
|
||||||
func getHost() string {
|
func GetHost() string {
|
||||||
h := creds.SMTPhost() + ":" + creds.SMTPport()
|
h := SMTPCreds.SMTPhost() + ":" + SMTPCreds.SMTPport()
|
||||||
//fmt.Printf("DEBUG getHost() = %#v\n", h)
|
//fmt.Printf("DEBUG GetHost() = %#v\n", h)
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,6 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/documize/community/core/log"
|
|
||||||
"io"
|
"io"
|
||||||
"mime"
|
"mime"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
|
@ -56,6 +55,8 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/documize/community/core/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -77,8 +78,8 @@ type Email struct {
|
||||||
ReadReceipt []string
|
ReadReceipt []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// newEmail creates an Email, and returns the pointer to it.
|
// NewEmail creates an Email, and returns the pointer to it.
|
||||||
func newEmail() *Email {
|
func NewEmail() *Email {
|
||||||
return &Email{Headers: textproto.MIMEHeader{}}
|
return &Email{Headers: textproto.MIMEHeader{}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,17 @@ type Context struct {
|
||||||
Transaction *sqlx.Tx
|
Transaction *sqlx.Tx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//GetAppURL returns full HTTP url for the app
|
||||||
|
func (c *Context) GetAppURL(endpoint string) string {
|
||||||
|
scheme := "http://"
|
||||||
|
|
||||||
|
if c.SSL {
|
||||||
|
scheme = "https://"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s%s/%s", scheme, c.AppURL, endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
// NewContext simply returns a blank Context type.
|
// NewContext simply returns a blank Context type.
|
||||||
func NewContext() Context {
|
func NewContext() Context {
|
||||||
return Context{}
|
return Context{}
|
||||||
|
|
|
@ -22,17 +22,17 @@ import (
|
||||||
|
|
||||||
// GenerateRandomPassword provides a string suitable for use as a password.
|
// GenerateRandomPassword provides a string suitable for use as a password.
|
||||||
func GenerateRandomPassword() string {
|
func GenerateRandomPassword() string {
|
||||||
c := 5
|
return GenerateRandom(5)
|
||||||
b := make([]byte, c)
|
|
||||||
_, err := rand.Read(b)
|
|
||||||
log.IfErr(err)
|
|
||||||
return hex.EncodeToString(b)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateSalt provides a string suitable for use as a salt value.
|
// GenerateSalt provides a string suitable for use as a salt value.
|
||||||
func GenerateSalt() string {
|
func GenerateSalt() string {
|
||||||
c := 20
|
return GenerateRandom(20)
|
||||||
b := make([]byte, c)
|
}
|
||||||
|
|
||||||
|
// GenerateRandom returns a string of the specified length using crypo/rand
|
||||||
|
func GenerateRandom(size int) string {
|
||||||
|
b := make([]byte, size)
|
||||||
_, err := rand.Read(b)
|
_, err := rand.Read(b)
|
||||||
log.IfErr(err)
|
log.IfErr(err)
|
||||||
return hex.EncodeToString(b)
|
return hex.EncodeToString(b)
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
@ -124,3 +125,27 @@ func WriteSuccessEmptyJSON(w http.ResponseWriter) {
|
||||||
_, err := w.Write([]byte("{}"))
|
_, err := w.Write([]byte("{}"))
|
||||||
log.IfErr(err)
|
log.IfErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WriteMarshalError(w http.ResponseWriter, err error) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
_, err2 := w.Write([]byte("{Error: 'JSON marshal failed'}"))
|
||||||
|
log.IfErr(err2)
|
||||||
|
log.Error("Failed to JSON marshal", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteJSON serializes data as JSON to HTTP response.
|
||||||
|
func WriteJSON(w http.ResponseWriter, v interface{}) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
|
j, err := json.Marshal(v)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
WriteMarshalError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.Write(j)
|
||||||
|
log.IfErr(err)
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue