Skip to content

Commit 91b318e

Browse files
authored
[major] migrate to typescript, add default and max expiration (SharzyL#58)
1 parent 2e26036 commit 91b318e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+8547
-2802
lines changed

.github/workflows/deploy.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
name: Deploy
1+
name: Test and Deploy
22
on:
33
push:
44
branches:
55
- goshujin
6+
- dev
67

78
jobs:
89
deploy:
@@ -13,16 +14,19 @@ jobs:
1314
- name: "Install Node"
1415
uses: actions/setup-node@v4
1516
with:
16-
node-version: "20"
17+
node-version: "22"
1718
cache: "yarn"
1819

1920
- name: "Setup"
2021
run: yarn install
2122

2223
- name: "Test"
23-
run: yarn test
24+
run: |
25+
yarn tsc --noEmit
26+
yarn test
2427
2528
- name: "Deploy"
29+
if: github.ref == 'refs/heads/main'
2630
env:
2731
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
2832
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}

.github/workflows/pr.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: PR tests
1+
name: PR Tests
22
on:
33
pull_request:
44

@@ -32,6 +32,11 @@ jobs:
3232
run: yarn install
3333

3434
- name: "Test"
35+
run: |
36+
yarn tsc --noEmit
37+
yarn test
38+
39+
- name: "Run Coverage"
3540
run: yarn coverage
3641

3742
- name: "Upload Coverage"

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ Run tests:
104104
$ yarn test
105105
```
106106

107+
Run typing check:
108+
```console
109+
$ yarn tsc --noEmit
110+
```
111+
107112
Run tests with coverage report:
108113
```console
109114
$ yarn coverage

doc/api.md

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ Upload your paste. It accept parameters in form-data:
124124

125125
- `c`: mandatory. The **content** of your paste, text of binary. It should be no larger than 10 MB. The `filename` in its `Content-Disposition` will be present when fetching the paste.
126126

127-
- `e`: optional. The **expiration** time of the paste. After this period of time, the paste is permanently deleted. It should be an integer or a float point number suffixed with an optional unit (seconds by default). Supported units: `s` (seconds), `m` (minutes), `h` (hours), `d` (days), `M` (months). For example, `360.24` means 360.25 seconds; `25d` is interpreted as 25 days. It should be no smaller than 60 seconds due to the limitation of Cloudflare KV storage.
127+
- `e`: optional. The **expiration** time of the paste. After this period of time, the paste is permanently deleted. It should be an integer or a float point number suffixed with an optional unit (seconds by default). Supported units: `s` (seconds), `m` (minutes), `h` (hours), `d` (days), `M` (months). For example, `360.24` means 360.25 seconds; `25d` is interpreted as 25 days. The actual expiration might be shorter than specified expiration due to limitations imposed by the administrator. If unspecified, a default expiration time setting is used.
128128

129129
- `s`: optional. The **password** which allows you to modify and delete the paste. If not specified, the worker will generate a random string as password.
130130

@@ -137,19 +137,19 @@ Upload your paste. It accept parameters in form-data:
137137
```json
138138
{
139139
"url": "https://shz.al/abcd",
140-
"admin": "https://shz.al/abcd:w2eHqyZGc@CQzWLN=BiJiQxZ",
141-
"expire": 100,
142-
"isPrivate": false
140+
"manageUrl": "https://shz.al/abcd:w2eHqyZGc@CQzWLN=BiJiQxZ",
141+
"expirationSeconds": 1209600,
142+
"expireAt": "2025-05-05T10:33:06.114Z"
143143
}
144144
```
145145

146146
Explanation of the fields:
147147

148148
- `url`: String. The URL to fetch the paste. When using a customized name, it looks like `https//shz.al/~myname`.
149-
- `suggestUrl`: String or null. The URL that may carry filename or URL redirection.
150-
- `admin`: String. The URL to update and delete the paste, which is `url` suffixed by `~` and the password.
151-
- `expire`: String or null. The expiration seconds.
152-
- `isPrivate`: Bool. Whether the paste is in private mode.
149+
- `suggestedUrl`: String or null. The URL that may carry filename or URL redirection.
150+
- `manageUrl`: String. The URL to update and delete the paste, which is `url` suffixed by `~` and the password.
151+
- `expirationSeconds`: String. The expiration seconds.
152+
- `expireAt`: String. An ISO String representing when the paste will expire.
153153

154154
If error occurs, the worker returns status code different from `200`:
155155

@@ -164,25 +164,27 @@ Usage example:
164164
$ curl -Fc="kawaii" -Fe=300 -Fn=hitagi https://shz.al # uploading some text
165165
{
166166
"url": "https://shz.al/~hitagi",
167-
"admin": "https://shz.al/~hitagi:22@-OJWcTOH2jprTJWYadmDv",
168-
"isPrivate": false,
169-
"expire": 300
167+
"manageUrl": "https://shz.al/~hitagi:22@-OJWcTOH2jprTJWYadmDv",
168+
"expirationSeconds": 300,
169+
"expireAt": "2025-05-05T10:33:06.114Z"
170170
}
171171

172172
$ curl [email protected] -Fn=panty -Fs=12345678 https://shz.al # uploading a file
173173
{
174174
"url": "https://shz.al/~panty",
175-
"admin": "https://shz.al/~panty:12345678",
176-
"isPrivate": false
175+
"manageUrl": "https://shz.al/~panty:12345678",
176+
"expirationSeconds": 1209600,
177+
"expireAt": "2025-05-05T10:33:06.114Z"
177178
}
178179

179180
# because `curl` takes some characters as filed separator, the fields should be
180181
# quoted by double-quotes if the field contains semicolon or comma
181182
$ curl [email protected] -Fn='"hi/hello;g,ood"' -Fs=12345678 https://shz.al
182183
{
183184
"url": "https://shz.al/~hi/hello;g,ood",
184-
"admin": "https://shz.al/~hi/hello;g,ood:QJhMKh5WR6z36QRAAn5Q5GZh",
185-
"isPrivate": false
185+
"manageUrl": "https://shz.al/~hi/hello;g,ood:QJhMKh5WR6z36QRAAn5Q5GZh",
186+
"expirationSeconds": 1209600,
187+
"expireAt": "2025-05-05T10:33:06.114Z"
186188
}
187189
```
188190

@@ -210,16 +212,17 @@ Usage example:
210212
$ curl -X PUT -Fc="kawaii~" -Fe=500 https://shz.al/~hitagi:22@-OJWcTOH2jprTJWYadmDv
211213
{
212214
"url": "https://shz.al/~hitagi",
213-
"admin": "https://shz.al/~hitagi:22@-OJWcTOH2jprTJWYadmDv",
214-
"isPrivate": false,
215-
"expire": 500
215+
"manageUrl": "https://shz.al/~hitagi:22@-OJWcTOH2jprTJWYadmDv",
216+
"expirationSeconds": 500,
217+
"expireAt": "2025-05-05T10:33:06.114Z"
216218
}
217219

218220
$ curl -X PUT -Fc="kawaii~" https://shz.al/~hitagi:22@-OJWcTOH2jprTJWYadmDv
219221
{
220222
"url": "https://shz.al/~hitagi",
221-
"admin": "https://shz.al/~hitagi:22@-OJWcTOH2jprTJWYadmDv",
222-
"isPrivate": false
223+
"manageUrl": "https://shz.al/~hitagi:22@-OJWcTOH2jprTJWYadmDv",
224+
"expirationSeconds": 500,
225+
"expireAt": "2025-05-05T10:33:06.114Z"
223226
}
224227
```
225228

frontend/index.html

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<h1>Yet Another Pastebin</h1>
1515
<p>This is an open source pastebin deployed on Cloudflare Workers. </p>
1616
<p><b>Usage</b>: paste any text here, submit, then share it with URL. </p>
17-
<p><b>Warning</b>: only for temporary share, files will be deleted without notice!</p>
17+
<p><b>Warning</b>: only for temporary share (<b>max 30 days</b>). Files will be deleted without notice!</p>
1818
<p>Refer to <a href='{{REPO}}'>GitHub</a> for more details.</p>
1919

2020
<div id='paste-input-panel'>
@@ -70,10 +70,10 @@ <h2>Settings</h2>
7070
<span class='small-label'>Example: {{BASE_URL}}/~stocking</span>
7171
</div>
7272
<div class='label-line'>
73-
<input type='radio' name='url-type' value='admin' id='paste-url-admin-radio'>
74-
<label for='paste-url-admin-radio' class='radio-label'>Update or delete an existing paste</label>
75-
<input type='url' spellcheck='false' id='paste-admin-url-input'
76-
name='admin-url' value='{{BASE_URL}}'>
73+
<input type='radio' name='url-type' value='manage' id='paste-url-manage-radio'>
74+
<label for='paste-url-manage-radio' class='radio-label'>Update or delete an existing paste</label>
75+
<input type='url' spellcheck='false' id='paste-manage-url-input'
76+
name='manage-url' value='{{BASE_URL}}'>
7777
</div>
7878
</div>
7979
<div id='paste-uploaded-panel' class='hidden'>
@@ -84,19 +84,16 @@ <h2>Uploaded paste</h2>
8484
</div>
8585
<label for='uploaded-url' class='small-label'>URL</label>
8686
<div class='uploaded-entry'>
87-
<input id='uploaded-admin-url' type='text' class='uploaded-table-text' readonly>
87+
<input id='uploaded-manage-url' type='text' class='uploaded-table-text' readonly>
8888
<button class='copy-button'>Copy</button>
8989
</div>
90-
<label for='suggest-url' class='small-label'>Admin URL</label>
90+
<label for='suggested-url' class='small-label'>Manage URL</label>
9191
<div class='uploaded-entry'>
92-
<input id='uploaded-suggest-url' type='text' class='uploaded-table-text' readonly>
92+
<input id='uploaded-suggested-url' type='text' class='uploaded-table-text' readonly>
9393
<button class='copy-button'>Copy</button>
9494
</div>
95-
<label for='uploaded-admin-url' class='small-label'>Suggest URL</label>
96-
<div class='uploaded-entry'>
97-
<input type='text' id='uploaded-expiration' class='uploaded-table-text' readonly>
98-
</div>
99-
<label for='uploaded-expiration' class='small-label'>Expiration (secs)</label>
95+
<label for='uploaded-manage-url' class='small-label'>Suggest URL</label>
96+
<p id='expiration-message'></p>
10097
</div>
10198
<div>
10299
<button id='submit-button' class='long-button px-3 py-2 my-1'>Upload</button>
Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ window.addEventListener('DOMContentLoaded', () => {
3434

3535
$('#deploy-date').text(getDateString(deploy_date))
3636

37-
function isAdminUrlLegal(url) {
37+
function isManageUrlLegal(url) {
3838
try {
3939
url = new URL(url)
4040
return url.origin === base_url && url.pathname.indexOf(':') >= 0
@@ -65,7 +65,7 @@ window.addEventListener('DOMContentLoaded', () => {
6565
let inputType = 'edit'
6666
let expiration = $('#paste-expiration-input').val()
6767
let passwd = ''
68-
let customName = '', adminUrl = '', file = null
68+
let customName = '', manageUrl = '', file = null
6969

7070
const NAME_REGEX = /^[a-zA-Z0-9+_\-\[\]*$@,;]{3,}$/
7171
const EXPIRE_REGEX = /^\d+\s*[smhdwMY]?$/
@@ -88,35 +88,35 @@ window.addEventListener('DOMContentLoaded', () => {
8888
expirationValid = true
8989
}
9090
const nameValid = urlType !== 'custom' || NAME_REGEX.test(customName)
91-
const adminUrlValid = urlType !== 'admin' || isAdminUrlLegal(adminUrl)
91+
const manageurlValid = urlType !== 'manage' || isManageUrlLegal(manageUrl)
9292

9393
if (!pasteNotEmpty) {
9494
disableSubmitButton('Paste is empty')
9595
} else if (!expirationValid) {
9696
disableSubmitButton(`Expiration “${expiration}” not valid`)
9797
} else if (!nameValid) {
9898
disableSubmitButton(`The customized URL should satisfy regex ${NAME_REGEX}`)
99-
} else if (!adminUrlValid) {
100-
disableSubmitButton(`Admin URL “${adminUrl}” not valid`)
99+
} else if (!manageurlValid) {
100+
disableSubmitButton(`Maange URL “${manageUrl}” not valid`)
101101
} else {
102102
submitButton.addClass('enabled')
103103
submitErrMsg.text('')
104104
}
105105

106-
if (urlType === 'admin') {
106+
if (urlType === 'manage') {
107107
submitButton.text('Update')
108108
deleteButton.removeClass('hidden')
109109
} else {
110110
submitButton.text('Submit')
111111
deleteButton.addClass('hidden')
112112
}
113113

114-
if (adminUrlValid) {
114+
if (manageurlValid) {
115115
deleteButton.addClass('enabled')
116116
submitButton.prop('title', '')
117117
} else {
118118
deleteButton.removeClass('enabled')
119-
submitErrMsg.text(`The admin URL should start with “${base_url}” and contain a colon`)
119+
submitErrMsg.text(`The manage URL should start with “${base_url}” and contain a colon`)
120120
}
121121
}
122122

@@ -175,15 +175,15 @@ window.addEventListener('DOMContentLoaded', () => {
175175
updateButtons()
176176
})
177177

178-
$('#paste-admin-url-input').on('input', event => {
179-
adminUrl = event.target.value
178+
$('#paste-manage-url-input').on('input', event => {
179+
manageUrl = event.target.value
180180
updateButtons()
181181
})
182182

183183
// submit the form
184184
submitButton.on('click', () => {
185185
if (submitButton.hasClass('enabled')) {
186-
if (urlType === 'admin') {
186+
if (urlType === 'manage') {
187187
putPaste()
188188
} else {
189189
postPaste()
@@ -211,7 +211,7 @@ window.addEventListener('DOMContentLoaded', () => {
211211

212212
$.ajax({
213213
method: 'PUT',
214-
url: adminUrl,
214+
url: manageUrl,
215215
data: fd,
216216
processData: false,
217217
contentType: false,
@@ -254,7 +254,7 @@ window.addEventListener('DOMContentLoaded', () => {
254254
let fd = new FormData()
255255
$.ajax({
256256
method: 'DELETE',
257-
url: adminUrl,
257+
url: manageUrl,
258258
data: fd,
259259
processData: false,
260260
success: () => {
@@ -273,12 +273,13 @@ window.addEventListener('DOMContentLoaded', () => {
273273
function renderUploaded(uploaded) {
274274
$('#paste-uploaded-panel').removeClass('hidden')
275275
$('#uploaded-url').prop('value', uploaded.url)
276-
$('#uploaded-admin-url').prop('value', uploaded.admin)
277-
if (uploaded.suggestUrl) {
278-
$('#uploaded-suggest-url').prop('value', uploaded.suggestUrl)
276+
$('#uploaded-manage-url').prop('value', uploaded.manageUrl)
277+
if (uploaded.suggestedUrl) {
278+
$('#uploaded-suggested-url').prop('value', uploaded.suggestedUrl)
279279
}
280-
if (uploaded.expire) {
281-
$('#uploaded-expiration').prop('value', uploaded.expire)
280+
if (uploaded.expiredAt) {
281+
const expiredAt = new Date(uploaded.expiredAt).toLocaleString()
282+
$('#expiration-message').text(`Will expire at ${expiredAt} (${uploaded.expirationSeconds} secs)`)
282283
}
283284
updateButtons()
284285
}
@@ -309,13 +310,13 @@ window.addEventListener('DOMContentLoaded', () => {
309310
$('#submit-button').addClass('enabled')
310311
}
311312

312-
function initAdmin() {
313+
function initManage() {
313314
const { role, short, passwd, ext } = parsePath(location.pathname)
314315
if (passwd.length > 0) {
315-
$('#paste-url-admin-radio').click()
316-
$('#paste-admin-url-input').val(location.href)
317-
urlType = 'admin'
318-
adminUrl = location.href
316+
$('#paste-url-manage-radio').trigger("click")
317+
$('#paste-manage-url-input').val(location.href)
318+
urlType = 'manage'
319+
manageUrl = location.href
319320
updateButtons()
320321
$.ajax({
321322
url: "/" + short,
@@ -328,5 +329,5 @@ window.addEventListener('DOMContentLoaded', () => {
328329
}
329330
}
330331

331-
initAdmin()
332+
initManage()
332333
})

frontend/style.css

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/tsconfig.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"extends": "../tsconfig.json",
3+
"compilerOptions": {
4+
"types": ["dom", "@tyeps/jquery"]
5+
}
6+
}

0 commit comments

Comments
 (0)