diff --git a/.travis.yml b/.travis.yml
index 7f9be694..529b7bae 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,6 +12,7 @@ before_install:
- sudo apt-get --yes remove postgresql\*
- sudo apt-get install -y postgresql-11 postgresql-client-11
- sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf
+ - sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/11/main/postgresql.conf
- sudo service postgresql restart 11
before_script:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1983bf49..ea780451 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,10 @@ All notable changes to the projects under this repository will be documented in
The following log documents the history of the server project.
+### Unreleased
+
+- Remove the deprecated features related to digests and repetition rules.
+
### 0.5.0 - 2020-02-06
#### Changed
diff --git a/jslib/src/operations/digests.ts b/jslib/src/operations/digests.ts
deleted file mode 100644
index 70b7a8c5..00000000
--- a/jslib/src/operations/digests.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import initDigestsService from '../services/digests';
-import { HttpClientConfig } from '../helpers/http';
-
-export default function init(c: HttpClientConfig) {
- const digestsService = initDigestsService(c);
-
- return {
- fetchAll: params => {
- return digestsService.fetchAll(params);
- },
-
- fetch: (noteUUID: string) => {
- return digestsService.fetch(noteUUID);
- }
- };
-}
diff --git a/jslib/src/operations/index.ts b/jslib/src/operations/index.ts
index 2d4826b6..7a13f93f 100644
--- a/jslib/src/operations/index.ts
+++ b/jslib/src/operations/index.ts
@@ -19,18 +19,15 @@
import { HttpClientConfig } from '../helpers/http';
import initBooksOperation from './books';
import initNotesOperation from './notes';
-import initDigestsOperation from './digests';
// init initializes operations with the given http configuration
// and returns an object of all services.
export default function initOperations(c: HttpClientConfig) {
const booksOperation = initBooksOperation(c);
const notesOperation = initNotesOperation(c);
- const digestsOperation = initDigestsOperation(c);
return {
books: booksOperation,
- notes: notesOperation,
- digests: digestsOperation
+ notes: notesOperation
};
}
diff --git a/jslib/src/operations/types.ts b/jslib/src/operations/types.ts
index 6ca0dcd0..6066020f 100644
--- a/jslib/src/operations/types.ts
+++ b/jslib/src/operations/types.ts
@@ -56,49 +56,3 @@ export type BookData = {
updated_at: string;
label: string;
};
-
-// BookDomain is the possible values for the field in the repetition_rule
-// indicating how to derive the source books for the repetition_rule.
-export enum BookDomain {
- // All incidates that all books are eligible to be the source books
- All = 'all',
- // Including incidates that some specified books are eligible to be the source books
- Including = 'including',
- // Excluding incidates that all books except for some specified books are eligible to be the source books
- Excluding = 'excluding'
-}
-
-export interface RepetitionRuleData {
- uuid: string;
- title: string;
- enabled: boolean;
- hour: number;
- minute: number;
- bookDomain: BookDomain;
- frequency: number;
- books: BookData[];
- lastActive: number;
- nextActive: number;
- noteCount: number;
- createdAt: string;
- updatedAt: string;
-}
-
-export interface ReceiptData {
- createdAt: string;
- updatedAt: string;
-}
-
-export interface DigestData {
- uuid: string;
- createdAt: string;
- updatedAt: string;
- version: number;
- notes: DigestNoteData[];
- isRead: boolean;
- repetitionRule: RepetitionRuleData;
-}
-
-export interface DigestNoteData extends NoteData {
- isReviewed: boolean;
-}
diff --git a/jslib/src/services/digests.ts b/jslib/src/services/digests.ts
deleted file mode 100644
index 618921b8..00000000
--- a/jslib/src/services/digests.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import { getHttpClient, HttpClientConfig } from '../helpers/http';
-import { getPath } from '../helpers/url';
-import { DigestData, DigestNoteData } from '../operations/types';
-import { mapNote } from './notes';
-
-function mapDigestNote(item): DigestNoteData {
- const note = mapNote(item);
-
- return {
- ...note,
- isReviewed: item.is_reviewed
- };
-}
-
-// mapDigest maps the presented digest response to DigestData
-function mapDigest(item): DigestData {
- return {
- uuid: item.uuid,
- createdAt: item.created_at,
- updatedAt: item.updated_at,
- version: item.version,
- notes: item.notes.map(mapDigestNote),
- repetitionRule: {
- uuid: item.repetition_rule.uuid,
- title: item.repetition_rule.title,
- enabled: item.repetition_rule.enabled,
- hour: item.repetition_rule.hour,
- minute: item.repetition_rule.minute,
- bookDomain: item.repetition_rule.book_domain,
- frequency: item.repetition_rule.frequency,
- books: item.repetition_rule.books,
- lastActive: item.repetition_rule.last_active,
- nextActive: item.repetition_rule.next_active,
- noteCount: item.repetition_rule.note_count,
- createdAt: item.repetition_rule.created_at,
- updatedAt: item.repetition_rule.updated_at
- },
- isRead: item.is_read
- };
-}
-
-export interface FetchAllResult {
- total: number;
- items: DigestData[];
-}
-
-export default function init(config: HttpClientConfig) {
- const client = getHttpClient(config);
-
- return {
- fetch: (digestUUID: string): Promise => {
- const endpoint = `/digests/${digestUUID}`;
-
- return client.get(endpoint).then(mapDigest);
- },
-
- fetchAll: ({ page, status }): Promise => {
- const path = '/digests';
-
- const endpoint = getPath(path, { page, status });
-
- return client.get(endpoint).then(res => {
- return {
- total: res.total,
- items: res.items.map(mapDigest)
- };
- });
- }
- };
-}
diff --git a/jslib/src/services/index.ts b/jslib/src/services/index.ts
index 4e2966bf..f486a177 100644
--- a/jslib/src/services/index.ts
+++ b/jslib/src/services/index.ts
@@ -21,9 +21,6 @@ import initUsersService from './users';
import initBooksService from './books';
import initNotesService from './notes';
import initPaymentService from './payment';
-import initDigestsService from './digests';
-import initRepetitionRulesService from './repetitionRules';
-import initNoteReviews from './noteReviews';
// init initializes service helpers with the given http configuration
// and returns an object of all services.
@@ -32,17 +29,11 @@ export default function initServices(c: HttpClientConfig) {
const booksService = initBooksService(c);
const notesService = initNotesService(c);
const paymentService = initPaymentService(c);
- const digestsService = initDigestsService(c);
- const repetitionRulesService = initRepetitionRulesService(c);
- const noteReviewsService = initNoteReviews(c);
return {
users: usersService,
books: booksService,
notes: notesService,
- payment: paymentService,
- digests: digestsService,
- repetitionRules: repetitionRulesService,
- noteReviews: noteReviewsService
+ payment: paymentService
};
}
diff --git a/jslib/src/services/noteReviews.ts b/jslib/src/services/noteReviews.ts
deleted file mode 100644
index 5d2996a6..00000000
--- a/jslib/src/services/noteReviews.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import { getHttpClient, HttpClientConfig } from '../helpers/http';
-
-export interface CreateDeleteNoteReviewPayload {
- digestUUID: string;
- noteUUID: string;
-}
-
-export default function init(config: HttpClientConfig) {
- const client = getHttpClient(config);
-
- return {
- create: ({
- digestUUID,
- noteUUID
- }: CreateDeleteNoteReviewPayload): Promise => {
- const endpoint = '/note_review';
- const payload = {
- digest_uuid: digestUUID,
- note_uuid: noteUUID
- };
-
- return client.post(endpoint, payload);
- },
-
- remove: ({
- digestUUID,
- noteUUID
- }: CreateDeleteNoteReviewPayload): Promise => {
- const endpoint = '/note_review';
- const payload = {
- digest_uuid: digestUUID,
- note_uuid: noteUUID
- };
-
- return client.del(endpoint, payload);
- }
- };
-}
diff --git a/jslib/src/services/repetitionRules.ts b/jslib/src/services/repetitionRules.ts
deleted file mode 100644
index 77333dc6..00000000
--- a/jslib/src/services/repetitionRules.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import { RepetitionRuleData, BookDomain } from '../operations/types';
-import { getHttpClient, HttpClientConfig } from '../helpers/http';
-import { getPath } from '../helpers/url';
-
-export interface CreateParams {
- title: string;
- hour: number;
- minute: number;
- book_domain: BookDomain;
- frequency: number;
- note_count: number;
- book_uuids: string[];
- enabled: boolean;
-}
-
-export type UpdateParams = Partial;
-
-function mapData(d): RepetitionRuleData {
- return {
- uuid: d.uuid,
- title: d.title,
- enabled: d.enabled,
- hour: d.hour,
- minute: d.minute,
- bookDomain: d.book_domain,
- frequency: d.frequency,
- books: d.books,
- noteCount: d.note_count,
- lastActive: d.last_active,
- nextActive: d.next_active,
- createdAt: d.created_at,
- updatedAt: d.updated_at
- };
-}
-
-export default function init(config: HttpClientConfig) {
- const client = getHttpClient(config);
-
- return {
- fetch: (uuid: string, queries = {}): Promise => {
- const path = `/repetition_rules/${uuid}`;
- const endpoint = getPath(path, queries);
-
- return client.get(endpoint).then(resp => {
- return mapData(resp);
- });
- },
- fetchAll: (): Promise => {
- const endpoint = '/repetition_rules';
-
- return client.get(endpoint).then(resp => {
- return resp.map(mapData);
- });
- },
- create: (params: CreateParams) => {
- const endpoint = '/repetition_rules';
-
- return client.post(endpoint, params).then(resp => {
- return mapData(resp);
- });
- },
- update: (uuid: string, params: UpdateParams, queries = {}) => {
- const path = `/repetition_rules/${uuid}`;
- const endpoint = getPath(path, queries);
-
- return client.patch(endpoint, params).then(resp => {
- return mapData(resp);
- });
- },
- remove: (uuid: string) => {
- const endpoint = `/repetition_rules/${uuid}`;
-
- return client.del(endpoint);
- }
- };
-}
diff --git a/pkg/cli/main_test.go b/pkg/cli/main_test.go
index a2e81d65..ecf819af 100644
--- a/pkg/cli/main_test.go
+++ b/pkg/cli/main_test.go
@@ -51,7 +51,8 @@ func TestMain(m *testing.M) {
func TestInit(t *testing.T) {
// Execute
- testutils.RunDnoteCmd(t, opts, binaryName)
+ // run an arbitrary command "view" due to https://github.com/spf13/cobra/issues/1056
+ testutils.RunDnoteCmd(t, opts, binaryName, "view")
defer testutils.RemoveDir(t, opts.HomeDir)
db := database.OpenTestDB(t, opts.DnoteDir)
diff --git a/pkg/server/app/digests.go b/pkg/server/app/digests.go
deleted file mode 100644
index d152a41b..00000000
--- a/pkg/server/app/digests.go
+++ /dev/null
@@ -1,206 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-package app
-
-import (
- "fmt"
-
- "github.com/dnote/dnote/pkg/server/database"
- "github.com/jinzhu/gorm"
- "github.com/pkg/errors"
-)
-
-func (a *App) getExistingDigestReceipt(userID, digestID int) (*database.DigestReceipt, error) {
- var ret database.DigestReceipt
- conn := a.DB.Where("user_id = ? AND digest_id = ?", userID, digestID).First(&ret)
-
- if conn.RecordNotFound() {
- return nil, nil
- }
- if err := conn.Error; err != nil {
- return nil, errors.Wrap(err, "querying existing digest receipt")
- }
-
- return &ret, nil
-}
-
-// GetUserDigestByUUID retrives a digest by the uuid for the given user
-func (a *App) GetUserDigestByUUID(userID int, uuid string) (*database.Digest, error) {
- var ret database.Digest
- conn := a.DB.Where("user_id = ? AND uuid = ?", userID, uuid).First(&ret)
-
- if conn.RecordNotFound() {
- return nil, nil
- }
- if err := conn.Error; err != nil {
- return nil, errors.Wrap(err, "finding digest")
- }
-
- return &ret, nil
-}
-
-// MarkDigestRead creates a new digest receipt. If one already exists for
-// the given digest and the user, it is a noop.
-func (a *App) MarkDigestRead(digest database.Digest, user database.User) (database.DigestReceipt, error) {
- db := a.DB
-
- existing, err := a.getExistingDigestReceipt(user.ID, digest.ID)
- if err != nil {
- return database.DigestReceipt{}, errors.Wrap(err, "checking existing digest receipt")
- }
- if existing != nil {
- return *existing, nil
- }
-
- dat := database.DigestReceipt{
- UserID: user.ID,
- DigestID: digest.ID,
- }
- if err := db.Create(&dat).Error; err != nil {
- return database.DigestReceipt{}, errors.Wrap(err, "creating digest receipt")
- }
-
- return dat, nil
-}
-
-// GetDigestsParam is the params for getting a list of digests
-type GetDigestsParam struct {
- UserID int
- Status string
- Offset int
- PerPage int
- Order string
-}
-
-func (p GetDigestsParam) getSubQuery() string {
- orderClause := p.getOrderClause("digests")
-
- return fmt.Sprintf(`SELECT
- digests.id AS digest_id,
- digests.created_at AS created_at,
- COUNT(digest_receipts.id) AS receipt_count
-FROM digests
-LEFT JOIN digest_receipts ON digest_receipts.digest_id = digests.id
-WHERE digests.user_id = %d
-GROUP BY digests.id, digests.created_at
-%s`, p.UserID, orderClause)
-}
-
-func (p GetDigestsParam) getSubQueryWhere() string {
- var ret string
-
- if p.Status == "unread" {
- ret = "WHERE t1.receipt_count = 0"
- } else if p.Status == "read" {
- ret = "WHERE t1.receipt_count > 0"
- }
-
- return ret
-}
-
-func (p GetDigestsParam) getOrderClause(table string) string {
- if p.Order == "" {
- return ""
- }
-
- return fmt.Sprintf(`ORDER BY %s.%s`, table, p.Order)
-}
-
-// CountDigests counts digests with the given user using the given criteria
-func (a *App) CountDigests(p GetDigestsParam) (int, error) {
- subquery := p.getSubQuery()
- whereClause := p.getSubQueryWhere()
- query := fmt.Sprintf(`SELECT COUNT(*) FROM (%s) AS t1 %s`, subquery, whereClause)
-
- result := struct {
- Count int
- }{}
- if err := a.DB.Raw(query).Scan(&result).Error; err != nil {
- return 0, errors.Wrap(err, "running count query")
- }
-
- return result.Count, nil
-}
-
-func (a *App) queryDigestIDs(p GetDigestsParam) ([]int, error) {
- subquery := p.getSubQuery()
- whereClause := p.getSubQueryWhere()
- orderClause := p.getOrderClause("t1")
- query := fmt.Sprintf(`SELECT t1.digest_id FROM (%s) AS t1 %s %s OFFSET ? LIMIT ?;`, subquery, whereClause, orderClause)
-
- ret := []int{}
- rows, err := a.DB.Raw(query, p.Offset, p.PerPage).Rows()
- if err != nil {
- return nil, errors.Wrap(err, "getting rows")
- }
- defer rows.Close()
-
- for rows.Next() {
- var id int
- if err := rows.Scan(&id); err != nil {
- return []int{}, errors.Wrap(err, "scanning row")
- }
-
- ret = append(ret, id)
- }
-
- return ret, nil
-}
-
-// GetDigests queries digests for the given user using the given criteria
-func (a *App) GetDigests(p GetDigestsParam) ([]database.Digest, error) {
- IDs, err := a.queryDigestIDs(p)
- if err != nil {
- return nil, errors.Wrap(err, "querying digest IDs")
- }
-
- var ret []database.Digest
- conn := a.DB.Where("id IN (?)", IDs).
- Order(p.Order).Preload("Rule").Preload("Receipts").
- Find(&ret)
- if err := conn.Error; err != nil && !conn.RecordNotFound() {
- return nil, errors.Wrap(err, "finding digests")
- }
-
- return ret, nil
-}
-
-// PreloadDigest preloads associations for the given digest. It returns a new digest.
-func (a *App) PreloadDigest(d database.Digest) (database.Digest, error) {
- var ret database.Digest
-
- conn := a.DB.Where("id = ?", d.ID).
- Preload("Notes", func(db *gorm.DB) *gorm.DB {
- return db.Order("notes.created_at DESC")
- }).
- Preload("Notes.Book").
- Preload("Notes.NoteReview", func(db *gorm.DB) *gorm.DB {
- return db.Where("note_reviews.digest_id = ?", d.ID)
- }).
- Preload("Rule").
- Preload("Receipts", func(db *gorm.DB) *gorm.DB {
- return db.Where("digest_receipts.user_id = ?", d.UserID)
- }).First(&ret)
-
- if err := conn.Error; err != nil {
- return ret, errors.Wrap(err, "preloading")
- }
-
- return ret, nil
-}
diff --git a/pkg/server/app/digests_test.go b/pkg/server/app/digests_test.go
deleted file mode 100644
index a8c5ae23..00000000
--- a/pkg/server/app/digests_test.go
+++ /dev/null
@@ -1,54 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-package app
-
-import (
- "testing"
-
- "github.com/dnote/dnote/pkg/assert"
- "github.com/dnote/dnote/pkg/server/database"
- "github.com/dnote/dnote/pkg/server/testutils"
-)
-
-func TestMarkDigestRead(t *testing.T) {
- defer testutils.ClearData()
-
- user := testutils.SetupUserData()
- digest := database.Digest{UserID: user.ID}
- testutils.MustExec(t, testutils.DB.Save(&digest), "preparing digest")
-
- a := NewTest(nil)
-
- // Multiple calls should not create more than 1 receipt
- for i := 0; i < 3; i++ {
- ret, err := a.MarkDigestRead(digest, user)
- if err != nil {
- t.Fatal(err, "failed to perform")
- }
-
- var receiptCount int
- testutils.MustExec(t, testutils.DB.Model(&database.DigestReceipt{}).Count(&receiptCount), "counting receipts")
- assert.Equalf(t, receiptCount, 1, "receipt count mismatch")
-
- var receipt database.DigestReceipt
- testutils.MustExec(t, testutils.DB.Where("id = ?", ret.ID).First(&receipt), "getting receipt")
- assert.Equalf(t, receipt.UserID, user.ID, "receipt UserID mismatch")
- assert.Equalf(t, receipt.DigestID, digest.ID, "receipt DigestID mismatch")
- }
-}
diff --git a/pkg/server/app/notes.go b/pkg/server/app/notes.go
index 16590ea9..f3dbc86e 100644
--- a/pkg/server/app/notes.go
+++ b/pkg/server/app/notes.go
@@ -157,11 +157,6 @@ func (a *App) DeleteNote(tx *gorm.DB, user database.User, note database.Note) (d
return note, errors.Wrap(err, "deleting note")
}
- // Delete associations
- if err := tx.Where("note_id = ?", note.ID).Delete(&database.DigestNote{}).Error; err != nil {
- return note, errors.Wrap(err, "deleting digest_notes")
- }
-
return note, nil
}
diff --git a/pkg/server/app/notes_test.go b/pkg/server/app/notes_test.go
index d15fbf74..e589dc7e 100644
--- a/pkg/server/app/notes_test.go
+++ b/pkg/server/app/notes_test.go
@@ -259,45 +259,3 @@ func TestDeleteNote(t *testing.T) {
}()
}
}
-
-func TestDeleteNote_DigestNotes(t *testing.T) {
- defer testutils.ClearData()
-
- user := testutils.SetupUserData()
-
- b1 := database.Book{UserID: user.ID, Label: "testBook"}
- testutils.MustExec(t, testutils.DB.Save(&b1), "preparing b1")
- n1 := database.Note{UserID: user.ID, Deleted: false, Body: "n1", BookUUID: b1.UUID}
- testutils.MustExec(t, testutils.DB.Save(&n1), "preparing n1")
- n2 := database.Note{UserID: user.ID, Deleted: false, Body: "n2", BookUUID: b1.UUID}
- testutils.MustExec(t, testutils.DB.Save(&n2), "preparing n2")
-
- d1 := database.Digest{UserID: user.ID}
- testutils.MustExec(t, testutils.DB.Save(&d1), "preparing d1")
- dn1 := database.DigestNote{NoteID: n1.ID, DigestID: d1.ID}
- testutils.MustExec(t, testutils.DB.Save(&dn1), "preparing dn1")
- dn2 := database.DigestNote{NoteID: n2.ID, DigestID: d1.ID}
- testutils.MustExec(t, testutils.DB.Save(&dn2), "preparing dn2")
-
- a := NewTest(nil)
-
- tx := testutils.DB.Begin()
- if _, err := a.DeleteNote(tx, user, n1); err != nil {
- tx.Rollback()
- t.Fatal(errors.Wrap(err, "deleting note"))
- }
- tx.Commit()
-
- var noteCount, digestNoteCount int
- var dn2Record database.DigestNote
-
- testutils.MustExec(t, testutils.DB.Model(&database.Note{}).Count(¬eCount), "counting notes")
- testutils.MustExec(t, testutils.DB.Model(&database.DigestNote{}).Count(&digestNoteCount), "counting digest_notes")
-
- assert.Equal(t, noteCount, 2, "note count mismatch")
- assert.Equal(t, digestNoteCount, 1, "digest_notes count mismatch")
-
- testutils.MustExec(t, testutils.DB.Where("id = ?", dn2.ID).First(&dn2Record), "finding dn2")
- assert.Equal(t, dn2Record.NoteID, dn2.NoteID, "dn2 NoteID mismatch")
- assert.Equal(t, dn2Record.DigestID, dn2.DigestID, "dn2 DigestID mismatch")
-}
diff --git a/pkg/server/app/users.go b/pkg/server/app/users.go
index 092d181c..68f73935 100644
--- a/pkg/server/app/users.go
+++ b/pkg/server/app/users.go
@@ -49,25 +49,6 @@ func createEmailPreference(user database.User, tx *gorm.DB) error {
return nil
}
-func createDefaultRepetitionRule(user database.User, tx *gorm.DB) error {
- r := database.RepetitionRule{
- Title: "Default repetition - all books",
- UserID: user.ID,
- Enabled: false,
- Hour: 20,
- Minute: 30,
- Frequency: 604800000,
- BookDomain: database.BookDomainAll,
- Books: []database.Book{},
- NoteCount: 20,
- }
- if err := tx.Save(&r).Error; err != nil {
- return errors.Wrap(err, "inserting repetition rule")
- }
-
- return nil
-}
-
// CreateUser creates a user
func (a *App) CreateUser(email, password string) (database.User, error) {
tx := a.DB.Begin()
@@ -111,10 +92,6 @@ func (a *App) CreateUser(email, password string) (database.User, error) {
tx.Rollback()
return database.User{}, errors.Wrap(err, "creating email preference")
}
- if err := createDefaultRepetitionRule(user, tx); err != nil {
- tx.Rollback()
- return database.User{}, errors.Wrap(err, "creating default repetition rule")
- }
if err := a.TouchLastLoginAt(user, tx); err != nil {
tx.Rollback()
return database.User{}, errors.Wrap(err, "updating last login")
diff --git a/pkg/server/database/consts.go b/pkg/server/database/consts.go
index fb8ab5e9..69f04711 100644
--- a/pkg/server/database/consts.go
+++ b/pkg/server/database/consts.go
@@ -25,8 +25,6 @@ const (
TokenTypeEmailVerification = "email_verification"
// TokenTypeEmailPreference is a type of a token for updating email preference
TokenTypeEmailPreference = "email_preference"
- // TokenTypeRepetition is a type of a token for viewing and editing repetition rules
- TokenTypeRepetition = "repetition_rules"
)
const (
diff --git a/pkg/server/database/database.go b/pkg/server/database/database.go
index 7558e703..30eea6f5 100644
--- a/pkg/server/database/database.go
+++ b/pkg/server/database/database.go
@@ -47,11 +47,6 @@ func InitSchema(db *gorm.DB) {
Token{},
EmailPreference{},
Session{},
- Digest{},
- DigestNote{},
- RepetitionRule{},
- DigestReceipt{},
- NoteReview{},
).Error; err != nil {
panic(err)
}
diff --git a/pkg/server/database/models.go b/pkg/server/database/models.go
index af8feb92..01c667d3 100644
--- a/pkg/server/database/models.go
+++ b/pkg/server/database/models.go
@@ -46,21 +46,20 @@ type Book struct {
// Note is a model for a note
type Note struct {
Model
- UUID string `json:"uuid" gorm:"index;type:uuid;default:uuid_generate_v4()"`
- Book Book `json:"book" gorm:"foreignkey:BookUUID"`
- User User `json:"user"`
- UserID int `json:"user_id" gorm:"index"`
- BookUUID string `json:"book_uuid" gorm:"index;type:uuid"`
- Body string `json:"content"`
- AddedOn int64 `json:"added_on"`
- EditedOn int64 `json:"edited_on"`
- TSV string `json:"-" gorm:"type:tsvector"`
- Public bool `json:"public" gorm:"default:false"`
- USN int `json:"-" gorm:"index"`
- Deleted bool `json:"-" gorm:"default:false"`
- Encrypted bool `json:"-" gorm:"default:false"`
- NoteReview NoteReview `json:"-"`
- Client string `gorm:"index"`
+ UUID string `json:"uuid" gorm:"index;type:uuid;default:uuid_generate_v4()"`
+ Book Book `json:"book" gorm:"foreignkey:BookUUID"`
+ User User `json:"user"`
+ UserID int `json:"user_id" gorm:"index"`
+ BookUUID string `json:"book_uuid" gorm:"index;type:uuid"`
+ Body string `json:"content"`
+ AddedOn int64 `json:"added_on"`
+ EditedOn int64 `json:"edited_on"`
+ TSV string `json:"-" gorm:"type:tsvector"`
+ Public bool `json:"public" gorm:"default:false"`
+ USN int `json:"-" gorm:"index"`
+ Deleted bool `json:"-" gorm:"default:false"`
+ Encrypted bool `json:"-" gorm:"default:false"`
+ Client string `gorm:"index"`
}
// User is a model for a user
@@ -127,59 +126,3 @@ type Session struct {
LastUsedAt time.Time
ExpiresAt time.Time
}
-
-// Digest is a digest of notes
-type Digest struct {
- Model
- UUID string `json:"uuid" gorm:"type:uuid;index;default:uuid_generate_v4()"`
- RuleID int `gorm:"index"`
- Rule RepetitionRule `json:"rule"`
- UserID int `gorm:"index"`
- Version int `gorm:"version"`
- Notes []Note `gorm:"many2many:digest_notes;"`
- Receipts []DigestReceipt `gorm:"polymorphic:Target;"`
-}
-
-// DigestNote is an intermediary to represent many-to-many relationship
-// between digests and notes
-type DigestNote struct {
- Model
- NoteID int `gorm:"index"`
- DigestID int `gorm:"index"`
-}
-
-// RepetitionRule is the rules for sending digest emails
-type RepetitionRule struct {
- Model
- UUID string `json:"uuid" gorm:"type:uuid;index;default:uuid_generate_v4()"`
- UserID int `json:"user_id" gorm:"index"`
- Title string `json:"title"`
- Enabled bool `json:"enabled"`
- Hour int `json:"hour" gorm:"index"`
- Minute int `json:"minute" gorm:"index"`
- // in milliseconds
- Frequency int64 `json:"frequency"`
- // in milliseconds
- LastActive int64 `json:"last_active"`
- // in milliseconds
- NextActive int64 `json:"next_active"`
- BookDomain string `json:"book_domain"`
- Books []Book `gorm:"many2many:repetition_rule_books;"`
- NoteCount int `json:"note_count"`
-}
-
-// DigestReceipt is a read receipt for digests
-type DigestReceipt struct {
- Model
- UserID int `json:"user_id" gorm:"index"`
- DigestID int `json:"digest_id" gorm:"index"`
-}
-
-// NoteReview is a record for reviewing a note in a digest
-type NoteReview struct {
- Model
- UUID string `json:"uuid" gorm:"index;type:uuid;default:uuid_generate_v4()"`
- UserID int `json:"user_id" gorm:"index"`
- DigestID int `json:"digest_id" gorm:"index"`
- NoteID int `json:"note_id" gorm:"index"`
-}
diff --git a/pkg/server/handlers/digests.go b/pkg/server/handlers/digests.go
deleted file mode 100644
index ea08e7af..00000000
--- a/pkg/server/handlers/digests.go
+++ /dev/null
@@ -1,144 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-package handlers
-
-import (
- "fmt"
- "net/http"
- "strconv"
-
- "github.com/dnote/dnote/pkg/server/app"
- "github.com/dnote/dnote/pkg/server/database"
- "github.com/dnote/dnote/pkg/server/helpers"
- "github.com/dnote/dnote/pkg/server/log"
- "github.com/dnote/dnote/pkg/server/presenters"
- "github.com/gorilla/mux"
- "github.com/pkg/errors"
-)
-
-func (a *API) getDigest(w http.ResponseWriter, r *http.Request) {
- user, ok := r.Context().Value(helpers.KeyUser).(database.User)
- if !ok {
- HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError)
- return
- }
-
- vars := mux.Vars(r)
- digestUUID := vars["digestUUID"]
-
- d, err := a.App.GetUserDigestByUUID(user.ID, digestUUID)
- if d == nil {
- RespondNotFound(w)
- return
- }
- if err != nil {
- HandleError(w, "finding digest", err, http.StatusInternalServerError)
- return
- }
-
- digest, err := a.App.PreloadDigest(*d)
- if err != nil {
- HandleError(w, "finding digest", err, http.StatusInternalServerError)
- return
- }
-
- // mark as read
- if _, err := a.App.MarkDigestRead(digest, user); err != nil {
- log.ErrorWrap(err, fmt.Sprintf("marking digest as read for %s", digest.UUID))
- }
-
- presented := presenters.PresentDigest(digest)
- respondJSON(w, http.StatusOK, presented)
-}
-
-// DigestsResponse is a response for getting digests
-type DigestsResponse struct {
- Total int `json:"total"`
- Items []presenters.Digest `json:"items"`
-}
-
-type getDigestsParams struct {
- page int
- status string
-}
-
-func parseGetDigestsParams(r *http.Request) (getDigestsParams, error) {
- var page int
- var err error
-
- q := r.URL.Query()
-
- pageStr := q.Get("page")
- if pageStr != "" {
- page, err = strconv.Atoi(pageStr)
- if err != nil {
- return getDigestsParams{}, errors.Wrap(err, "parsing page")
- }
- } else {
- page = 1
- }
-
- status := q.Get("status")
-
- return getDigestsParams{
- page: page,
- status: status,
- }, nil
-}
-
-func (a *API) getDigests(w http.ResponseWriter, r *http.Request) {
- user, ok := r.Context().Value(helpers.KeyUser).(database.User)
- if !ok {
- HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError)
- return
- }
-
- params, err := parseGetDigestsParams(r)
- if err != nil {
- HandleError(w, "parsing params", err, http.StatusBadRequest)
- return
- }
-
- perPage := 30
- offset := (params.page - 1) * perPage
- p := app.GetDigestsParam{
- UserID: user.ID,
- Offset: offset,
- PerPage: perPage,
- Status: params.status,
- Order: "created_at DESC",
- }
-
- digests, err := a.App.GetDigests(p)
- if err != nil {
- HandleError(w, "querying digests", err, http.StatusInternalServerError)
- return
- }
-
- total, err := a.App.CountDigests(p)
- if err != nil {
- HandleError(w, "counting digests", err, http.StatusInternalServerError)
- return
- }
-
- respondJSON(w, http.StatusOK, DigestsResponse{
- Total: total,
- Items: presenters.PresentDigests(digests),
- })
-}
diff --git a/pkg/server/handlers/digests_test.go b/pkg/server/handlers/digests_test.go
deleted file mode 100644
index 0d4e89c6..00000000
--- a/pkg/server/handlers/digests_test.go
+++ /dev/null
@@ -1,132 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-package handlers
-
-import (
- "fmt"
- "net/http"
- "testing"
-
- "github.com/dnote/dnote/pkg/assert"
- "github.com/dnote/dnote/pkg/server/database"
- "github.com/dnote/dnote/pkg/server/testutils"
-)
-
-func TestGetDigest_Permission(t *testing.T) {
- defer testutils.ClearData()
-
- // Setup
- server := MustNewServer(t, nil)
- defer server.Close()
-
- owner := testutils.SetupUserData()
- nonOwner := testutils.SetupUserData()
- digest := database.Digest{
- UserID: owner.ID,
- }
- testutils.MustExec(t, testutils.DB.Save(&digest), "preparing digest")
-
- t.Run("owner", func(t *testing.T) {
- // Execute
- req := testutils.MakeReq(server.URL, "GET", fmt.Sprintf("/digests/%s", digest.UUID), "")
- res := testutils.HTTPAuthDo(t, req, owner)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusOK, "")
- })
-
- t.Run("non owner", func(t *testing.T) {
- // Execute
- req := testutils.MakeReq(server.URL, "GET", fmt.Sprintf("/digests/%s", digest.UUID), "")
- res := testutils.HTTPAuthDo(t, req, nonOwner)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusNotFound, "")
- })
-
- t.Run("guest", func(t *testing.T) {
- // Execute
- req := testutils.MakeReq(server.URL, "GET", fmt.Sprintf("/digests/%s", digest.UUID), "")
- res := testutils.HTTPDo(t, req)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusUnauthorized, "")
- })
-}
-
-func TestGetDigest_Receipt(t *testing.T) {
- defer testutils.ClearData()
-
- // Setup
- server := MustNewServer(t, nil)
- defer server.Close()
-
- user := testutils.SetupUserData()
- digest := database.Digest{
- UserID: user.ID,
- }
- testutils.MustExec(t, testutils.DB.Save(&digest), "preparing digest")
-
- // multiple requests should create at most one receipt
- for i := 0; i < 3; i++ {
- // Execute and test
- req := testutils.MakeReq(server.URL, "GET", fmt.Sprintf("/digests/%s", digest.UUID), "")
- res := testutils.HTTPAuthDo(t, req, user)
- assert.StatusCodeEquals(t, res, http.StatusOK, "")
-
- var receiptCount int
- testutils.MustExec(t, testutils.DB.Model(&database.DigestReceipt{}).Count(&receiptCount), "counting receipts")
- assert.Equal(t, receiptCount, 1, "counting receipt")
-
- var receipt database.DigestReceipt
- testutils.MustExec(t, testutils.DB.Where("user_id = ?", user.ID).First(&receipt), "finding receipt")
- }
-}
-
-func TestGetDigests(t *testing.T) {
- defer testutils.ClearData()
-
- // Setup
- server := MustNewServer(t, nil)
- defer server.Close()
-
- user := testutils.SetupUserData()
- digest := database.Digest{
- UserID: user.ID,
- }
- testutils.MustExec(t, testutils.DB.Save(&digest), "preparing digest")
-
- t.Run("user", func(t *testing.T) {
- // Execute
- req := testutils.MakeReq(server.URL, "GET", "/digests", "")
- res := testutils.HTTPAuthDo(t, req, user)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusOK, "")
- })
-
- t.Run("guest", func(t *testing.T) {
- // Execute
- req := testutils.MakeReq(server.URL, "GET", fmt.Sprintf("/digests/%s", digest.UUID), "")
- res := testutils.HTTPDo(t, req)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusUnauthorized, "")
- })
-}
diff --git a/pkg/server/handlers/note_review.go b/pkg/server/handlers/note_review.go
deleted file mode 100644
index 5ed2b921..00000000
--- a/pkg/server/handlers/note_review.go
+++ /dev/null
@@ -1,150 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-package handlers
-
-import (
- "encoding/json"
- "net/http"
-
- "github.com/dnote/dnote/pkg/server/database"
- "github.com/dnote/dnote/pkg/server/helpers"
- "github.com/jinzhu/gorm"
- "github.com/pkg/errors"
-)
-
-type createNoteReviewParams struct {
- DigestUUID string `json:"digest_uuid"`
- NoteUUID string `json:"note_uuid"`
-}
-
-func getDigestByUUID(db *gorm.DB, uuid string) (*database.Digest, error) {
- var ret database.Digest
- conn := db.Where("uuid = ?", uuid).First(&ret)
-
- if conn.RecordNotFound() {
- return nil, nil
- }
- if err := conn.Error; err != nil {
- return nil, errors.Wrap(err, "finding digest")
- }
-
- return &ret, nil
-}
-
-func (a *API) createNoteReview(w http.ResponseWriter, r *http.Request) {
- user, ok := r.Context().Value(helpers.KeyUser).(database.User)
- if !ok {
- HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError)
- return
- }
-
- var params createNoteReviewParams
- err := json.NewDecoder(r.Body).Decode(¶ms)
- if err != nil {
- HandleError(w, "decoding params", err, http.StatusInternalServerError)
- return
- }
-
- digest, err := a.App.GetUserDigestByUUID(user.ID, params.DigestUUID)
- if digest == nil {
- http.Error(w, "digest not found for the given uuid", http.StatusBadRequest)
- return
- }
- if err != nil {
- HandleError(w, "finding digest", err, http.StatusInternalServerError)
- return
- }
-
- note, err := a.App.GetUserNoteByUUID(user.ID, params.NoteUUID)
- if note == nil {
- http.Error(w, "note not found for the given uuid", http.StatusBadRequest)
- return
- }
- if err != nil {
- HandleError(w, "finding note", err, http.StatusInternalServerError)
- return
- }
-
- var nr database.NoteReview
- if err := a.App.DB.FirstOrCreate(&nr, database.NoteReview{
- UserID: user.ID,
- DigestID: digest.ID,
- NoteID: note.ID,
- }).Error; err != nil {
- HandleError(w, "saving note review", err, http.StatusInternalServerError)
- return
- }
-}
-
-type deleteNoteReviewParams struct {
- DigestUUID string `json:"digest_uuid"`
- NoteUUID string `json:"note_uuid"`
-}
-
-func (a *API) deleteNoteReview(w http.ResponseWriter, r *http.Request) {
- user, ok := r.Context().Value(helpers.KeyUser).(database.User)
- if !ok {
- HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError)
- return
- }
-
- var params deleteNoteReviewParams
- err := json.NewDecoder(r.Body).Decode(¶ms)
- if err != nil {
- HandleError(w, "decoding params", err, http.StatusInternalServerError)
- return
- }
-
- db := a.App.DB
-
- note, err := a.App.GetUserNoteByUUID(user.ID, params.NoteUUID)
- if note == nil {
- http.Error(w, "note not found for the given uuid", http.StatusBadRequest)
- return
- }
- if err != nil {
- HandleError(w, "finding note", err, http.StatusInternalServerError)
- return
- }
-
- digest, err := a.App.GetUserDigestByUUID(user.ID, params.DigestUUID)
- if digest == nil {
- http.Error(w, "digest not found for the given uuid", http.StatusBadRequest)
- return
- }
- if err != nil {
- HandleError(w, "finding digest", err, http.StatusInternalServerError)
- return
- }
-
- var nr database.NoteReview
- conn := db.Where("note_id = ? AND digest_id = ? AND user_id = ?", note.ID, digest.ID, user.ID).First(&nr)
- if conn.RecordNotFound() {
- http.Error(w, "no record found", http.StatusBadRequest)
- return
- } else if err := conn.Error; err != nil {
- HandleError(w, "finding record", err, http.StatusInternalServerError)
- return
- }
-
- if err := db.Delete(&nr).Error; err != nil {
- HandleError(w, "deleting record", err, http.StatusInternalServerError)
- return
- }
-}
diff --git a/pkg/server/handlers/note_review_test.go b/pkg/server/handlers/note_review_test.go
deleted file mode 100644
index 43e9386c..00000000
--- a/pkg/server/handlers/note_review_test.go
+++ /dev/null
@@ -1,113 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-package handlers
-
-import (
- "fmt"
- "net/http"
- "testing"
-
- "github.com/dnote/dnote/pkg/assert"
- "github.com/dnote/dnote/pkg/clock"
- "github.com/dnote/dnote/pkg/server/app"
- "github.com/dnote/dnote/pkg/server/database"
- "github.com/dnote/dnote/pkg/server/testutils"
-)
-
-func TestCreateNoteReview(t *testing.T) {
- defer testutils.ClearData()
-
- // Setup
- server := MustNewServer(t, &app.App{
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- user := testutils.SetupUserData()
- b1 := database.Book{
- UserID: user.ID,
- Label: "js",
- }
- testutils.MustExec(t, testutils.DB.Save(&b1), "preparing b1")
- n1 := database.Note{
- UserID: user.ID,
- BookUUID: b1.UUID,
- }
- testutils.MustExec(t, testutils.DB.Save(&n1), "preparing n1")
- d1 := database.Digest{
- UserID: user.ID,
- }
- testutils.MustExec(t, testutils.DB.Save(&d1), "preparing d1")
-
- // multiple requests should create at most one receipt
- for i := 0; i < 3; i++ {
- dat := fmt.Sprintf(`{"note_uuid": "%s", "digest_uuid": "%s"}`, n1.UUID, d1.UUID)
- req := testutils.MakeReq(server.URL, http.MethodPost, "/note_review", dat)
- res := testutils.HTTPAuthDo(t, req, user)
- assert.StatusCodeEquals(t, res, http.StatusOK, "")
-
- var noteReviewCount int
- testutils.MustExec(t, testutils.DB.Model(&database.NoteReview{}).Count(¬eReviewCount), "counting note_reviews")
- assert.Equalf(t, noteReviewCount, 1, "counting note_review")
-
- var noteReviewRecord database.NoteReview
- testutils.MustExec(t, testutils.DB.Where("user_id = ? AND note_id = ? AND digest_id = ?", user.ID, n1.ID, d1.ID).First(¬eReviewRecord), "finding note_review record")
- }
-}
-
-func TestDeleteNoteReview(t *testing.T) {
- defer testutils.ClearData()
-
- // Setup
- server := MustNewServer(t, &app.App{
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- user := testutils.SetupUserData()
- b1 := database.Book{
- UserID: user.ID,
- Label: "js",
- }
- testutils.MustExec(t, testutils.DB.Save(&b1), "preparing b1")
- n1 := database.Note{
- UserID: user.ID,
- BookUUID: b1.UUID,
- }
- testutils.MustExec(t, testutils.DB.Save(&n1), "preparing n1")
- d1 := database.Digest{
- UserID: user.ID,
- }
- testutils.MustExec(t, testutils.DB.Save(&d1), "preparing d1")
- nr1 := database.NoteReview{
- UserID: user.ID,
- NoteID: n1.ID,
- DigestID: d1.ID,
- }
- testutils.MustExec(t, testutils.DB.Save(&nr1), "preparing nr1")
-
- dat := fmt.Sprintf(`{"note_uuid": "%s", "digest_uuid": "%s"}`, n1.UUID, d1.UUID)
- req := testutils.MakeReq(server.URL, http.MethodDelete, "/note_review", dat)
- res := testutils.HTTPAuthDo(t, req, user)
- assert.StatusCodeEquals(t, res, http.StatusOK, "")
-
- var noteReviewCount int
- testutils.MustExec(t, testutils.DB.Model(&database.NoteReview{}).Count(¬eReviewCount), "counting note_reviews")
- assert.Equal(t, noteReviewCount, 0, "counting note_review")
-}
diff --git a/pkg/server/handlers/repetition_rules.go b/pkg/server/handlers/repetition_rules.go
deleted file mode 100644
index 747bb101..00000000
--- a/pkg/server/handlers/repetition_rules.go
+++ /dev/null
@@ -1,454 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-package handlers
-
-import (
- "encoding/json"
- "net/http"
- "time"
-
- "github.com/dnote/dnote/pkg/server/database"
- "github.com/dnote/dnote/pkg/server/helpers"
- "github.com/dnote/dnote/pkg/server/presenters"
- "github.com/gorilla/mux"
- "github.com/pkg/errors"
-)
-
-func (a *API) getRepetitionRule(w http.ResponseWriter, r *http.Request) {
- user, ok := r.Context().Value(helpers.KeyUser).(database.User)
- if !ok {
- HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError)
- return
- }
-
- vars := mux.Vars(r)
- repetitionRuleUUID := vars["repetitionRuleUUID"]
-
- if ok := helpers.ValidateUUID(repetitionRuleUUID); !ok {
- http.Error(w, "invalid uuid", http.StatusBadRequest)
- return
- }
-
- var repetitionRule database.RepetitionRule
- if err := a.App.DB.Where("user_id = ? AND uuid = ?", user.ID, repetitionRuleUUID).Preload("Books").Find(&repetitionRule).Error; err != nil {
- HandleError(w, "getting repetition rules", err, http.StatusInternalServerError)
- return
- }
-
- resp := presenters.PresentRepetitionRule(repetitionRule)
- respondJSON(w, http.StatusOK, resp)
-}
-
-func (a *API) getRepetitionRules(w http.ResponseWriter, r *http.Request) {
- user, ok := r.Context().Value(helpers.KeyUser).(database.User)
- if !ok {
- HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError)
- return
- }
-
- var repetitionRules []database.RepetitionRule
- if err := a.App.DB.Where("user_id = ?", user.ID).Preload("Books").Order("last_active DESC").Find(&repetitionRules).Error; err != nil {
- HandleError(w, "getting repetition rules", err, http.StatusInternalServerError)
- return
- }
-
- resp := presenters.PresentRepetitionRules(repetitionRules)
- respondJSON(w, http.StatusOK, resp)
-}
-
-func validateBookDomain(val string) error {
- if val == database.BookDomainAll || val == database.BookDomainIncluding || val == database.BookDomainExluding {
- return nil
- }
-
- return errors.Errorf("invalid book_domain %s", val)
-}
-
-type repetitionRuleParams struct {
- Title *string `json:"title"`
- Enabled *bool `json:"enabled"`
- Hour *int `json:"hour"`
- Minute *int `json:"minute"`
- Frequency *int64 `json:"frequency"`
- BookDomain *string `json:"book_domain"`
- BookUUIDs *[]string `json:"book_uuids"`
- NoteCount *int `json:"note_count"`
-}
-
-func (r repetitionRuleParams) GetEnabled() bool {
- if r.Enabled == nil {
- return false
- }
-
- return *r.Enabled
-}
-
-func (r repetitionRuleParams) GetFrequency() int64 {
- if r.Frequency == nil {
- return 0
- }
-
- return *r.Frequency
-}
-
-func (r repetitionRuleParams) GetTitle() string {
- if r.Title == nil {
- return ""
- }
-
- return *r.Title
-}
-
-func (r repetitionRuleParams) GetNoteCount() int {
- if r.NoteCount == nil {
- return 0
- }
-
- return *r.NoteCount
-}
-
-func (r repetitionRuleParams) GetBookDomain() string {
- if r.BookDomain == nil {
- return ""
- }
-
- return *r.BookDomain
-}
-
-func (r repetitionRuleParams) GetBookUUIDs() []string {
- if r.BookUUIDs == nil {
- return []string{}
- }
-
- return *r.BookUUIDs
-}
-
-func (r repetitionRuleParams) GetHour() int {
- if r.Hour == nil {
- return 0
- }
-
- return *r.Hour
-}
-
-func (r repetitionRuleParams) GetMinute() int {
- if r.Minute == nil {
- return 0
- }
-
- return *r.Minute
-}
-
-func validateRepetitionRuleParams(p repetitionRuleParams) error {
- if p.Frequency != nil && p.GetFrequency() == 0 {
- return errors.New("frequency is required")
- }
-
- if p.Title != nil {
- title := p.GetTitle()
-
- if len(title) == 0 {
- return errors.New("Title is required")
- }
- if len(title) > 50 {
- return errors.New("Title is too long")
- }
- }
-
- if p.NoteCount != nil && p.GetNoteCount() == 0 {
- return errors.New("note count has to be greater than 0")
- }
-
- if p.BookDomain != nil {
- bookDomain := p.GetBookDomain()
- if err := validateBookDomain(bookDomain); err != nil {
- return err
- }
-
- bookUUIDs := p.GetBookUUIDs()
- if bookDomain == database.BookDomainAll {
- if len(bookUUIDs) > 0 {
- return errors.New("a global repetition should not specify book_uuids")
- }
- } else {
- if len(bookUUIDs) == 0 {
- return errors.New("book_uuids is required")
- }
- }
- }
-
- if p.Hour != nil {
- hour := p.GetHour()
-
- if hour < 0 && hour > 23 {
- return errors.New("invalid hour")
- }
- }
-
- if p.Minute != nil {
- minute := p.GetMinute()
-
- if minute < 0 && minute > 60 {
- return errors.New("invalid minute")
- }
- }
-
- return nil
-}
-
-func validateCreateRepetitionRuleParams(p repetitionRuleParams) error {
- if p.Title == nil {
- return errors.New("title is required")
- }
- if p.Frequency == nil {
- return errors.New("frequency is required")
- }
- if p.NoteCount == nil {
- return errors.New("note_count is required")
- }
- if p.BookDomain == nil {
- return errors.New("book_domain is required")
- }
- if p.Hour == nil {
- return errors.New("hour is required")
- }
- if p.Minute == nil {
- return errors.New("minute is required")
- }
- if p.Enabled == nil {
- return errors.New("enabled is required")
- }
-
- return nil
-}
-
-func parseCreateRepetitionRuleParams(r *http.Request) (repetitionRuleParams, error) {
- var ret repetitionRuleParams
-
- d := json.NewDecoder(r.Body)
- d.DisallowUnknownFields()
-
- if err := d.Decode(&ret); err != nil {
- return ret, errors.Wrap(err, "decoding json")
- }
-
- if err := validateCreateRepetitionRuleParams(ret); err != nil {
- return ret, errors.Wrap(err, "validating params")
- }
-
- if err := validateRepetitionRuleParams(ret); err != nil {
- return ret, errors.Wrap(err, "validating params")
- }
-
- return ret, nil
-}
-
-type calcNextActiveParams struct {
- Hour int
- Minute int
- Frequency int64
-}
-
-// calcNextActive calculates the NextActive value for a repetition rule by adding the given
-// frequency to the given present date time at the given hour and minute.
-func calcNextActive(now time.Time, p calcNextActiveParams) int64 {
- t0 := time.Date(now.Year(), now.Month(), now.Day(), p.Hour, p.Minute, 0, 0, now.Location()).UnixNano() / int64(time.Millisecond)
-
- return t0 + p.Frequency
-}
-
-func (a *API) createRepetitionRule(w http.ResponseWriter, r *http.Request) {
- user, ok := r.Context().Value(helpers.KeyUser).(database.User)
- if !ok {
- HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError)
- return
- }
-
- params, err := parseCreateRepetitionRuleParams(r)
- if err != nil {
- http.Error(w, err.Error(), http.StatusBadRequest)
- return
- }
-
- var books []database.Book
- if err := a.App.DB.Where("user_id = ? AND uuid IN (?)", user.ID, params.GetBookUUIDs()).Find(&books).Error; err != nil {
- HandleError(w, "finding books", nil, http.StatusInternalServerError)
- return
- }
-
- nextActive := calcNextActive(a.App.Clock.Now(), calcNextActiveParams{
- Hour: params.GetHour(),
- Minute: params.GetMinute(),
- Frequency: params.GetFrequency(),
- })
-
- record := database.RepetitionRule{
- UserID: user.ID,
- Title: params.GetTitle(),
- Hour: params.GetHour(),
- Minute: params.GetMinute(),
- Frequency: params.GetFrequency(),
- BookDomain: params.GetBookDomain(),
- NextActive: nextActive,
- Books: books,
- NoteCount: params.GetNoteCount(),
- Enabled: params.GetEnabled(),
- }
- if err := a.App.DB.Create(&record).Error; err != nil {
- HandleError(w, "creating a repetition rule", err, http.StatusInternalServerError)
- return
- }
-
- resp := presenters.PresentRepetitionRule(record)
- respondJSON(w, http.StatusCreated, resp)
-}
-
-func parseUpdateDigestParams(r *http.Request) (repetitionRuleParams, error) {
- var ret repetitionRuleParams
-
- if err := json.NewDecoder(r.Body).Decode(&ret); err != nil {
- return ret, errors.Wrap(err, "decoding json")
- }
-
- if err := validateRepetitionRuleParams(ret); err != nil {
- return ret, errors.Wrap(err, "validating params")
- }
-
- return ret, nil
-}
-
-func (a *API) deleteRepetitionRule(w http.ResponseWriter, r *http.Request) {
- user, ok := r.Context().Value(helpers.KeyUser).(database.User)
- if !ok {
- HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError)
- return
- }
-
- vars := mux.Vars(r)
- repetitionRuleUUID := vars["repetitionRuleUUID"]
-
- var rule database.RepetitionRule
- conn := a.App.DB.Where("uuid = ? AND user_id = ?", repetitionRuleUUID, user.ID).First(&rule)
-
- if conn.RecordNotFound() {
- http.Error(w, "Not found", http.StatusNotFound)
- return
- } else if err := conn.Error; err != nil {
- HandleError(w, "finding the repetition rule", err, http.StatusInternalServerError)
- return
- }
-
- if err := a.App.DB.Exec("DELETE from repetition_rules WHERE uuid = ?", rule.UUID).Error; err != nil {
- HandleError(w, "deleting the repetition rule", err, http.StatusInternalServerError)
- }
-
- w.WriteHeader(http.StatusOK)
-}
-
-func (a *API) updateRepetitionRule(w http.ResponseWriter, r *http.Request) {
- user, ok := r.Context().Value(helpers.KeyUser).(database.User)
- if !ok {
- HandleError(w, "No authenticated user found", nil, http.StatusInternalServerError)
- return
- }
-
- vars := mux.Vars(r)
- repetitionRuleUUID := vars["repetitionRuleUUID"]
-
- params, err := parseUpdateDigestParams(r)
- if err != nil {
- http.Error(w, "parsing params", http.StatusBadRequest)
- return
- }
-
- tx := a.App.DB.Begin()
-
- var repetitionRule database.RepetitionRule
- if err := tx.Where("user_id = ? AND uuid = ?", user.ID, repetitionRuleUUID).Preload("Books").First(&repetitionRule).Error; err != nil {
- HandleError(w, "finding record", nil, http.StatusInternalServerError)
- return
- }
-
- if params.Title != nil {
- repetitionRule.Title = params.GetTitle()
- }
- if params.Enabled != nil {
- enabled := params.GetEnabled()
- repetitionRule.Enabled = enabled
-
- if enabled && !repetitionRule.Enabled {
- repetitionRule.NextActive = calcNextActive(a.App.Clock.Now(), calcNextActiveParams{
- Hour: repetitionRule.Hour,
- Minute: repetitionRule.Minute,
- Frequency: repetitionRule.Frequency,
- })
- } else if !enabled && repetitionRule.Enabled {
- repetitionRule.NextActive = 0
- }
- }
- if params.Hour != nil {
- repetitionRule.Hour = params.GetHour()
- }
- if params.Minute != nil {
- repetitionRule.Minute = params.GetMinute()
- }
- if params.Frequency != nil {
- frequency := params.GetFrequency()
-
- repetitionRule.Frequency = frequency
- repetitionRule.NextActive = calcNextActive(a.App.Clock.Now(), calcNextActiveParams{
- Hour: repetitionRule.Hour,
- Minute: repetitionRule.Minute,
- Frequency: frequency,
- })
- }
- if params.NoteCount != nil {
- repetitionRule.NoteCount = params.GetNoteCount()
- }
- if params.BookDomain != nil {
- repetitionRule.BookDomain = params.GetBookDomain()
- }
- if params.BookUUIDs != nil {
- var books []database.Book
- if err := tx.Where("user_id = ? AND uuid IN (?)", user.ID, *params.BookUUIDs).Find(&books).Error; err != nil {
- HandleError(w, "finding books", err, http.StatusInternalServerError)
- return
- }
-
- if err := tx.Model(&repetitionRule).Association("Books").Replace(books).Error; err != nil {
- tx.Rollback()
- HandleError(w, "updating books association for a repetitionRule", err, http.StatusInternalServerError)
- return
- }
- }
-
- if err := tx.Save(&repetitionRule).Error; err != nil {
- tx.Rollback()
- HandleError(w, "creating a repetition rule", err, http.StatusInternalServerError)
- return
- }
-
- if err := tx.Commit().Error; err != nil {
- tx.Rollback()
- HandleError(w, "committing a transaction", err, http.StatusInternalServerError)
- }
-
- resp := presenters.PresentRepetitionRule(repetitionRule)
- respondJSON(w, http.StatusOK, resp)
-}
diff --git a/pkg/server/handlers/repetition_rules_test.go b/pkg/server/handlers/repetition_rules_test.go
deleted file mode 100644
index bae5f0c0..00000000
--- a/pkg/server/handlers/repetition_rules_test.go
+++ /dev/null
@@ -1,656 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-package handlers
-
-import (
- "encoding/json"
- "fmt"
- "net/http"
- "testing"
- "time"
-
- "github.com/dnote/dnote/pkg/assert"
- "github.com/dnote/dnote/pkg/clock"
- "github.com/dnote/dnote/pkg/server/app"
- "github.com/dnote/dnote/pkg/server/database"
- "github.com/dnote/dnote/pkg/server/presenters"
- "github.com/dnote/dnote/pkg/server/testutils"
- "github.com/pkg/errors"
-)
-
-func TestGetRepetitionRule(t *testing.T) {
-
- defer testutils.ClearData()
-
- // Setup
- server := MustNewServer(t, &app.App{
-
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- user := testutils.SetupUserData()
-
- b1 := database.Book{
- USN: 11,
- Label: "js",
- }
- testutils.MustExec(t, testutils.DB.Save(&b1), "preparing book1")
-
- r1 := database.RepetitionRule{
- Title: "Rule 1",
- Frequency: (time.Hour * 24 * 7).Milliseconds(),
- Hour: 21,
- Minute: 0,
- LastActive: 0,
- UserID: user.ID,
- BookDomain: database.BookDomainExluding,
- Books: []database.Book{b1},
- NoteCount: 5,
- }
- testutils.MustExec(t, testutils.DB.Save(&r1), "preparing rule1")
-
- // Execute
- req := testutils.MakeReq(server.URL, "GET", fmt.Sprintf("/repetition_rules/%s", r1.UUID), "")
- res := testutils.HTTPAuthDo(t, req, user)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusOK, "")
-
- var payload presenters.RepetitionRule
- if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
- t.Fatal(errors.Wrap(err, "decoding payload"))
- }
-
- var r1Record database.RepetitionRule
- testutils.MustExec(t, testutils.DB.Where("uuid = ?", r1.UUID).First(&r1Record), "finding r1Record")
- var b1Record database.Book
- testutils.MustExec(t, testutils.DB.Where("uuid = ?", b1.UUID).First(&b1Record), "finding b1Record")
-
- expected := presenters.RepetitionRule{
- UUID: r1Record.UUID,
- Title: r1Record.Title,
- Enabled: r1Record.Enabled,
- Hour: r1Record.Hour,
- Minute: r1Record.Minute,
- Frequency: r1Record.Frequency,
- BookDomain: r1Record.BookDomain,
- NoteCount: r1Record.NoteCount,
- LastActive: r1Record.LastActive,
- Books: []presenters.Book{
- {
- UUID: b1Record.UUID,
- USN: b1Record.USN,
- Label: b1Record.Label,
- CreatedAt: presenters.FormatTS(b1Record.CreatedAt),
- UpdatedAt: presenters.FormatTS(b1Record.UpdatedAt),
- },
- },
- CreatedAt: presenters.FormatTS(r1Record.CreatedAt),
- UpdatedAt: presenters.FormatTS(r1Record.UpdatedAt),
- }
-
- assert.DeepEqual(t, payload, expected, "payload mismatch")
-}
-
-func TestGetRepetitionRules(t *testing.T) {
-
- defer testutils.ClearData()
-
- // Setup
- server := MustNewServer(t, &app.App{
-
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- user := testutils.SetupUserData()
-
- b1 := database.Book{
- USN: 11,
- Label: "js",
- }
- testutils.MustExec(t, testutils.DB.Save(&b1), "preparing book1")
-
- r1 := database.RepetitionRule{
- Title: "Rule 1",
- Frequency: (time.Hour * 24 * 7).Milliseconds(),
- Hour: 21,
- Minute: 0,
- LastActive: 1257714000000,
- UserID: user.ID,
- BookDomain: database.BookDomainExluding,
- Books: []database.Book{b1},
- NoteCount: 5,
- }
- testutils.MustExec(t, testutils.DB.Save(&r1), "preparing rule1")
-
- r2 := database.RepetitionRule{
- Title: "Rule 2",
- Frequency: (time.Hour * 24 * 7 * 2).Milliseconds(),
- Hour: 2,
- Minute: 0,
- LastActive: 0,
- UserID: user.ID,
- BookDomain: database.BookDomainExluding,
- Books: []database.Book{},
- NoteCount: 5,
- }
- testutils.MustExec(t, testutils.DB.Save(&r2), "preparing rule2")
-
- // Execute
- req := testutils.MakeReq(server.URL, "GET", "/repetition_rules", "")
- res := testutils.HTTPAuthDo(t, req, user)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusOK, "")
-
- var payload []presenters.RepetitionRule
- if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
- t.Fatal(errors.Wrap(err, "decoding payload"))
- }
-
- var r1Record, r2Record database.RepetitionRule
- testutils.MustExec(t, testutils.DB.Where("uuid = ?", r1.UUID).First(&r1Record), "finding r1Record")
- testutils.MustExec(t, testutils.DB.Where("uuid = ?", r2.UUID).First(&r2Record), "finding r2Record")
- var b1Record database.Book
- testutils.MustExec(t, testutils.DB.Where("uuid = ?", b1.UUID).First(&b1Record), "finding b1Record")
-
- expected := []presenters.RepetitionRule{
- {
- UUID: r1Record.UUID,
- Title: r1Record.Title,
- Enabled: r1Record.Enabled,
- Hour: r1Record.Hour,
- Minute: r1Record.Minute,
- Frequency: r1Record.Frequency,
- BookDomain: r1Record.BookDomain,
- NoteCount: r1Record.NoteCount,
- LastActive: r1Record.LastActive,
- Books: []presenters.Book{
- {
- UUID: b1Record.UUID,
- USN: b1Record.USN,
- Label: b1Record.Label,
- CreatedAt: presenters.FormatTS(b1Record.CreatedAt),
- UpdatedAt: presenters.FormatTS(b1Record.UpdatedAt),
- },
- },
- CreatedAt: presenters.FormatTS(r1Record.CreatedAt),
- UpdatedAt: presenters.FormatTS(r1Record.UpdatedAt),
- },
- {
- UUID: r2Record.UUID,
- Title: r2Record.Title,
- Enabled: r2Record.Enabled,
- Hour: r2Record.Hour,
- Minute: r2Record.Minute,
- Frequency: r2Record.Frequency,
- BookDomain: r2Record.BookDomain,
- NoteCount: r2Record.NoteCount,
- LastActive: r2Record.LastActive,
- Books: []presenters.Book{},
- CreatedAt: presenters.FormatTS(r2Record.CreatedAt),
- UpdatedAt: presenters.FormatTS(r2Record.UpdatedAt),
- },
- }
-
- assert.DeepEqual(t, payload, expected, "payload mismatch")
-}
-
-func TestCreateRepetitionRules(t *testing.T) {
- t.Run("all books", func(t *testing.T) {
-
- defer testutils.ClearData()
-
- // Setup
- c := clock.NewMock()
- t0 := time.Date(2009, time.November, 1, 2, 3, 4, 5, time.UTC)
- c.SetNow(t0)
-
- server := MustNewServer(t, &app.App{
-
- Clock: c,
- })
- defer server.Close()
-
- user := testutils.SetupUserData()
-
- // Execute
- dat := `{
- "title": "Rule 1",
- "enabled": true,
- "hour": 8,
- "minute": 30,
- "frequency": 604800000,
- "book_domain": "all",
- "book_uuids": [],
- "note_count": 20
-}`
- req := testutils.MakeReq(server.URL, "POST", "/repetition_rules", dat)
- res := testutils.HTTPAuthDo(t, req, user)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusCreated, "")
-
- var ruleCount int
- testutils.MustExec(t, testutils.DB.Model(&database.RepetitionRule{}).Count(&ruleCount), "counting rules")
- assert.Equalf(t, ruleCount, 1, "reperition rule count mismatch")
-
- var rule database.RepetitionRule
- testutils.MustExec(t, testutils.DB.Preload("Books").First(&rule), "finding b1Record")
-
- assert.NotEqual(t, rule.UUID, "", "rule UUID mismatch")
- assert.Equal(t, rule.Title, "Rule 1", "rule Title mismatch")
- assert.Equal(t, rule.Enabled, true, "rule Enabled mismatch")
- assert.Equal(t, rule.Hour, 8, "rule HourTitle mismatch")
- assert.Equal(t, rule.Minute, 30, "rule Minute mismatch")
- assert.Equal(t, rule.Frequency, int64(604800000), "rule Frequency mismatch")
- assert.Equal(t, rule.LastActive, int64(0), "rule LastActive mismatch")
- assert.Equal(t, rule.NextActive, int64(1257064200000+604800000), "rule NextActive mismatch")
- assert.Equal(t, rule.BookDomain, "all", "rule BookDomain mismatch")
- assert.DeepEqual(t, rule.Books, []database.Book{}, "rule Books mismatch")
- assert.Equal(t, rule.NoteCount, 20, "rule NoteCount mismatch")
- })
-
- bookDomainTestCases := []string{
- "including",
- "excluding",
- }
- for _, tc := range bookDomainTestCases {
- t.Run(tc, func(t *testing.T) {
-
- defer testutils.ClearData()
-
- // Setup
- c := clock.NewMock()
- t0 := time.Date(2009, time.November, 1, 2, 3, 4, 5, time.UTC)
- c.SetNow(t0)
-
- server := MustNewServer(t, &app.App{
-
- Clock: c,
- })
- defer server.Close()
-
- user := testutils.SetupUserData()
-
- b1 := database.Book{
- UserID: user.ID,
- Label: "css",
- }
- testutils.MustExec(t, testutils.DB.Save(&b1), "preparing b1")
-
- // Execute
- dat := fmt.Sprintf(`{
- "title": "Rule 1",
- "enabled": true,
- "hour": 8,
- "minute": 30,
- "frequency": 604800000,
- "book_domain": "%s",
- "book_uuids": ["%s"],
- "note_count": 20
-}`, tc, b1.UUID)
- req := testutils.MakeReq(server.URL, "POST", "/repetition_rules", dat)
- res := testutils.HTTPAuthDo(t, req, user)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusCreated, "")
-
- var ruleCount int
- testutils.MustExec(t, testutils.DB.Model(&database.RepetitionRule{}).Count(&ruleCount), "counting rules")
- assert.Equalf(t, ruleCount, 1, "reperition rule count mismatch")
-
- var rule database.RepetitionRule
- testutils.MustExec(t, testutils.DB.Preload("Books").First(&rule), "finding b1Record")
-
- var b1Record database.Book
- testutils.MustExec(t, testutils.DB.Where("id = ?", b1.ID).First(&b1Record), "finding b1Record")
-
- assert.NotEqual(t, rule.UUID, "", "rule UUID mismatch")
- assert.Equal(t, rule.Title, "Rule 1", "rule Title mismatch")
- assert.Equal(t, rule.Enabled, true, "rule Enabled mismatch")
- assert.Equal(t, rule.Hour, 8, "rule HourTitle mismatch")
- assert.Equal(t, rule.Minute, 30, "rule Minute mismatch")
- assert.Equal(t, rule.LastActive, int64(0), "rule LastActive mismatch")
- assert.Equal(t, rule.NextActive, int64(1257064200000+604800000), "rule NextActive mismatch")
- assert.Equal(t, rule.Frequency, int64(604800000), "rule Frequency mismatch")
- assert.Equal(t, rule.BookDomain, tc, "rule BookDomain mismatch")
- assert.DeepEqual(t, rule.Books, []database.Book{b1Record}, "rule Books mismatch")
- assert.Equal(t, rule.NoteCount, 20, "rule NoteCount mismatch")
- })
- }
-}
-
-func TestUpdateRepetitionRules(t *testing.T) {
-
- defer testutils.ClearData()
-
- // Setup
- c := clock.NewMock()
- t0 := time.Date(2009, time.November, 1, 2, 3, 4, 5, time.UTC)
- c.SetNow(t0)
- server := MustNewServer(t, &app.App{
-
- Clock: c,
- })
- defer server.Close()
-
- user := testutils.SetupUserData()
-
- // Execute
- r1 := database.RepetitionRule{
- Title: "Rule 1",
- UserID: user.ID,
- Enabled: false,
- Hour: 8,
- Minute: 30,
- Frequency: 604800000,
- LastActive: 1257064200000,
- NextActive: 1263088980000,
- BookDomain: "all",
- Books: []database.Book{},
- NoteCount: 20,
- }
- testutils.MustExec(t, testutils.DB.Save(&r1), "preparing r1")
- b1 := database.Book{
- UserID: user.ID,
- USN: 11,
- Label: "js",
- }
- testutils.MustExec(t, testutils.DB.Save(&b1), "preparing book1")
-
- dat := fmt.Sprintf(`{
- "title": "Rule 1 - edited",
- "enabled": true,
- "hour": 18,
- "minute": 40,
- "frequency": 259200000,
- "book_domain": "including",
- "book_uuids": ["%s"],
- "note_count": 30
-}`, b1.UUID)
- endpoint := fmt.Sprintf("/repetition_rules/%s", r1.UUID)
- req := testutils.MakeReq(server.URL, "PATCH", endpoint, dat)
- res := testutils.HTTPAuthDo(t, req, user)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusOK, "")
-
- var totalRuleCount int
- testutils.MustExec(t, testutils.DB.Model(&database.RepetitionRule{}).Count(&totalRuleCount), "counting rules")
- assert.Equalf(t, totalRuleCount, 1, "reperition rule count mismatch")
-
- var rule database.RepetitionRule
- testutils.MustExec(t, testutils.DB.Preload("Books").First(&rule), "finding b1Record")
-
- var b1Record database.Book
- testutils.MustExec(t, testutils.DB.Where("id = ?", b1.ID).First(&b1Record), "finding b1Record")
-
- assert.NotEqual(t, rule.UUID, "", "rule UUID mismatch")
- assert.Equal(t, rule.Title, "Rule 1 - edited", "rule Title mismatch")
- assert.Equal(t, rule.Enabled, true, "rule Enabled mismatch")
- assert.Equal(t, rule.Hour, 18, "rule HourTitle mismatch")
- assert.Equal(t, rule.Minute, 40, "rule Minute mismatch")
- assert.Equal(t, rule.Frequency, int64(259200000), "rule Frequency mismatch")
- assert.Equal(t, rule.LastActive, int64(1257064200000), "rule LastActive mismatch")
- assert.Equal(t, rule.NextActive, int64(1257100800000+259200000), "rule NextActive mismatch")
- assert.Equal(t, rule.BookDomain, "including", "rule BookDomain mismatch")
- assert.DeepEqual(t, rule.Books, []database.Book{b1Record}, "rule Books mismatch")
- assert.Equal(t, rule.NoteCount, 30, "rule NoteCount mismatch")
-}
-
-func TestDeleteRepetitionRules(t *testing.T) {
-
- defer testutils.ClearData()
-
- // Setup
- server := MustNewServer(t, &app.App{
-
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- user := testutils.SetupUserData()
-
- // Execute
- r1 := database.RepetitionRule{
- Title: "Rule 1",
- UserID: user.ID,
- Enabled: true,
- Hour: 8,
- Minute: 30,
- Frequency: 604800000,
- BookDomain: "all",
- Books: []database.Book{},
- NoteCount: 20,
- }
- testutils.MustExec(t, testutils.DB.Save(&r1), "preparing r1")
-
- r2 := database.RepetitionRule{
- Title: "Rule 1",
- UserID: user.ID,
- Enabled: true,
- Hour: 8,
- Minute: 30,
- Frequency: 604800000,
- BookDomain: "all",
- Books: []database.Book{},
- NoteCount: 20,
- }
- testutils.MustExec(t, testutils.DB.Save(&r2), "preparing r2")
-
- endpoint := fmt.Sprintf("/repetition_rules/%s", r1.UUID)
- req := testutils.MakeReq(server.URL, "DELETE", endpoint, "")
- res := testutils.HTTPAuthDo(t, req, user)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusOK, "")
-
- var totalRuleCount int
- testutils.MustExec(t, testutils.DB.Model(&database.RepetitionRule{}).Count(&totalRuleCount), "counting rules")
- assert.Equalf(t, totalRuleCount, 1, "reperition rule count mismatch")
-
- var r2Count int
- testutils.MustExec(t, testutils.DB.Model(&database.RepetitionRule{}).Where("id = ?", r2.ID).Count(&r2Count), "counting r2")
- assert.Equalf(t, r2Count, 1, "r2 count mismatch")
-}
-
-func TestCreateUpdateRepetitionRules_BadRequest(t *testing.T) {
- testCases := []string{
- // empty title
- `{
- "title": "",
- "enabled": true,
- "hour": 8,
- "minute": 30,
- "frequency": 604800000,
- "book_domain": "all",
- "book_uuids": [],
- "note_count": 20
- }`,
- // empty frequency
- `{
- "title": "Rule 1",
- "enabled": true,
- "hour": 8,
- "minute": 30,
- "frequency": 0,
- "book_domain": "some_invalid_book_domain",
- "book_uuids": [],
- "note_count": 20
- }`,
- // empty note count
- `{
- "title": "Rule 1",
- "enabled": true,
- "hour": 8,
- "minute": 30,
- "frequency": 604800000,
- "book_domain": "all",
- "book_uuids": [],
- "note_count": 0
- }`,
- // invalid book doamin
- `{
- "title": "Rule 1",
- "enabled": true,
- "hour": 8,
- "minute": 30,
- "frequency": 604800000,
- "book_domain": "some_invalid_book_domain",
- "book_uuids": [],
- "note_count": 20
- }`,
- // invalid combination of book domain and book_uuids
- `{
- "title": "Rule 1",
- "enabled": true,
- "hour": 8,
- "minute": 30,
- "frequency": 604800000,
- "book_domain": "excluding",
- "book_uuids": [],
- "note_count": 20
- }`,
- `{
- "title": "Rule 1",
- "enabled": true,
- "hour": 8,
- "minute": 30,
- "frequency": 604800000,
- "book_domain": "including",
- "book_uuids": [],
- "note_count": 20
- }`,
- }
-
- for idx, tc := range testCases {
- t.Run(fmt.Sprintf("test case - create %d", idx), func(t *testing.T) {
-
- defer testutils.ClearData()
-
- // Setup
- server := MustNewServer(t, &app.App{
-
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- user := testutils.SetupUserData()
-
- // Execute
- req := testutils.MakeReq(server.URL, "POST", "/repetition_rules", tc)
- res := testutils.HTTPAuthDo(t, req, user)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusBadRequest, "")
-
- var ruleCount int
- testutils.MustExec(t, testutils.DB.Model(&database.RepetitionRule{}).Count(&ruleCount), "counting rules")
- assert.Equalf(t, ruleCount, 0, "reperition rule count mismatch")
- })
-
- t.Run(fmt.Sprintf("test case %d - update", idx), func(t *testing.T) {
-
- defer testutils.ClearData()
-
- // Setup
- user := testutils.SetupUserData()
- r1 := database.RepetitionRule{
- Title: "Rule 1",
- UserID: user.ID,
- Enabled: false,
- Hour: 8,
- Minute: 30,
- Frequency: 604800000,
- BookDomain: "all",
- Books: []database.Book{},
- NoteCount: 20,
- }
- testutils.MustExec(t, testutils.DB.Save(&r1), "preparing r1")
- b1 := database.Book{
- UserID: user.ID,
- USN: 11,
- Label: "js",
- }
- testutils.MustExec(t, testutils.DB.Save(&b1), "preparing book1")
-
- server := MustNewServer(t, &app.App{
-
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- // Execute
- req := testutils.MakeReq(server.URL, "PATCH", fmt.Sprintf("/repetition_rules/%s", r1.UUID), tc)
- res := testutils.HTTPAuthDo(t, req, user)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusBadRequest, "")
-
- var ruleCount int
- testutils.MustExec(t, testutils.DB.Model(&database.RepetitionRule{}).Count(&ruleCount), "counting rules")
- assert.Equalf(t, ruleCount, 1, "reperition rule count mismatch")
- })
- }
-}
-
-func TestCreateRepetitionRules_BadRequest(t *testing.T) {
- testCases := []string{
- // no enabeld field
- `{
- "title": "Rule #1",
- "hour": 8,
- "minute": 30,
- "frequency": 604800000,
- "book_domain": "all",
- "book_uuids": [],
- "note_count": 20
- }`,
- }
-
- for idx, tc := range testCases {
- t.Run(fmt.Sprintf("test case %d", idx), func(t *testing.T) {
-
- defer testutils.ClearData()
-
- // Setup
- server := MustNewServer(t, &app.App{
-
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- user := testutils.SetupUserData()
-
- // Execute
- req := testutils.MakeReq(server.URL, "POST", "/repetition_rules", tc)
- res := testutils.HTTPAuthDo(t, req, user)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusBadRequest, "")
-
- var ruleCount int
- testutils.MustExec(t, testutils.DB.Model(&database.RepetitionRule{}).Count(&ruleCount), "counting rules")
- assert.Equalf(t, ruleCount, 0, "reperition rule count mismatch")
- })
- }
-}
diff --git a/pkg/server/handlers/routes.go b/pkg/server/handlers/routes.go
index ac1ff925..d0a85ee6 100644
--- a/pkg/server/handlers/routes.go
+++ b/pkg/server/handlers/routes.go
@@ -352,15 +352,6 @@ func (a *API) NewRouter() (*mux.Router, error) {
{"GET", "/notes", a.auth(a.getNotes, nil), false},
{"GET", "/notes/{noteUUID}", a.getNote, true},
{"GET", "/calendar", a.auth(a.getCalendar, nil), true},
- {"GET", "/repetition_rules", a.auth(a.getRepetitionRules, nil), true},
- {"GET", "/repetition_rules/{repetitionRuleUUID}", a.tokenAuth(a.getRepetitionRule, database.TokenTypeRepetition, &proOnly), true},
- {"POST", "/repetition_rules", a.auth(a.createRepetitionRule, &proOnly), true},
- {"PATCH", "/repetition_rules/{repetitionRuleUUID}", a.tokenAuth(a.updateRepetitionRule, database.TokenTypeRepetition, &proOnly), true},
- {"DELETE", "/repetition_rules/{repetitionRuleUUID}", a.auth(a.deleteRepetitionRule, &proOnly), true},
- {"GET", "/digests/{digestUUID}", a.auth(a.getDigest, nil), true},
- {"GET", "/digests", a.auth(a.getDigests, nil), true},
- {"POST", "/note_review", a.auth(a.createNoteReview, nil), true},
- {"DELETE", "/note_review", a.auth(a.deleteNoteReview, nil), true},
// migration of classic users
{"GET", "/classic/presignin", cors(a.classicPresignin), true},
diff --git a/pkg/server/handlers/v3_auth_test.go b/pkg/server/handlers/v3_auth_test.go
index ca668d16..964a6c6f 100644
--- a/pkg/server/handlers/v3_auth_test.go
+++ b/pkg/server/handlers/v3_auth_test.go
@@ -130,10 +130,6 @@ func TestRegister(t *testing.T) {
assert.Equal(t, user.StripeCustomerID, "", "StripeCustomerID mismatch")
assert.Equal(t, user.MaxUSN, 0, "MaxUSN mismatch")
- var repetitionRuleCount int
- testutils.MustExec(t, testutils.DB.Model(&database.RepetitionRule{}).Where("user_id = ?", account.UserID).Count(&repetitionRuleCount), "counting repetition rules")
- assert.Equal(t, repetitionRuleCount, 1, "repetitionRuleCount mismatch")
-
// welcome email
assert.Equalf(t, len(emailBackend.Emails), 1, "email queue count mismatch")
assert.DeepEqual(t, emailBackend.Emails[0].To, []string{tc.email}, "email to mismatch")
diff --git a/pkg/server/job/job.go b/pkg/server/job/job.go
index da83e188..15450c00 100644
--- a/pkg/server/job/job.go
+++ b/pkg/server/job/job.go
@@ -24,7 +24,6 @@ import (
"github.com/dnote/dnote/pkg/clock"
"github.com/dnote/dnote/pkg/server/config"
"github.com/dnote/dnote/pkg/server/job/remind"
- "github.com/dnote/dnote/pkg/server/job/repetition"
"github.com/dnote/dnote/pkg/server/log"
"github.com/dnote/dnote/pkg/server/mailer"
"github.com/jinzhu/gorm"
@@ -103,7 +102,6 @@ func scheduleJob(c *cron.Cron, spec string, cmd func()) {
func (r *Runner) schedule(ch chan error) {
// Schedule jobs
cr := cron.New()
- scheduleJob(cr, "* * * * *", func() { r.DoRepetition() })
scheduleJob(cr, "0 8 * * *", func() { r.RemindNoRecentNotes() })
cr.Start()
@@ -131,29 +129,6 @@ func (r *Runner) Do() error {
return nil
}
-// DoRepetition creates spaced repetitions and delivers the results based on the rules
-func (r *Runner) DoRepetition() {
- c := repetition.Context{
- DB: r.DB,
- Clock: r.Clock,
- EmailTmpl: r.EmailTmpl,
- EmailBackend: r.EmailBackend,
- Config: r.Config,
- }
-
- result, err := repetition.Do(c)
- m := log.WithFields(log.Fields{
- "success_count": result.SuccessCount,
- "failed_rule_uuids": result.FailedRuleUUIDs,
- })
-
- if err == nil {
- m.Info("successfully processed repetition job")
- } else {
- m.ErrorWrap(err, "error processing repetition job")
- }
-}
-
// RemindNoRecentNotes remind users if no notes have been added recently
func (r *Runner) RemindNoRecentNotes() {
c := remind.Context{
diff --git a/pkg/server/job/remind/inactive.go b/pkg/server/job/remind/inactive.go
index 13d17a2a..e7fc0f03 100644
--- a/pkg/server/job/remind/inactive.go
+++ b/pkg/server/job/remind/inactive.go
@@ -143,17 +143,22 @@ func (c *Context) process(info inactiveUserInfo) error {
return errors.Wrap(err, "getting sender email")
}
+ tok, err := mailer.GetToken(c.DB, info.userID, database.TokenTypeEmailPreference)
+ if err != nil {
+ return errors.Wrap(err, "getting email token")
+ }
+
tmplData := mailer.InactiveReminderTmplData{
WebURL: c.Config.WebURL,
SampleNoteUUID: info.sampleNoteUUID,
- Token: "blah",
+ Token: tok.Value,
}
body, err := c.EmailTmpl.Execute(mailer.EmailTypeInactiveReminder, mailer.EmailKindText, tmplData)
if err != nil {
return errors.Wrap(err, "executing inactive email template")
}
- if err := c.EmailBackend.Queue("Your knowledge base stopped growing", sender, []string{info.email}, mailer.EmailKindText, body); err != nil {
+ if err := c.EmailBackend.Queue("Your Dnote stopped growing", sender, []string{info.email}, mailer.EmailKindText, body); err != nil {
return errors.Wrap(err, "queueing email")
}
diff --git a/pkg/server/job/repetition/main_test.go b/pkg/server/job/repetition/main_test.go
deleted file mode 100644
index 5d88f0c9..00000000
--- a/pkg/server/job/repetition/main_test.go
+++ /dev/null
@@ -1,35 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-package repetition
-
-import (
- "os"
- "testing"
-
- "github.com/dnote/dnote/pkg/server/testutils"
-)
-
-func TestMain(m *testing.M) {
- testutils.InitTestDB()
-
- code := m.Run()
- testutils.ClearData()
-
- os.Exit(code)
-}
diff --git a/pkg/server/job/repetition/repetition.go b/pkg/server/job/repetition/repetition.go
deleted file mode 100644
index 24eb7488..00000000
--- a/pkg/server/job/repetition/repetition.go
+++ /dev/null
@@ -1,297 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-package repetition
-
-import (
- "fmt"
- "os"
- "time"
-
- "github.com/dnote/dnote/pkg/clock"
- "github.com/dnote/dnote/pkg/server/app"
- "github.com/dnote/dnote/pkg/server/config"
- "github.com/dnote/dnote/pkg/server/database"
- "github.com/dnote/dnote/pkg/server/log"
- "github.com/dnote/dnote/pkg/server/mailer"
- "github.com/dnote/dnote/pkg/server/operations"
- "github.com/jinzhu/gorm"
- "github.com/pkg/errors"
-)
-
-// Context holds data that repetition job needs in order to perform
-type Context struct {
- DB *gorm.DB
- Clock clock.Clock
- EmailTmpl mailer.Templates
- EmailBackend mailer.Backend
- Config config.Config
-}
-
-// BuildEmailParams is the params for building an email
-type BuildEmailParams struct {
- Now time.Time
- User database.User
- Digest database.Digest
- Rule database.RepetitionRule
-}
-
-// BuildEmail builds an email for the spaced repetition
-func BuildEmail(db *gorm.DB, emailTmpl mailer.Templates, p BuildEmailParams) (string, string, error) {
- subject := fmt.Sprintf("%s #%d", p.Rule.Title, p.Digest.Version)
- tok, err := mailer.GetToken(db, p.User, database.TokenTypeRepetition)
- if err != nil {
- return "", "", errors.Wrap(err, "getting email frequency token")
- }
-
- t1 := p.Now.AddDate(0, 0, -3).UnixNano()
- t2 := p.Now.AddDate(0, 0, -7).UnixNano()
-
- noteInfos := []mailer.DigestNoteInfo{}
- for _, note := range p.Digest.Notes {
- var stage int
- if note.AddedOn > t1 {
- stage = 1
- } else if note.AddedOn > t2 && note.AddedOn < t1 {
- stage = 2
- } else if note.AddedOn < t2 {
- stage = 3
- }
-
- info := mailer.NewNoteInfo(note, stage)
- noteInfos = append(noteInfos, info)
- }
-
- bookCount := 0
- bookMap := map[string]bool{}
- for _, n := range p.Digest.Notes {
- if ok := bookMap[n.Book.Label]; !ok {
- bookCount++
- bookMap[n.Book.Label] = true
- }
- }
-
- tmplData := mailer.DigestTmplData{
- EmailSessionToken: tok.Value,
- DigestUUID: p.Digest.UUID,
- DigestVersion: p.Digest.Version,
- RuleUUID: p.Rule.UUID,
- RuleTitle: p.Rule.Title,
- WebURL: os.Getenv("WebURL"),
- }
- body, err := emailTmpl.Execute(mailer.EmailTypeDigest, mailer.EmailKindText, tmplData)
- if err != nil {
- return "", "", errors.Wrap(err, "executing digest email template")
- }
-
- return subject, body, nil
-}
-
-func (c Context) getEligibleRules(now time.Time) ([]database.RepetitionRule, error) {
- hour := now.Hour()
- minute := now.Minute()
-
- var ret []database.RepetitionRule
- if err := c.DB.
- Where("users.cloud AND repetition_rules.hour = ? AND repetition_rules.minute = ? AND repetition_rules.enabled", hour, minute).
- Joins("INNER JOIN users ON users.id = repetition_rules.user_id").
- Find(&ret).Error; err != nil {
- return nil, errors.Wrap(err, "querying db")
- }
-
- return ret, nil
-}
-
-func build(tx *gorm.DB, rule database.RepetitionRule) (database.Digest, error) {
- notes, err := getBalancedNotes(tx, rule)
- if err != nil {
- return database.Digest{}, errors.Wrap(err, "getting notes")
- }
-
- digest, err := operations.CreateDigest(tx, rule, notes)
- if err != nil {
- return database.Digest{}, errors.Wrap(err, "creating digest")
- }
-
- return digest, nil
-}
-
-func (c Context) notify(now time.Time, user database.User, digest database.Digest, rule database.RepetitionRule) error {
- var account database.Account
- if err := c.DB.Where("user_id = ?", user.ID).First(&account).Error; err != nil {
- return errors.Wrap(err, "getting account")
- }
-
- if !account.Email.Valid || !account.EmailVerified {
- log.WithFields(log.Fields{
- "user_id": user.ID,
- }).Info("Skipping repetition delivery because email is not valid or verified")
- return nil
- }
-
- subject, body, err := BuildEmail(c.DB, c.EmailTmpl, BuildEmailParams{
- Now: now,
- User: user,
- Digest: digest,
- Rule: rule,
- })
- if err != nil {
- return errors.Wrap(err, "making email")
- }
-
- sender, err := app.GetSenderEmail(c.Config, "noreply@getdnote.com")
- if err != nil {
- return errors.Wrap(err, "getting sender email")
- }
-
- if err := c.EmailBackend.Queue(subject, sender, []string{account.Email.String}, mailer.EmailKindText, body); err != nil {
- return errors.Wrap(err, "queueing email")
- }
-
- if err := c.DB.Create(&database.Notification{
- Type: mailer.EmailTypeDigest,
- UserID: user.ID,
- }).Error; err != nil {
- return errors.Wrap(err, "creating notification")
- }
-
- return nil
-}
-
-func checkCooldown(now time.Time, rule database.RepetitionRule) bool {
- present := now.UnixNano() / int64(time.Millisecond)
-
- return present >= rule.NextActive
-}
-
-func getNextActive(base int64, frequency int64, now time.Time) int64 {
- candidate := base + frequency
- if candidate >= now.UnixNano()/int64(time.Millisecond) {
- return candidate
- }
-
- return getNextActive(candidate, frequency, now)
-}
-
-func touchTimestamp(tx *gorm.DB, rule database.RepetitionRule, now time.Time) error {
- lastActive := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), 0, 0, now.Location()).UnixNano() / int64(time.Millisecond)
-
- rule.LastActive = lastActive
- rule.NextActive = getNextActive(rule.LastActive, rule.Frequency, now)
-
- if err := tx.Save(&rule).Error; err != nil {
- return errors.Wrap(err, "updating repetition rule")
- }
-
- return nil
-}
-
-func (c Context) process(now time.Time, rule database.RepetitionRule) error {
- log.WithFields(log.Fields{
- "uuid": rule.UUID,
- }).Info("processing repetition")
-
- tx := c.DB.Begin()
-
- if !checkCooldown(now, rule) {
- log.WithFields(log.Fields{
- "uuid": rule.UUID,
- }).Info("Skipping repetition processing due to cooldown")
- return nil
- }
-
- var user database.User
- if err := tx.Where("id = ?", rule.UserID).First(&user).Error; err != nil {
- return errors.Wrap(err, "getting user")
- }
- if !user.Cloud {
- log.WithFields(log.Fields{
- "user_id": user.ID,
- }).Info("Skipping repetition due to lack of subscription")
- return nil
- }
-
- digest, err := build(tx, rule)
- if err != nil {
- tx.Rollback()
- return errors.Wrap(err, "building repetition")
- }
-
- if err := touchTimestamp(tx, rule, now); err != nil {
- tx.Rollback()
- return errors.Wrap(err, "touching last_active")
- }
-
- if err := tx.Commit().Error; err != nil {
- tx.Rollback()
- return errors.Wrap(err, "committing transaction")
- }
-
- if err := c.notify(now, user, digest, rule); err != nil {
- return errors.Wrap(err, "notifying user")
- }
-
- log.WithFields(log.Fields{
- "uuid": rule.UUID,
- }).Info("finished processing repetition")
-
- return nil
-}
-
-// Result holds the result of the job
-type Result struct {
- SuccessCount int
- FailedRuleUUIDs []string
-}
-
-// Do creates spaced repetitions and delivers the results based on the rules
-func Do(c Context) (Result, error) {
- now := c.Clock.Now().UTC()
- result := Result{}
-
- rules, err := c.getEligibleRules(now)
- if err != nil {
- return result, errors.Wrap(err, "getting eligible repetition rules")
- }
-
- log.WithFields(log.Fields{
- "hour": now.Hour(),
- "minute": now.Minute(),
- "num_rules": len(rules),
- }).Info("processing rules")
-
- for _, rule := range rules {
- err := c.process(now, rule)
-
- if err == nil {
- result.SuccessCount = result.SuccessCount + 1
- } else {
- log.WithFields(log.Fields{
- "rule uuid": rule.UUID,
- }).ErrorWrap(err, "Could not process the repetition rule")
-
- result.FailedRuleUUIDs = append(result.FailedRuleUUIDs, rule.UUID)
- }
- }
-
- if len(result.FailedRuleUUIDs) > 0 {
- return result, errors.New("failed to process some rules")
- }
-
- return result, nil
-}
diff --git a/pkg/server/job/repetition/repetition_test.go b/pkg/server/job/repetition/repetition_test.go
deleted file mode 100644
index 64cc6412..00000000
--- a/pkg/server/job/repetition/repetition_test.go
+++ /dev/null
@@ -1,501 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-package repetition
-
-import (
- "os"
- "sort"
- "testing"
- "time"
-
- "github.com/dnote/dnote/pkg/assert"
- "github.com/dnote/dnote/pkg/clock"
- "github.com/dnote/dnote/pkg/server/database"
- "github.com/dnote/dnote/pkg/server/mailer"
- "github.com/dnote/dnote/pkg/server/testutils"
- "github.com/pkg/errors"
-)
-
-func assertLastActive(t *testing.T, ruleUUID string, lastActive int64) {
- var rule database.RepetitionRule
- testutils.MustExec(t, testutils.DB.Where("uuid = ?", ruleUUID).First(&rule), "finding rule1")
-
- assert.Equal(t, rule.LastActive, lastActive, "LastActive mismatch")
-}
-
-func assertDigestCount(t *testing.T, rule database.RepetitionRule, expected int) {
- var digestCount int
- testutils.MustExec(t, testutils.DB.Model(&database.Digest{}).Where("rule_id = ? AND user_id = ?", rule.ID, rule.UserID).Count(&digestCount), "counting digest")
- assert.Equal(t, digestCount, expected, "digest count mismatch")
-}
-
-func getTestContext(c clock.Clock, be *testutils.MockEmailbackendImplementation) Context {
- emailTmplDir := os.Getenv("DNOTE_TEST_EMAIL_TEMPLATE_DIR")
-
- return Context{
- DB: testutils.DB,
- Clock: c,
- EmailTmpl: mailer.NewTemplates(&emailTmplDir),
- EmailBackend: be,
- }
-}
-
-func mustDo(t *testing.T, c Context) {
- _, err := Do(c)
- if err != nil {
- t.Fatal(errors.Wrap(err, "performing"))
- }
-}
-
-func TestDo(t *testing.T) {
- t.Run("processes the rule on time", func(t *testing.T) {
- defer testutils.ClearData()
-
- // Set up
- user := testutils.SetupUserData()
- a := testutils.SetupAccountData(user, "alice@example.com", "pass1234")
- testutils.MustExec(t, testutils.DB.Model(&a).Update("email_verified", true), "updating email_verified")
-
- t0 := time.Date(2009, time.November, 1, 0, 0, 0, 0, time.UTC)
- t1 := time.Date(2009, time.November, 4, 12, 2, 0, 0, time.UTC)
- r1 := database.RepetitionRule{
- Title: "Rule 1",
- Frequency: (time.Hour * 24 * 3).Milliseconds(), // three days
- Hour: 12,
- Minute: 2,
- Enabled: true,
- LastActive: 0,
- NextActive: t1.UnixNano() / int64(time.Millisecond),
- UserID: user.ID,
- BookDomain: database.BookDomainAll,
- Model: database.Model{
- CreatedAt: t0,
- UpdatedAt: t0,
- },
- }
-
- testutils.MustExec(t, testutils.DB.Save(&r1), "preparing rule1")
-
- c := clock.NewMock()
- be := testutils.MockEmailbackendImplementation{}
- con := getTestContext(c, &be)
-
- // Test
- // 1 day later
- c.SetNow(time.Date(2009, time.November, 2, 12, 2, 1, 0, time.UTC))
- mustDo(t, con)
- assertLastActive(t, r1.UUID, int64(0))
- assertDigestCount(t, r1, 0)
- assert.Equalf(t, len(be.Emails), 0, "email queue count mismatch")
-
- // 2 days later
- c.SetNow(time.Date(2009, time.November, 3, 12, 2, 1, 0, time.UTC))
- mustDo(t, con)
- assertLastActive(t, r1.UUID, int64(0))
- assertDigestCount(t, r1, 0)
- assert.Equal(t, len(be.Emails), 0, "email queue count mismatch")
-
- // 3 days later - should be processed
- c.SetNow(time.Date(2009, time.November, 4, 12, 1, 1, 0, time.UTC))
- mustDo(t, con)
- assertLastActive(t, r1.UUID, int64(0))
- assertDigestCount(t, r1, 0)
- assert.Equal(t, len(be.Emails), 0, "email queue count mismatch")
-
- c.SetNow(time.Date(2009, time.November, 4, 12, 2, 1, 0, time.UTC))
- mustDo(t, con)
- assertLastActive(t, r1.UUID, int64(1257336120000))
- assertDigestCount(t, r1, 1)
- assert.Equal(t, len(be.Emails), 1, "email queue count mismatch")
-
- c.SetNow(time.Date(2009, time.November, 4, 12, 3, 1, 0, time.UTC))
- mustDo(t, con)
- assertLastActive(t, r1.UUID, int64(1257336120000))
- assertDigestCount(t, r1, 1)
- assert.Equal(t, len(be.Emails), 1, "email queue count mismatch")
-
- // 4 day later
- c.SetNow(time.Date(2009, time.November, 5, 12, 2, 1, 0, time.UTC))
- mustDo(t, con)
- assertLastActive(t, r1.UUID, int64(1257336120000))
- assertDigestCount(t, r1, 1)
- assert.Equal(t, len(be.Emails), 1, "email queue count mismatch")
-
- // 5 days later
- c.SetNow(time.Date(2009, time.November, 6, 12, 2, 1, 0, time.UTC))
- mustDo(t, con)
- assertLastActive(t, r1.UUID, int64(1257336120000))
- assertDigestCount(t, r1, 1)
- assert.Equal(t, len(be.Emails), 1, "email queue count mismatch")
-
- // 6 days later - should be processed
- c.SetNow(time.Date(2009, time.November, 7, 12, 2, 1, 0, time.UTC))
- mustDo(t, con)
- assertLastActive(t, r1.UUID, int64(1257595320000))
- assertDigestCount(t, r1, 2)
- assert.Equal(t, len(be.Emails), 2, "email queue count mismatch")
-
- // 7 days later
- c.SetNow(time.Date(2009, time.November, 8, 12, 2, 1, 0, time.UTC))
- mustDo(t, con)
- assertLastActive(t, r1.UUID, int64(1257595320000))
- assertDigestCount(t, r1, 2)
- assert.Equal(t, len(be.Emails), 2, "email queue count mismatch")
-
- // 8 days later
- c.SetNow(time.Date(2009, time.November, 9, 12, 2, 1, 0, time.UTC))
- mustDo(t, con)
- assertLastActive(t, r1.UUID, int64(1257595320000))
- assertDigestCount(t, r1, 2)
- assert.Equal(t, len(be.Emails), 2, "email queue count mismatch")
-
- // 9 days later - should be processed
- c.SetNow(time.Date(2009, time.November, 10, 12, 2, 1, 0, time.UTC))
- mustDo(t, con)
- assertLastActive(t, r1.UUID, int64(1257854520000))
- assertDigestCount(t, r1, 3)
- assert.Equal(t, len(be.Emails), 3, "email queue count mismatch")
- })
-
- /*
- * |----|----|----|----|----|----|----|----|----|----|----|----|----|
- * t0 t1 td t2 tu t3 t4
- *
- * Suppose a repetition with a frequency of 3 days.
- *
- * t0 - original last_active value (Nov 1, 2009)
- * t1 - original next_active value (Nov 4, 2009)
- * td - server goes down
- * t2 - repetition processing is missed (Nov 7, 2009)
- * tu - server comes up
- * t3 - new last_active value (Nov 10, 2009)
- * t4 - new next_active value (Nov 13, 2009)
- */
- t.Run("recovers correct next_active value if missed processing in the past", func(t *testing.T) {
- defer testutils.ClearData()
-
- // Set up
- user := testutils.SetupUserData()
- a := testutils.SetupAccountData(user, "alice@example.com", "pass1234")
- testutils.MustExec(t, testutils.DB.Model(&a).Update("email_verified", true), "updating email_verified")
-
- t0 := time.Date(2009, time.November, 1, 12, 2, 0, 0, time.UTC)
- t1 := time.Date(2009, time.November, 4, 12, 2, 0, 0, time.UTC)
- r1 := database.RepetitionRule{
- Title: "Rule 1",
- Frequency: (time.Hour * 24 * 3).Milliseconds(), // three days
- Hour: 12,
- Minute: 2,
- Enabled: true,
- LastActive: t0.UnixNano() / int64(time.Millisecond),
- NextActive: t1.UnixNano() / int64(time.Millisecond),
- UserID: user.ID,
- BookDomain: database.BookDomainAll,
- Model: database.Model{
- CreatedAt: t0,
- UpdatedAt: t0,
- },
- }
-
- testutils.MustExec(t, testutils.DB.Save(&r1), "preparing rule1")
-
- c := clock.NewMock()
- c.SetNow(time.Date(2009, time.November, 10, 12, 2, 1, 0, time.UTC))
- be := &testutils.MockEmailbackendImplementation{}
-
- mustDo(t, getTestContext(c, be))
-
- var rule database.RepetitionRule
- testutils.MustExec(t, testutils.DB.Where("uuid = ?", r1.UUID).First(&rule), "finding rule1")
-
- assert.Equal(t, rule.LastActive, time.Date(2009, time.November, 10, 12, 2, 0, 0, time.UTC).UnixNano()/int64(time.Millisecond), "LastActive mismsatch")
- assert.Equal(t, rule.NextActive, time.Date(2009, time.November, 13, 12, 2, 0, 0, time.UTC).UnixNano()/int64(time.Millisecond), "NextActive mismsatch")
- assertDigestCount(t, r1, 1)
- assert.Equal(t, len(be.Emails), 1, "email queue count mismatch")
- })
-}
-
-func TestDo_Disabled(t *testing.T) {
- defer testutils.ClearData()
-
- // Set up
- user := testutils.SetupUserData()
- a := testutils.SetupAccountData(user, "alice@example.com", "pass1234")
- testutils.MustExec(t, testutils.DB.Model(&a).Update("email_verified", true), "updating email_verified")
-
- t0 := time.Date(2009, time.November, 1, 0, 0, 0, 0, time.UTC)
- t1 := time.Date(2009, time.November, 4, 12, 2, 0, 0, time.UTC)
- r1 := database.RepetitionRule{
- Title: "Rule 1",
- Frequency: (time.Hour * 24 * 3).Milliseconds(), // three days
- Hour: 12,
- Minute: 2,
- LastActive: 0,
- NextActive: t1.UnixNano() / int64(time.Millisecond),
- UserID: user.ID,
- Enabled: false,
- BookDomain: database.BookDomainAll,
- Model: database.Model{
- CreatedAt: t0,
- UpdatedAt: t0,
- },
- }
-
- testutils.MustExec(t, testutils.DB.Save(&r1), "preparing rule1")
-
- // Execute
- c := clock.NewMock()
- c.SetNow(time.Date(2009, time.November, 4, 12, 2, 0, 0, time.UTC))
- be := &testutils.MockEmailbackendImplementation{}
-
- mustDo(t, getTestContext(c, be))
-
- // Test
- assertLastActive(t, r1.UUID, int64(0))
- assertDigestCount(t, r1, 0)
- assert.Equal(t, len(be.Emails), 0, "email queue count mismatch")
-}
-
-func TestDo_BalancedStrategy(t *testing.T) {
- type testData struct {
- User database.User
- Book1 database.Book
- Book2 database.Book
- Book3 database.Book
- Note1 database.Note
- Note2 database.Note
- Note3 database.Note
- }
-
- setup := func() testData {
- user := testutils.SetupUserData()
- a := testutils.SetupAccountData(user, "alice@example.com", "pass1234")
- testutils.MustExec(t, testutils.DB.Model(&a).Update("email_verified", true), "updating email_verified")
-
- b1 := database.Book{
- UserID: user.ID,
- Label: "js",
- }
- testutils.MustExec(t, testutils.DB.Save(&b1), "preparing b1")
- b2 := database.Book{
- UserID: user.ID,
- Label: "css",
- }
- testutils.MustExec(t, testutils.DB.Save(&b2), "preparing b2")
- b3 := database.Book{
- UserID: user.ID,
- Label: "golang",
- }
- testutils.MustExec(t, testutils.DB.Save(&b3), "preparing b3")
-
- n1 := database.Note{
- UserID: user.ID,
- BookUUID: b1.UUID,
- }
- testutils.MustExec(t, testutils.DB.Save(&n1), "preparing n1")
- n2 := database.Note{
- UserID: user.ID,
- BookUUID: b2.UUID,
- }
- testutils.MustExec(t, testutils.DB.Save(&n2), "preparing n2")
- n3 := database.Note{
- UserID: user.ID,
- BookUUID: b3.UUID,
- }
- testutils.MustExec(t, testutils.DB.Save(&n3), "preparing n3")
-
- return testData{
- User: user,
- Book1: b1,
- Book2: b2,
- Book3: b3,
- Note1: n1,
- Note2: n2,
- Note3: n3,
- }
- }
-
- t.Run("all books", func(t *testing.T) {
- defer testutils.ClearData()
-
- // Set up
- dat := setup()
-
- t0 := time.Date(2009, time.November, 1, 12, 0, 0, 0, time.UTC)
- t1 := time.Date(2009, time.November, 8, 21, 0, 0, 0, time.UTC)
- r1 := database.RepetitionRule{
- Title: "Rule 1",
- Frequency: (time.Hour * 24 * 7).Milliseconds(),
- Hour: 21,
- Minute: 0,
- LastActive: 0,
- NextActive: t1.UnixNano() / int64(time.Millisecond),
- Enabled: true,
- UserID: dat.User.ID,
- BookDomain: database.BookDomainAll,
- NoteCount: 5,
- Model: database.Model{
- CreatedAt: t0,
- UpdatedAt: t0,
- },
- }
- testutils.MustExec(t, testutils.DB.Save(&r1), "preparing rule1")
-
- // Execute
- c := clock.NewMock()
- c.SetNow(time.Date(2009, time.November, 8, 21, 0, 0, 0, time.UTC))
- be := &testutils.MockEmailbackendImplementation{}
-
- mustDo(t, getTestContext(c, be))
-
- // Test
- assertLastActive(t, r1.UUID, int64(1257714000000))
- assertDigestCount(t, r1, 1)
- assert.Equal(t, len(be.Emails), 1, "email queue count mismatch")
-
- var repetition database.Digest
- testutils.MustExec(t, testutils.DB.Where("rule_id = ? AND user_id = ?", r1.ID, r1.UserID).Preload("Notes").First(&repetition), "finding repetition")
-
- sort.SliceStable(repetition.Notes, func(i, j int) bool {
- n1 := repetition.Notes[i]
- n2 := repetition.Notes[j]
-
- return n1.ID < n2.ID
- })
-
- var n1Record, n2Record, n3Record database.Note
- testutils.MustExec(t, testutils.DB.Where("uuid = ?", dat.Note1.UUID).First(&n1Record), "finding n1")
- testutils.MustExec(t, testutils.DB.Where("uuid = ?", dat.Note2.UUID).First(&n2Record), "finding n2")
- testutils.MustExec(t, testutils.DB.Where("uuid = ?", dat.Note3.UUID).First(&n3Record), "finding n3")
- expected := []database.Note{n1Record, n2Record, n3Record}
- assert.DeepEqual(t, repetition.Notes, expected, "result mismatch")
- })
-
- t.Run("excluding books", func(t *testing.T) {
- defer testutils.ClearData()
-
- // Set up
- dat := setup()
-
- t0 := time.Date(2009, time.November, 1, 12, 0, 0, 0, time.UTC)
- t1 := time.Date(2009, time.November, 8, 21, 0, 0, 0, time.UTC)
- r1 := database.RepetitionRule{
- Title: "Rule 1",
- Frequency: (time.Hour * 24 * 7).Milliseconds(),
- Hour: 21,
- Enabled: true,
- Minute: 0,
- LastActive: 0,
- NextActive: t1.UnixNano() / int64(time.Millisecond),
- UserID: dat.User.ID,
- BookDomain: database.BookDomainExluding,
- Books: []database.Book{dat.Book1},
- NoteCount: 5,
- Model: database.Model{
- CreatedAt: t0,
- UpdatedAt: t0,
- },
- }
- testutils.MustExec(t, testutils.DB.Save(&r1), "preparing rule1")
-
- // Execute
- c := clock.NewMock()
- c.SetNow(time.Date(2009, time.November, 8, 21, 0, 1, 0, time.UTC))
- be := &testutils.MockEmailbackendImplementation{}
-
- mustDo(t, getTestContext(c, be))
-
- // Test
- assertLastActive(t, r1.UUID, int64(1257714000000))
- assertDigestCount(t, r1, 1)
- assert.Equal(t, len(be.Emails), 1, "email queue count mismatch")
-
- var repetition database.Digest
- testutils.MustExec(t, testutils.DB.Where("rule_id = ? AND user_id = ?", r1.ID, r1.UserID).Preload("Notes").First(&repetition), "finding repetition")
-
- sort.SliceStable(repetition.Notes, func(i, j int) bool {
- n1 := repetition.Notes[i]
- n2 := repetition.Notes[j]
-
- return n1.ID < n2.ID
- })
-
- var n2Record, n3Record database.Note
- testutils.MustExec(t, testutils.DB.Where("uuid = ?", dat.Note2.UUID).First(&n2Record), "finding n2")
- testutils.MustExec(t, testutils.DB.Where("uuid = ?", dat.Note3.UUID).First(&n3Record), "finding n3")
- expected := []database.Note{n2Record, n3Record}
- assert.DeepEqual(t, repetition.Notes, expected, "result mismatch")
- })
-
- t.Run("including books", func(t *testing.T) {
- defer testutils.ClearData()
-
- // Set up
- dat := setup()
-
- t0 := time.Date(2009, time.November, 1, 12, 0, 0, 0, time.UTC)
- t1 := time.Date(2009, time.November, 8, 21, 0, 0, 0, time.UTC)
- r1 := database.RepetitionRule{
- Title: "Rule 1",
- Frequency: (time.Hour * 24 * 7).Milliseconds(),
- Hour: 21,
- Enabled: true,
- Minute: 0,
- LastActive: 0,
- NextActive: t1.UnixNano() / int64(time.Millisecond),
- UserID: dat.User.ID,
- BookDomain: database.BookDomainIncluding,
- Books: []database.Book{dat.Book1, dat.Book2},
- NoteCount: 5,
- Model: database.Model{
- CreatedAt: t0,
- UpdatedAt: t0,
- },
- }
- testutils.MustExec(t, testutils.DB.Save(&r1), "preparing rule1")
-
- // Execute
- c := clock.NewMock()
- c.SetNow(time.Date(2009, time.November, 8, 21, 0, 0, 0, time.UTC))
- be := &testutils.MockEmailbackendImplementation{}
-
- mustDo(t, getTestContext(c, be))
-
- // Test
- assertLastActive(t, r1.UUID, int64(1257714000000))
- assertDigestCount(t, r1, 1)
- assert.Equal(t, len(be.Emails), 1, "email queue count mismatch")
-
- var repetition database.Digest
- testutils.MustExec(t, testutils.DB.Where("rule_id = ? AND user_id = ?", r1.ID, r1.UserID).Preload("Notes").First(&repetition), "finding repetition")
-
- sort.SliceStable(repetition.Notes, func(i, j int) bool {
- n1 := repetition.Notes[i]
- n2 := repetition.Notes[j]
-
- return n1.ID < n2.ID
- })
-
- var n1Record, n2Record database.Note
- testutils.MustExec(t, testutils.DB.Where("uuid = ?", dat.Note1.UUID).First(&n1Record), "finding n1")
- testutils.MustExec(t, testutils.DB.Where("uuid = ?", dat.Note2.UUID).First(&n2Record), "finding n2")
- expected := []database.Note{n1Record, n2Record}
- assert.DeepEqual(t, repetition.Notes, expected, "result mismatch")
- })
-}
diff --git a/pkg/server/job/repetition/strategy.go b/pkg/server/job/repetition/strategy.go
deleted file mode 100644
index f8bfd499..00000000
--- a/pkg/server/job/repetition/strategy.go
+++ /dev/null
@@ -1,140 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-package repetition
-
-import (
- "sort"
- "time"
-
- "github.com/dnote/dnote/pkg/server/database"
- "github.com/jinzhu/gorm"
- "github.com/pkg/errors"
-)
-
-func getRuleBookIDs(db *gorm.DB, ruleID int) ([]int, error) {
- var ret []int
- if err := db.Table("repetition_rule_books").Select("book_id").Where("repetition_rule_id = ?", ruleID).Pluck("book_id", &ret).Error; err != nil {
- return nil, errors.Wrap(err, "querying book_ids")
- }
-
- return ret, nil
-}
-
-func applyBookDomain(db *gorm.DB, noteQuery *gorm.DB, rule database.RepetitionRule) (*gorm.DB, error) {
- ret := noteQuery
-
- if rule.BookDomain != database.BookDomainAll {
- bookIDs, err := getRuleBookIDs(db, rule.ID)
- if err != nil {
- return nil, errors.Wrap(err, "getting book_ids")
- }
-
- ret = ret.Joins("INNER JOIN books ON notes.book_uuid = books.uuid")
-
- if rule.BookDomain == database.BookDomainExluding {
- ret = ret.Where("books.id NOT IN (?)", bookIDs)
- } else if rule.BookDomain == database.BookDomainIncluding {
- ret = ret.Where("books.id IN (?)", bookIDs)
- }
- }
-
- return ret, nil
-}
-
-func getNotes(db, conn *gorm.DB, rule database.RepetitionRule, dst *[]database.Note) error {
- c, err := applyBookDomain(db, conn, rule)
- if err != nil {
- return errors.Wrap(err, "building query for book threahold 1")
- }
-
- // TODO: ordering by random() does not scale if table grows large
- if err := c.Where("notes.user_id = ?", rule.UserID).Order("random()").Limit(rule.NoteCount).Preload("Book").Find(&dst).Error; err != nil {
- return errors.Wrap(err, "getting notes")
- }
-
- return nil
-}
-
-// getBalancedNotes returns a set of notes with a 'balanced' ratio of added_on dates
-func getBalancedNotes(db *gorm.DB, rule database.RepetitionRule) ([]database.Note, error) {
- now := time.Now()
- t1 := now.AddDate(0, 0, -3).UnixNano()
- t2 := now.AddDate(0, 0, -7).UnixNano()
-
- baseConn := db.Where("notes.deleted IS NOT true")
-
- // Get notes into three buckets with different threshold values
- var stage1, stage2, stage3 []database.Note
- if err := getNotes(db, baseConn.Where("notes.added_on > ?", t1), rule, &stage1); err != nil {
- return nil, errors.Wrap(err, "Failed to get notes with threshold 1")
- }
- if err := getNotes(db, baseConn.Where("notes.added_on > ? AND notes.added_on < ?", t2, t1), rule, &stage2); err != nil {
- return nil, errors.Wrap(err, "Failed to get notes with threshold 2")
- }
- if err := getNotes(db, baseConn.Where("notes.added_on < ?", t2), rule, &stage3); err != nil {
- return nil, errors.Wrap(err, "Failed to get notes with threshold 3")
- }
-
- notes := []database.Note{}
-
- // pick one from each bucket at a time until the result is filled
- i1 := 0
- i2 := 0
- i3 := 0
- k := 0
- for {
- if i1+i2+i3 >= rule.NoteCount {
- break
- }
-
- // if there are not enough notes to fill the result, break
- if len(stage1) == i1 && len(stage2) == i2 && len(stage3) == i3 {
- break
- }
-
- if k%3 == 0 {
- if len(stage1) > i1 {
- i1++
- }
- } else if k%3 == 1 {
- if len(stage2) > i2 {
- i2++
- }
- } else if k%3 == 2 {
- if len(stage3) > i3 {
- i3++
- }
- }
-
- k++
- }
-
- notes = append(notes, stage1[:i1]...)
- notes = append(notes, stage2[:i2]...)
- notes = append(notes, stage3[:i3]...)
-
- sort.SliceStable(notes, func(i, j int) bool {
- n1 := notes[i]
- n2 := notes[j]
-
- return n1.AddedOn > n2.AddedOn
- })
-
- return notes, nil
-}
diff --git a/pkg/server/job/repetition/strategy_test.go b/pkg/server/job/repetition/strategy_test.go
deleted file mode 100644
index 80d9bbe7..00000000
--- a/pkg/server/job/repetition/strategy_test.go
+++ /dev/null
@@ -1,112 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-package repetition
-
-import (
- "testing"
-
- "github.com/dnote/dnote/pkg/assert"
- "github.com/dnote/dnote/pkg/server/database"
- "github.com/dnote/dnote/pkg/server/testutils"
- "github.com/pkg/errors"
-)
-
-func init() {
- testutils.InitTestDB()
-}
-
-func TestApplyBookDomain(t *testing.T) {
- defer testutils.ClearData()
-
- user := testutils.SetupUserData()
- b1 := database.Book{
- UserID: user.ID,
- Label: "js",
- }
- testutils.MustExec(t, testutils.DB.Save(&b1), "preparing b1")
- b2 := database.Book{
- UserID: user.ID,
- Label: "css",
- }
- testutils.MustExec(t, testutils.DB.Save(&b2), "preparing b2")
- b3 := database.Book{
- UserID: user.ID,
- Label: "golang",
- }
- testutils.MustExec(t, testutils.DB.Save(&b3), "preparing b3")
-
- n1 := database.Note{
- UserID: user.ID,
- BookUUID: b1.UUID,
- }
- testutils.MustExec(t, testutils.DB.Save(&n1), "preparing n1")
- n2 := database.Note{
- UserID: user.ID,
- BookUUID: b2.UUID,
- }
- testutils.MustExec(t, testutils.DB.Save(&n2), "preparing n2")
- n3 := database.Note{
- UserID: user.ID,
- BookUUID: b3.UUID,
- }
- testutils.MustExec(t, testutils.DB.Save(&n3), "preparing n3")
-
- var n1Record, n2Record, n3Record database.Note
- testutils.MustExec(t, testutils.DB.Where("uuid = ?", n1.UUID).First(&n1Record), "finding n1")
- testutils.MustExec(t, testutils.DB.Where("uuid = ?", n2.UUID).First(&n2Record), "finding n2")
- testutils.MustExec(t, testutils.DB.Where("uuid = ?", n3.UUID).First(&n3Record), "finding n3")
-
- t.Run("book domain all", func(t *testing.T) {
- rule := database.RepetitionRule{
- UserID: user.ID,
- BookDomain: database.BookDomainAll,
- }
-
- conn, err := applyBookDomain(testutils.DB, testutils.DB, rule)
- if err != nil {
- t.Fatal(errors.Wrap(err, "executing").Error())
- }
-
- var result []database.Note
- testutils.MustExec(t, conn.Order("id ASC").Find(&result), "finding notes")
-
- expected := []database.Note{n1Record, n2Record, n3Record}
- assert.DeepEqual(t, result, expected, "result mismatch")
- })
-
- t.Run("book domain exclude", func(t *testing.T) {
- rule := database.RepetitionRule{
- UserID: user.ID,
- BookDomain: database.BookDomainExluding,
- Books: []database.Book{b1},
- }
- testutils.MustExec(t, testutils.DB.Save(&rule), "preparing rule")
-
- conn, err := applyBookDomain(testutils.DB, testutils.DB, rule)
- if err != nil {
- t.Fatal(errors.Wrap(err, "executing").Error())
- }
-
- var result []database.Note
- testutils.MustExec(t, conn.Order("id ASC").Find(&result), "finding notes")
-
- expected := []database.Note{n2Record, n3Record}
- assert.DeepEqual(t, result, expected, "result mismatch")
- })
-}
diff --git a/pkg/server/mailer/mailer.go b/pkg/server/mailer/mailer.go
index aa940f22..60a95bac 100644
--- a/pkg/server/mailer/mailer.go
+++ b/pkg/server/mailer/mailer.go
@@ -36,8 +36,6 @@ var (
EmailTypeResetPassword = "reset_password"
// EmailTypeResetPasswordAlert represents a password change notification email
EmailTypeResetPasswordAlert = "reset_password_alert"
- // EmailTypeDigest represents a weekly digest email
- EmailTypeDigest = "digest"
// EmailTypeEmailVerification represents an email verification email
EmailTypeEmailVerification = "verify_email"
// EmailTypeWelcome represents an welcome email
@@ -117,10 +115,6 @@ func NewTemplates(srcDir *string) Templates {
if err != nil {
panic(errors.Wrap(err, "initializing password reset template"))
}
- digestText, err := initTextTmpl(box, EmailTypeDigest)
- if err != nil {
- panic(errors.Wrap(err, "initializing digest template"))
- }
T := Templates{}
T.set(EmailTypeResetPassword, EmailKindText, passwordResetText)
@@ -129,7 +123,6 @@ func NewTemplates(srcDir *string) Templates {
T.set(EmailTypeWelcome, EmailKindText, welcomeText)
T.set(EmailTypeInactiveReminder, EmailKindText, inactiveReminderText)
T.set(EmailTypeSubscriptionConfirmation, EmailKindText, subscriptionConfirmationText)
- T.set(EmailTypeDigest, EmailKindText, digestText)
return T
}
diff --git a/pkg/server/mailer/templates/main.go b/pkg/server/mailer/templates/main.go
index e64b0584..5d3d055e 100644
--- a/pkg/server/mailer/templates/main.go
+++ b/pkg/server/mailer/templates/main.go
@@ -21,61 +21,15 @@ package main
import (
"log"
"net/http"
- "time"
"github.com/dnote/dnote/pkg/server/config"
"github.com/dnote/dnote/pkg/server/database"
- "github.com/dnote/dnote/pkg/server/job/repetition"
"github.com/dnote/dnote/pkg/server/mailer"
"github.com/jinzhu/gorm"
"github.com/joho/godotenv"
_ "github.com/lib/pq"
- "github.com/pkg/errors"
)
-func (c Context) digestHandler(w http.ResponseWriter, r *http.Request) {
- db := c.DB
-
- q := r.URL.Query()
- digestUUID := q.Get("digest_uuid")
- if digestUUID == "" {
- http.Error(w, errors.New("Please provide digest_uuid query param").Error(), http.StatusBadRequest)
- return
- }
-
- var user database.User
- if err := db.First(&user).Error; err != nil {
- http.Error(w, errors.Wrap(err, "Failed to find user").Error(), http.StatusInternalServerError)
- return
- }
-
- var digest database.Digest
- if err := db.Where("uuid = ?", digestUUID).Preload("Notes").First(&digest).Error; err != nil {
- http.Error(w, errors.Wrap(err, "finding digest").Error(), http.StatusInternalServerError)
- return
- }
-
- var rule database.RepetitionRule
- if err := db.Where("id = ?", digest.RuleID).First(&rule).Error; err != nil {
- http.Error(w, errors.Wrap(err, "finding digest").Error(), http.StatusInternalServerError)
- return
- }
-
- now := time.Now()
- _, body, err := repetition.BuildEmail(db, c.Tmpl, repetition.BuildEmailParams{
- Now: now,
- User: user,
- Digest: digest,
- Rule: rule,
- })
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- w.Write([]byte(body))
-}
-
func (c Context) passwordResetHandler(w http.ResponseWriter, r *http.Request) {
data := mailer.EmailResetPasswordTmplData{
AccountEmail: "alice@example.com",
@@ -176,7 +130,6 @@ func main() {
ctx := Context{DB: db, Tmpl: tmpl}
http.HandleFunc("/", ctx.homeHandler)
- http.HandleFunc("/digest", ctx.digestHandler)
http.HandleFunc("/email-verification", ctx.emailVerificationHandler)
http.HandleFunc("/password-reset", ctx.passwordResetHandler)
http.HandleFunc("/password-reset-alert", ctx.passwordResetAlertHandler)
diff --git a/pkg/server/mailer/templates/src/digest.txt b/pkg/server/mailer/templates/src/digest.txt
deleted file mode 100644
index 7b580a65..00000000
--- a/pkg/server/mailer/templates/src/digest.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-REFRESH YOUR MEMORY
-
-There is a new automated spaced repetition "{{ .RuleTitle }} #{{ .DigestVersion }}"
-
- {{ .WebURL }}/digests/{{ .DigestUUID }}
-
-
-MANAGE THE RULE
-
-Go to the following link to manage the notification and other settings for "{{ .RuleTitle }}"
-
- {{ .WebURL }}/preferences/repetitions/{{ .RuleUUID }}?token={{ .EmailSessionToken }}
-
-- Dnote team
-
diff --git a/pkg/server/mailer/templates/src/inactive.txt b/pkg/server/mailer/templates/src/inactive.txt
index ae6ec6ec..b6f4d508 100644
--- a/pkg/server/mailer/templates/src/inactive.txt
+++ b/pkg/server/mailer/templates/src/inactive.txt
@@ -1,8 +1,8 @@
Hi, nothing has been added to your Dnote for some time.
-What about revisiting one of your previous knowledge? {{ .WebURL }}/notes/{{ .SampleNoteUUID }}
+What about revisiting one of your previous notes? {{ .WebURL }}/notes/{{ .SampleNoteUUID }}
-Expand your knowledge base at {{ .WebURL }}/new or using Dnote apps.
+You can add new notes at {{ .WebURL }}/new or using Dnote apps.
- Dnote team
diff --git a/pkg/server/mailer/tokens.go b/pkg/server/mailer/tokens.go
index 705f4df5..a2ae431c 100644
--- a/pkg/server/mailer/tokens.go
+++ b/pkg/server/mailer/tokens.go
@@ -40,10 +40,10 @@ func generateRandomToken(bits int) (string, error) {
// GetToken returns an token of the given kind for the user
// by first looking up any unused record and creating one if none exists.
-func GetToken(db *gorm.DB, user database.User, kind string) (database.Token, error) {
+func GetToken(db *gorm.DB, userID int, kind string) (database.Token, error) {
var tok database.Token
conn := db.
- Where("user_id = ? AND type =? AND used_at IS NULL", user.ID, kind).
+ Where("user_id = ? AND type =? AND used_at IS NULL", userID, kind).
First(&tok)
tokenVal, err := generateRandomToken(16)
@@ -53,7 +53,7 @@ func GetToken(db *gorm.DB, user database.User, kind string) (database.Token, err
if conn.RecordNotFound() {
tok = database.Token{
- UserID: user.ID,
+ UserID: userID,
Type: kind,
Value: tokenVal,
}
diff --git a/pkg/server/mailer/types.go b/pkg/server/mailer/types.go
index ed0f3000..5306fed6 100644
--- a/pkg/server/mailer/types.go
+++ b/pkg/server/mailer/types.go
@@ -18,45 +18,6 @@
package mailer
-import (
- "time"
-
- "github.com/dnote/dnote/pkg/server/database"
- "github.com/justincampbell/timeago"
-)
-
-// DigestNoteInfo contains note information for digest emails
-type DigestNoteInfo struct {
- UUID string
- Content string
- BookLabel string
- TimeAgo string
- Stage int
-}
-
-// NewNoteInfo returns a new NoteInfo
-func NewNoteInfo(note database.Note, stage int) DigestNoteInfo {
- tm := time.Unix(0, int64(note.AddedOn))
-
- return DigestNoteInfo{
- UUID: note.UUID,
- Content: note.Body,
- BookLabel: note.Book.Label,
- TimeAgo: timeago.FromTime(tm),
- Stage: stage,
- }
-}
-
-// DigestTmplData is a template data for digest emails
-type DigestTmplData struct {
- EmailSessionToken string
- DigestUUID string
- DigestVersion int
- RuleUUID string
- RuleTitle string
- WebURL string
-}
-
// EmailVerificationTmplData is a template data for email verification emails
type EmailVerificationTmplData struct {
Token string
diff --git a/pkg/server/operations/digests.go b/pkg/server/operations/digests.go
deleted file mode 100644
index acd062e6..00000000
--- a/pkg/server/operations/digests.go
+++ /dev/null
@@ -1,45 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-package operations
-
-import (
- "github.com/dnote/dnote/pkg/server/database"
- "github.com/jinzhu/gorm"
- "github.com/pkg/errors"
-)
-
-// CreateDigest creates a new digest
-func CreateDigest(db *gorm.DB, rule database.RepetitionRule, notes []database.Note) (database.Digest, error) {
- var maxVersion int
- if err := db.Raw("SELECT COALESCE(max(version), 0) FROM digests WHERE rule_id = ?", rule.ID).Row().Scan(&maxVersion); err != nil {
- return database.Digest{}, errors.Wrap(err, "finding max version")
- }
-
- digest := database.Digest{
- RuleID: rule.ID,
- UserID: rule.UserID,
- Version: maxVersion + 1,
- Notes: notes,
- }
- if err := db.Save(&digest).Error; err != nil {
- return database.Digest{}, errors.Wrap(err, "saving digest")
- }
-
- return digest, nil
-}
diff --git a/pkg/server/operations/digests_test.go b/pkg/server/operations/digests_test.go
deleted file mode 100644
index 9fab1125..00000000
--- a/pkg/server/operations/digests_test.go
+++ /dev/null
@@ -1,68 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-package operations
-
-import (
- // "fmt"
- "testing"
-
- "github.com/dnote/dnote/pkg/assert"
- "github.com/dnote/dnote/pkg/server/database"
- "github.com/dnote/dnote/pkg/server/testutils"
- "github.com/pkg/errors"
-)
-
-func TestCreateDigest(t *testing.T) {
- t.Run("no previous digest", func(t *testing.T) {
- defer testutils.ClearData()
-
- db := testutils.DB
-
- user := testutils.SetupUserData()
- rule := database.RepetitionRule{UserID: user.ID}
- testutils.MustExec(t, testutils.DB.Save(&rule), "preparing rule")
-
- result, err := CreateDigest(db, rule, nil)
- if err != nil {
- t.Fatal(errors.Wrap(err, "performing"))
- }
-
- assert.Equal(t, result.Version, 1, "Version mismatch")
- })
-
- t.Run("with previous digest", func(t *testing.T) {
- defer testutils.ClearData()
-
- db := testutils.DB
-
- user := testutils.SetupUserData()
- rule := database.RepetitionRule{UserID: user.ID}
- testutils.MustExec(t, testutils.DB.Save(&rule), "preparing rule")
-
- d := database.Digest{UserID: user.ID, RuleID: rule.ID, Version: 8}
- testutils.MustExec(t, testutils.DB.Save(&d), "preparing digest")
-
- result, err := CreateDigest(db, rule, nil)
- if err != nil {
- t.Fatal(errors.Wrap(err, "performing"))
- }
-
- assert.Equal(t, result.Version, 9, "Version mismatch")
- })
-}
diff --git a/pkg/server/presenters/digest.go b/pkg/server/presenters/digest.go
deleted file mode 100644
index 64966ab1..00000000
--- a/pkg/server/presenters/digest.go
+++ /dev/null
@@ -1,89 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-package presenters
-
-import (
- "time"
-
- "github.com/dnote/dnote/pkg/server/database"
-)
-
-// Digest is a presented digest
-type Digest struct {
- UUID string `json:"uuid"`
- Version int `json:"version"`
- RepetitionRule RepetitionRule `json:"repetition_rule"`
- Notes []DigestNote `json:"notes"`
- IsRead bool `json:"is_read"`
- CreatedAt time.Time `json:"created_at"`
- UpdatedAt time.Time `json:"updated_at"`
-}
-
-// DigestNote is a presented note inside a digest
-type DigestNote struct {
- Note
- IsReviewed bool `json:"is_reviewed"`
-}
-
-func presentDigestNote(note database.Note) DigestNote {
- ret := DigestNote{
- Note: PresentNote(note),
- IsReviewed: note.NoteReview.UUID != "",
- }
-
- return ret
-}
-
-func presentDigestNotes(notes []database.Note) []DigestNote {
- ret := []DigestNote{}
-
- for _, note := range notes {
- n := presentDigestNote(note)
- ret = append(ret, n)
- }
-
- return ret
-}
-
-// PresentDigest presents a digest
-func PresentDigest(digest database.Digest) Digest {
- ret := Digest{
- UUID: digest.UUID,
- Notes: presentDigestNotes(digest.Notes),
- Version: digest.Version,
- RepetitionRule: PresentRepetitionRule(digest.Rule),
- IsRead: len(digest.Receipts) > 0,
- CreatedAt: digest.CreatedAt,
- UpdatedAt: digest.UpdatedAt,
- }
-
- return ret
-}
-
-// PresentDigests presetns digests
-func PresentDigests(digests []database.Digest) []Digest {
- ret := []Digest{}
-
- for _, digest := range digests {
- p := PresentDigest(digest)
- ret = append(ret, p)
- }
-
- return ret
-}
diff --git a/pkg/server/presenters/digest_receipt.go b/pkg/server/presenters/digest_receipt.go
deleted file mode 100644
index 674fe018..00000000
--- a/pkg/server/presenters/digest_receipt.go
+++ /dev/null
@@ -1,53 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-package presenters
-
-import (
- "time"
-
- "github.com/dnote/dnote/pkg/server/database"
-)
-
-// DigestReceipt is a presented receipt
-type DigestReceipt struct {
- CreatedAt time.Time `json:"created_at"`
- UpdatedAt time.Time `json:"updated_at"`
-}
-
-// PresentDigestReceipt presents a receipt
-func PresentDigestReceipt(receipt database.DigestReceipt) DigestReceipt {
- ret := DigestReceipt{
- CreatedAt: receipt.CreatedAt,
- UpdatedAt: receipt.UpdatedAt,
- }
-
- return ret
-}
-
-// PresentDigestReceipts presents receipts
-func PresentDigestReceipts(receipts []database.DigestReceipt) []DigestReceipt {
- ret := []DigestReceipt{}
-
- for _, receipt := range receipts {
- r := PresentDigestReceipt(receipt)
- ret = append(ret, r)
- }
-
- return ret
-}
diff --git a/pkg/server/presenters/repetition_rule.go b/pkg/server/presenters/repetition_rule.go
deleted file mode 100644
index 9219a746..00000000
--- a/pkg/server/presenters/repetition_rule.go
+++ /dev/null
@@ -1,75 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-package presenters
-
-import (
- "time"
-
- "github.com/dnote/dnote/pkg/server/database"
-)
-
-// RepetitionRule is a presented digest rule
-type RepetitionRule struct {
- UUID string `json:"uuid"`
- Title string `json:"title"`
- Enabled bool `json:"enabled"`
- Hour int `json:"hour" gorm:"index"`
- Minute int `json:"minute" gorm:"index"`
- Frequency int64 `json:"frequency"`
- BookDomain string `json:"book_domain"`
- LastActive int64 `json:"last_active"`
- NextActive int64 `json:"next_active"`
- Books []Book `json:"books"`
- NoteCount int `json:"note_count"`
- CreatedAt time.Time `json:"created_at"`
- UpdatedAt time.Time `json:"updated_at"`
-}
-
-// PresentRepetitionRule presents a digest rule
-func PresentRepetitionRule(d database.RepetitionRule) RepetitionRule {
- ret := RepetitionRule{
- UUID: d.UUID,
- Title: d.Title,
- Enabled: d.Enabled,
- Hour: d.Hour,
- Minute: d.Minute,
- Frequency: d.Frequency,
- BookDomain: d.BookDomain,
- NoteCount: d.NoteCount,
- LastActive: d.LastActive,
- NextActive: d.NextActive,
- Books: PresentBooks(d.Books),
- CreatedAt: FormatTS(d.CreatedAt),
- UpdatedAt: FormatTS(d.UpdatedAt),
- }
-
- return ret
-}
-
-// PresentRepetitionRules presents a slice of digest rules
-func PresentRepetitionRules(ds []database.RepetitionRule) []RepetitionRule {
- ret := []RepetitionRule{}
-
- for _, d := range ds {
- p := PresentRepetitionRule(d)
- ret = append(ret, p)
- }
-
- return ret
-}
diff --git a/pkg/server/presenters/repetition_rule_test.go b/pkg/server/presenters/repetition_rule_test.go
deleted file mode 100644
index d3597688..00000000
--- a/pkg/server/presenters/repetition_rule_test.go
+++ /dev/null
@@ -1,90 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-package presenters
-
-import (
- "fmt"
- "testing"
-
- "github.com/dnote/dnote/pkg/assert"
- "github.com/dnote/dnote/pkg/server/database"
-)
-
-func TestPresentRepetitionRule(t *testing.T) {
- b1 := database.Book{UUID: "1cf8794f-4d61-4a9d-a9da-18f8db9e53cc", Label: "foo"}
- b2 := database.Book{UUID: "ede00f3b-eab1-469c-ae12-c60cebeeef17", Label: "bar"}
- d1 := database.RepetitionRule{
- UUID: "c725afb5-8bf1-4581-a0e7-0f683c15f3d0",
- Title: "test title",
- Enabled: true,
- Hour: 1,
- Minute: 2,
- LastActive: 1571293000,
- NextActive: 1571394000,
- NoteCount: 10,
- BookDomain: database.BookDomainAll,
- Books: []database.Book{b1, b2},
- }
-
- testCases := []struct {
- input database.RepetitionRule
- expected RepetitionRule
- }{
- {
- input: d1,
- expected: RepetitionRule{
- UUID: d1.UUID,
- Title: d1.Title,
- Enabled: d1.Enabled,
- Hour: d1.Hour,
- Minute: d1.Minute,
- BookDomain: d1.BookDomain,
- NoteCount: d1.NoteCount,
- LastActive: d1.LastActive,
- NextActive: d1.NextActive,
- Books: []Book{
- {
- UUID: b1.UUID,
- USN: b1.USN,
- CreatedAt: b1.CreatedAt,
- UpdatedAt: b1.UpdatedAt,
- Label: b1.Label,
- },
- {
- UUID: b2.UUID,
- USN: b2.USN,
- CreatedAt: b2.CreatedAt,
- UpdatedAt: b2.UpdatedAt,
- Label: b2.Label,
- },
- },
- CreatedAt: d1.CreatedAt,
- UpdatedAt: d1.UpdatedAt,
- },
- },
- }
-
- for idx, tc := range testCases {
- t.Run(fmt.Sprintf("test case %d", idx), func(t *testing.T) {
- result := PresentRepetitionRule(tc.input)
-
- assert.DeepEqual(t, result, tc.expected, "result mismatch")
- })
- }
-}
diff --git a/pkg/server/testutils/main.go b/pkg/server/testutils/main.go
index 99bcbb0c..62b06b81 100644
--- a/pkg/server/testutils/main.go
+++ b/pkg/server/testutils/main.go
@@ -84,21 +84,6 @@ func ClearData() {
if err := DB.Delete(&database.Session{}).Error; err != nil {
panic(errors.Wrap(err, "Failed to clear sessions"))
}
- if err := DB.Delete(&database.Digest{}).Error; err != nil {
- panic(errors.Wrap(err, "Failed to clear digests"))
- }
- if err := DB.Delete(&database.DigestNote{}).Error; err != nil {
- panic(errors.Wrap(err, "Failed to clear digests"))
- }
- if err := DB.Delete(&database.DigestReceipt{}).Error; err != nil {
- panic(errors.Wrap(err, "Failed to clear digest receipts"))
- }
- if err := DB.Delete(&database.RepetitionRule{}).Error; err != nil {
- panic(errors.Wrap(err, "Failed to clear repetition rules"))
- }
- if err := DB.Delete(&database.NoteReview{}).Error; err != nil {
- panic(errors.Wrap(err, "Failed to clear note review"))
- }
}
// SetupUserData creates and returns a new user for testing purposes
diff --git a/web/src/components/Common/EmailPreferenceForm.scss b/web/src/components/Common/EmailPreferenceForm.scss
deleted file mode 100644
index 4ee397ef..00000000
--- a/web/src/components/Common/EmailPreferenceForm.scss
+++ /dev/null
@@ -1,34 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-.heading {
- font-size: 1.7rem;
- margin-bottom: 7px;
-}
-
-.radio {
- display: inline-block;
-
- & ~ .radio {
- margin-left: 26px;
- }
-
- label {
- padding-left: 3px;
- }
-}
diff --git a/web/src/components/Common/EmailPreferenceForm.tsx b/web/src/components/Common/EmailPreferenceForm.tsx
deleted file mode 100644
index 7abebae6..00000000
--- a/web/src/components/Common/EmailPreferenceForm.tsx
+++ /dev/null
@@ -1,138 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React, { useState } from 'react';
-
-import services from 'web/libs/services';
-import { useDispatch } from '../../store';
-import { receiveEmailPreference } from '../../store/auth';
-import Button from './Button';
-
-import styles from './EmailPreferenceForm.scss';
-
-const digestWeekly = 'weekly';
-const digestNever = 'never';
-
-function getDigestFrequency(emailPreference: any): string {
- if (emailPreference.inactive_reminder) {
- return digestWeekly;
- }
-
- return digestNever;
-}
-
-interface Props {
- emailPreference: any;
- setSuccessMsg: (string) => void;
- setFailureMsg: (string) => void;
- token?: string;
- actionsClassName?: string;
-}
-
-const EmailPreferenceForm: React.FunctionComponent = ({
- emailPreference,
- token,
- setSuccessMsg,
- setFailureMsg,
- actionsClassName
-}) => {
- const freq = getDigestFrequency(emailPreference);
- const [digestFrequency, setDigestFrequency] = useState(freq);
- const [inProgress, setInProgress] = useState(false);
- const dispatch = useDispatch();
-
- function handleSubmit(e) {
- e.preventDefault();
-
- setSuccessMsg('');
- setFailureMsg('');
- setInProgress(true);
-
- services.users
- .updateEmailPreference({ inactiveReminder: true, token })
- .then(updatedPreference => {
- dispatch(receiveEmailPreference(updatedPreference));
-
- setSuccessMsg('Updated email preference');
- setInProgress(false);
- })
- .catch(err => {
- setFailureMsg(`Failed to update. Error: ${err.message}`);
- setInProgress(false);
- });
- }
-
- return (
-
-
-
-
-
- );
-};
-
-export default EmailPreferenceForm;
diff --git a/web/src/components/Digest/ClearSearchBar.scss b/web/src/components/Digest/ClearSearchBar.scss
deleted file mode 100644
index 30b03804..00000000
--- a/web/src/components/Digest/ClearSearchBar.scss
+++ /dev/null
@@ -1,43 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-@import '../App/responsive';
-@import '../App/theme';
-@import '../App/variables';
-@import '../App/font';
-@import '../App/rem';
-
-.wrapper {
- margin-top: rem(12px);
- font-weight: 600;
-}
-
-.button {
- color: $gray;
-
- &:hover {
- color: $gray;
- }
-}
-
-.text {
- @include font-size('small');
-
- margin-left: rem(4px);
- margin-top: rem(2px);
-}
diff --git a/web/src/components/Digest/ClearSearchBar.tsx b/web/src/components/Digest/ClearSearchBar.tsx
deleted file mode 100644
index 0932baae..00000000
--- a/web/src/components/Digest/ClearSearchBar.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React from 'react';
-import { Link } from 'react-router-dom';
-
-import { getDigestPath } from 'web/libs/paths';
-import { SearchParams } from './types';
-import CloseIcon from '../Icons/Close';
-import styles from './ClearSearchBar.scss';
-
-interface Props {
- params: SearchParams;
- digestUUID: string;
-}
-
-const ClearSearchBar: React.FunctionComponent = ({
- params,
- digestUUID
-}) => {
- const isActive = params.sort !== '' || params.status !== '';
-
- if (!isActive) {
- return null;
- }
-
- return (
-
-
-
-
- Clear the current filters, and sorts
-
-
-
- );
-};
-
-export default ClearSearchBar;
diff --git a/web/src/components/Digest/Digest.scss b/web/src/components/Digest/Digest.scss
deleted file mode 100644
index 7d0051e0..00000000
--- a/web/src/components/Digest/Digest.scss
+++ /dev/null
@@ -1,69 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-@import '../App/responsive';
-@import '../App/theme';
-@import '../App/variables';
-@import '../App/font';
-@import '../App/rem';
-
-.item {
- text-align: left;
-
- & ~ & {
- margin-top: rem(20px);
- }
-}
-
-.wrapper {
- margin-top: rem(12px);
-
- @include breakpoint(lg) {
- margin-top: rem(20px);
- }
-}
-
-.list {
- width: 100%;
- list-style: none;
- padding-left: 0;
- margin-bottom: 0;
- display: inline-block;
-}
-
-.action {
- color: $light-gray;
-
- &:hover {
- color: $link-hover;
- text-decoration: underline;
- }
-
- & ~ & {
- margin-left: rem(12px);
- }
-}
-
-.error-flash {
- margin-top: rem(20px);
-}
-
-.clear-search-bar {
- margin-top: rem(12px);
- font-weight: 600;
-}
diff --git a/web/src/components/Digest/Empty.scss b/web/src/components/Digest/Empty.scss
deleted file mode 100644
index 88b7abe2..00000000
--- a/web/src/components/Digest/Empty.scss
+++ /dev/null
@@ -1,29 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-@import '../App/responsive';
-@import '../App/theme';
-@import '../App/variables';
-@import '../App/font';
-@import '../App/rem';
-
-.wrapper {
- padding: rem(40px) rem(16px);
- text-align: center;
- color: $gray;
-}
diff --git a/web/src/components/Digest/Empty.tsx b/web/src/components/Digest/Empty.tsx
deleted file mode 100644
index 42f2ef49..00000000
--- a/web/src/components/Digest/Empty.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import React from 'react';
-
-import { SearchParams, Status } from './types';
-import styles from './Empty.scss';
-
-interface Props {
- params: SearchParams;
-}
-
-const Empty: React.FunctionComponent = ({ params }) => {
- if (params.status === Status.Unreviewed) {
- return (
-
- You have completed reviewing this digest.
-
- );
- }
-
- return
No results matched your filters.
;
-};
-
-export default Empty;
diff --git a/web/src/components/Digest/Header/Content.scss b/web/src/components/Digest/Header/Content.scss
deleted file mode 100644
index 62307e90..00000000
--- a/web/src/components/Digest/Header/Content.scss
+++ /dev/null
@@ -1,70 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-@import '../../App/responsive';
-@import '../../App/theme';
-@import '../../App/variables';
-@import '../../App/font';
-@import '../../App/rem';
-
-.meta {
- margin-top: rem(4px);
- @include font-size('small');
-}
-
-.sep {
- margin: 0 rem(8px);
-}
-
-.header {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- z-index: 1;
- margin-bottom: rem(20px);
-
- padding-top: rem(12px);
-
- @include breakpoint(md) {
- flex-direction: row;
- justify-content: space-between;
- align-items: flex-end;
- background-color: transparent;
- }
-
- @include breakpoint(lg) {
- padding-top: 0;
- }
-}
-
-.header-container {
- z-index: 1;
-
- &.header-sticky {
- background-color: $white;
- position: sticky;
- top: $header-height;
- box-shadow: 0 3px 5px rgba(0, 0, 0, 0.18);
-
- .header {
- padding-top: rem(12px);
- padding-bottom: rem(12px);
- margin-bottom: 0;
- }
- }
-}
diff --git a/web/src/components/Digest/Header/Content.tsx b/web/src/components/Digest/Header/Content.tsx
deleted file mode 100644
index d0a55621..00000000
--- a/web/src/components/Digest/Header/Content.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React, { Fragment } from 'react';
-
-import { pluralize } from 'web/libs/string';
-import { DigestData, DigestNoteData } from 'jslib/operations/types';
-import Time from '../../Common/Time';
-import formatTime from '../../../helpers/time/format';
-import { getDigestTitle } from '../helpers';
-import Progress from './Progress';
-import styles from './Content.scss';
-
-function formatCreatedAt(d: Date) {
- const now = new Date();
-
- const currentYear = now.getFullYear();
- const year = d.getFullYear();
-
- if (currentYear === year) {
- return formatTime(d, '%MMM %DD');
- }
-
- return formatTime(d, '%MMM %DD, %YYYY');
-}
-
-function getViewedCount(notes: DigestNoteData[]): number {
- let count = 0;
-
- for (let i = 0; i < notes.length; ++i) {
- const n = notes[i];
-
- if (n.isReviewed) {
- count++;
- }
- }
-
- return count;
-}
-
-interface Props {
- digest: DigestData;
-}
-
-const Content: React.FunctionComponent = ({ digest }) => {
- const viewedCount = getViewedCount(digest.notes);
-
- return (
-
-
-
-
-
- );
-};
-
-export default Content;
diff --git a/web/src/components/Digest/Header/Placeholder.scss b/web/src/components/Digest/Header/Placeholder.scss
deleted file mode 100644
index 542081d8..00000000
--- a/web/src/components/Digest/Header/Placeholder.scss
+++ /dev/null
@@ -1,49 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-@import '../../App/responsive';
-@import '../../App/theme';
-@import '../../App/variables';
-@import '../../App/font';
-@import '../../App/rem';
-
-.wrapper {
- position: relative;
- width: 100%;
-}
-
-.title {
- height: rem(24px);
- width: 100%;
-
- @include breakpoint(md) {
- height: rem(32px);
- width: rem(400px);
- }
-}
-
-.meta {
- width: rem(80px);
- height: rem(16px);
- margin-top: rem(12px);
-
- @include breakpoint(md) {
- height: rem(20px);
- width: rem(320px);
- }
-}
diff --git a/web/src/components/Digest/Header/Placeholder.tsx b/web/src/components/Digest/Header/Placeholder.tsx
deleted file mode 100644
index 34e0530e..00000000
--- a/web/src/components/Digest/Header/Placeholder.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React from 'react';
-import classnames from 'classnames';
-
-import styles from './Placeholder.scss';
-
-interface Props {}
-
-const HeaderPlaceholder: React.FunctionComponent = () => {
- return (
-
-
-
-
-
- );
-};
-
-export default HeaderPlaceholder;
diff --git a/web/src/components/Digest/Header/Progress.scss b/web/src/components/Digest/Header/Progress.scss
deleted file mode 100644
index b45fe13f..00000000
--- a/web/src/components/Digest/Header/Progress.scss
+++ /dev/null
@@ -1,75 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-@import '../../App/responsive';
-@import '../../App/theme';
-@import '../../App/variables';
-@import '../../App/font';
-@import '../../App/rem';
-
-.wrapper {
- margin-top: rem(4px);
-
- display: flex;
- align-items: center;
- width: 100%;
-
- @include breakpoint(md) {
- width: rem(220px);
- margin-top: rem(0);
- margin-bottom: rem(8px);
-
- display: initial;
- align-items: initial;
- width: auto;
- }
-}
-
-.bar-wrapper {
- display: flex;
- height: 8px;
- overflow: hidden;
- background-color: #c5c6c8;
- border-radius: 4px;
- width: rem(120px);
- margin-left: rem(12px);
-
- @include breakpoint(md) {
- width: rem(220px);
- margin-top: rem(4px);
- margin-left: 0;
- }
-}
-
-.bar {
- transition: width 0.5s ease-out 0s;
- width: 0%;
- background: $first;
-}
-
-.perc {
- font-style: italic;
-}
-
-.caption {
- @include font-size('small');
-
- &.caption-strong {
- font-weight: 600;
- }
-}
diff --git a/web/src/components/Digest/Header/Progress.tsx b/web/src/components/Digest/Header/Progress.tsx
deleted file mode 100644
index cc011d15..00000000
--- a/web/src/components/Digest/Header/Progress.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React from 'react';
-import classnames from 'classnames';
-
-import { pluralize } from 'web/libs/string';
-import styles from './Progress.scss';
-
-interface Props {
- total: number;
- current: number;
-}
-
-function calcPercentage(current: number, total: number): number {
- if (total === 0) {
- return 100;
- }
-
- return (current / total) * 100;
-}
-
-function getCaption(current, total): string {
- if (current === total && total !== 0) {
- return 'Review completed';
- }
-
- return `${current} of ${total} ${pluralize('note', current)} reviewed`;
-}
-
-const Progress: React.FunctionComponent = ({ total, current }) => {
- const isComplete = current === total;
- const perc = calcPercentage(current, total);
- const width = `${perc}%`;
-
- return (
-
- );
-};
-
-export default Progress;
diff --git a/web/src/components/Digest/Header/index.tsx b/web/src/components/Digest/Header/index.tsx
deleted file mode 100644
index 67d81612..00000000
--- a/web/src/components/Digest/Header/index.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React, { useState } from 'react';
-import classnames from 'classnames';
-
-import { DigestData } from 'jslib/operations/types';
-import { useEventListener } from 'web/libs/hooks';
-import { getScrollYPos } from 'web/libs/dom';
-import Placeholder from './Placeholder';
-import Content from './Content';
-import styles from './Content.scss';
-
-interface Props {
- isFetched: boolean;
- digest: DigestData;
-}
-
-const stickyThresholdY = 24;
-
-function checkSticky(y: number): boolean {
- return y > stickyThresholdY;
-}
-
-const Header: React.FunctionComponent = ({ digest, isFetched }) => {
- const [isSticky, setIsSticky] = useState(false);
-
- function handleScroll() {
- const y = getScrollYPos();
- const nextSticky = checkSticky(y);
-
- if (nextSticky) {
- setIsSticky(true);
- } else if (!nextSticky) {
- setIsSticky(false);
- }
- }
-
- useEventListener(document, 'scroll', handleScroll);
-
- return (
-
-
-
- {isFetched ? : }
-
-
-
- );
-};
-
-export default Header;
diff --git a/web/src/components/Digest/NoteItem/Header.scss b/web/src/components/Digest/NoteItem/Header.scss
deleted file mode 100644
index d66145c5..00000000
--- a/web/src/components/Digest/NoteItem/Header.scss
+++ /dev/null
@@ -1,71 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-@import '../../App/responsive';
-@import '../../App/theme';
-@import '../../App/variables';
-@import '../../App/font';
-@import '../../App/rem';
-
-.wrapper {
- position: relative;
-}
-
-.title {
- height: rem(24px);
- width: 100%;
-
- @include breakpoint(md) {
- height: rem(32px);
- width: rem(400px);
- }
-}
-
-.meta {
- width: rem(80px);
- height: rem(16px);
- margin-top: rem(12px);
-
- @include breakpoint(md) {
- height: rem(20px);
- width: rem(320px);
- }
-}
-
-.caret-collapsed {
- transform: rotate(270deg);
-}
-
-.header-action {
- align-self: stretch;
-}
-
-.book-label {
- max-width: rem(200px);
- margin-left: rem(8px);
-
- @include breakpoint(sm) {
- max-width: rem(200px);
- }
- @include breakpoint(md) {
- max-width: rem(420px);
- }
- @include breakpoint(lg) {
- max-width: rem(600px);
- }
-}
diff --git a/web/src/components/Digest/NoteItem/Header.tsx b/web/src/components/Digest/NoteItem/Header.tsx
deleted file mode 100644
index ca1d0f21..00000000
--- a/web/src/components/Digest/NoteItem/Header.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React from 'react';
-import { Link } from 'react-router-dom';
-import classnames from 'classnames';
-
-import { DigestNoteData } from 'jslib/operations/types';
-import { getHomePath } from 'web/libs/paths';
-import ReviewButton from './ReviewButton';
-import Button from '../../Common/Button';
-import CaretIcon from '../../Icons/CaretSolid';
-
-import noteStyles from '../../Common/Note/Note.scss';
-import styles from './Header.scss';
-
-interface Props {
- note: DigestNoteData;
- setCollapsed: (boolean) => void;
- onSetReviewed: (string, boolean) => Promise;
- setErrMessage: (string) => void;
- collapsed: boolean;
-}
-
-const Header: React.FunctionComponent = ({
- note,
- collapsed,
- setCollapsed,
- onSetReviewed,
- setErrMessage
-}) => {
- let fill;
- if (collapsed) {
- fill = '#8c8c8c';
- } else {
- fill = '#000000';
- }
-
- return (
-
-
-
-
-
-
- {note.book.label}
-
-
-
-
-
-
-
-
- );
-};
-
-export default Header;
diff --git a/web/src/components/Digest/NoteItem/ReviewButton.scss b/web/src/components/Digest/NoteItem/ReviewButton.scss
deleted file mode 100644
index bef7c873..00000000
--- a/web/src/components/Digest/NoteItem/ReviewButton.scss
+++ /dev/null
@@ -1,39 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-@import '../../App/responsive';
-@import '../../App/theme';
-@import '../../App/variables';
-@import '../../App/font';
-@import '../../App/rem';
-
-.wrapper {
- border: 1px solid $border-color;
- margin-bottom: 0;
- display: flex;
- // align-items: center;
- padding: rem(4px) rem(8px);
- border-radius: rem(4px);
- margin-left: rem(12px);
-}
-
-.text {
- @include font-size('small');
- margin-left: rem(4px);
- user-select: none;
-}
diff --git a/web/src/components/Digest/NoteItem/ReviewButton.tsx b/web/src/components/Digest/NoteItem/ReviewButton.tsx
deleted file mode 100644
index ec97eaaf..00000000
--- a/web/src/components/Digest/NoteItem/ReviewButton.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React, { useState } from 'react';
-import classnames from 'classnames';
-
-import digestStyles from '../Digest.scss';
-import styles from './ReviewButton.scss';
-
-interface Props {
- noteUUID: string;
- isReviewed: boolean;
- setCollapsed: (boolean) => void;
- onSetReviewed: (string, boolean) => Promise;
- setErrMessage: (string) => void;
-}
-
-const ReviewButton: React.FunctionComponent = ({
- noteUUID,
- isReviewed,
- setCollapsed,
- onSetReviewed,
- setErrMessage
-}) => {
- const [checked, setChecked] = useState(isReviewed);
-
- return (
-
- );
-};
-
-export default ReviewButton;
diff --git a/web/src/components/Digest/NoteItem/index.tsx b/web/src/components/Digest/NoteItem/index.tsx
deleted file mode 100644
index ecf6e3ab..00000000
--- a/web/src/components/Digest/NoteItem/index.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React, { Fragment, useState } from 'react';
-import { Link } from 'react-router-dom';
-
-import { DigestNoteData } from 'jslib/operations/types';
-import { getNotePath } from 'web/libs/paths';
-import Note from '../../Common/Note';
-import Flash from '../../Common/Flash';
-import NoteItemHeader from './Header';
-import styles from '../Digest.scss';
-
-interface Props {
- note: DigestNoteData;
- onSetReviewed: (string, boolean) => Promise;
-}
-
-const NoteItem: React.FunctionComponent = ({ note, onSetReviewed }) => {
- const [collapsed, setCollapsed] = useState(note.isReviewed);
- const [errorMessage, setErrMessage] = useState('');
-
- return (
-
- );
-};
-
-export default NoteItem;
diff --git a/web/src/components/Digest/NoteList.tsx b/web/src/components/Digest/NoteList.tsx
deleted file mode 100644
index 790197f1..00000000
--- a/web/src/components/Digest/NoteList.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React from 'react';
-import Helmet from 'react-helmet';
-
-import { DigestData } from 'jslib/operations/types';
-import { DigestNoteData } from 'jslib/operations/types';
-import { getDigestTitle } from './helpers';
-import { useDispatch } from '../../store';
-import { setDigestNoteReviewed } from '../../store/digest';
-import Placeholder from '../Common/Note/Placeholder';
-import NoteItem from './NoteItem';
-import Empty from './Empty';
-import { SearchParams } from './types';
-import styles from './Digest.scss';
-
-interface Props {
- notes: DigestNoteData[];
- digest: DigestData;
- params: SearchParams;
- isFetched: boolean;
- isFetching: boolean;
-}
-
-const NoteList: React.FunctionComponent = ({
- isFetched,
- isFetching,
- params,
- notes,
- digest
-}) => {
- const dispatch = useDispatch();
-
- function handleSetReviewed(noteUUID: string, isReviewed: boolean) {
- return dispatch(
- setDigestNoteReviewed({ digestUUID: digest.uuid, noteUUID, isReviewed })
- );
- }
-
- if (isFetching) {
- return (
-
- );
-};
-
-export default NoteList;
diff --git a/web/src/components/Digest/Toolbar/SortMenu.tsx b/web/src/components/Digest/Toolbar/SortMenu.tsx
deleted file mode 100644
index 46b8de71..00000000
--- a/web/src/components/Digest/Toolbar/SortMenu.tsx
+++ /dev/null
@@ -1,121 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React, { useState, useRef } from 'react';
-import { Link, withRouter, RouteComponentProps } from 'react-router-dom';
-import classnames from 'classnames';
-
-import { parseSearchString } from 'jslib/helpers/url';
-import { getDigestPath } from 'web/libs/paths';
-import { blacklist } from 'jslib/helpers/obj';
-import SelectMenu from '../../Common/PageToolbar/SelectMenu';
-import selectMenuStyles from '../../Common/PageToolbar/SelectMenu.scss';
-import { Sort } from '../types';
-import styles from './Toolbar.scss';
-
-interface Props extends RouteComponentProps {
- digestUUID: string;
- sort: Sort;
- disabled?: boolean;
-}
-
-const SortMenu: React.FunctionComponent = ({
- digestUUID,
- sort,
- disabled,
- location
-}) => {
- const [isOpen, setIsOpen] = useState(false);
- const optRefs = [useRef(null), useRef(null)];
- const searchObj = parseSearchString(location.search);
-
- const options = [
- {
- name: 'newest',
- value: (
- {
- setIsOpen(false);
- }}
- ref={optRefs[0]}
- tabIndex={-1}
- >
- Newest
-
- )
- },
- {
- name: 'oldest',
- value: (
- {
- setIsOpen(false);
- }}
- ref={optRefs[1]}
- tabIndex={-1}
- >
- Oldest
-
- )
- }
- ];
-
- const isActive = sort === Sort.Oldest;
-
- let defaultCurrentOptionIdx: number;
- let sortText: string;
- if (sort === Sort.Oldest) {
- defaultCurrentOptionIdx = 1;
- sortText = 'Oldest';
- } else {
- defaultCurrentOptionIdx = 0;
- sortText = 'Newest';
- }
-
- return (
-
- );
-};
-
-export default withRouter(SortMenu);
diff --git a/web/src/components/Digest/Toolbar/StatusMenu.tsx b/web/src/components/Digest/Toolbar/StatusMenu.tsx
deleted file mode 100644
index 2cc6f830..00000000
--- a/web/src/components/Digest/Toolbar/StatusMenu.tsx
+++ /dev/null
@@ -1,145 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React, { useState, useRef } from 'react';
-import { Link, withRouter, RouteComponentProps } from 'react-router-dom';
-import classnames from 'classnames';
-
-import { getDigestPath } from 'web/libs/paths';
-import { parseSearchString } from 'jslib/helpers/url';
-import { blacklist } from 'jslib/helpers/obj';
-import SelectMenu from '../../Common/PageToolbar/SelectMenu';
-import selectMenuStyles from '../../Common/PageToolbar/SelectMenu.scss';
-import { Status } from '../types';
-import styles from './Toolbar.scss';
-
-interface Props extends RouteComponentProps {
- digestUUID: string;
- status: Status;
- disabled?: boolean;
-}
-
-const StatusMenu: React.FunctionComponent = ({
- digestUUID,
- status,
- disabled,
- location
-}) => {
- const [isOpen, setIsOpen] = useState(false);
- const optRefs = [useRef(null), useRef(null), useRef(null)];
- const searchObj = parseSearchString(location.search);
-
- const options = [
- {
- name: 'unreviewed',
- value: (
- {
- setIsOpen(false);
- }}
- ref={optRefs[0]}
- tabIndex={-1}
- >
- Unreviewed
-
- )
- },
- {
- name: 'reviewed',
- value: (
- {
- setIsOpen(false);
- }}
- ref={optRefs[1]}
- tabIndex={-1}
- >
- Reviewed
-
- )
- },
- {
- name: 'all',
- value: (
- {
- setIsOpen(false);
- }}
- ref={optRefs[2]}
- tabIndex={-1}
- >
- All
-
- )
- }
- ];
-
- const isActive = status === Status.Reviewed || status === Status.All;
-
- let defaultCurrentOptionIdx: number;
- let statusText: string;
- if (status === Status.Reviewed) {
- defaultCurrentOptionIdx = 1;
- statusText = 'Reviewed';
- } else if (status === Status.All) {
- defaultCurrentOptionIdx = 2;
- statusText = 'All';
- } else {
- defaultCurrentOptionIdx = 0;
- statusText = 'Unreviewed';
- }
-
- return (
-
- );
-};
-
-export default withRouter(StatusMenu);
diff --git a/web/src/components/Digest/Toolbar/Toolbar.scss b/web/src/components/Digest/Toolbar/Toolbar.scss
deleted file mode 100644
index ad43cde5..00000000
--- a/web/src/components/Digest/Toolbar/Toolbar.scss
+++ /dev/null
@@ -1,47 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-@import '../../App/responsive';
-@import '../../App/theme';
-@import '../../App/variables';
-@import '../../App/font';
-@import '../../App/rem';
-
-.wrapper {
- display: flex;
- justify-content: flex-start;
- align-items: center;
- margin-top: rem(12px);
-
- @include breakpoint(md) {
- justify-content: flex-end;
- }
-
- @include breakpoint(lg) {
- padding: 0 rem(16px);
- margin-top: 0;
- }
-}
-
-.active-menu-trigger {
- font-weight: 600;
-}
-
-.menu-trigger ~ .menu-trigger {
- margin-left: rem(12px);
-}
diff --git a/web/src/components/Digest/Toolbar/index.tsx b/web/src/components/Digest/Toolbar/index.tsx
deleted file mode 100644
index 4647972a..00000000
--- a/web/src/components/Digest/Toolbar/index.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React from 'react';
-
-import PageToolbar from '../../Common/PageToolbar';
-import SortMenu from './SortMenu';
-import StatusMenu from './StatusMenu';
-import { Sort, Status } from '../types';
-import styles from './Toolbar.scss';
-
-interface Props {
- digestUUID: string;
- sort: Sort;
- status: Status;
- isFetched: boolean;
-}
-
-const Toolbar: React.FunctionComponent = ({
- digestUUID,
- sort,
- status,
- isFetched
-}) => {
- return (
-
-
-
-
-
- );
-};
-
-export default Toolbar;
diff --git a/web/src/components/Digest/helpers.ts b/web/src/components/Digest/helpers.ts
deleted file mode 100644
index 7262b2e8..00000000
--- a/web/src/components/Digest/helpers.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import { DigestData } from 'jslib/operations/types';
-
-// getDigestTitle returns a title for the digest
-export function getDigestTitle(digest: DigestData) {
- return `${digest.repetitionRule.title} #${digest.version}`;
-}
diff --git a/web/src/components/Digest/index.tsx b/web/src/components/Digest/index.tsx
deleted file mode 100644
index 9027ded9..00000000
--- a/web/src/components/Digest/index.tsx
+++ /dev/null
@@ -1,169 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React, { useEffect } from 'react';
-import { withRouter, RouteComponentProps } from 'react-router-dom';
-import Helmet from 'react-helmet';
-import { Location } from 'history';
-
-import { DigestNoteData } from 'jslib/operations/types';
-import { parseSearchString } from 'jslib/helpers/url';
-import { usePrevious } from 'web/libs/hooks';
-import { Sort, Status, SearchParams } from './types';
-import { getDigest } from '../../store/digest';
-import { useDispatch, useSelector } from '../../store';
-import Header from './Header';
-import Toolbar from './Toolbar';
-import NoteList from './NoteList';
-import Flash from '../Common/Flash';
-import ClearSearchBar from './ClearSearchBar';
-import styles from './Digest.scss';
-
-function useFetchData(digestUUID: string) {
- const dispatch = useDispatch();
-
- const { digest } = useSelector(state => {
- return {
- digest: state.digest
- };
- });
-
- const prevDigestUUID = usePrevious(digestUUID);
-
- useEffect(() => {
- if (!digest.isFetched || (digestUUID && prevDigestUUID !== digestUUID)) {
- dispatch(getDigest(digestUUID));
- }
- }, [dispatch, digestUUID, digest.isFetched, prevDigestUUID]);
-}
-
-interface Match {
- digestUUID: string;
-}
-
-interface Props extends RouteComponentProps {}
-
-function getNotes(notes: DigestNoteData[], p: SearchParams): DigestNoteData[] {
- const filtered = notes.filter(note => {
- if (p.status === Status.Reviewed) {
- return note.isReviewed;
- }
- if (p.status === Status.Unreviewed) {
- return !note.isReviewed;
- }
-
- return true;
- });
-
- return filtered.concat().sort((i, j) => {
- if (p.sort === Sort.Oldest) {
- return new Date(i.createdAt).getTime() - new Date(j.createdAt).getTime();
- }
-
- return new Date(j.createdAt).getTime() - new Date(i.createdAt).getTime();
- });
-}
-
-const statusMap = {
- [Status.All]: Status.All,
- [Status.Reviewed]: Status.Reviewed,
- [Status.Unreviewed]: Status.Unreviewed
-};
-
-const sortMap = {
- [Sort.Newest]: Sort.Newest,
- [Sort.Oldest]: Sort.Oldest
-};
-
-function parseSearchParams(location: Location): SearchParams {
- const searchObj = parseSearchString(location.search);
-
- const status = statusMap[searchObj.status] || Status.Unreviewed;
- const sort = sortMap[searchObj.sort] || Sort.Newest;
-
- return {
- sort,
- status,
- books: []
- };
-}
-
-const Digest: React.FunctionComponent = ({ location, match }) => {
- const { digestUUID } = match.params;
-
- useFetchData(digestUUID);
-
- const { digest } = useSelector(state => {
- return {
- digest: state.digest
- };
- });
-
- const params = parseSearchParams(location);
- const notes = getNotes(digest.data.notes, params);
-
- return (
-
-
- Digest
-
-
-
-
-
-
- Spaced repetition is deprecated and will be removed in the next major
- release.
-
-
-
-
- );
-};
-
-export default withRouter(Digest);
diff --git a/web/src/components/Digest/types.ts b/web/src/components/Digest/types.ts
deleted file mode 100644
index 576c06a2..00000000
--- a/web/src/components/Digest/types.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-// Sort is a set of possible values for sort query parameters
-export enum Sort {
- Newest = '',
- Oldest = 'created-asc'
-}
-
-// Status is a set of possible values for status query parameters
-export enum Status {
- Unreviewed = '',
- Reviewed = 'reviewed',
- All = 'all'
-}
-
-export interface SearchParams {
- sort: Sort;
- status: Status;
- books: string[];
-}
diff --git a/web/src/components/Digests/Digests.scss b/web/src/components/Digests/Digests.scss
deleted file mode 100644
index ab1a9bb9..00000000
--- a/web/src/components/Digests/Digests.scss
+++ /dev/null
@@ -1,31 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-@import '../App/responsive';
-@import '../App/theme';
-@import '../App/variables';
-@import '../App/font';
-@import '../App/rem';
-
-.flash {
- margin-top: rem(20px);
-
- @include breakpoint(lg) {
- margin-top: 0;
- }
-}
diff --git a/web/src/components/Digests/Empty.scss b/web/src/components/Digests/Empty.scss
deleted file mode 100644
index 93845513..00000000
--- a/web/src/components/Digests/Empty.scss
+++ /dev/null
@@ -1,41 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-@import '../App/responsive';
-@import '../App/theme';
-@import '../App/variables';
-@import '../App/font';
-@import '../App/rem';
-
-.wrapper {
- padding: rem(40px) rem(16px);
- text-align: center;
- color: $gray;
-}
-
-.support {
- margin-top: rem(20px);
-}
-
-.md-support {
- display: none;
-
- @include breakpoint(md) {
- display: block;
- }
-}
diff --git a/web/src/components/Digests/Empty.tsx b/web/src/components/Digests/Empty.tsx
deleted file mode 100644
index f83c57fa..00000000
--- a/web/src/components/Digests/Empty.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import React from 'react';
-import { Link } from 'react-router-dom';
-
-import { getRepetitionsPath } from 'web/libs/paths';
-import styles from './Empty.scss';
-
-interface Props {}
-
-const Empty: React.FunctionComponent = () => {
- return (
-
-
No digests were found.
-
-
- You could create repetition rules{' '}
- first.
-
-
-
- Digests are automatically created based on your repetition rules.
-
-
- );
-};
-
-export default Empty;
diff --git a/web/src/components/Digests/Item.scss b/web/src/components/Digests/Item.scss
deleted file mode 100644
index f890ee3f..00000000
--- a/web/src/components/Digests/Item.scss
+++ /dev/null
@@ -1,67 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-@import '../App/responsive';
-@import '../App/theme';
-@import '../App/rem';
-@import '../App/font';
-
-.wrapper {
- background: white;
- position: relative;
- border-bottom: 1px solid $border-color;
-
- &:first-child {
- border-top-left-radius: rem(4px);
- border-top-right-radius: rem(4px);
- }
- &:last-child {
- border-bottom-left-radius: rem(4px);
- border-bottom-right-radius: rem(4px);
- }
-
- &.unread {
- .title {
- font-weight: 600;
- }
- }
- &.read {
- .title {
- color: $gray;
- }
- }
-}
-
-.link {
- color: $black;
- display: flex;
- justify-content: space-between;
- padding: rem(12px) rem(16px);
- border: 2px solid transparent;
-
- &:hover {
- text-decoration: none;
- background: $light-blue;
- color: inherit;
- }
-}
-
-.ts {
- color: $gray;
- @include font-size('small');
-}
diff --git a/web/src/components/Digests/Item.tsx b/web/src/components/Digests/Item.tsx
deleted file mode 100644
index dec76c0b..00000000
--- a/web/src/components/Digests/Item.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React from 'react';
-import { Link } from 'react-router-dom';
-import classnames from 'classnames';
-
-import { DigestData } from 'jslib/operations/types';
-import { getDigestPath } from 'web/libs/paths';
-import Time from '../Common/Time';
-import { timeAgo } from '../../helpers/time';
-import styles from './Item.scss';
-
-interface Props {
- item: DigestData;
-}
-
-const Item: React.FunctionComponent = ({ item }) => {
- const createdAt = new Date(item.createdAt);
-
- return (
-
- );
-};
-
-export default Item;
diff --git a/web/src/components/Digests/List.scss b/web/src/components/Digests/List.scss
deleted file mode 100644
index 5bafcd83..00000000
--- a/web/src/components/Digests/List.scss
+++ /dev/null
@@ -1,31 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-@import '../App/responsive';
-@import '../App/theme';
-@import '../App/rem';
-@import '../App/font';
-
-.wrapper {
- box-shadow: 0 0 8px rgba(0, 0, 0, 0.14);
- border-radius: rem(4px);
-
- @include breakpoint(md) {
- margin-top: rem(16px);
- }
-}
diff --git a/web/src/components/Digests/List.tsx b/web/src/components/Digests/List.tsx
deleted file mode 100644
index 99426a24..00000000
--- a/web/src/components/Digests/List.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React from 'react';
-import classnames from 'classnames';
-
-import { DigestData } from 'jslib/operations/types';
-import { getRange } from 'jslib/helpers/arr';
-import Item from './Item';
-import Empty from './Empty';
-import Placeholder from './Placeholder';
-import styles from './List.scss';
-
-interface Props {
- isFetched: boolean;
- isFetching: boolean;
- items: DigestData[];
-}
-
-const List: React.FunctionComponent = ({
- items,
- isFetched,
- isFetching
-}) => {
- if (isFetching) {
- return (
-
- );
-};
-
-export default List;
diff --git a/web/src/components/Digests/Placeholder.scss b/web/src/components/Digests/Placeholder.scss
deleted file mode 100644
index 3dd1e0f1..00000000
--- a/web/src/components/Digests/Placeholder.scss
+++ /dev/null
@@ -1,33 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-@import '../App/responsive';
-@import '../App/theme';
-@import '../App/rem';
-
-.wrapper {
- padding: rem(12px) rem(16px);
-}
-
-.title {
- height: rem(20px);
-
- @include breakpoint(md) {
- width: 152px;
- }
-}
diff --git a/web/src/components/Digests/Placeholder.tsx b/web/src/components/Digests/Placeholder.tsx
deleted file mode 100644
index 84009207..00000000
--- a/web/src/components/Digests/Placeholder.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React from 'react';
-import classnames from 'classnames';
-
-import itemStyles from './Item.scss';
-import styles from './Placeholder.scss';
-
-export default () => {
- return (
-
-
-
- );
-};
diff --git a/web/src/components/Digests/Toolbar/StatusMenu.tsx b/web/src/components/Digests/Toolbar/StatusMenu.tsx
deleted file mode 100644
index 6bfc74c2..00000000
--- a/web/src/components/Digests/Toolbar/StatusMenu.tsx
+++ /dev/null
@@ -1,123 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React, { useState, useRef } from 'react';
-import { Link } from 'react-router-dom';
-
-import { getDigestsPath } from 'web/libs/paths';
-import SelectMenu from '../../Common/PageToolbar/SelectMenu';
-import selectMenuStyles from '../../Common/PageToolbar/SelectMenu.scss';
-import { Status } from '../types';
-import styles from './Toolbar.scss';
-
-interface Props {
- status: Status;
- disabled?: boolean;
-}
-
-const StatusMenu: React.FunctionComponent = ({ status, disabled }) => {
- const [isOpen, setIsOpen] = useState(false);
- const optRefs = [useRef(null), useRef(null), useRef(null)];
-
- const options = [
- {
- name: 'all',
- value: (
- {
- setIsOpen(false);
- }}
- ref={optRefs[0]}
- tabIndex={-1}
- >
- All
-
- )
- },
- {
- name: 'unread',
- value: (
- {
- setIsOpen(false);
- }}
- ref={optRefs[1]}
- tabIndex={-1}
- >
- Unread
-
- )
- },
- {
- name: 'read',
- value: (
- {
- setIsOpen(false);
- }}
- ref={optRefs[2]}
- tabIndex={-1}
- >
- Read
-
- )
- }
- ];
-
- let defaultCurrentOptionIdx: number;
- let triggerText: string;
- if (status === Status.Read) {
- defaultCurrentOptionIdx = 2;
- triggerText = 'Read';
- } else if (status === Status.Unread) {
- defaultCurrentOptionIdx = 1;
- triggerText = 'Unread';
- } else {
- defaultCurrentOptionIdx = 0;
- triggerText = 'All';
- }
-
- return (
-
- );
-};
-
-export default StatusMenu;
diff --git a/web/src/components/Digests/Toolbar/Toolbar.scss b/web/src/components/Digests/Toolbar/Toolbar.scss
deleted file mode 100644
index b94afc9a..00000000
--- a/web/src/components/Digests/Toolbar/Toolbar.scss
+++ /dev/null
@@ -1,33 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-@import '../../App/responsive';
-@import '../../App/theme';
-@import '../../App/rem';
-@import '../../App/font';
-
-.toolbar {
- display: flex;
- justify-content: space-between;
-}
-
-.select-menu-wrapper {
- display: flex;
- align-items: center;
- margin-left: rem(8px);
-}
diff --git a/web/src/components/Digests/Toolbar/index.tsx b/web/src/components/Digests/Toolbar/index.tsx
deleted file mode 100644
index a81bb2d7..00000000
--- a/web/src/components/Digests/Toolbar/index.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React from 'react';
-
-import { getDigestsPath } from 'web/libs/paths';
-import PageToolbar from '../../Common/PageToolbar';
-import Paginator from '../../Common/PageToolbar/Paginator';
-import StatusMenu from './StatusMenu';
-import { Status } from '../types';
-import styles from './Toolbar.scss';
-
-interface Props {
- total: number;
- page: number;
- status: Status;
-}
-
-const PER_PAGE = 30;
-
-const Toolbar: React.FunctionComponent = ({ total, page, status }) => {
- return (
-
-
-
- {
- return getDigestsPath({ page: p });
- }}
- />
-
- );
-};
-
-export default Toolbar;
diff --git a/web/src/components/Digests/index.tsx b/web/src/components/Digests/index.tsx
deleted file mode 100644
index 5487f46a..00000000
--- a/web/src/components/Digests/index.tsx
+++ /dev/null
@@ -1,104 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React, { useEffect } from 'react';
-import { RouteComponentProps } from 'react-router-dom';
-import Helmet from 'react-helmet';
-
-import { usePrevious } from 'web/libs/hooks';
-import { parseSearchString } from 'jslib/helpers/url';
-import { useDispatch, useSelector } from '../../store';
-import { getDigests } from '../../store/digests';
-import { Status } from './types';
-import Flash from '../Common/Flash';
-import List from './List';
-import Toolbar from './Toolbar';
-import styles from './Digests.scss';
-
-function useFetchDigests(params: { page: number; status: Status }) {
- const dispatch = useDispatch();
-
- const prevParams = usePrevious(params);
-
- useEffect(() => {
- if (
- !prevParams ||
- prevParams.page !== params.page ||
- prevParams.status !== params.status
- ) {
- dispatch(getDigests(params));
- }
- }, [dispatch, params, prevParams]);
-}
-
-interface Props extends RouteComponentProps {}
-
-const Digests: React.FunctionComponent = ({ location }) => {
- const { digests } = useSelector(state => {
- return {
- digests: state.digests,
- user: state.auth.user.data
- };
- });
- const { page, status } = parseSearchString(location.search);
- useFetchDigests({
- page: page || 1,
- status
- });
-
- return (
-
-
- Digests
-
-
-
-
-
Digests
-
-
-
-
-
- Error getting digests: {digests.errorMessage}
-
-
-
- Spaced repetition is deprecated and will be removed in the next major
- release.
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default Digests;
diff --git a/web/src/components/Digests/types.tsx b/web/src/components/Digests/types.tsx
deleted file mode 100644
index e2780e25..00000000
--- a/web/src/components/Digests/types.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-export enum Status {
- All = '',
- Read = 'read',
- Unread = 'unread'
-}
diff --git a/web/src/components/Preferences/Repetitions/Content.tsx b/web/src/components/Preferences/Repetitions/Content.tsx
deleted file mode 100644
index 423f30a1..00000000
--- a/web/src/components/Preferences/Repetitions/Content.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React, { useState } from 'react';
-
-import { RepetitionRuleData } from 'jslib/operations/types';
-import services from 'web/libs/services';
-import Button from '../../Common/Button';
-import styles from './EmailPreferenceRepetition.scss';
-
-interface Props {
- data: RepetitionRuleData;
- setSuccessMsg: (string) => void;
- setFailureMsg: (string) => void;
- token?: string;
-}
-
-const Content: React.FunctionComponent = ({
- data,
- token,
- setSuccessMsg,
- setFailureMsg
-}) => {
- const [inProgress, setInProgress] = useState(false);
- const [isEnabled, setIsEnabled] = useState(data.enabled);
-
- function handleSubmit(e) {
- e.preventDefault();
-
- setSuccessMsg('');
- setFailureMsg('');
- setInProgress(true);
-
- services.repetitionRules
- .update(data.uuid, { enabled: isEnabled }, { token })
- .then(() => {
- setSuccessMsg('Updated the repetition.');
- setInProgress(false);
- })
- .catch(err => {
- setFailureMsg(`Failed to update. Error: ${err.message}`);
- setInProgress(false);
- });
- }
-
- return (
-
-
Toggle the repetition for "{data.title}"
-
-
-
- );
-};
-
-export default Content;
diff --git a/web/src/components/Preferences/Repetitions/EmailPreferenceRepetition.scss b/web/src/components/Preferences/Repetitions/EmailPreferenceRepetition.scss
deleted file mode 100644
index fce2807f..00000000
--- a/web/src/components/Preferences/Repetitions/EmailPreferenceRepetition.scss
+++ /dev/null
@@ -1,68 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-@import '../../App/responsive';
-@import '../../App/theme';
-@import '../../App/rem';
-@import '../../App/font';
-
-.wrapper {
- text-align: center;
- height: 100vh;
- padding: rem(52px) 0;
- background: $lighter-gray;
-}
-
-.heading {
- @include font-size('2x-large');
- color: $black;
- font-weight: 300;
- margin-top: rem(16px);
-}
-
-.body {
- text-align: left;
- padding: rem(20px) rem(28px);
- margin-top: rem(20px);
- max-width: rem(700px);
- margin-left: auto;
- margin-right: auto;
- background-color: #fff;
-}
-
-.footer {
- @include font-size('small');
- text-align: center;
- margin-top: rem(20px);
-
- a {
- color: $gray;
- }
-}
-
-.radio {
- display: inline-block;
-
- & ~ .radio {
- margin-left: 26px;
- }
-
- label {
- padding-left: 3px;
- }
-}
diff --git a/web/src/components/Preferences/Repetitions/index.tsx b/web/src/components/Preferences/Repetitions/index.tsx
deleted file mode 100644
index 01080cfa..00000000
--- a/web/src/components/Preferences/Repetitions/index.tsx
+++ /dev/null
@@ -1,125 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import classnames from 'classnames';
-import { parseSearchString } from 'jslib/helpers/url';
-import React, { useEffect, useState } from 'react';
-import Helmet from 'react-helmet';
-import { Link, RouteComponentProps, withRouter } from 'react-router-dom';
-import { getLoginPath } from 'web/libs/paths';
-import services from 'web/libs/services';
-import Flash from '../../Common/Flash';
-import Logo from '../../Icons/Logo';
-import Content from './Content';
-import styles from './EmailPreferenceRepetition.scss';
-
-interface Match {
- repetitionUUID: string;
-}
-interface Props extends RouteComponentProps {}
-
-const EmailPreferenceRepetition: React.FunctionComponent = ({
- location,
- match
-}) => {
- const [data, setData] = useState(null);
- const [isFetching, setIsFetching] = useState(false);
- const [successMsg, setSuccessMsg] = useState('');
- const [failureMsg, setFailureMsg] = useState('');
-
- const { token } = parseSearchString(location.search);
- const { repetitionUUID } = match.params;
-
- useEffect(() => {
- if (data !== null) {
- return;
- }
-
- setIsFetching(true);
-
- services.repetitionRules
- .fetch(repetitionUUID, { token })
- .then(repetition => {
- setData(repetition);
- setIsFetching(false);
- })
- .catch(err => {
- if (err.response.status === 401) {
- setFailureMsg('Your email token has expired or is not valid.');
- } else {
- setFailureMsg(err.message);
- }
-
- setIsFetching(false);
- });
- }, [data, repetitionUUID, setData, setFailureMsg, setIsFetching, token]);
-
- const isFetched = data !== null;
-
- return (
-
- );
-};
-
-export default withRouter(EmailPreferenceRepetition);
diff --git a/web/src/components/Repetition/Content.tsx b/web/src/components/Repetition/Content.tsx
deleted file mode 100644
index 6ff1857d..00000000
--- a/web/src/components/Repetition/Content.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React, { Fragment, useState, useEffect } from 'react';
-
-import classnames from 'classnames';
-import {
- getNewRepetitionPath,
- getSettingsPath,
- SettingSections,
- repetitionsPathDef
-} from 'web/libs/paths';
-import { Link } from 'react-router-dom';
-import { useDispatch, useSelector } from '../../store';
-import { getRepetitionRules } from '../../store/repetitionRules';
-import RepetitionList from './RepetitionList';
-import DeleteRepetitionRuleModal from './DeleteRepetitionRuleModal';
-import Flash from '../Common/Flash';
-import { setMessage } from '../../store/ui';
-import styles from './Repetition.scss';
-
-const Content: React.FunctionComponent = () => {
- const dispatch = useDispatch();
- useEffect(() => {
- dispatch(getRepetitionRules());
- }, [dispatch]);
-
- const { repetitionRules, user } = useSelector(state => {
- return {
- repetitionRules: state.repetitionRules,
- user: state.auth.user.data
- };
- });
-
- const [ruleUUIDToDelete, setRuleUUIDToDelete] = useState('');
-
- return (
-
-
-
-
Repetition
-
- {!user.pro ? (
-
- ) : (
-
- New
-
- )}
-
-
-
-
-
- Spaced repetition is deprecated and will be removed in the next major
- release.
-
-
-
- Please verify your email address in order to receive digests.{' '}
-
- Go to settings.
-
-
-
-
-
-
-
-
- {
- setRuleUUIDToDelete('');
- }}
- setSuccessMessage={message => {
- dispatch(
- setMessage({
- message,
- kind: 'info',
- path: repetitionsPathDef
- })
- );
- }}
- />
-
- );
-};
-
-export default Content;
diff --git a/web/src/components/Repetition/DeleteRepetitionRuleModal.scss b/web/src/components/Repetition/DeleteRepetitionRuleModal.scss
deleted file mode 100644
index 59e75dcb..00000000
--- a/web/src/components/Repetition/DeleteRepetitionRuleModal.scss
+++ /dev/null
@@ -1,46 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-@import '../App/rem';
-@import '../App/font';
-@import '../App/theme';
-
-.wrapper {
- position: relative;
- border-radius: 4px;
- box-shadow: 0 0 8px rgba(0, 0, 0, 0.14);
-}
-
-.title {
- @include font-size('medium');
-}
-
-.label {
- width: 100%;
-}
-.input {
- width: 100%;
-}
-.actions {
- display: flex;
- justify-content: flex-end;
- margin-top: rem(8px);
-}
-.rule-label {
- font-weight: 600;
-}
diff --git a/web/src/components/Repetition/DeleteRepetitionRuleModal.tsx b/web/src/components/Repetition/DeleteRepetitionRuleModal.tsx
deleted file mode 100644
index 7f388e0a..00000000
--- a/web/src/components/Repetition/DeleteRepetitionRuleModal.tsx
+++ /dev/null
@@ -1,174 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import { RepetitionRuleData } from 'jslib/operations/types';
-import React, { useEffect, useState } from 'react';
-import { RouteComponentProps, withRouter } from 'react-router-dom';
-import services from 'web/libs/services';
-import { useDispatch, useSelector } from '../../store';
-import { removeRepetitionRule } from '../../store/repetitionRules';
-import Button from '../Common/Button';
-import Flash from '../Common/Flash';
-import Modal, { Body, Header } from '../Common/Modal';
-import styles from './DeleteRepetitionRuleModal.scss';
-
-function getRepetitionRuleByUUID(
- repetitionRules,
- uuid
-): RepetitionRuleData | null {
- for (let i = 0; i < repetitionRules.length; ++i) {
- const r = repetitionRules[i];
-
- if (r.uuid === uuid) {
- return r;
- }
- }
-
- return null;
-}
-
-interface Props extends RouteComponentProps {
- isOpen: boolean;
- onDismiss: () => void;
- setSuccessMessage: (string) => void;
- repetitionRuleUUID: string;
-}
-
-const DeleteRepetitionModal: React.FunctionComponent = ({
- isOpen,
- onDismiss,
- setSuccessMessage,
- repetitionRuleUUID
-}) => {
- const [inProgress, setInProgress] = useState(false);
- const [errMessage, setErrMessage] = useState('');
- const dispatch = useDispatch();
-
- const { repetitionRules } = useSelector(state => {
- return {
- repetitionRules: state.repetitionRules
- };
- });
-
- const rule = getRepetitionRuleByUUID(
- repetitionRules.data,
- repetitionRuleUUID
- );
-
- const labelId = 'delete-rule-modal-label';
- const descId = 'delete-rule-modal-desc';
-
- useEffect(() => {
- if (!isOpen) {
- setErrMessage('');
- }
- }, [isOpen]);
-
- if (rule === null) {
- return null;
- }
-
- return (
-
-
-
- {
- setErrMessage('');
- }}
- hasBorder={false}
- when={Boolean(errMessage)}
- noMargin
- >
- {errMessage}
-
-
-
-
- This action will permanently remove the following repetition rule:{' '}
-
- {rule.title}
-
-
-
-
-
-
- );
-};
-
-export default withRouter(DeleteRepetitionModal);
diff --git a/web/src/components/Repetition/Edit/Content.tsx b/web/src/components/Repetition/Edit/Content.tsx
deleted file mode 100644
index 45bb4e18..00000000
--- a/web/src/components/Repetition/Edit/Content.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import { booksToOptions } from 'jslib/helpers/select';
-import { RepetitionRuleData } from 'jslib/operations/types';
-import React from 'react';
-import { RouteComponentProps, withRouter } from 'react-router-dom';
-import { getRepetitionsPath, repetitionsPathDef } from 'web/libs/paths';
-import services from 'web/libs/services';
-import { useDispatch } from '../../../store';
-import { setMessage } from '../../../store/ui';
-import Form, { FormState, serializeFormState } from '../Form';
-
-interface Props extends RouteComponentProps {
- setErrMsg: (string) => void;
- data: RepetitionRuleData;
-}
-
-const RepetitionEditContent: React.FunctionComponent = ({
- history,
- setErrMsg,
- data
-}) => {
- const dispatch = useDispatch();
-
- async function handleSubmit(state: FormState) {
- const payload = serializeFormState(state);
-
- try {
- await services.repetitionRules.update(data.uuid, payload);
-
- const dest = getRepetitionsPath();
- history.push(dest);
-
- dispatch(
- setMessage({
- message: `Updated the repetition rule: "${data.title}"`,
- kind: 'info',
- path: repetitionsPathDef
- })
- );
- } catch (e) {
- console.log(e);
- setErrMsg(e.message);
- }
- }
-
- const initialFormState = {
- title: data.title,
- enabled: data.enabled,
- hour: data.hour,
- minute: data.minute,
- frequency: data.frequency,
- noteCount: data.noteCount,
- bookDomain: data.bookDomain,
- books: booksToOptions(data.books)
- };
-
- return (
-
- );
-};
-
-export default withRouter(RepetitionEditContent);
diff --git a/web/src/components/Repetition/Edit/index.tsx b/web/src/components/Repetition/Edit/index.tsx
deleted file mode 100644
index 421b194b..00000000
--- a/web/src/components/Repetition/Edit/index.tsx
+++ /dev/null
@@ -1,91 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React, { useEffect, useState } from 'react';
-import classnames from 'classnames';
-import Helmet from 'react-helmet';
-import { Link, RouteComponentProps } from 'react-router-dom';
-
-import { RepetitionRuleData } from 'jslib/operations/types';
-import services from 'web/libs/services';
-import { getRepetitionsPath } from 'web/libs/paths';
-import PayWall from '../../Common/PayWall';
-import { useDispatch } from '../../../store';
-import Flash from '../../Common/Flash';
-import repetitionStyles from '../Repetition.scss';
-import Content from './Content';
-
-interface Match {
- repetitionUUID: string;
-}
-
-interface Props extends RouteComponentProps {}
-
-const EditRepetition: React.FunctionComponent = ({ match }) => {
- const dispatch = useDispatch();
- const [errMsg, setErrMsg] = useState('');
- const [data, setData] = useState(null);
-
- useEffect(() => {
- const { repetitionUUID } = match.params;
- services.repetitionRules
- .fetch(repetitionUUID)
- .then(rule => {
- setData(rule);
- })
- .catch(err => {
- setErrMsg(err.message);
- });
- }, [dispatch, match]);
-
- return (
-
- );
-};
-
-export default withRouter(NewRepetition);
diff --git a/web/src/components/Repetition/Repetition.scss b/web/src/components/Repetition/Repetition.scss
deleted file mode 100644
index b0198c58..00000000
--- a/web/src/components/Repetition/Repetition.scss
+++ /dev/null
@@ -1,40 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-@import '../App/rem';
-@import '../App/font';
-@import '../App/theme';
-
-.header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- height: 40px;
-}
-
-.content {
- margin-top: rem(20px);
-}
-
-.flash {
- margin-top: rem(20px);
-
- @include breakpoint(lg) {
- margin-top: 0;
- }
-}
diff --git a/web/src/components/Repetition/RepetitionItem/Actions.tsx b/web/src/components/Repetition/RepetitionItem/Actions.tsx
deleted file mode 100644
index 20d6ab7e..00000000
--- a/web/src/components/Repetition/RepetitionItem/Actions.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import classnames from 'classnames';
-import React, { useRef, useState } from 'react';
-import { Link } from 'react-router-dom';
-import { getEditRepetitionPath } from '../../../libs/paths';
-import ItemActions from '../../Common/ItemActions';
-import ItemActionsStyles from '../../Common/ItemActions/ItemActions.scss';
-
-interface Props {
- isActive: boolean;
- onDelete: () => void;
- repetitionUUID: string;
- disabled?: boolean;
-}
-
-const Actions: React.FunctionComponent = ({
- isActive,
- onDelete,
- repetitionUUID,
- disabled
-}) => {
- const [isOpen, setIsOpen] = useState(false);
-
- const optRefs = [useRef(null), useRef(null)];
- const options = [
- {
- name: 'edit',
- value: (
-
- Edit
-
- )
- },
- {
- name: 'remove',
- value: (
-
- )
- }
- ];
-
- return (
-
- );
-};
-
-export default Actions;
diff --git a/web/src/components/Repetition/RepetitionItem/BookMeta.tsx b/web/src/components/Repetition/RepetitionItem/BookMeta.tsx
deleted file mode 100644
index bbfe54d3..00000000
--- a/web/src/components/Repetition/RepetitionItem/BookMeta.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React from 'react';
-
-import { BookDomain } from 'jslib/operations/types';
-import { pluralize } from 'web/libs/string';
-import styles from './RepetitionItem.scss';
-
-interface ContentProps {
- bookDomain: BookDomain;
- bookCount: number;
-}
-
-const Content: React.FunctionComponent = ({
- bookDomain,
- bookCount
-}) => {
- if (bookDomain === BookDomain.All) {
- return From all books;
- }
-
- let verb;
- if (bookDomain === BookDomain.Excluding) {
- verb = 'Excluding';
- } else if (bookDomain === BookDomain.Including) {
- verb = 'From';
- }
-
- return (
-
- {verb} {bookCount} {pluralize('book', bookCount)}
-
- );
-};
-
-interface Props {
- bookDomain: BookDomain;
- bookCount: number;
-}
-
-const BookMeta: React.FunctionComponent = ({
- bookDomain,
- bookCount
-}) => {
- return (
-
-
-
- );
-};
-
-export default BookMeta;
diff --git a/web/src/components/Repetition/RepetitionItem/Placeholder.scss b/web/src/components/Repetition/RepetitionItem/Placeholder.scss
deleted file mode 100644
index 5c012fee..00000000
--- a/web/src/components/Repetition/RepetitionItem/Placeholder.scss
+++ /dev/null
@@ -1,44 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-@import '../../App/rem';
-@import '../../App/font';
-@import '../../App/theme';
-
-.wrapper {
- display: block;
-}
-
-.line {
- height: rem(16px);
-
- & ~ & {
- margin-top: rem(12px);
- }
-}
-
-.title {
- width: rem(200px);
-}
-
-.line1 {
- width: rem(160px);
-}
-.line2 {
- width: rem(180px);
-}
diff --git a/web/src/components/Repetition/RepetitionItem/Placeholder.tsx b/web/src/components/Repetition/RepetitionItem/Placeholder.tsx
deleted file mode 100644
index fd05356d..00000000
--- a/web/src/components/Repetition/RepetitionItem/Placeholder.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React from 'react';
-import classnames from 'classnames';
-
-import styles from './Placeholder.scss';
-import itemStyles from './RepetitionItem.scss';
-
-interface Props {}
-
-const Placeholder: React.FunctionComponent = () => {
- return (
-
-
-
-
-
- );
-};
-
-export default Placeholder;
diff --git a/web/src/components/Repetition/RepetitionItem/RepetitionItem.scss b/web/src/components/Repetition/RepetitionItem/RepetitionItem.scss
deleted file mode 100644
index 1b566db6..00000000
--- a/web/src/components/Repetition/RepetitionItem/RepetitionItem.scss
+++ /dev/null
@@ -1,110 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-@import '../../App/rem';
-@import '../../App/font';
-@import '../../App/theme';
-
-.wrapper {
- background: #fff;
- position: relative;
- display: flex;
- padding: rem(16px) rem(24px);
-
- &:not(:last-child) {
- border-bottom: 1px solid #d8d8d8;
- }
-}
-
-.content {
- display: flex;
- flex-grow: 1;
- flex-direction: column;
-
- @include breakpoint(md) {
- flex-direction: row;
- }
-}
-
-.left {
-}
-
-.right {
- display: flex;
- flex-grow: 1;
- padding-right: rem(24px);
-
- @include breakpoint(md) {
- justify-content: flex-end;
- }
-}
-
-.meta {
- @include font-size('small');
- margin-top: rem(4px);
-
- @include breakpoint(md) {
- color: $dark-gray;
- }
-}
-
-.sep {
- margin: 0 rem(4px);
-}
-
-.item {
- background: #fff;
- position: relative;
- border-bottom: 1px solid #d8d8d8;
-}
-
-.title {
- @include font-size('regular');
-}
-
-.status {
- @include font-size('x-small');
- border: 1px solid $green;
- border-radius: rem(4px);
- display: inline-block;
- padding: rem(2px) rem(4px);
- font-weight: 600;
-
- &.active {
- color: $green;
- }
-}
-
-.detail-list {
- @include font-size('small');
- display: flex;
- flex-direction: column;
- justify-content: center;
-
- @include breakpoint(md) {
- min-width: rem(180px);
- }
-}
-
-.book-meta {
- display: none;
-
- @include breakpoint(md) {
- display: block;
- }
-}
diff --git a/web/src/components/Repetition/RepetitionItem/index.tsx b/web/src/components/Repetition/RepetitionItem/index.tsx
deleted file mode 100644
index 2ae13866..00000000
--- a/web/src/components/Repetition/RepetitionItem/index.tsx
+++ /dev/null
@@ -1,156 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import classnames from 'classnames';
-import { RepetitionRuleData } from 'jslib/operations/types';
-import React, { useState } from 'react';
-import {
- msToDuration,
- msToHTMLTimeDuration,
- relativeTimeDiff,
- timeAgo
-} from 'web/helpers/time';
-import Time from '../../Common/Time';
-import Actions from './Actions';
-import BookMeta from './BookMeta';
-import styles from './RepetitionItem.scss';
-
-interface Props {
- item: RepetitionRuleData;
- setRuleUUIDToDelete: React.Dispatch;
- pro: boolean;
-}
-
-function formatLastActive(ms: number): string {
- return timeAgo(ms);
-}
-
-function formatNextActive(ms: number): string {
- const now = new Date().getTime();
- const diff = relativeTimeDiff(now, ms);
-
- return diff.text;
-}
-
-const RepetitionItem: React.FunctionComponent = ({
- item,
- pro,
- setRuleUUIDToDelete
-}) => {
- const [isHovered, setIsHovered] = useState(false);
-
- return (
-
- );
-};
-
-export default RepetitionItem;
diff --git a/web/src/components/Repetition/RepetitionList.scss b/web/src/components/Repetition/RepetitionList.scss
deleted file mode 100644
index 0a260d69..00000000
--- a/web/src/components/Repetition/RepetitionList.scss
+++ /dev/null
@@ -1,31 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-@import '../App/rem';
-@import '../App/font';
-@import '../App/theme';
-
-.wrapper {
- position: relative;
- border-radius: 4px;
- box-shadow: 0 0 8px rgba(0, 0, 0, 0.14);
-}
-
-.title {
- @include font-size('medium');
-}
diff --git a/web/src/components/Repetition/RepetitionList.tsx b/web/src/components/Repetition/RepetitionList.tsx
deleted file mode 100644
index 658f06a2..00000000
--- a/web/src/components/Repetition/RepetitionList.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React, { Fragment } from 'react';
-import classnames from 'classnames';
-
-import { RepetitionRuleData } from 'jslib/operations/types';
-import RepetitionItem from './RepetitionItem';
-import Placeholder from './RepetitionItem/Placeholder';
-import styles from './RepetitionList.scss';
-
-interface Props {
- isFetching: boolean;
- isFetched: boolean;
- items: RepetitionRuleData[];
- setRuleUUIDToDelete: React.Dispatch;
- pro: boolean;
-}
-
-const ReptitionList: React.FunctionComponent = ({
- isFetching,
- isFetched,
- items,
- setRuleUUIDToDelete,
- pro
-}) => {
- if (isFetching) {
- return (
-
-
-
-
-
-
- );
- }
- if (!isFetched) {
- return null;
- }
-
- return (
-
- {items.map(i => {
- return (
-
- );
- })}
-
- );
-};
-
-export default ReptitionList;
diff --git a/web/src/components/Repetition/index.tsx b/web/src/components/Repetition/index.tsx
deleted file mode 100644
index dd79ffe3..00000000
--- a/web/src/components/Repetition/index.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import React from 'react';
-import Helmet from 'react-helmet';
-
-import Content from './Content';
-
-const Repetition: React.FunctionComponent = () => {
- return (
-
-
- Repetition
-
-
-
-
- );
-};
-
-export default Repetition;
diff --git a/web/src/libs/paths.ts b/web/src/libs/paths.ts
index 2c0e56af..986bf167 100644
--- a/web/src/libs/paths.ts
+++ b/web/src/libs/paths.ts
@@ -30,25 +30,16 @@ export const loginPathDef = '/login';
export const joinPathDef = '/join';
export const settingsPathDef = '/settings/:section';
export const subscriptionsPathDef = '/subscriptions';
-export const prefEditRepetitionPathDef =
- '/preferences/repetitions/:repetitionUUID';
export const verifyEmailPathDef = '/verify-email/:token';
export const classicMigrationPathDef = '/classic/:step?';
export const passwordResetRequestPathDef = '/password-reset';
export const passwordResetConfirmPathDef = '/password-reset/:token';
-export const repetitionsPathDef = '/repetition';
-export const repetitionPathDef = '/repetition/:repetitionUUID';
-export const newRepetitionRulePathDef = '/repetition/new';
-export const editRepetitionRulePathDef = '/repetition/:repetitionUUID/edit';
export const emailPreferencePathDef = '/email-preferences';
-export const digestsPathDef = '/digests';
-export const digestPathDef = '/digests/:digestUUID';
// layout definitions
export const noHeaderPaths = [
loginPathDef,
joinPathDef,
- prefEditRepetitionPathDef,
verifyEmailPathDef,
classicMigrationPathDef,
passwordResetRequestPathDef,
@@ -59,7 +50,6 @@ export const noFooterPaths = [
loginPathDef,
joinPathDef,
subscriptionsPathDef,
- prefEditRepetitionPathDef,
verifyEmailPathDef,
classicMigrationPathDef,
passwordResetRequestPathDef,
@@ -132,27 +122,6 @@ export function getBooksPath(searchObj = {}): Location {
return getLocation({ pathname: booksPathDef, searchObj });
}
-export function getRepetitionsPath(searchObj = {}): Location {
- return getLocation({ pathname: repetitionsPathDef, searchObj });
-}
-
-export function getDigestsPath(searchObj = {}): Location {
- return getLocation({ pathname: digestsPathDef, searchObj });
-}
-
-export function getDigestPath(digestUUID: string, searchObj = {}): Location {
- const path = `/digests/${digestUUID}`;
-
- return getLocation({
- pathname: path,
- searchObj
- });
-}
-
-export function getNewRepetitionPath(searchObj = {}): Location {
- return getLocation({ pathname: newRepetitionRulePathDef, searchObj });
-}
-
export function populateParams(pathDef: string, params: any) {
const parts = pathDef.split('/');
@@ -173,13 +142,6 @@ export function populateParams(pathDef: string, params: any) {
return builder.join('/');
}
-export function getEditRepetitionPath(uuid: string, searchObj = {}): Location {
- const pathname = populateParams(editRepetitionRulePathDef, {
- repetitionUUID: uuid
- });
- return getLocation({ pathname, searchObj });
-}
-
export function getNotePath(noteUUID: string, searchObj = {}): Location {
const path = `/notes/${noteUUID}`;
diff --git a/web/src/routes.tsx b/web/src/routes.tsx
index 5b99d0db..9c0cb8d2 100644
--- a/web/src/routes.tsx
+++ b/web/src/routes.tsx
@@ -29,21 +29,15 @@ import Join from './components/Join';
import Settings from './components/Settings';
import NotFound from './components/Common/NotFound';
import VerifyEmail from './components/VerifyEmail';
-import PreferenceEditRepetition from './components/Preferences/Repetitions';
import New from './components/New';
import Edit from './components/Edit';
import Note from './components/Note';
import Books from './components/Books';
import Classic from './components/Classic';
import Checkout from './components/Subscription/Checkout';
-import Repetition from './components/Repetition';
-import NewRepetition from './components/Repetition/New';
-import EditRepetition from './components/Repetition/Edit';
import PasswordResetRequest from './components/PasswordReset/Request';
import PasswordResetConfirm from './components/PasswordReset/Confirm';
import EmailPreference from './components/EmailPreference';
-import Digests from './components/Digests';
-import Digest from './components/Digest';
// paths
import {
@@ -59,15 +53,9 @@ import {
passwordResetRequestPathDef,
passwordResetConfirmPathDef,
getJoinPath,
- prefEditRepetitionPathDef,
verifyEmailPathDef,
classicMigrationPathDef,
- repetitionsPathDef,
- newRepetitionRulePathDef,
- editRepetitionRulePathDef,
- emailPreferencePathDef,
- digestsPathDef,
- digestPathDef
+ emailPreferencePathDef
} from './libs/paths';
const AuthenticatedHome = userOnly(Home);
@@ -83,9 +71,6 @@ const AuthenticatedSubscriptionCheckout = userOnly(
Checkout,
getJoinPath().pathname
);
-const AuthenticatedRepetition = userOnly(Repetition);
-const AuthenticatedNewRepetition = userOnly(NewRepetition);
-const AuthenticatedEditRepetition = userOnly(EditRepetition);
const routes = [
{
@@ -133,11 +118,6 @@ const routes = [
exact: true,
component: VerifyEmail
},
- {
- path: prefEditRepetitionPathDef,
- exact: true,
- component: PreferenceEditRepetition
- },
{
path: noteNewPathDef,
exact: true,
@@ -158,36 +138,11 @@ const routes = [
exact: true,
component: GuestPasswordResetConfirm
},
- {
- path: repetitionsPathDef,
- exact: true,
- component: AuthenticatedRepetition
- },
- {
- path: newRepetitionRulePathDef,
- exact: true,
- component: AuthenticatedNewRepetition
- },
- {
- path: editRepetitionRulePathDef,
- exact: true,
- component: AuthenticatedEditRepetition
- },
{
path: emailPreferencePathDef,
exact: true,
component: EmailPreference
},
- {
- path: digestsPathDef,
- exact: true,
- component: Digests
- },
- {
- path: digestPathDef,
- exact: true,
- component: Digest
- },
{
component: NotFound
}
diff --git a/web/src/store/digest/actions.ts b/web/src/store/digest/actions.ts
deleted file mode 100644
index 22d73e4f..00000000
--- a/web/src/store/digest/actions.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import operations from 'web/libs/operations';
-import services from 'web/libs/services';
-import { DigestData } from 'jslib/operations/types';
-import {
- RECEIVE,
- START_FETCHING,
- ERROR,
- RESET,
- SET_NOTE_REVIEWED,
- SetNoteReviewed
-} from './type';
-import { ThunkAction } from '../types';
-
-export function receiveDigest(digest: DigestData) {
- return {
- type: RECEIVE,
- data: { digest }
- };
-}
-
-export function resetDigest() {
- return {
- type: RESET
- };
-}
-
-function startFetchingDigest() {
- return {
- type: START_FETCHING
- };
-}
-
-function receiveDigestError(errorMessage: string) {
- return {
- type: ERROR,
- data: { errorMessage }
- };
-}
-
-function setNoteReviewed(
- noteUUID: string,
- isReviewed: boolean
-): SetNoteReviewed {
- return {
- type: SET_NOTE_REVIEWED,
- data: {
- noteUUID,
- isReviewed
- }
- };
-}
-
-interface GetDigestFacets {
- q?: string;
-}
-
-export const getDigest = (
- digestUUID: string
-): ThunkAction => {
- return dispatch => {
- dispatch(startFetchingDigest());
-
- return operations.digests
- .fetch(digestUUID)
- .then(digest => {
- dispatch(receiveDigest(digest));
-
- return digest;
- })
- .catch(err => {
- console.log('getDigest error', err.message);
- dispatch(receiveDigestError(err.message));
- });
- };
-};
-
-export const setDigestNoteReviewed = ({
- digestUUID,
- noteUUID,
- isReviewed
-}: {
- digestUUID: string;
- noteUUID: string;
- isReviewed: boolean;
-}): ThunkAction => {
- return dispatch => {
- if (!isReviewed) {
- return services.noteReviews.remove({ noteUUID, digestUUID }).then(() => {
- dispatch(setNoteReviewed(noteUUID, false));
- });
- }
-
- return services.noteReviews.create({ digestUUID, noteUUID }).then(() => {
- dispatch(setNoteReviewed(noteUUID, true));
- });
- };
-};
diff --git a/web/src/store/digest/index.ts b/web/src/store/digest/index.ts
deleted file mode 100644
index 82479ec2..00000000
--- a/web/src/store/digest/index.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-export * from './actions';
-export * from './reducers';
-export * from './type';
diff --git a/web/src/store/digest/reducers.ts b/web/src/store/digest/reducers.ts
deleted file mode 100644
index 136cad2e..00000000
--- a/web/src/store/digest/reducers.ts
+++ /dev/null
@@ -1,113 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import { BookDomain } from 'jslib/operations/types';
-import {
- RECEIVE,
- START_FETCHING,
- ERROR,
- RESET,
- SET_NOTE_REVIEWED,
- DigestState,
- DigestActionType
-} from './type';
-
-const initialState: DigestState = {
- data: {
- uuid: '',
- createdAt: '',
- updatedAt: '',
- version: 0,
- notes: [],
- isRead: false,
- repetitionRule: {
- uuid: '',
- title: '',
- enabled: false,
- hour: 0,
- minute: 0,
- bookDomain: BookDomain.All,
- frequency: 0,
- books: [],
- lastActive: 0,
- nextActive: 0,
- noteCount: 0,
- createdAt: '',
- updatedAt: ''
- }
- },
- isFetching: false,
- isFetched: false,
- errorMessage: null
-};
-
-export default function(
- state = initialState,
- action: DigestActionType
-): DigestState {
- switch (action.type) {
- case START_FETCHING: {
- return {
- ...state,
- errorMessage: null,
- isFetching: true,
- isFetched: false
- };
- }
- case ERROR: {
- return {
- ...state,
- isFetching: false,
- errorMessage: action.data.errorMessage
- };
- }
- case RECEIVE: {
- return {
- ...state,
- data: action.data.digest,
- isFetching: false,
- isFetched: true
- };
- }
- case SET_NOTE_REVIEWED: {
- return {
- ...state,
- data: {
- ...state.data,
- notes: state.data.notes.map(note => {
- if (action.data.noteUUID === note.uuid) {
- const isReviewed = action.data.isReviewed;
-
- return {
- ...note,
- isReviewed
- };
- }
-
- return note;
- })
- }
- };
- }
- case RESET: {
- return initialState;
- }
- default:
- return state;
- }
-}
diff --git a/web/src/store/digest/type.ts b/web/src/store/digest/type.ts
deleted file mode 100644
index 7c63ae3a..00000000
--- a/web/src/store/digest/type.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import { DigestData } from 'jslib/operations/types';
-import { RemoteData } from '../types';
-
-export type DigestState = RemoteData;
-
-export const RECEIVE = 'digest/RECEIVE';
-export const START_FETCHING = 'digest/START_FETCHING';
-export const ERROR = 'digest/ERROR';
-export const RESET = 'digest/RESET';
-export const SET_NOTE_REVIEWED = 'digest/SET_NOTE_REVIEWED';
-
-export interface ReceiveDigest {
- type: typeof RECEIVE;
- data: {
- digest: DigestData;
- };
-}
-
-export interface StartFetchingDigest {
- type: typeof START_FETCHING;
-}
-
-export interface ResetDigest {
- type: typeof RESET;
-}
-
-export interface ReceiveDigestError {
- type: typeof ERROR;
- data: {
- errorMessage: string;
- };
-}
-
-export interface SetNoteReviewed {
- type: typeof SET_NOTE_REVIEWED;
- data: {
- noteUUID: string;
- isReviewed: boolean;
- };
-}
-
-export type DigestActionType =
- | ReceiveDigest
- | StartFetchingDigest
- | ReceiveDigestError
- | ResetDigest
- | SetNoteReviewed;
diff --git a/web/src/store/digests/actions.ts b/web/src/store/digests/actions.ts
deleted file mode 100644
index f037db00..00000000
--- a/web/src/store/digests/actions.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import operations from 'web/libs/operations';
-import { START_FETCHING, RECEIVE, RECEIVE_ERROR, RESET } from './type';
-
-function receiveDigests(total, items, page) {
- return {
- type: RECEIVE,
- data: {
- total,
- items,
- page
- }
- };
-}
-
-function startFetchingDigests() {
- return {
- type: START_FETCHING
- };
-}
-
-function receiveError(error) {
- return {
- type: RECEIVE_ERROR,
- data: {
- error
- }
- };
-}
-
-export function resetDigests() {
- return {
- type: RESET
- };
-}
-
-export function getDigests(params: { page: number; status: string }) {
- return async dispatch => {
- try {
- dispatch(startFetchingDigests());
-
- const res = await operations.digests.fetchAll(params);
-
- dispatch(receiveDigests(res.total, res.items, params.page));
- } catch (err) {
- console.log('Error fetching digests', err.stack);
- dispatch(receiveError(err.message));
- }
- };
-}
diff --git a/web/src/store/digests/index.ts b/web/src/store/digests/index.ts
deleted file mode 100644
index 82479ec2..00000000
--- a/web/src/store/digests/index.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-export * from './actions';
-export * from './reducers';
-export * from './type';
diff --git a/web/src/store/digests/reducers.ts b/web/src/store/digests/reducers.ts
deleted file mode 100644
index cecc212f..00000000
--- a/web/src/store/digests/reducers.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import {
- START_FETCHING,
- RECEIVE,
- RECEIVE_ERROR,
- RESET,
- DigestsState,
- DigestsActionType
-} from './type';
-
-const initialState: DigestsState = {
- data: [],
- total: 0,
- page: 0,
- isFetching: false,
- isFetched: false,
- errorMessage: null
-};
-
-export default function(
- state = initialState,
- action: DigestsActionType
-): DigestsState {
- switch (action.type) {
- case START_FETCHING: {
- return {
- ...state,
- errorMessage: null,
- isFetching: true,
- isFetched: false
- };
- }
- case RECEIVE: {
- return {
- ...state,
- isFetching: false,
- isFetched: true,
- total: action.data.total,
- page: action.data.page,
- data: action.data.items
- };
- }
- case RECEIVE_ERROR: {
- return {
- ...state,
- errorMessage: action.data.error
- };
- }
- case RESET: {
- return initialState;
- }
- default:
- return state;
- }
-}
diff --git a/web/src/store/digests/type.ts b/web/src/store/digests/type.ts
deleted file mode 100644
index 1c103391..00000000
--- a/web/src/store/digests/type.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import { DigestData } from 'jslib/operations/types';
-
-import { RemoteData } from '../types';
-
-export interface DigestsState extends RemoteData {
- total: number;
- page: number;
-}
-
-export const START_FETCHING = 'digests/START_FETCHING';
-export const RECEIVE = 'digests/RECEIVE';
-export const RECEIVE_ERROR = 'digests/RECEIVE_ERROR';
-export const RESET = 'digests/RESET';
-
-export interface StartFetchingAction {
- type: typeof START_FETCHING;
-}
-
-export interface ResetAction {
- type: typeof RESET;
-}
-
-export interface ReceiveErrorAction {
- type: typeof RECEIVE_ERROR;
- data: {
- error: string;
- };
-}
-
-export interface ReceiveAction {
- type: typeof RECEIVE;
- data: {
- items: DigestData[];
- total: number;
- page: number;
- };
-}
-
-export type DigestsActionType =
- | StartFetchingAction
- | ReceiveAction
- | ReceiveErrorAction
- | ResetAction;
diff --git a/web/src/store/index.ts b/web/src/store/index.ts
index f13160aa..873d0859 100644
--- a/web/src/store/index.ts
+++ b/web/src/store/index.ts
@@ -28,9 +28,6 @@ import ui from './ui/reducers';
import route from './route/reducers';
import notes from './notes/reducers';
import filters from './filters/reducers';
-import repetitionRules from './repetitionRules/reducers';
-import digests from './digests/reducers';
-import digest from './digest/reducers';
const rootReducer = combineReducers({
auth,
@@ -41,10 +38,7 @@ const rootReducer = combineReducers({
note,
ui,
route,
- filters,
- digests,
- digest,
- repetitionRules
+ filters
});
// configuruStore returns a new store that contains the appliation state
diff --git a/web/src/store/repetitionRules/actions.ts b/web/src/store/repetitionRules/actions.ts
deleted file mode 100644
index 5516d294..00000000
--- a/web/src/store/repetitionRules/actions.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import services from 'web/libs/services';
-import { RepetitionRuleData } from 'jslib/operations/types';
-import { CreateParams } from 'jslib/services/repetitionRules';
-import {
- RECEIVE,
- ADD,
- REMOVE,
- START_FETCHING,
- FINISH_FETCHING,
- RECEIVE_ERROR,
- ReceiveRepetitionRulesAction,
- ReceiveRepetitionRulesErrorAction,
- StartFetchingRepetitionRulesAction,
- FinishFetchingRepetitionRulesAction,
- AddRepetitionRuleAction,
- RemoveRepetitionRuleAction
-} from './type';
-import { ThunkAction } from '../types';
-
-function receiveRepetitionRules(
- repetitionRules: RepetitionRuleData[]
-): ReceiveRepetitionRulesAction {
- return {
- type: RECEIVE,
- data: { repetitionRules }
- };
-}
-
-function receiveRepetitionRulesError(
- err: string
-): ReceiveRepetitionRulesErrorAction {
- return {
- type: RECEIVE_ERROR,
- data: { err }
- };
-}
-
-function startFetchingRepetitionRules(): StartFetchingRepetitionRulesAction {
- return {
- type: START_FETCHING
- };
-}
-
-function finishFetchingRepetitionRules(): FinishFetchingRepetitionRulesAction {
- return {
- type: FINISH_FETCHING
- };
-}
-
-export const getRepetitionRules = (): ThunkAction => {
- return dispatch => {
- dispatch(startFetchingRepetitionRules());
-
- return services.repetitionRules
- .fetchAll()
- .then(data => {
- dispatch(receiveRepetitionRules(data));
- dispatch(finishFetchingRepetitionRules());
- })
- .catch(err => {
- console.log('getRepetitionRules error', err);
- dispatch(receiveRepetitionRulesError(err));
- });
- };
-};
-
-export function addRepetitionRule(
- repetitionRule: RepetitionRuleData
-): AddRepetitionRuleAction {
- return {
- type: ADD,
- data: { repetitionRule }
- };
-}
-
-export const createRepetitionRule = (
- p: CreateParams
-): ThunkAction => {
- return dispatch => {
- return services.repetitionRules.create(p).then(data => {
- dispatch(addRepetitionRule(data));
-
- return data;
- });
- };
-};
-
-export function removeRepetitionRule(uuid: string): RemoveRepetitionRuleAction {
- return {
- type: REMOVE,
- data: { uuid }
- };
-}
diff --git a/web/src/store/repetitionRules/index.ts b/web/src/store/repetitionRules/index.ts
deleted file mode 100644
index 82479ec2..00000000
--- a/web/src/store/repetitionRules/index.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-export * from './actions';
-export * from './reducers';
-export * from './type';
diff --git a/web/src/store/repetitionRules/reducers.ts b/web/src/store/repetitionRules/reducers.ts
deleted file mode 100644
index 36137a24..00000000
--- a/web/src/store/repetitionRules/reducers.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import {
- RepetitionRulesState,
- RepetitionRulesActionType,
- RECEIVE,
- RECEIVE_ERROR,
- ADD,
- REMOVE,
- START_FETCHING,
- FINISH_FETCHING
-} from './type';
-
-const initialState: RepetitionRulesState = {
- data: [],
- isFetching: false,
- isFetched: false,
- errorMessage: ''
-};
-
-export default function(
- state = initialState,
- action: RepetitionRulesActionType
-): RepetitionRulesState {
- switch (action.type) {
- case START_FETCHING: {
- return {
- ...state,
- isFetching: true,
- isFetched: false
- };
- }
- case FINISH_FETCHING: {
- return {
- ...state,
- isFetching: false,
- isFetched: true
- };
- }
- case RECEIVE: {
- return {
- ...state,
- data: action.data.repetitionRules
- };
- }
- case RECEIVE_ERROR: {
- return {
- ...state,
- errorMessage: action.data.err
- };
- }
- case REMOVE: {
- return {
- ...state,
- data: state.data.filter(item => {
- return item.uuid !== action.data.uuid;
- })
- };
- }
- case ADD: {
- const data = [...state.data, action.data.repetitionRule];
-
- return {
- ...state,
- data
- };
- }
- default:
- return state;
- }
-}
diff --git a/web/src/store/repetitionRules/type.ts b/web/src/store/repetitionRules/type.ts
deleted file mode 100644
index 9d9af1fc..00000000
--- a/web/src/store/repetitionRules/type.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-/* Copyright (C) 2019, 2020 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 .
- */
-
-import { RepetitionRuleData } from 'jslib/operations/types';
-import { RemoteData } from '../types';
-
-export type RepetitionRulesState = RemoteData;
-
-export const RECEIVE = 'repetitionRules/RECEIVE';
-export const RECEIVE_ERROR = 'repetitionRules/RECEIVE_ERROR';
-export const ADD = 'repetitionRules/ADD';
-export const REMOVE = 'repetitionRules/REMOVE';
-export const START_FETCHING = 'repetitionRules/START_FETCHING';
-export const FINISH_FETCHING = 'repetitionRules/FINISH_FETCHING';
-
-export interface ReceiveRepetitionRulesAction {
- type: typeof RECEIVE;
- data: {
- repetitionRules: RepetitionRuleData[];
- };
-}
-
-export interface ReceiveRepetitionRulesErrorAction {
- type: typeof RECEIVE_ERROR;
- data: {
- err: string;
- };
-}
-
-export interface StartFetchingRepetitionRulesAction {
- type: typeof START_FETCHING;
-}
-
-export interface FinishFetchingRepetitionRulesAction {
- type: typeof FINISH_FETCHING;
-}
-
-export interface AddRepetitionRuleAction {
- type: typeof ADD;
- data: {
- repetitionRule: RepetitionRuleData;
- };
-}
-
-export interface RemoveRepetitionRuleAction {
- type: typeof REMOVE;
- data: {
- uuid: string;
- };
-}
-
-export type RepetitionRulesActionType =
- | ReceiveRepetitionRulesAction
- | ReceiveRepetitionRulesErrorAction
- | StartFetchingRepetitionRulesAction
- | FinishFetchingRepetitionRulesAction
- | AddRepetitionRuleAction
- | RemoveRepetitionRuleAction;
diff --git a/web/src/store/types.ts b/web/src/store/types.ts
index 3a03c5ff..4142d4a4 100644
--- a/web/src/store/types.ts
+++ b/web/src/store/types.ts
@@ -28,10 +28,6 @@ import { NotesState } from './notes/type';
import { UIState } from './ui/type';
import { RouteState } from './route/type';
import { FiltersState } from './filters/type';
-import { DigestsState } from './digests/type';
-import { DigestState } from './digest/type';
-
-import { RepetitionRulesState } from './repetitionRules/type';
// RemoteData represents a data in Redux store that is fetched from a remote source.
// It contains the state related to the fetching of the data as well as the data itself.
@@ -53,9 +49,6 @@ export interface AppState {
ui: UIState;
route: RouteState;
filters: FiltersState;
- digests: DigestsState;
- digest: DigestState;
- repetitionRules: RepetitionRulesState;
}
// ThunkAction is a thunk action type