Skip to content

Commit

Permalink
Merge pull request #3 from not-three/feat/save-duration
Browse files Browse the repository at this point in the history
feature: save duration
fix: #4
  • Loading branch information
scolastico authored May 28, 2024
2 parents 6c00af9 + 98d085b commit a356e40
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 27 deletions.
10 changes: 6 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@ ARG NUXT_APP_BASE_URL=/
ENV NUXT_APP_BASE_URL=$NUXT_APP_BASE_URL

RUN pnpm build
RUN cp -r .output /app/SERVER_OUTPUT
RUN cp -rL .output /app/SERVER_OUTPUT

RUN pnpm generate
RUN cp -r .output/public /app/CLIENT_OUTPUT
RUN cp -rL .output/public /app/CLIENT_OUTPUT

RUN mkdir /app/SERVER_OUTPUT/public/node_modules && \
mkdir /app/CLIENT_OUTPUT/node_modules && \
cp -r node_modules/monaco-editor-workers /app/SERVER_OUTPUT/public/node_modules/monaco-editor-workers && \
cp -r node_modules/monaco-editor /app/CLIENT_OUTPUT/node_modules/monaco-editor
cp -rL node_modules/monaco-editor-workers /app/SERVER_OUTPUT/public/node_modules/monaco-editor-workers && \
cp -rL node_modules/monaco-editor /app/SERVER_OUTPUT/public/node_modules/monaco-editor && \
cp -rL node_modules/monaco-editor-workers /app/CLIENT_OUTPUT/node_modules/monaco-editor-workers && \
cp -rL node_modules/monaco-editor /app/CLIENT_OUTPUT/node_modules/monaco-editor

### Production Image
FROM node:20-alpine
Expand Down
47 changes: 42 additions & 5 deletions components/nav-bar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,28 @@
<yes-no
title="Are you sure?"
:message="warning"
:visible="visible"
:visible="visible === 1"
@yes="copyDecrypted"
@no="visible = false"
@no="visible = 0"
/>
<yes-no
title="Save File"
message="How many days should the file be stored?"
:visible="visible === 2"
alt-yes="Save"
alt-no="Cancel"
@yes="saveCustomTime"
@no="visible = 0"
>
<input
v-model.number="expiresCustomTime"
class="mb-4 w-full text-center text-black"
type="number"
min="1"
:max="maxExpireDays"
step="1"
/>
</yes-no>
<img id="logo" src="/assets/img/icon.svg" class="h-5 w-5 border-white border" alt="!3" />
<h1 class="font-bold select-none transition-all duration-200 max-w-0 -mr-1 overflow-hidden whitespace-pre">
not-th.re
Expand Down Expand Up @@ -43,6 +61,13 @@ const props = defineProps<{
const expiresObject = ref({ hours: 0, minutes: 0, seconds: 0 })
const expiresString = ref('XX:XX:XX')
const scheduler = ref(0)
const maxExpireDays = ref(30)
const expiresCustomTime = ref(30)
watch(() => props.defaultExpires, (value) => {
maxExpireDays.value = Math.floor(value / 1000 / 60 / 60 / 24)
expiresCustomTime.value = maxExpireDays.value
}, { immediate: true })
watch(() => props.expires, (value) => {
if (!props.expires) {
Expand Down Expand Up @@ -84,7 +109,7 @@ onUnmounted(() => {
clearTimeout(scheduler.value)
})
const visible = ref(false)
const visible = ref(0)
const route = useRoute()
const warning = [
'With this url the server will be able to decrypt your data.',
Expand All @@ -97,6 +122,7 @@ const entries = computed(() => ([
name: 'file',
entries: [
['save', 'Save for ' + Math.floor(props.defaultExpires / 1000 / 60 / 60 / 24) + ' days'],
['save-custom', 'Save for custom time'],
['duplicate', 'Duplicate'],
['new', 'New'],
] as [string, string][],
Expand Down Expand Up @@ -131,7 +157,7 @@ function getBaseURL() {
function copyDecrypted() {
const url = getBaseURL();
navigator.clipboard.writeText(`${url}decrypt/${route.params.id}/${location.hash.substring(1)}`)
visible.value = false
visible.value = 0
}
function handle(entry: string) {
Expand All @@ -148,7 +174,7 @@ function handle(entry: string) {
navigator.clipboard.writeText(`${url}raw/${route.params.id}`)
break
case 'share-decrypted':
visible.value = true
visible.value = 1
break
case 'github':
window.open('https://github.com/not-three/main', '_blank')
Expand All @@ -160,9 +186,20 @@ function handle(entry: string) {
console.log(JSON.stringify(props.config))
window.open(props.config.terms, '_blank')
break
case 'save-custom':
visible.value = 2
break
default:
if (!['save', 'duplicate', 'new'].includes(entry)) throw new Error('Invalid entry')
emit(entry as any);
}
}
function saveCustomTime() {
const days = expiresCustomTime.value;
if (days < 0 || days > maxExpireDays.value) return;
expiresCustomTime.value = days;
emit('save', days * 24 * 60 * 60 * 1000);
visible.value = 0;
}
</script>
11 changes: 9 additions & 2 deletions components/yes-no.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<p class="my-4">
{{ props.message }}
</p>
<slot />
<div class="grid grid-cols-2 gap-2 text-black">
<button
@click="emits('yes')"
Expand All @@ -33,8 +34,13 @@
@click="emits('no')"
class="yes-no-button"
>
<span class="font-bold underline">N</span>
<span>o</span>
<template v-if="props.altNo">
{{ props.altNo }}
</template>
<template v-else>
<span class="font-bold underline">N</span>
<span>o</span>
</template>
</button>
</div>
</div>
Expand All @@ -50,6 +56,7 @@ const props = defineProps<{
message: string;
disableNo?: boolean;
altYes?: string;
altNo?: string;
}>();
const emits = defineEmits(['yes', 'no'])
watch(() => props.visible, (value) => {
Expand Down
32 changes: 21 additions & 11 deletions mixins/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ export default defineNuxtComponent({
api: null as any,
errorVisible: false,
errorMessage: '',
defaultExpires: 0,
defaultExpires: 1,
}),
async mounted() {
const handler = (event: any) => {
if (this.readOnly) return;
if (event.origin !== location.origin) return;
if (event.data?.type !== 'DUPLICATE_SHARE') return;
this.content = event.data.content;
window.removeEventListener('message', handler);
event.source.postMessage({ type: 'DUPLICATE_SHARE_OK' }, event.origin);
}
window.addEventListener('message', handler);
const api = await this.getApi()
Expand All @@ -35,34 +37,42 @@ export default defineNuxtComponent({
this.errorMessage = message;
this.errorVisible = true;
},
async saveD() {
async saveD(expires?: number) {
if (this.readOnly) return this.showError('Cannot save readonly note');
if (!this.content) return this.showError('No content to save');
const secret = Math.random().toString(36).substring(2);
const encrypted = CryptoJS.AES.encrypt(this.content, secret).toString();
const res = await (await this.getApi()).post('create', { content: encrypted });
const res = await (await this.getApi()).post('create', {
...(expires ? { expires } : {}),
content: encrypted,
});
// window.location.href = `/q/${res.data.id}#${secret}`;
this.$router.push('/q/' + res.data.id + '#' + secret);
},
async duplicateD() {
if (!this.content) return this.showError('No content to duplicate');
const win = window.open(location.origin, '_blank');
win?.addEventListener('load', async () => {
let counter = 0;
let interval = window.setInterval(() => {
if (counter++ > 200) {
window.clearInterval(interval);
}
win?.postMessage({ type: 'DUPLICATE_SHARE', content: this.content }, location.origin);
}, 100);
let ok = false;
let tries = 0;
win.addEventListener('message', (event) => {
if (event.origin !== location.origin) return;
if (event.data?.type !== 'DUPLICATE_SHARE_OK') return;
ok = true;
});
while (!ok && tries < 200) {
win.postMessage({ type: 'DUPLICATE_SHARE', content: this.content }, location.origin);
tries++;
await new Promise(r => setTimeout(r, 100));
}
});
},
async newD() {
window.open(location.origin, '_blank');
},
async getApi(): Promise<AxiosInstance> {
if (this.api) return this.api;
this.configData = (await Axios.get('/config.json')).data;
this.configData = (await Axios.get(this.$config.app.baseURL + 'config.json')).data;
this.api = Axios.create({
baseURL: this.configData.baseURL,
})
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"type": "module",
"scripts": {
"build": "nuxt build",
"dev": "USE_SQLITE=true nuxt dev",
"dev": "IP_HEADER=false USE_SQLITE=true nuxt dev",
"generate": "SKIP_DB=true nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare",
Expand Down
2 changes: 1 addition & 1 deletion pages/q/[id].vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ export default defineNuxtComponent({
}),
async mounted() {
try {
this.readOnly = true;
const api = await this.getApi();
const secret = location.hash.substring(1);
this.decryptURL = api.defaults.baseURL + `decrypt/${this.$route.params.id}/${secret}`;
const res = await api.get(`json/${this.$route.params.id}`);
this.content = CryptoJS.AES.decrypt(res.data.content, secret).toString(CryptoJS.enc.Utf8);
this.isReady = true;
this.readOnly = true;
this.expires = res.data.expires;
} catch (e) {
console.error(e);
Expand Down
19 changes: 17 additions & 2 deletions server/api/create.post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { nanoid } from 'nanoid'
export default defineEventHandler(async (event) => {
const db = event.context.db as KnexInstance;
const body = await readBody(event);
const defaultExpires = 1000 * (Number(process.env.EXPIRY) || 60 * 60 * 24 * 30);
if (typeof body.content !== 'string') {
setResponseStatus(event, 400);
return {success: false, error: 'Invalid content'}
Expand All @@ -16,7 +17,21 @@ export default defineEventHandler(async (event) => {
setResponseStatus(event, 400);
return {success: false, error: 'Content too short'}
}
const realIP = event.node.req.connection.remoteAddress;
if (body.expires) {
if (typeof body.expires !== 'number') {
setResponseStatus(event, 400);
return {success: false, error: 'Invalid expiry'}
}
if (body.expires < 30_000) {
setResponseStatus(event, 400);
return {success: false, error: 'Expiry too short'}
}
if (body.expires > defaultExpires) {
setResponseStatus(event, 400);
return {success: false, error: 'Expiry too long'}
}
}
const realIP = event.node.req.socket.remoteAddress || '127.0.0.1';
const ipEnv = process.env.IP_HEADER;
const ip = ipEnv !== 'false' ? event.node.req.headers[ipEnv || 'x-real-ip'] || realIP : realIP;
const last60Minutes = new Date(Date.now() - (1000 * 60 * 60));
Expand All @@ -31,7 +46,7 @@ export default defineEventHandler(async (event) => {
id, ip,
content: body.content,
created_at: db.fn.now(),
expires_at: new Date(Date.now() + (1000 * (Number(process.env.EXPIRY) || 60 * 60 * 24 * 30))),
expires_at: new Date(Date.now() + (body.expires || defaultExpires)),
});
return {success: true, id};
})
2 changes: 1 addition & 1 deletion server/api/status.get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ export default defineEventHandler(async (event) => {
}
return {
success: true,
count: Object.entries(count as any)[0][1] as number,
count: Number(Object.entries(count as any)[0][1]),
};
});

0 comments on commit a356e40

Please sign in to comment.