mirror of
https://github.com/dnote/dnote
synced 2026-03-16 15:35:52 +01:00
348 lines
8.8 KiB
Go
348 lines
8.8 KiB
Go
/* Copyright (C) 2019 Monomax Software Pty Ltd
|
|
*
|
|
* This file is part of Dnote.
|
|
*
|
|
* Dnote is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Dnote is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with Dnote. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/dnote/dnote/server/api/helpers"
|
|
"github.com/dnote/dnote/server/api/presenters"
|
|
"github.com/dnote/dnote/server/database"
|
|
"github.com/gorilla/mux"
|
|
"github.com/jinzhu/gorm"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
func respondWithNote(w http.ResponseWriter, note database.Note) {
|
|
presentedNote := presenters.PresentNote(note)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if err := json.NewEncoder(w).Encode(presentedNote); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
|
|
func preloadNote(conn *gorm.DB) *gorm.DB {
|
|
return conn.Preload("Book").Preload("User")
|
|
}
|
|
|
|
func (a *App) getDemoNote(w http.ResponseWriter, r *http.Request) {
|
|
db := database.DBConn
|
|
vars := mux.Vars(r)
|
|
noteUUID := vars["noteUUID"]
|
|
|
|
demoUserID, err := helpers.GetDemoUserID()
|
|
if err != nil {
|
|
http.Error(w, errors.Wrap(err, "finding demo user").Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
var note database.Note
|
|
conn := db.Where("uuid = ? AND user_id = ?", noteUUID, demoUserID)
|
|
conn = preloadNote(conn)
|
|
conn.Find(¬e)
|
|
|
|
if conn.RecordNotFound() {
|
|
http.Error(w, "not found", http.StatusNotFound)
|
|
return
|
|
} else if err := conn.Error; err != nil {
|
|
http.Error(w, errors.Wrap(err, "finding note").Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
respondWithNote(w, note)
|
|
}
|
|
|
|
func (a *App) getNote(w http.ResponseWriter, r *http.Request) {
|
|
user, ok := r.Context().Value(helpers.KeyUser).(database.User)
|
|
if !ok {
|
|
http.Error(w, "No authenticated user found", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
db := database.DBConn
|
|
vars := mux.Vars(r)
|
|
noteUUID := vars["noteUUID"]
|
|
|
|
var note database.Note
|
|
conn := db.Debug().Where("uuid = ? AND user_id = ?", noteUUID, user.ID)
|
|
conn = preloadNote(conn)
|
|
conn.Find(¬e)
|
|
|
|
if conn.RecordNotFound() {
|
|
http.Error(w, "not found", http.StatusNotFound)
|
|
return
|
|
} else if err := conn.Error; err != nil {
|
|
http.Error(w, errors.Wrap(err, "finding note").Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
respondWithNote(w, note)
|
|
}
|
|
|
|
/**** getNotesHandler */
|
|
|
|
// GetNotesResponse is a reponse by getNotesHandler
|
|
type GetNotesResponse struct {
|
|
Notes []presenters.Note `json:"notes"`
|
|
Total int `json:"total"`
|
|
PrevDate *int64 `json:"prev_date"`
|
|
}
|
|
|
|
func (a *App) getDemoNotes(w http.ResponseWriter, r *http.Request) {
|
|
userID, err := helpers.GetDemoUserID()
|
|
if err != nil {
|
|
http.Error(w, errors.Wrap(err, "finding demo user id").Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
query := r.URL.Query()
|
|
|
|
respondGetNotes(userID, query, w)
|
|
}
|
|
|
|
func (a *App) getNotes(w http.ResponseWriter, r *http.Request) {
|
|
user, ok := r.Context().Value(helpers.KeyUser).(database.User)
|
|
if !ok {
|
|
http.Error(w, "No authenticated user found", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
query := r.URL.Query()
|
|
|
|
respondGetNotes(user.ID, query, w)
|
|
}
|
|
|
|
func respondGetNotes(userID int, query url.Values, w http.ResponseWriter) {
|
|
err := validateGetNotesQuery(query)
|
|
if err != nil {
|
|
http.Error(w, errors.Wrap(err, "validating query parameters").Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
q, err := parseGetNotesQuery(query)
|
|
if err != nil {
|
|
http.Error(w, errors.Wrap(err, "parsing query parameters").Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
dateLowerbound, dateUpperbound := getDateBounds(q.Year, q.Month)
|
|
|
|
baseConn := getNotesBaseQuery(userID, q)
|
|
conn := baseConn.Where("notes.added_on >= ? AND notes.added_on < ?", dateLowerbound, dateUpperbound)
|
|
|
|
var total int
|
|
err = conn.Debug().Model(database.Note{}).Count(&total).Error
|
|
if err != nil {
|
|
http.Error(w, errors.Wrap(err, "counting total").Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
notes := []database.Note{}
|
|
if total != 0 {
|
|
conn = orderGetNotes(conn)
|
|
conn = preloadNote(conn)
|
|
conn = paginate(conn, q.Page)
|
|
|
|
err = conn.Find(¬es).Error
|
|
if err != nil {
|
|
http.Error(w, errors.Wrap(err, "finding notes").Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
|
|
// peek the prev date
|
|
var prevDateUpperbound int64
|
|
if len(notes) > 0 {
|
|
lastNote := notes[len(notes)-1]
|
|
prevDateUpperbound = lastNote.AddedOn
|
|
} else {
|
|
prevDateUpperbound = dateLowerbound
|
|
}
|
|
|
|
prevDate, err := getPrevDate(baseConn, prevDateUpperbound)
|
|
if err != nil {
|
|
http.Error(w, errors.Wrap(err, "getting prevDate").Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
presentedNotes := presenters.PresentNotes(notes)
|
|
|
|
response := GetNotesResponse{
|
|
Notes: presentedNotes,
|
|
Total: total,
|
|
PrevDate: prevDate,
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if err := json.NewEncoder(w).Encode(response); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
|
|
func getPrevDate(baseConn *gorm.DB, dateUpperbound int64) (*int64, error) {
|
|
var prevNote database.Note
|
|
|
|
conn := baseConn.
|
|
Select("notes.added_on").
|
|
Where("notes.added_on < ?", dateUpperbound).
|
|
Order("notes.added_on DESC")
|
|
|
|
if conn.First(&prevNote).RecordNotFound() {
|
|
return nil, nil
|
|
}
|
|
|
|
if err := conn.Error; err != nil {
|
|
return nil, errors.Wrap(err, "querying previous note")
|
|
}
|
|
|
|
return &prevNote.AddedOn, nil
|
|
}
|
|
|
|
func validateGetNotesQuery(q url.Values) error {
|
|
if q.Get("year") == "" {
|
|
return errors.New("'year' is required")
|
|
}
|
|
if q.Get("month") == "" {
|
|
return errors.New("'month' is required")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type getNotesQuery struct {
|
|
Year int
|
|
Month int
|
|
Page int
|
|
BookUUID string
|
|
Encrypted *bool
|
|
}
|
|
|
|
func parseGetNotesQuery(q url.Values) (getNotesQuery, error) {
|
|
yearStr := q.Get("year")
|
|
monthStr := q.Get("month")
|
|
bookStr := q.Get("book")
|
|
pageStr := q.Get("page")
|
|
encryptedStr := q.Get("encrypted")
|
|
|
|
var page int
|
|
if len(pageStr) > 0 {
|
|
p, err := strconv.Atoi(pageStr)
|
|
if err != nil {
|
|
return getNotesQuery{}, errors.Wrap(err, "parsing page")
|
|
}
|
|
|
|
page = p
|
|
} else {
|
|
page = 1
|
|
}
|
|
|
|
year, err := strconv.Atoi(yearStr)
|
|
if err != nil {
|
|
return getNotesQuery{}, errors.Wrapf(err, "invalid year %s", yearStr)
|
|
}
|
|
month, err := strconv.Atoi(monthStr)
|
|
if err != nil {
|
|
return getNotesQuery{}, errors.Wrapf(err, "invalid month %s", monthStr)
|
|
}
|
|
if month < 1 || month > 12 {
|
|
return getNotesQuery{}, errors.Errorf("Invalid month %s", monthStr)
|
|
}
|
|
|
|
var encrypted *bool
|
|
if strings.ToLower(encryptedStr) == "true" {
|
|
*encrypted = true
|
|
} else if strings.ToLower(encryptedStr) == "false" {
|
|
*encrypted = false
|
|
}
|
|
|
|
ret := getNotesQuery{
|
|
Year: year,
|
|
Month: month,
|
|
Page: page,
|
|
BookUUID: bookStr,
|
|
Encrypted: encrypted,
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func getDateBounds(year, month int) (int64, int64) {
|
|
var yearUpperbound, monthUpperbound int
|
|
|
|
if month == 12 {
|
|
monthUpperbound = 1
|
|
yearUpperbound = year + 1
|
|
} else {
|
|
monthUpperbound = month + 1
|
|
yearUpperbound = year
|
|
}
|
|
|
|
lower := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC).UnixNano()
|
|
upper := time.Date(yearUpperbound, time.Month(monthUpperbound), 1, 0, 0, 0, 0, time.UTC).UnixNano()
|
|
|
|
return lower, upper
|
|
}
|
|
|
|
func getNotesBaseQuery(userID int, q getNotesQuery) *gorm.DB {
|
|
db := database.DBConn
|
|
|
|
conn := db.Where("notes.user_id = ? AND notes.deleted = ?", userID, false)
|
|
|
|
if len(q.BookUUID) > 0 {
|
|
conn = conn.Joins("INNER JOIN books ON books.uuid = notes.book_uuid").
|
|
Where("books.uuid = ?", q.BookUUID)
|
|
}
|
|
if q.Encrypted != nil {
|
|
conn = conn.Where("notes.encrypted = ?", *q.Encrypted)
|
|
}
|
|
|
|
return conn
|
|
}
|
|
|
|
func orderGetNotes(conn *gorm.DB) *gorm.DB {
|
|
return conn.Order("notes.added_on DESC, notes.id DESC")
|
|
}
|
|
|
|
func (a *App) legacyGetNotes(w http.ResponseWriter, r *http.Request) {
|
|
user, ok := r.Context().Value(helpers.KeyUser).(database.User)
|
|
if !ok {
|
|
http.Error(w, "No authenticated user found", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
var notes []database.Note
|
|
db := database.DBConn
|
|
if err := db.Where("user_id = ? AND encrypted = false", user.ID).Find(¬es).Error; err != nil {
|
|
http.Error(w, "finding notes", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
presented := presenters.PresentNotes(notes)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if err := json.NewEncoder(w).Encode(presented); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|