Skip to content

Commit

Permalink
Merge branch 'main' into feat/47
Browse files Browse the repository at this point in the history
  • Loading branch information
HugoRCD committed Jan 9, 2025
2 parents 8b5505f + a965515 commit e30c49c
Show file tree
Hide file tree
Showing 21 changed files with 272 additions and 40 deletions.
59 changes: 51 additions & 8 deletions apps/currencia/app/components/chart/CryptoCard.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import NumberFlow, { NumberFlowGroup } from '@number-flow/vue'
import type { Crypto } from '~~/types/Crypto'
type CryptoCardProps = {
Expand All @@ -12,12 +13,26 @@ function getRandomInt(min: number, max: number = 100) {
return Math.floor(Math.random() * (max - min + 1)) + min
}
const price = useCryptoPrice(cryptoItem.symbol)
const eventSource = new EventSource(`${location.origin}/api/crypto/${cryptoItem.symbol}`)
eventSource.onmessage = (event) => {
price.value = +event.data
}
onUnmounted(() => {
eventSource.close()
})
const crypto = reactive({
name: cryptoItem.name,
symbol: cryptoItem.symbol,
logo: cryptoItem.logo,
price: cryptoItem.data[cryptoItem.data.length - 1][1],
change: getRandomInt(-30, 30),
})
const diff = computed(() => {
return getRandomInt(price.value / 100, -price.value / 100)
})
</script>

Expand All @@ -36,15 +51,43 @@ const crypto = reactive({
<span class="text-lg font-semibold text-gray-700 dark:text-gray-200">{{ crypto.name }}</span>
<span class="ml-2 text-sm text-gray-500 dark:text-gray-400">{{ crypto.symbol }}</span>
</div>
<div class="flex flex-col gap-1">
<div v-if="price" class="flex flex-col gap-1">
<div class="flex flex-row items-center">
<span class="text-2xl font-semibold text-gray-700 dark:text-gray-200">{{ crypto.price.toLocaleString() }}</span>
<span class="ml-2 text-sm text-gray-500 dark:text-gray-400">$</span>
</div>
<div class="flex flex-row items-center">
<span :class="crypto.change > 0 ? 'positive' : 'negative'" class="text-sm font-semibold"> {{ crypto.change }}% </span>
<NumberFlowGroup>
<div style="--number-flow-char-height: 0.85em" class="flex flex-col gap-1 font-semibold">
<NumberFlow
:value="price"
suffix="$"
:locales="['fr-FR']"
continuous
class="text-2xl font-semibold tabular-nums"
/>
<NumberFlow
:value="diff"
suffix="%"
:class="[
'text-sm transition-colors duration-300',
diff < 0 ? 'text-red-500' : 'text-emerald-500'
]"
/>
</div>
</NumberFlowGroup>
</div>
</div>
<div v-else class="flex flex-col gap-1">
<USkeleton
class="h-5 w-28"
:ui="{
background: 'bg-gray-50 dark:bg-gray-900'
}"
/>
<USkeleton
class="h-4 w-20"
:ui="{
background: 'bg-gray-50 dark:bg-gray-900'
}"
/>
</div>
</div>
</template>

Expand Down
15 changes: 12 additions & 3 deletions apps/currencia/app/composables/useCrypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@ export const usePublicCrypto = () => {
return useState<Crypto[]>('cryptos', () => [])
}

export function useCrypto() {
export function useCryptoPrice(symbol: string) {
return useState<number>(`crypto-${symbol}-price`)
}

export function useCryptoPrices(symbol: string) {
return useState<number[]>(`crypto-${symbol}-prices`)
}

export function useCryptoService() {
const publicCryptos = usePublicCrypto()
const { user } = useUserSession()

Expand Down Expand Up @@ -51,11 +59,12 @@ export function useCrypto() {
async function deleteCrypto(id: number) {
deleteLoading.value = true
try {
const response = await $fetch(`/api/admin/crypto/${id}`, {
await $fetch(`/api/admin/crypto/${id}`, {
method: 'DELETE',
})
const index = cryptos.value.findIndex((crypto) => crypto.id === id)
cryptos.value.splice(index, 1)
toast.success('Crypto deleted successfully.')
if (response.value) cryptos.value = response.value
} catch (error) {
toast.error('Whoops! Something went wrong.')
}
Expand Down
2 changes: 1 addition & 1 deletion apps/currencia/app/pages/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ definePageMeta({
layout: 'app',
})
await useCrypto().fetchPublicCryptos()
await useCryptoService().fetchPublicCryptos()
</script>

<template>
Expand Down
2 changes: 1 addition & 1 deletion apps/currencia/app/pages/app/admin/cryptos.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script setup lang="ts">
import type { Crypto, UpsertCryptoDto } from '~~/types/Crypto'
const { modal, cryptos, loading, getLoading, fetchCryptos, upsertCrypto, deleteCrypto } = useCrypto()
const { modal, cryptos, loading, getLoading, fetchCryptos, upsertCrypto, deleteCrypto } = useCryptoService()
const columns = [
{
Expand Down
2 changes: 1 addition & 1 deletion apps/currencia/app/pages/app/crypto/[symbol].vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ onUnmounted(() => {
continuous
class="font-semibold tabular-nums"
/>
<USkeleton v-else class="h-8 w-24" />
<USkeleton v-else class="h-6 w-28" />
</span>
</div>

Expand Down
21 changes: 11 additions & 10 deletions apps/currencia/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,34 @@
"prisma:generate": "prisma generate",
"prisma:pull": "prisma db pull",
"prisma:push": "prisma db push",
"prisma:migrate": "prisma migrate dev"
"prisma:migrate": "prisma migrate dev",
"prisma:seed": "bun ./prisma/seed.ts"
},
"devDependencies": {
"@hrcd/eslint-config": "^2.1.1",
"@iconify-json/lucide": "^1.2.20",
"@iconify-json/lucide": "^1.2.22",
"@nuxt/fonts": "^0.10.3",
"@nuxt/image": "^1.8.1",
"@nuxt/image": "^1.9.0",
"@nuxt/scripts": "^0.9.5",
"@nuxt/ui": "^2.20.0",
"@prisma/client": "^6.1.0",
"@prisma/client": "^6.2.1",
"apexcharts": "^4.3.0",
"@vueuse/core": "^12.2.0",
"@vueuse/nuxt": "^12.2.0",
"@vueuse/core": "^12.3.0",
"@vueuse/nuxt": "^12.3.0",
"dayjs": "^1.11.13",
"dayjs-nuxt": "^2.1.11",
"eslint": "^9.17.0",
"nuxt": "3.14.1592",
"nuxt": "3.15.1",
"nuxt-auth-utils": "^0.5.7",
"nuxt-build-cache": "^0.1.1",
"prisma": "^6.1.0",
"prisma": "^6.2.1",
"resend": "^4.0.1",
"vue-sonner": "^1.3.0",
"vue3-apexcharts": "^1.8.0",
"zod": "^3.24.1",
"@shelve/cli": "^2.11.0"
"@shelve/cli": "^2.12.0"
},
"dependencies": {
"@number-flow/vue": "^0.3.3"
"@number-flow/vue": "^0.4.1"
}
}
11 changes: 11 additions & 0 deletions apps/currencia/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ model Crypto {
visible Boolean @default(true)
portfolioData PortfolioData[]
watchlist Watchlist[]
prices Prices[]
}

model Prices {
id Int @id @default(autoincrement())
cryptoId Int
price Float
timestamp BigInt
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
crypto Crypto @relation(fields: [cryptoId], references: [id])
}

model Watchlist {
Expand Down
34 changes: 34 additions & 0 deletions apps/currencia/prisma/seed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { cryptos } from '@currencia/cryptos'
import { PrismaClient } from '@prisma/client'

export const prisma = new PrismaClient()

async function main() {
for (const crypto of cryptos) {
await prisma.crypto.upsert({
where: { symbol: crypto.symbol },
update: {
name: crypto.name,
symbol: crypto.symbol,
logo: crypto.logo,
description: crypto.description,
},
create: {
name: crypto.name,
symbol: crypto.symbol,
logo: crypto.logo,
description: crypto.description
},
})
}
}

main()
.then(async () => {
await prisma.$disconnect()
})
.catch(async (e) => {
console.error(e)
await prisma.$disconnect()
process.exit(1)
})
2 changes: 1 addition & 1 deletion apps/currencia/server/api/crypto/[symbol]/index.get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default defineEventHandler(async (event) => {

const interval = setInterval(async () => {
await eventStream.push(String(sendRandomNumber()))
}, 1500)
}, 2000)

eventStream.onClosed(async () => {
clearInterval(interval)
Expand Down
33 changes: 33 additions & 0 deletions apps/currencia/server/api/crypto/[symbol]/index.post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { z } from 'zod'

const symbolParams = z.object({
symbol: z.string(),
})

export default defineEventHandler(async (event) => {
const { symbol } = await getValidatedRouterParams(event, symbolParams.parse)
const { price, timestamp } = await readValidatedBody(event, z.object({
price: z.number(),
timestamp: z.bigint(),
}).parse)

const crypto = await prisma.crypto.findUnique({
where: {
symbol,
}
})
if (!crypto) return createError({ statusCode: 404, message: 'Crypto not found' })
await prisma.prices.create({
data: {
cryptoId: crypto.id,
timestamp,
price
}
})
return {
statusCode: 201,
body: {
message: 'Price saved successfully'
}
}
})
1 change: 0 additions & 1 deletion apps/currencia/types/Crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ export type Crypto = {
symbol: string;
logo: string;
description: string;
data: [timestamp: number, value: number][];
visible: boolean;
createdAt?: string;
updatedAt?: string;
Expand Down
7 changes: 7 additions & 0 deletions apps/formatter/nitro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,12 @@ export default defineNitroConfig({
'* * * * *': ['sync:mongo', 'format:data']
},

runtimeConfig: {
rabbit: {
url: '',
queue: ''
}
},

compatibilityDate: '2024-12-29'
})
8 changes: 6 additions & 2 deletions apps/formatter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@
"format:data": "nitro task run format:data"
},
"devDependencies": {
"nitropack": "latest",
"@currencia/cryptos": "workspace:*",
"@currencia/mongo": "workspace:*",
"@currencia/utils": "workspace:*"
"@currencia/utils": "workspace:*",
"@types/amqplib": "^0.10.6",
"nitropack": "latest"
},
"dependencies": {
"amqplib": "^0.10.5"
}
}
32 changes: 26 additions & 6 deletions apps/formatter/server/tasks/sync/mongo.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
import { MongoDBClient } from '@currencia/mongo'
import { RabbitMQClient } from '~/utils/rabbit'

export default defineTask({
meta: {
name: 'sync:mongo',
description: 'Send MongoID to RabbitMQ',
},
async run() {
const client = await MongoDBClient.create()
console.log('Syncing MongoID to RabbitMQ')
const price = await client.getLatestPrices()
console.log('Price:', price)
console.log('Ids to sync:', price?._id)
return { result: 'Success' }
const runtimeConfig = useRuntimeConfig()
const rabbitClient = new RabbitMQClient({
url: runtimeConfig.rabbit.url,
queue: runtimeConfig.rabbit.queue,
})
const mongoClient = await MongoDBClient.create()

try {
console.log('Syncing MongoID to RabbitMQ')

const prices = await mongoClient.getLatestPrices()

await rabbitClient.connect()

await rabbitClient.publishMessages({ _id: prices._id })

console.log('IDs sent to RabbitMQ successfully')

return { result: 'Success' }
} catch (error) {
console.error('Error in sync task:', error)
throw error
} finally {
await rabbitClient.disconnect()
}
},
})
Loading

0 comments on commit e30c49c

Please sign in to comment.