Skip to content

Commit

Permalink
Use a CDN for file upload links (#174)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmyersdev authored Mar 31, 2022
1 parent 5ea6331 commit 6d8ee46
Show file tree
Hide file tree
Showing 14 changed files with 326 additions and 268 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
FIREBASE_PROJECT_ID=
FIREBASE_TOKEN=
VITE_APP_CDN_URL=
VITE_DISCORD_INVITE_LINK=
VITE_FATHOM_EVENT_CTA_CONTINUE_WITH_PRO=
VITE_FATHOM_EVENT_CTA_MODAL_UPGRADE=
Expand All @@ -20,6 +21,8 @@ VITE_FIREBASE_EMULATOR_AUTH=
VITE_FIREBASE_EMULATOR_BYPASS=
VITE_FIREBASE_EMULATOR_FIRESTORE_HOST=
VITE_FIREBASE_EMULATOR_FIRESTORE_PORT=
VITE_FIREBASE_EMULATOR_STORAGE_HOST=
VITE_FIREBASE_EMULATOR_STORAGE_PORT=
VITE_FIREBASE_LOG_LEVEL=
VITE_FIREBASE_MESSAGING_SENDER_ID=
VITE_FIREBASE_PROJECT_ID=
Expand Down
36 changes: 36 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: CI Checks

on:
pull_request

jobs:
build:
runs-on: ubuntu-latest
env:
VITE_APP_CDN_URL: ""
VITE_DISCORD_INVITE_LINK: ""
VITE_FATHOM_EVENT_CTA_CONTINUE_WITH_PRO: ""
VITE_FATHOM_EVENT_CTA_MODAL_UPGRADE: ""
VITE_FATHOM_EVENT_CTA_OPEN_APP: ""
VITE_FATHOM_EVENT_CTA_SETTINGS_UPGRADE: ""
VITE_FATHOM_EVENT_CTA_SIGN_UP_NOW: ""
VITE_FATHOM_EVENT_CTA_TRY_IT_OUT: ""
VITE_FATHOM_EVENT_CTA_UPGRADE_TO_PRO: ""
VITE_FATHOM_GOAL_ACCOUNT_REGISTRATION: ""
VITE_FATHOM_GOAL_CTA_SYNC_DOCS: ""
VITE_FATHOM_SITE_ID: ""
VITE_FATHOM_SITE_URL: ""
VITE_FIREBASE_API_KEY: ""
VITE_FIREBASE_APP_ID: ""
VITE_FIREBASE_AUTH_DOMAIN: ""
VITE_FIREBASE_DATABASE_URL: ""
VITE_FIREBASE_LOG_LEVEL: ""
VITE_FIREBASE_MESSAGING_SENDER_ID: ""
VITE_FIREBASE_PROJECT_ID: ""
VITE_FIREBASE_STORAGE_BUCKET: ""
VITE_STRIPE_MONTHLY_PRICE: ""
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- run: yarn install --frozen-lockfile
- run: yarn build
1 change: 1 addition & 0 deletions .github/workflows/gh-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ jobs:
runs-on: ubuntu-latest
environment: github-pages
env:
VITE_APP_CDN_URL: ${{ secrets.VITE_APP_CDN_URL }}
VITE_DISCORD_INVITE_LINK: ${{ secrets.VITE_DISCORD_INVITE_LINK }}
VITE_FATHOM_EVENT_CTA_CONTINUE_WITH_PRO: ${{ secrets.VUE_APP_FATHOM_EVENT_CTA_CONTINUE_WITH_PRO }}
VITE_FATHOM_EVENT_CTA_MODAL_UPGRADE: ${{ secrets.VITE_FATHOM_EVENT_CTA_MODAL_UPGRADE }}
Expand Down
25 changes: 0 additions & 25 deletions .github/workflows/package.yml

This file was deleted.

4 changes: 4 additions & 0 deletions firebase/firebase.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
"host": "0.0.0.0",
"port": 32776
},
"storage": {
"host": "0.0.0.0",
"port": 32778
},
"ui": {
"enabled": true,
"host": "0.0.0.0",
Expand Down
56 changes: 34 additions & 22 deletions firebase/storage.rules
Original file line number Diff line number Diff line change
@@ -1,28 +1,40 @@
rules_version = '2';
rules_version = '2'

service firebase.storage {
match /b/{bucket}/o {
match /files/v1/{uid} {
match /{file=**} {
allow read: if true;
allow write: if authenticated() && authorized() && pro() && underQuota();
}

function authenticated() {
return request.auth != null;
}

function authorized() {
return request.auth.uid == uid;
}

function pro() {
return request.auth.token.stripeRole == 'subscriber';
}

function underQuota() {
return request.resource.size < 10 * 1024 * 1024
}
match /{file=**} {
allow read: if true
allow create: if allowed() && ownsRequest()
allow delete: if allowed() && ownsResource()
allow update: if allowed() && ownsRequest() && ownsResource()
}

function allowed() {
return authenticated() && underQuota() && (ambassador() || pro())
}

function ambassador() {
return request.auth.token.ambassador
}

function authenticated() {
return request.auth != null
}

function ownsResource() {
return request.auth.uid == resource.metadata.ownerId
}

function ownsRequest() {
return request.auth.uid == request.resource.metadata.ownerId
}

function pro() {
return request.auth.token.stripeRole == 'subscriber'
}

function underQuota() {
return request.resource.size < 10 * 1024 * 1024
}
}
}
2 changes: 1 addition & 1 deletion src/components/Banner.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<section v-if="subscription.pro" class="bg-gray-100 dark:bg-darkest text-center xl:text-left shadow rounded p-4 md:p-2">
<section v-if="!subscription.pro" class="bg-gray-100 dark:bg-darkest text-center xl:text-left shadow rounded p-4 md:p-2">
<div class="flex flex-col lg:flex-row items-center justify-between gap-4">
<strong class="inline-flex items-center lg:ml-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
Expand Down
11 changes: 6 additions & 5 deletions src/components/ChangeLog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
<template #footer>
<div class="flex items-center justify-end gap-2">
<button class="button-flat button-size-medium" @click="closeChangeLog">Dismiss</button>
<router-link v-if="auth.isEvaluated && !auth.user" @click="trackCta" :to="{ name: 'account' }" class="text-blue-400 button-flat button-color-surface button-size-medium">
<router-link v-if="!subscription.pro" @click="trackCta" :to="{ name: 'account' }" class="text-blue-400 button-flat button-color-surface button-size-medium">
<span>Upgrade</span>
</router-link>
<router-link v-else-if="!user" @click="trackCta" :to="{ name: 'account' }" class="text-blue-400 button-flat button-color-surface button-size-medium">
<span>Sign Up</span>
</router-link>
</div>
Expand All @@ -19,14 +22,12 @@

<script lang="ts" setup>
import moment from 'moment'
import { computed, onMounted, ref } from 'vue'
import { useStore } from 'vuex'
import { onMounted, ref } from 'vue'
import { subscription, user } from '/src/common/account'
import ChangeSet from '/src/components/ChangeSet.vue'
import Modal from '/src/components/Modal.vue'
const auth = computed(() => useStore().state.auth)
const changeSets = ref<any>([])
const lastUpdated = localStorage.getItem('changelog:v1')
const showChangeLog = ref(false)
Expand Down
28 changes: 18 additions & 10 deletions src/components/Editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<div class="md:container md:mx-auto flex flex-grow">
<div class="editor flex flex-col flex-grow min-w-0 p-4 md:px-16 md:py-0">
<div class="gutter h-8" @click="focusEditorStart"></div>
<Ink ref="editable" @paste="handlePaste" class="ink" :options="options" v-model="doc" />
<Ink ref="editable" class="ink" :options="options" v-model="doc" />
<p v-if="showReadabilityBar" class="text-gray-400 dark:text-gray-600 text-right">{{ numberOfWords }} words | {{ readTimeDescription }}</p>
<div class="gutter h-8 flex-grow" @click="focusEditorEnd"></div>
</div>
Expand All @@ -23,6 +23,7 @@
import Ink from '@writewithocto/vue-ink'
import { defineComponent } from 'vue'
import { subscription } from '/src/common/account'
import { readTime, wordCount } from '/src/common/readability.ts'
import { addFile } from '/src/firebase/storage.ts'
Expand Down Expand Up @@ -90,10 +91,12 @@ export default defineComponent({
options() {
return {
files: {
dragAndDrop: true,
clipboard: this.pro,
dragAndDrop: this.pro,
handler: (files) => {
this.uploadFiles(files)
return this.uploadFiles(files)
},
injectMarkup: false,
},
interface: {
appearance: this.appearance,
Expand All @@ -104,6 +107,9 @@ export default defineComponent({
vim: this.settings.keyMap === 'vim',
}
},
pro() {
return subscription.value.pro
},
readTime() {
return readTime(this.text, this.wordsPerMinute)
},
Expand Down Expand Up @@ -162,9 +168,6 @@ export default defineComponent({
getSelections() {
return Array.from(this.$refs.editable.selections())
},
handlePaste(event) {
this.uploadFiles(event.clipboardData.files)
},
async input(text) {
this.$emit('input', text)
},
Expand All @@ -178,11 +181,16 @@ export default defineComponent({
this.$store.dispatch(SET_RIGHT_SIDEBAR_VISIBILITY, !this.showRightSidebar)
},
uploadFiles(files) {
Array.from(files).forEach((file) => {
addFile(file).then((url) => {
this.$refs.editable.instance.insert(`![](${url})`)
return Promise.all(
Array.from(files).map(async (file) => {
return addFile(file).then((uploadedFile) => {
// Todo: Handle non-image files
if (/^image\/.*/.test(uploadedFile.mimeType)) {
this.$refs.editable.instance.insert(`![](${uploadedFile.url})`)
}
})
})
})
)
},
},
mounted() {
Expand Down
6 changes: 6 additions & 0 deletions src/data/changelog.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
[
{
"changes": [
"Drag-and-drop or paste files to upload and attach to the current doc (Octo Pro)."
],
"timestamp": "2022-03-30T22:47:29-0400"
},
{
"changes": [
"Changelog notifications are displayed when Octo updates.",
Expand Down
7 changes: 7 additions & 0 deletions src/firebase.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { initializeApp } from 'firebase/app'
import { connectAuthEmulator, getAuth } from 'firebase/auth'
import { connectFirestoreEmulator, getFirestore, initializeFirestore, setLogLevel } from 'firebase/firestore'
import { connectStorageEmulator, getStorage } from 'firebase/storage'

// firebase config
const config = {
Expand Down Expand Up @@ -39,5 +40,11 @@ export const init = () => {
import.meta.env.VITE_FIREBASE_EMULATOR_FIRESTORE_HOST,
import.meta.env.VITE_FIREBASE_EMULATOR_FIRESTORE_PORT,
)

connectStorageEmulator(
getStorage(),
import.meta.env.VITE_FIREBASE_EMULATOR_STORAGE_HOST,
import.meta.env.VITE_FIREBASE_EMULATOR_STORAGE_PORT,
)
}
}
28 changes: 16 additions & 12 deletions src/firebase/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,31 @@ import { getDownloadURL, getStorage, ref, uploadBytes } from 'firebase/storage'
import mime from 'mime-types'
import { nanoid } from 'nanoid'

export const createName = (file: File): string => {
const name = nanoid()
const extension = mime.extension(file.type)

return [name, extension].join('.')
}

export const addFile = async (file: File) => {
const user = getAuth().currentUser

if (user) {
const fileName = createName(file)
const fileExtension = resolveExtension(file)
const fileName = [nanoid(10), fileExtension].join('.')
const storageRef = ref(getStorage())
const filesRef = ref(storageRef, `files/v1/${user.uid}`)
const fileRef = ref(filesRef, fileName)
const fileRef = ref(storageRef, fileName)
const metadata = {
contentType: file.type,
customMetadata: {
ownerId: user.uid,
},
}

return uploadBytes(fileRef, file, metadata).then((snapshot) => {
return getDownloadURL(snapshot.ref)
return uploadBytes(fileRef, file, metadata).then((_snapshot) => {
return {
extension: fileExtension,
mimeType: file.type,
url: `${import.meta.env.VITE_APP_CDN_URL}/${fileName}`,
}
})
}
}

export const resolveExtension = (file: File) => {
return mime.extension(file.type)
}
3 changes: 2 additions & 1 deletion src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,10 @@ getAuth().onAuthStateChanged(async (user) => {
await user.getIdToken(true)

const decodedToken = await user.getIdTokenResult()
const pro = decodedToken.claims.ambassador || (decodedToken.claims.stripeRole === 'subscriber')

store.commit(SET_SUBSCRIPTION, {
pro: decodedToken.claims.stripeRole === 'subscriber'
pro,
})
}
})
Loading

0 comments on commit 6d8ee46

Please sign in to comment.