feat: v1 API + frontend migration
Some checks failed
continuous-integration/drone/pr Build is failing

This commit is contained in:
2026-07-03 14:06:33 -04:00
parent ff44db311d
commit c9e4d345b8
212 changed files with 26747 additions and 398 deletions

View File

@@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.31.1
package database

View File

@@ -4,10 +4,13 @@ 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 {
@@ -36,12 +39,54 @@ func (suite *DocumentsTestSuite) SetupTest() {
suite.NoError(err)
}
// DOCUMENT - TODO:
// - 󰊕 (q *Queries) GetDocumentProgress
// - 󰊕 (q *Queries) GetDocumentWithStats
// - 󰊕 (q *Queries) GetDocumentsSize
// - 󰊕 (q *Queries) GetDocumentsWithStats
// - 󰊕 (q *Queries) GetMissingDocuments
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)
}
func (suite *DocumentsTestSuite) TestGetDocument() {
doc, err := suite.dbm.Queries.GetDocument(context.Background(), documentID)
suite.Nil(err, "should have nil err")
@@ -63,6 +108,265 @@ 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")
@@ -85,7 +389,10 @@ func (suite *DocumentsTestSuite) TestGetDeletedDocuments() {
// TODO - Convert GetWantedDocuments -> (sqlc.slice('document_ids'));
func (suite *DocumentsTestSuite) TestGetWantedDocuments() {
wantedDocs, err := suite.dbm.Queries.GetWantedDocuments(context.Background(), fmt.Sprintf("[\"%s\"]", documentID))
wantedDocs, err := suite.dbm.Queries.GetWantedDocuments(context.Background(), GetWantedDocumentsParams{
JsonEach: fmt.Sprintf("[\"%s\"]", documentID),
DocumentIds: fmt.Sprintf("[\"%s\"]", documentID),
})
suite.Nil(err, "should have nil err")
suite.Len(wantedDocs, 1, "should have one wanted document")
}

View File

@@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.31.1
package database

View File

@@ -202,6 +202,7 @@ 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)
@@ -215,10 +216,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 $query
) OR $query IS NULL
docs.author LIKE sqlc.narg('query')
)
)
ORDER BY dus.last_read DESC, docs.created_at DESC
LIMIT $limit
@@ -396,3 +397,40 @@ SET
isbn10 = COALESCE(excluded.isbn10, isbn10),
isbn13 = COALESCE(excluded.isbn13, isbn13)
RETURNING *;
-- name: GetDocumentsWithStatsCount :one
SELECT COUNT(*) AS count
FROM documents AS docs
WHERE
(docs.id = sqlc.narg('id') OR $id IS NULL)
AND (docs.deleted = sqlc.narg(deleted) OR $deleted IS NULL)
AND (
(
docs.title LIKE sqlc.narg('query') OR
docs.author LIKE $query
) OR $query IS NULL
);
-- name: GetProgressCount :one
SELECT COUNT(*) AS count
FROM document_progress AS progress
WHERE
progress.user_id = $user_id
AND (
(
CAST($doc_filter AS BOOLEAN) = TRUE
AND document_id = $document_id
) OR $doc_filter = FALSE
);
-- name: GetActivityCount :one
SELECT COUNT(*) AS count
FROM activity
WHERE
activity.user_id = $user_id
AND (
(
CAST($doc_filter AS BOOLEAN) = TRUE
AND document_id = $document_id
) OR $doc_filter = FALSE
);

View File

@@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// sqlc v1.31.1
// source: query.sql
package database
@@ -264,6 +264,32 @@ func (q *Queries) GetActivity(ctx context.Context, arg GetActivityParams) ([]Get
return items, nil
}
const getActivityCount = `-- name: GetActivityCount :one
SELECT COUNT(*) AS count
FROM activity
WHERE
activity.user_id = ?1
AND (
(
CAST(?2 AS BOOLEAN) = TRUE
AND document_id = ?3
) OR ?2 = FALSE
)
`
type GetActivityCountParams struct {
UserID string `json:"user_id"`
DocFilter bool `json:"doc_filter"`
DocumentID string `json:"document_id"`
}
func (q *Queries) GetActivityCount(ctx context.Context, arg GetActivityCountParams) (int64, error) {
row := q.db.QueryRowContext(ctx, getActivityCount, arg.UserID, arg.DocFilter, arg.DocumentID)
var count int64
err := row.Scan(&count)
return count, err
}
const getDailyReadStats = `-- name: GetDailyReadStats :many
WITH RECURSIVE last_30_days AS (
SELECT LOCAL_DATE(STRFTIME('%Y-%m-%dT%H:%M:%SZ', 'now'), timezone) AS date
@@ -640,6 +666,7 @@ 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)
@@ -653,10 +680,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
@@ -664,12 +691,12 @@ OFFSET ?5
`
type GetDocumentsWithStatsParams struct {
UserID string `json:"user_id"`
ID *string `json:"id"`
Deleted *bool `json:"-"`
Query *string `json:"query"`
Offset int64 `json:"offset"`
Limit int64 `json:"limit"`
UserID string `json:"user_id"`
ID *string `json:"id"`
Deleted *bool `json:"-"`
Query interface{} `json:"query"`
Offset int64 `json:"offset"`
Limit int64 `json:"limit"`
}
type GetDocumentsWithStatsRow struct {
@@ -734,6 +761,33 @@ func (q *Queries) GetDocumentsWithStats(ctx context.Context, arg GetDocumentsWit
return items, nil
}
const getDocumentsWithStatsCount = `-- name: GetDocumentsWithStatsCount :one
SELECT COUNT(*) AS count
FROM documents AS docs
WHERE
(docs.id = ?1 OR ?1 IS NULL)
AND (docs.deleted = ?2 OR ?2 IS NULL)
AND (
(
docs.title LIKE ?3 OR
docs.author LIKE ?3
) OR ?3 IS NULL
)
`
type GetDocumentsWithStatsCountParams struct {
ID *string `json:"id"`
Deleted *bool `json:"-"`
Query *string `json:"query"`
}
func (q *Queries) GetDocumentsWithStatsCount(ctx context.Context, arg GetDocumentsWithStatsCountParams) (int64, error) {
row := q.db.QueryRowContext(ctx, getDocumentsWithStatsCount, arg.ID, arg.Deleted, arg.Query)
var count int64
err := row.Scan(&count)
return count, err
}
const getLastActivity = `-- name: GetLastActivity :one
SELECT start_time
FROM activity
@@ -897,6 +951,32 @@ func (q *Queries) GetProgress(ctx context.Context, arg GetProgressParams) ([]Get
return items, nil
}
const getProgressCount = `-- name: GetProgressCount :one
SELECT COUNT(*) AS count
FROM document_progress AS progress
WHERE
progress.user_id = ?1
AND (
(
CAST(?2 AS BOOLEAN) = TRUE
AND document_id = ?3
) OR ?2 = FALSE
)
`
type GetProgressCountParams struct {
UserID string `json:"user_id"`
DocFilter bool `json:"doc_filter"`
DocumentID string `json:"document_id"`
}
func (q *Queries) GetProgressCount(ctx context.Context, arg GetProgressCountParams) (int64, error) {
row := q.db.QueryRowContext(ctx, getProgressCount, arg.UserID, arg.DocFilter, arg.DocumentID)
var count int64
err := row.Scan(&count)
return count, err
}
const getUser = `-- name: GetUser :one
SELECT id, pass, auth_hash, admin, timezone, created_at FROM users
WHERE id = ?1 LIMIT 1
@@ -1088,17 +1168,22 @@ WHERE (
AND documents.filepath IS NULL
)
OR (documents.id IS NULL)
OR CAST(?1 AS TEXT) != CAST(?1 AS TEXT)
OR CAST(?2 AS TEXT) != CAST(?2 AS TEXT)
`
type GetWantedDocumentsParams struct {
JsonEach interface{} `json:"json_each"`
DocumentIds string `json:"document_ids"`
}
type GetWantedDocumentsRow struct {
ID string `json:"id"`
WantFile bool `json:"want_file"`
WantMetadata bool `json:"want_metadata"`
}
func (q *Queries) GetWantedDocuments(ctx context.Context, documentIds string) ([]GetWantedDocumentsRow, error) {
rows, err := q.db.QueryContext(ctx, getWantedDocuments, documentIds)
func (q *Queries) GetWantedDocuments(ctx context.Context, arg GetWantedDocumentsParams) ([]GetWantedDocumentsRow, error) {
rows, err := q.db.QueryContext(ctx, getWantedDocuments, arg.JsonEach, arg.DocumentIds)
if err != nil {
return nil, err
}