34 Commits

Author SHA1 Message Date
evan 64aa000323 build(dev): use air for backend live reload
continuous-integration/drone/pr Build is failing
2026-05-02 15:39:28 -04:00
evan a865457bbf build(dev): split frontend and backend dev targets 2026-05-02 15:36:04 -04:00
evan a950d50440 feat(dev): add local auth bypass mode
continuous-integration/drone/pr Build is failing
2026-05-02 15:32:48 -04:00
evan 00faf9cea8 feat(pagination): paginate activity and progress lists 2026-05-02 15:32:10 -04:00
evan 75c872264f chore: remove unnecessary crap ai added
continuous-integration/drone/pr Build is failing
2026-04-03 19:46:05 -04:00
evan 0930054847 more reader
continuous-integration/drone/pr Build is failing
2026-04-03 13:45:17 -04:00
evan aa812c6917 wip reader migration 2026-04-03 12:15:48 -04:00
evan 8ec3349b7c chore(api): update to allow CRUD progress and activity in v1 2026-04-03 10:37:50 -04:00
evan decc3f0195 fix: toast theme & error msgs 2026-04-03 10:08:13 -04:00
evan b13f9b362c theme draft 2 (done?) 2026-03-22 17:21:34 -04:00
evan 6c2c4f6b8b remove dumb auth 2026-03-22 17:21:34 -04:00
evan d38392ac9a theme draft 1 2026-03-22 17:21:34 -04:00
evan 63ad73755d wip 22 2026-03-22 17:21:34 -04:00
evan 784e53c557 wip 21 2026-03-22 17:21:34 -04:00
evan 9ed63b2695 wip 20 2026-03-22 17:21:34 -04:00
evan 27e651c4f5 wip 19 2026-03-22 17:21:34 -04:00
evan 7e96e41ba4 wip 18 2026-03-22 17:21:33 -04:00
evan ee1d62858b wip 17 2026-03-22 17:21:33 -04:00
evan 4d133994ab wip 16 2026-03-22 17:21:33 -04:00
evan ba919bbde4 wip 15 2026-03-22 17:21:33 -04:00
evan 197a1577c2 wip 14 2026-03-22 17:21:33 -04:00
evan fd9afe86b0 wip 13 2026-03-22 17:21:33 -04:00
evan 93707ff513 wip 12 2026-03-22 17:21:33 -04:00
evan 75e0228fe0 wip 11 2026-03-22 17:21:33 -04:00
evan b1b8eb297e wip 10 2026-03-22 17:21:33 -04:00
evan 7c47f2d2eb wip 9 2026-03-22 17:21:33 -04:00
evan c46dcb440d wip 8 2026-03-22 17:21:33 -04:00
evan 5cb17bace7 wip 7 2026-03-22 17:21:32 -04:00
evan ecf77fd105 wip 6 2026-03-22 17:21:32 -04:00
evan e289d1a29b wip 5 2026-03-22 17:21:32 -04:00
evan 3e9a193d08 wip 4 2026-03-22 17:21:32 -04:00
evan 4306d86080 wip 3 2026-03-22 17:21:32 -04:00
evan d40f8fc375 wip 2 2026-03-22 17:21:32 -04:00
evan c84bc2522e wip 1 2026-03-22 17:21:32 -04:00
5 changed files with 25 additions and 351 deletions
+4 -18
View File
@@ -3,10 +3,8 @@ type: docker
name: default
trigger:
ref:
- refs/heads/master
- refs/tags/**
- refs/pull/**
branch:
- master
steps:
# Unit Tests
@@ -21,26 +19,14 @@ steps:
commands:
- git fetch --tags
# Compute docker tag: git tag for tag builds, pr-<num> for PRs, dev for master
- name: compute tags
image: alpine
commands:
- |
if [ "$DRONE_BUILD_EVENT" = "tag" ]; then
echo "$DRONE_TAG" > .tags
elif [ "$DRONE_BUILD_EVENT" = "pull_request" ]; then
echo "pr-$DRONE_PULL_REQUEST" > .tags
else
echo "dev" > .tags
fi
- cat .tags
# Publish docker image
- name: publish docker
image: plugins/docker
settings:
repo: gitea.va.reichard.io/evan/antholume
registry: gitea.va.reichard.io
tags:
- dev
custom_dns:
- 8.8.8.8
username:
+3 -6
View File
@@ -124,13 +124,10 @@ func (api *API) appGetDocuments(c *gin.Context) {
return
}
length, err := api.db.Queries.GetDocumentsWithStatsCount(c, database.GetDocumentsWithStatsCountParams{
Deleted: ptr.Of(false),
Query: query,
})
length, err := api.db.Queries.GetDocumentsSize(c, query)
if err != nil {
log.Error("GetDocumentsWithStatsCount DB Error: ", err)
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDocumentsWithStatsCount DB Error: %v", err))
log.Error("GetDocumentsSize DB Error: ", err)
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDocumentsSize DB Error: %v", err))
return
}
+7 -314
View File
@@ -4,13 +4,10 @@ import (
"context"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/suite"
"reichard.io/antholume/config"
"reichard.io/antholume/pkg/ptr"
"reichard.io/antholume/utils"
)
type DocumentsTestSuite struct {
@@ -39,54 +36,12 @@ func (suite *DocumentsTestSuite) SetupTest() {
suite.NoError(err)
}
func (suite *DocumentsTestSuite) seedDocumentStats() {
suite.createTestUserAndDevice()
var err error
_, err = suite.dbm.Queries.AddActivity(context.Background(), AddActivityParams{
DocumentID: documentID,
DeviceID: deviceID,
UserID: userID,
StartTime: time.Now().UTC().Format(time.RFC3339),
Duration: 60,
StartPercentage: 0.10,
EndPercentage: 0.20,
})
suite.Require().NoError(err)
_, err = suite.dbm.Queries.UpdateProgress(context.Background(), UpdateProgressParams{
UserID: userID,
DocumentID: documentID,
DeviceID: deviceID,
Percentage: 0.42,
Progress: "/6/2[test]",
})
suite.Require().NoError(err)
err = suite.dbm.CacheTempTables(context.Background())
suite.Require().NoError(err)
}
func (suite *DocumentsTestSuite) createTestUserAndDevice() {
rawAuthHash, err := utils.GenerateToken(64)
suite.Require().NoError(err)
authHash := fmt.Sprintf("%x", rawAuthHash)
_, err = suite.dbm.Queries.CreateUser(context.Background(), CreateUserParams{
ID: userID,
Pass: &userPass,
AuthHash: &authHash,
})
suite.Require().NoError(err)
_, err = suite.dbm.Queries.UpsertDevice(context.Background(), UpsertDeviceParams{
ID: deviceID,
UserID: userID,
DeviceName: deviceName,
})
suite.Require().NoError(err)
}
// DOCUMENT - TODO:
// - 󰊕 (q *Queries) GetDocumentProgress
// - 󰊕 (q *Queries) GetDocumentWithStats
// - 󰊕 (q *Queries) GetDocumentsSize
// - 󰊕 (q *Queries) GetDocumentsWithStats
// - 󰊕 (q *Queries) GetMissingDocuments
func (suite *DocumentsTestSuite) TestGetDocument() {
doc, err := suite.dbm.Queries.GetDocument(context.Background(), documentID)
suite.Nil(err, "should have nil err")
@@ -108,265 +63,6 @@ func (suite *DocumentsTestSuite) TestUpsertDocument() {
suite.Equal(documentAuthor, *doc.Author, "should have document author")
}
func (suite *DocumentsTestSuite) TestGetDocumentProgress() {
suite.seedDocumentStats()
progress, err := suite.dbm.Queries.GetDocumentProgress(context.Background(), GetDocumentProgressParams{
UserID: userID,
DocumentID: documentID,
})
suite.NoError(err)
suite.Equal(userID, progress.UserID)
suite.Equal(documentID, progress.DocumentID)
suite.Equal(deviceID, progress.DeviceID)
suite.Equal(deviceName, progress.DeviceName)
suite.Equal(0.42, progress.Percentage)
suite.Equal("/6/2[test]", progress.Progress)
}
func (suite *DocumentsTestSuite) TestGetDocumentWithStats() {
suite.seedDocumentStats()
doc, err := suite.dbm.GetDocument(context.Background(), documentID, userID)
suite.NoError(err)
suite.Equal(documentID, doc.ID)
suite.Equal(documentTitle, *doc.Title)
suite.Equal(documentAuthor, *doc.Author)
suite.Equal(documentWords, *doc.Words)
suite.Equal(float64(42), doc.Percentage)
suite.Equal(int64(60), doc.TotalTimeSeconds)
suite.Equal(int64(500), doc.Wpm)
suite.Equal(int64(6), doc.SecondsPerPercent)
}
func (suite *DocumentsTestSuite) TestGetDocumentsSize() {
count, err := suite.dbm.Queries.GetDocumentsSize(context.Background(), nil)
suite.NoError(err)
suite.Equal(int64(1), count)
count, err = suite.dbm.Queries.GetDocumentsSize(context.Background(), "%testTitle%")
suite.NoError(err)
suite.Equal(int64(1), count)
count, err = suite.dbm.Queries.GetDocumentsSize(context.Background(), "%missing%")
suite.NoError(err)
suite.Equal(int64(0), count)
}
func (suite *DocumentsTestSuite) TestGetDocumentsWithStatsCount() {
count, err := suite.dbm.Queries.GetDocumentsWithStatsCount(context.Background(), GetDocumentsWithStatsCountParams{})
suite.NoError(err)
suite.Equal(int64(1), count)
count, err = suite.dbm.Queries.GetDocumentsWithStatsCount(context.Background(), GetDocumentsWithStatsCountParams{
Query: ptr.Of("%testTitle%"),
})
suite.NoError(err)
suite.Equal(int64(1), count)
count, err = suite.dbm.Queries.GetDocumentsWithStatsCount(context.Background(), GetDocumentsWithStatsCountParams{
Query: ptr.Of("%testAuthor%"),
})
suite.NoError(err)
suite.Equal(int64(1), count)
count, err = suite.dbm.Queries.GetDocumentsWithStatsCount(context.Background(), GetDocumentsWithStatsCountParams{
Query: ptr.Of("%missing%"),
})
suite.NoError(err)
suite.Equal(int64(0), count)
count, err = suite.dbm.Queries.GetDocumentsWithStatsCount(context.Background(), GetDocumentsWithStatsCountParams{
ID: ptr.Of(documentID),
})
suite.NoError(err)
suite.Equal(int64(1), count)
count, err = suite.dbm.Queries.GetDocumentsWithStatsCount(context.Background(), GetDocumentsWithStatsCountParams{
ID: ptr.Of("missing-id"),
})
suite.NoError(err)
suite.Equal(int64(0), count)
_, err = suite.dbm.Queries.DeleteDocument(context.Background(), documentID)
suite.Require().NoError(err)
count, err = suite.dbm.Queries.GetDocumentsWithStatsCount(context.Background(), GetDocumentsWithStatsCountParams{
Deleted: ptr.Of(false),
})
suite.NoError(err)
suite.Equal(int64(0), count)
count, err = suite.dbm.Queries.GetDocumentsWithStatsCount(context.Background(), GetDocumentsWithStatsCountParams{
Deleted: ptr.Of(true),
})
suite.NoError(err)
suite.Equal(int64(1), count)
count, err = suite.dbm.Queries.GetDocumentsWithStatsCount(context.Background(), GetDocumentsWithStatsCountParams{})
suite.NoError(err)
suite.Equal(int64(1), count)
count, err = suite.dbm.Queries.GetDocumentsWithStatsCount(context.Background(), GetDocumentsWithStatsCountParams{
ID: ptr.Of(documentID),
Deleted: ptr.Of(true),
})
suite.NoError(err)
suite.Equal(int64(1), count)
}
func (suite *DocumentsTestSuite) TestGetDocumentsWithStats() {
suite.seedDocumentStats()
rows, err := suite.dbm.Queries.GetDocumentsWithStats(context.Background(), GetDocumentsWithStatsParams{
UserID: userID,
Deleted: ptr.Of(false),
Query: ptr.Of("%testTitle%"),
Offset: 0,
Limit: 10,
})
suite.NoError(err)
suite.Len(rows, 1)
suite.Equal(documentID, rows[0].ID)
suite.Equal(documentTitle, *rows[0].Title)
suite.Equal(float64(42), rows[0].Percentage)
suite.Equal(int64(60), rows[0].TotalTimeSeconds)
_, err = suite.dbm.Queries.DeleteDocument(context.Background(), documentID)
suite.NoError(err)
rows, err = suite.dbm.Queries.GetDocumentsWithStats(context.Background(), GetDocumentsWithStatsParams{
UserID: userID,
Deleted: ptr.Of(false),
Offset: 0,
Limit: 10,
})
suite.NoError(err)
suite.Len(rows, 0)
}
func (suite *DocumentsTestSuite) TestGetDocumentsWithStatsFilters() {
suite.createTestUserAndDevice()
otherDocID := "testDocument2"
otherTitle := "otherTitle"
otherAuthor := "otherAuthor"
otherWords := int64(3000)
_, err := suite.dbm.Queries.UpsertDocument(context.Background(), UpsertDocumentParams{
ID: otherDocID,
Title: &otherTitle,
Author: &otherAuthor,
Words: &otherWords,
})
suite.Require().NoError(err)
_, err = suite.dbm.Queries.AddActivity(context.Background(), AddActivityParams{
DocumentID: documentID,
DeviceID: deviceID,
UserID: userID,
StartTime: time.Now().Add(-2 * time.Hour).UTC().Format(time.RFC3339),
Duration: 60,
StartPercentage: 0.10,
EndPercentage: 0.20,
})
suite.Require().NoError(err)
_, err = suite.dbm.Queries.UpdateProgress(context.Background(), UpdateProgressParams{
UserID: userID,
DocumentID: documentID,
DeviceID: deviceID,
Percentage: 0.42,
Progress: "/6/2[test]",
})
suite.Require().NoError(err)
_, err = suite.dbm.Queries.AddActivity(context.Background(), AddActivityParams{
DocumentID: otherDocID,
DeviceID: deviceID,
UserID: userID,
StartTime: time.Now().Add(-1 * time.Hour).UTC().Format(time.RFC3339),
Duration: 30,
StartPercentage: 0.20,
EndPercentage: 0.30,
})
suite.Require().NoError(err)
err = suite.dbm.CacheTempTables(context.Background())
suite.Require().NoError(err)
rows, err := suite.dbm.Queries.GetDocumentsWithStats(context.Background(), GetDocumentsWithStatsParams{
UserID: userID,
ID: ptr.Of(documentID),
Offset: 0,
Limit: 10,
})
suite.NoError(err)
suite.Len(rows, 1)
suite.Equal(documentID, rows[0].ID)
rows, err = suite.dbm.Queries.GetDocumentsWithStats(context.Background(), GetDocumentsWithStatsParams{
UserID: userID,
Query: ptr.Of("%otherAuthor%"),
Offset: 0,
Limit: 10,
})
suite.NoError(err)
suite.Len(rows, 1)
suite.Equal(otherDocID, rows[0].ID)
rows, err = suite.dbm.Queries.GetDocumentsWithStats(context.Background(), GetDocumentsWithStatsParams{
UserID: userID,
Query: ptr.Of("%does-not-match%"),
Offset: 0,
Limit: 10,
})
suite.NoError(err)
suite.Len(rows, 0)
_, err = suite.dbm.Queries.DeleteDocument(context.Background(), otherDocID)
suite.Require().NoError(err)
rows, err = suite.dbm.Queries.GetDocumentsWithStats(context.Background(), GetDocumentsWithStatsParams{
UserID: userID,
Deleted: ptr.Of(true),
Offset: 0,
Limit: 10,
})
suite.NoError(err)
suite.Len(rows, 1)
suite.Equal(otherDocID, rows[0].ID)
rows, err = suite.dbm.Queries.GetDocumentsWithStats(context.Background(), GetDocumentsWithStatsParams{
UserID: userID,
Offset: 0,
Limit: 10,
})
suite.NoError(err)
suite.Len(rows, 2)
rows, err = suite.dbm.Queries.GetDocumentsWithStats(context.Background(), GetDocumentsWithStatsParams{
UserID: userID,
Offset: 0,
Limit: 1,
})
suite.NoError(err)
suite.Len(rows, 1)
suite.Equal(otherDocID, rows[0].ID)
rows, err = suite.dbm.Queries.GetDocumentsWithStats(context.Background(), GetDocumentsWithStatsParams{
UserID: userID,
Offset: 1,
Limit: 1,
})
suite.NoError(err)
suite.Len(rows, 1)
suite.Equal(documentID, rows[0].ID)
}
func (suite *DocumentsTestSuite) TestDeleteDocument() {
changed, err := suite.dbm.Queries.DeleteDocument(context.Background(), documentID)
suite.Nil(err, "should have nil err")
@@ -389,10 +85,7 @@ func (suite *DocumentsTestSuite) TestGetDeletedDocuments() {
// TODO - Convert GetWantedDocuments -> (sqlc.slice('document_ids'));
func (suite *DocumentsTestSuite) TestGetWantedDocuments() {
wantedDocs, err := suite.dbm.Queries.GetWantedDocuments(context.Background(), GetWantedDocumentsParams{
JsonEach: fmt.Sprintf("[\"%s\"]", documentID),
DocumentIds: fmt.Sprintf("[\"%s\"]", documentID),
})
wantedDocs, err := suite.dbm.Queries.GetWantedDocuments(context.Background(), fmt.Sprintf("[\"%s\"]", documentID))
suite.Nil(err, "should have nil err")
suite.Len(wantedDocs, 1, "should have one wanted document")
}
+3 -4
View File
@@ -202,7 +202,6 @@ SELECT
END AS REAL), 2) AS percentage,
CAST(CASE
WHEN dus.total_time_seconds IS NULL THEN 0.0
WHEN dus.read_percentage IS NULL OR dus.read_percentage <= 0 THEN 0.0
ELSE
CAST(dus.total_time_seconds AS REAL)
/ (dus.read_percentage * 100.0)
@@ -216,10 +215,10 @@ WHERE
(docs.id = sqlc.narg('id') OR $id IS NULL)
AND (docs.deleted = sqlc.narg(deleted) OR $deleted IS NULL)
AND (
$query IS NULL OR (
(
docs.title LIKE sqlc.narg('query') OR
docs.author LIKE sqlc.narg('query')
)
docs.author LIKE $query
) OR $query IS NULL
)
ORDER BY dus.last_read DESC, docs.created_at DESC
LIMIT $limit
+8 -9
View File
@@ -666,7 +666,6 @@ SELECT
END AS REAL), 2) AS percentage,
CAST(CASE
WHEN dus.total_time_seconds IS NULL THEN 0.0
WHEN dus.read_percentage IS NULL OR dus.read_percentage <= 0 THEN 0.0
ELSE
CAST(dus.total_time_seconds AS REAL)
/ (dus.read_percentage * 100.0)
@@ -680,10 +679,10 @@ WHERE
(docs.id = ?2 OR ?2 IS NULL)
AND (docs.deleted = ?3 OR ?3 IS NULL)
AND (
?4 IS NULL OR (
(
docs.title LIKE ?4 OR
docs.author LIKE ?4
)
) OR ?4 IS NULL
)
ORDER BY dus.last_read DESC, docs.created_at DESC
LIMIT ?6
@@ -691,12 +690,12 @@ OFFSET ?5
`
type GetDocumentsWithStatsParams struct {
UserID string `json:"user_id"`
ID *string `json:"id"`
Deleted *bool `json:"-"`
Query interface{} `json:"query"`
Offset int64 `json:"offset"`
Limit int64 `json:"limit"`
UserID string `json:"user_id"`
ID *string `json:"id"`
Deleted *bool `json:"-"`
Query *string `json:"query"`
Offset int64 `json:"offset"`
Limit int64 `json:"limit"`
}
type GetDocumentsWithStatsRow struct {