Skip to content

Commit 6641e09

Browse files
committed
redirect to latest version if old version couldn't be found & implement document file expiration
1 parent d5d9ef2 commit 6641e09

File tree

14 files changed

+348
-186
lines changed

14 files changed

+348
-186
lines changed

README.md

Lines changed: 66 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -503,17 +503,19 @@ See below for more information.
503503

504504
To create a document with a single file you have to send a `POST` request to `/documents` with the `content` as body.
505505

506-
| Header | Type | Description |
507-
|---------------------|--------|---------------------------------------|
508-
| Content-Disposition | string | The form & file name of the document. |
509-
| Content-Type? | string | The content type of the document. |
510-
| Language? | string | The language of the document. |
511-
512-
| Query Parameter | Type | Description |
513-
|-----------------|------------------------------|----------------------------------------------|
514-
| language? | [language](#language-enum) | The language of the document. |
515-
| formatter? | [formatter](#formatter-enum) | With which formatter to render the document. |
516-
| style? | style name | Which style to use for the formatter |
506+
| Header | Type | Description |
507+
|---------------------|-----------|---------------------------------------------------------|
508+
| Content-Disposition | string | The form & file name of the document. |
509+
| Content-Type? | string | The content type of the document. |
510+
| Language? | string | The language of the document. |
511+
| Expires-At? | Timestamp | When the document file should expire in RFC 3339 format |
512+
513+
| Query Parameter | Type | Description |
514+
|-----------------|------------------------------|---------------------------------------------------------|
515+
| language? | [language](#language-enum) | The language of the document. |
516+
| formatter? | [formatter](#formatter-enum) | With which formatter to render the document. |
517+
| style? | style name | Which style to use for the formatter |
518+
| expires_at? | Timestamp | When the document file should expire in RFC 3339 format |
517519

518520
<details>
519521
<summary>Example</summary>
@@ -540,11 +542,12 @@ second `file-1` and so on.
540542
| formatter? | [formatter](#formatter-enum) | With which formatter to render the document. |
541543
| style? | style name | Which style to use for the formatter |
542544

543-
| Part Header | Type | Description |
544-
|---------------------|--------|--------------------------------------------|
545-
| Content-Disposition | string | The form & file name of the document. |
546-
| Content-Type? | string | The content type/language of the document. |
547-
| Language? | string | The language of the document. |
545+
| Part Header | Type | Description |
546+
|---------------------|-----------|---------------------------------------------------------|
547+
| Content-Disposition | string | The form & file name of the document. |
548+
| Content-Type? | string | The content type/language of the document. |
549+
| Language? | string | The language of the document. |
550+
| Expires-At? | Timestamp | When the document file should expire in RFC 3339 format |
548551

549552
<details>
550553
<summary>Example</summary>
@@ -584,14 +587,16 @@ update the document.
584587
"content": "package main\n\nfunc main() {\n println(\"Hello World!\")\n}",
585588
// only if formatter is set
586589
"formatted": "...",
587-
"language": "Go"
590+
"language": "Go",
591+
"expires_at": null
588592
},
589593
{
590594
"name": "untitled1",
591595
"content": "Hello World!",
592596
// only if formatter is set
593597
"formatted": "...",
594-
"language": "plaintext"
598+
"language": "plaintext",
599+
"expires_at": null
595600
}
596601
],
597602
"token": "kiczgez33j7qkvqdg9f7ksrd8jk88wba"
@@ -623,14 +628,16 @@ The response will be a `200 OK` with the document content as `application/json`
623628
"content": "package main\n\nfunc main() {\n println(\"Hello World!\")\n}",
624629
// only if formatter is set
625630
"formatted": "...",
626-
"language": "Go"
631+
"language": "Go",
632+
"expires_at": null
627633
},
628634
{
629635
"name": "untitled1",
630636
"content": "Hello World!",
631637
// only if formatter is set
632638
"formatted": "...",
633-
"language": "plaintext"
639+
"language": "plaintext",
640+
"expires_at": null
634641
}
635642
]
636643
}
@@ -659,7 +666,8 @@ The response will be a `200 OK` with the document content as `application/json`
659666
"content": "package main\n\nfunc main() {\n println(\"Hello World!\")\n}",
660667
// only if formatter is set
661668
"formatted": "...",
662-
"language": "Go"
669+
"language": "Go",
670+
"expires_at": null
663671
}
664672
```
665673

@@ -689,15 +697,17 @@ The response will be a `200 OK` with the document content as `application/json`
689697
"content": "package main\n\nfunc main() {\n println(\"Hello World!\")\n}",
690698
// only if formatter is set
691699
"formatted": "...",
692-
"language": "Go"
700+
"language": "Go",
701+
"expires_at": null
693702
},
694703
{
695704
"name": "untitled1",
696705
// only if withContent is set
697706
"content": "Hello World!",
698707
// only if formatter is set
699708
"formatted": "...",
700-
"language": "plaintext"
709+
"language": "plaintext",
710+
"expires_at": null
701711
}
702712
]
703713
},
@@ -710,14 +720,16 @@ The response will be a `200 OK` with the document content as `application/json`
710720
"content": "package main\n\nfunc main() {\n println(\"Hello!\")\n}",
711721
// only if formatter is set
712722
"formatted": "...",
713-
"language": "Go"
723+
"language": "Go",
724+
"expires_at": null
714725
},
715726
{
716727
"name": "untitled1",
717728
"content": "Hello!",
718729
// only if formatter is set
719730
"formatted": "...",
720-
"language": "plaintext"
731+
"language": "plaintext",
732+
"expires_at": null
721733
}
722734
]
723735
}
@@ -738,18 +750,20 @@ as `multipart/form-data`. See below for more information.
738750
To create a document with a single file you have to send a `PATCH` request to `/documents/{key}` with the `content` as
739751
body.
740752

741-
| Header | Type | Description |
742-
|---------------------|--------|-----------------------------------------------------------|
743-
| Content-Disposition | string | The form & file name of the document. |
744-
| Content-Type? | string | The content type of the document. |
745-
| Language? | string | The language of the document. |
746-
| Authorization? | string | The update token of the document. (prefix with `Bearer `) |
747-
748-
| Query Parameter | Type | Description |
749-
|-----------------|------------------------------|----------------------------------------------|
750-
| language? | [language](#language-enum) | The language of the document. |
751-
| formatter? | [formatter](#formatter-enum) | With which formatter to render the document. |
752-
| style? | style name | Which style to use for the formatter |
753+
| Header | Type | Description |
754+
|---------------------|-----------|-----------------------------------------------------------|
755+
| Content-Disposition | string | The form & file name of the document. |
756+
| Content-Type? | string | The content type of the document. |
757+
| Language? | string | The language of the document. |
758+
| Authorization? | string | The update token of the document. (prefix with `Bearer `) |
759+
| Expires-At? | Timestamp | When the document file should expire in RFC 3339 format |
760+
761+
| Query Parameter | Type | Description |
762+
|-----------------|------------------------------|---------------------------------------------------------|
763+
| language? | [language](#language-enum) | The language of the document. |
764+
| formatter? | [formatter](#formatter-enum) | With which formatter to render the document. |
765+
| style? | style name | Which style to use for the formatter |
766+
| expires_at? | Timestamp | When the document file should expire in RFC 3339 format |
753767

754768
<details>
755769
<summary>Example</summary>
@@ -780,11 +794,12 @@ second `file-1` and so on.
780794
| formatter? | [formatter](#formatter-enum) | With which formatter to render the document. |
781795
| style? | style name | Which style to use for the formatter |
782796

783-
| Part Header | Type | Description |
784-
|---------------------|--------|---------------------------------------|
785-
| Content-Disposition | string | The form & file name of the document. |
786-
| Content-Type? | string | The content type of the document. |
787-
| Language? | string | The language of the document. |
797+
| Part Header | Type | Description |
798+
|---------------------|-----------|---------------------------------------------------------|
799+
| Content-Disposition | string | The form & file name of the document. |
800+
| Content-Type? | string | The content type of the document. |
801+
| Language? | string | The language of the document. |
802+
| Expires-At? | Timestamp | When the document file should expire in RFC 3339 format |
788803

789804
<details>
790805
<summary>Example</summary>
@@ -822,14 +837,16 @@ update the document.
822837
"content": "package main\n\nfunc main() {\n println(\"Hello World Updated!\")\n}",
823838
// only if formatter is set
824839
"formatted": "...",
825-
"language": "Go"
840+
"language": "Go",
841+
"expires_at": null
826842
},
827843
{
828844
"name": "untitled1",
829845
"content": "Hello World Updated!",
830846
// only if formatter is set
831847
"formatted": "...",
832-
"language": "plaintext"
848+
"language": "plaintext",
849+
"expires_at": null
833850
}
834851
],
835852
"token": "kiczgez33j7qkvqdg9f7ksrd8jk88wba"
@@ -875,14 +892,16 @@ the document.
875892
"content": "package main\n\nfunc main() {\n println(\"Hello World Updated!\")\n}",
876893
// only if formatter is set
877894
"formatted": "...",
878-
"language": "Go"
895+
"language": "Go",
896+
"expires_at": null
879897
},
880898
{
881899
"name": "untitled1",
882900
"content": "Hello World Updated!",
883901
// only if formatter is set
884902
"formatted": "...",
885-
"language": "plaintext"
903+
"language": "plaintext",
904+
"expires_at": null
886905
}
887906
]
888907
}
@@ -965,6 +984,7 @@ following JSON body:
965984
"name": "main.go",
966985
"content": "package main\n\nfunc main() {\n println(\"Hello World Updated!\")\n}",
967986
"language": "Go",
987+
"expires_at": null
968988
}
969989
]
970990
}
@@ -1116,10 +1136,6 @@ A successful request will return a `204 No Content` response with an empty body.
11161136
for `GET /documents/{key}`.
11171137
- `GET`/`HEAD` `/{key}/{version}/preview` - Get the preview of a document version, query parameters are the same as
11181138
for `GET /documents/{key}/versions/{version}`.
1119-
- `GET`/`HEAD` `/documents/{key}/preview` - Get the preview of a document, query parameters are the same as
1120-
for `GET /documents/{key}`.
1121-
- `GET`/`HEAD` `/documents/{key}/versions/{version}/preview` - Get the preview of a document version, query parameters
1122-
are the same as for `GET /documents/{key}/versions/{version}`.
11231139
- `GET`/`HEAD` `/raw/{key}` - Get the raw content of a document, query parameters are the same as
11241140
for `GET /documents/{key}`.
11251141
- `GET`/`HEAD` `/raw/{key}/files/{filename}` - Get the raw content of a document file, query parameters are the same as

gobin/database/db.go

Lines changed: 5 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@ import (
1515
"github.com/jackc/pgx/v5/stdlib"
1616
"github.com/jackc/pgx/v5/tracelog"
1717
"github.com/jmoiron/sqlx"
18-
"github.com/topi314/tint"
1918
"go.opentelemetry.io/otel/attribute"
20-
"go.opentelemetry.io/otel/codes"
2119
"go.opentelemetry.io/otel/semconv/v1.21.0"
2220
"go.opentelemetry.io/otel/trace"
2321
_ "modernc.org/sqlite"
@@ -142,67 +140,24 @@ func New(ctx context.Context, cfg Config, schema string) (*DB, error) {
142140
return nil, fmt.Errorf("failed to execute schema: %w", err)
143141
}
144142

145-
cleanupContext, cancel := context.WithCancel(context.Background())
146143
db := &DB{
147-
dbx: dbx,
148-
cleanupCancel: cancel,
149-
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
144+
dbx: dbx,
145+
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
150146
}
151147

152-
go db.cleanup(cleanupContext, cfg.CleanupInterval, cfg.ExpireAfter)
153-
154148
return db, nil
155149
}
156150

157151
type DB struct {
158-
dbx *sqlx.DB
159-
cleanupCancel context.CancelFunc
160-
rand *rand.Rand
161-
tracer trace.Tracer
152+
dbx *sqlx.DB
153+
rand *rand.Rand
154+
tracer trace.Tracer
162155
}
163156

164157
func (d *DB) Close() error {
165158
return d.dbx.Close()
166159
}
167160

168-
func (d *DB) cleanup(ctx context.Context, cleanUpInterval time.Duration, expireAfter time.Duration) {
169-
if expireAfter <= 0 {
170-
return
171-
}
172-
if cleanUpInterval <= 0 {
173-
cleanUpInterval = 10 * time.Minute
174-
}
175-
slog.Info("Starting document cleanup...")
176-
ticker := time.NewTicker(cleanUpInterval)
177-
defer func() {
178-
ticker.Stop()
179-
slog.Info("document cleanup stopped")
180-
}()
181-
182-
for {
183-
select {
184-
case <-ctx.Done():
185-
return
186-
case <-ticker.C:
187-
d.doCleanup(expireAfter)
188-
}
189-
}
190-
}
191-
192-
func (d *DB) doCleanup(expireAfter time.Duration) {
193-
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
194-
defer cancel()
195-
196-
ctx, span := d.tracer.Start(ctx, "doCleanup")
197-
defer span.End()
198-
199-
if err := d.DeleteExpiredDocuments(ctx, expireAfter); err != nil && !errors.Is(err, context.Canceled) {
200-
span.SetStatus(codes.Error, "failed to delete expired documents")
201-
span.RecordError(err)
202-
slog.ErrorContext(ctx, "failed to delete expired documents", tint.Err(err))
203-
}
204-
}
205-
206161
func (d *DB) randomString(length int) string {
207162
b := make([]rune, length)
208163
for i := range b {

0 commit comments

Comments
 (0)