Skip to content

Commit 56a06a2

Browse files
add link to blog and update CI
1 parent cafff59 commit 56a06a2

File tree

5 files changed

+145
-27
lines changed

5 files changed

+145
-27
lines changed

.github/workflows/publish.yml

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,44 @@ on:
66
- "main"
77

88
env:
9+
ENV_KEYS: >-
10+
MONGO_URI
11+
DISCORD_CLIENT_ID
12+
DISCORD_CLIENT_SECRET
13+
DISCORD_BOT_TOKEN
14+
DISCORD_GUILD_MEMBER_ROLE_ID
15+
DISCORD_GUILD_ID
16+
JWT_SECRET
17+
SPACE_ELECTRICITY_TRACKER_SLUG
18+
19+
# Application environment variables
20+
GIT_COMMIT_SHA: ${{ github.sha }}
21+
MONGO_URI: ${{ secrets.MONGO_URI }}
22+
DISCORD_CLIENT_ID: ${{ vars.DISCORD_CLIENT_ID }}
23+
DISCORD_CLIENT_SECRET: ${{ secrets.DISCORD_CLIENT_SECRET }}
24+
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }}
25+
DISCORD_GUILD_MEMBER_ROLE_ID: ${{ vars.DISCORD_GUILD_MEMBER_ROLE_ID }}
26+
DISCORD_GUILD_ID: ${{ vars.DISCORD_GUILD_ID }}
27+
JWT_SECRET: ${{ secrets.JWT_SECRET }}
28+
SPACE_ELECTRICITY_TRACKER_SLUG: ${{ vars.SPACE_ELECTRICITY_TRACKER_SLUG }}
29+
30+
# Docker image configuration
31+
IMAGE_NAME: nu31hackerspace/space
32+
DOCKER_STACK_NAME: nu31space
933
REGISTRY: ghcr.io
10-
DOCKER_STACKE_NAME: nu31space
34+
35+
# Deployment configuration
1136
DEPLOY_USER: deploy
12-
HOST: 167.235.52.168
37+
HOST: ${{ vars.HOST }}
38+
DEPLOY_SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_PRIVATE_KEY }}
1339

1440
jobs:
1541
build-and-push-image:
16-
strategy:
17-
matrix:
18-
include:
19-
- name: space
20-
context: .
21-
image: space
22-
2342
runs-on: ubuntu-latest
2443
permissions:
2544
contents: read
2645
packages: write
46+
attestations: write
2747
id-token: write
2848

2949
steps:
@@ -39,16 +59,16 @@ jobs:
3959
username: ${{ github.actor }}
4060
password: ${{ secrets.GITHUB_TOKEN }}
4161

42-
- name: Build and push main app image
62+
- name: Build and push Docker image
4363
uses: docker/build-push-action@v6
4464
with:
45-
context: ${{ matrix.context }}
65+
context: .
4666
push: true
4767
tags: |
48-
${{ env.REGISTRY }}/nu31hackerspace/${{ matrix.image }}:latest
49-
${{ env.REGISTRY }}/nu31hackerspace/${{ matrix.image }}:${{ github.sha }}
68+
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
69+
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
5070
build-args: |
51-
GIT_COMMIT_SHA=${{ github.sha }}
71+
GIT_COMMIT_SHA=${{ env.GIT_COMMIT_SHA }}
5272
5373
deploy:
5474
runs-on: ubuntu-latest
@@ -58,24 +78,25 @@ jobs:
5878
- name: Checkout repository
5979
uses: actions/checkout@v4
6080

61-
- name: Create env file
81+
- name: Create .env file for Docker Stack
6282
run: |
63-
echo "GIT_COMMIT_HASH='${{ github.sha }}'" >> ./envfile
64-
echo "MONGO_URI='${{ secrets.MONGO_URI }}'" >> ./envfile
65-
echo "DISCORD_CLIENT_ID='${{ vars.DISCORD_CLIENT_ID }}'" >> ./envfile
66-
echo "DISCORD_CLIENT_SECRET='${{ secrets.DISCORD_CLIENT_SECRET }}'" >> ./envfile
67-
echo "DISCORD_BOT_TOKEN='${{ secrets.DISCORD_BOT_TOKEN }}'" >> ./envfile
68-
echo "DISCORD_GUILD_MEMBER_ROLE_ID='${{ vars.DISCORD_GUILD_MEMBER_ROLE_ID }}'" >> ./envfile
69-
echo "DISCORD_GUILD_ID='${{ vars.DISCORD_GUILD_ID }}'" >> ./envfile
70-
echo "JWT_SECRET='${{ secrets.JWT_SECRET }}'" >> ./envfile
71-
echo "SPACE_ELECTRICITY_TRACKER_SLUG='${{ vars.SPACE_ELECTRICITY_TRACKER_SLUG }}'" >> ./envfile
83+
echo "IMAGE_NAME='$IMAGE_NAME'" >> .env
84+
echo "GIT_COMMIT_SHA='$GIT_COMMIT_SHA'" >> .env
85+
for key in $ENV_KEYS; do
86+
echo "$key='${!key}'" >> .env
87+
done
7288
7389
- name: Docker Stack Deploy
7490
uses: cssnr/stack-deploy-action@v1
7591
with:
76-
name: ${{ env.DOCKER_STACKE_NAME }}
92+
name: ${{ env.DOCKER_STACK_NAME }}
7793
file: docker-stack.yml
7894
host: ${{ env.HOST }}
95+
port: 22
7996
user: ${{ env.DEPLOY_USER }}
80-
ssh_key: ${{ secrets.DEPLOY_SSH_PRIVATE_KEY }}
81-
env_file: ./envfile
97+
ssh_key: ${{ env.DEPLOY_SSH_PRIVATE_KEY }}
98+
env_file: .env
99+
detach: false
100+
prune: true
101+
resolve_image: 'always'
102+
summary: true

app/components/Header.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
</NuxtLink>
88
<MainBadge v-if="!isLoading && status" :variant="badgeVariant" :label="badgeLabel" />
99
</div>
10+
<nav class="flex items-center space-x-6">
11+
<NuxtLink to="/blog" class="text-label-primary hover:text-accent-primary transition-colors font-medium">
12+
Блог
13+
</NuxtLink>
14+
</nav>
1015
<div class="flex items-center space-x-4">
1116
<NuxtLink v-if="isLoggedIn && avatarUrl" to="/profile"
1217
class="flex items-center space-x-3 hover:opacity-80 transition-opacity">

app/pages/blog/index.vue

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<template>
2+
<div class="min-h-screen bg-background-primary">
3+
<div class="container mx-auto px-4 py-8 max-w-5xl">
4+
<h1 class="text-3xl font-bold text-accent-primary mb-8">Блог</h1>
5+
6+
<div v-if="pending" class="text-center py-12">
7+
<p class="text-label-secondary">Завантаження статей...</p>
8+
</div>
9+
10+
<div v-else-if="error" class="text-center py-12">
11+
<p class="text-red-500">Помилка завантаження: {{ error.message }}</p>
12+
</div>
13+
14+
<div v-else-if="articles && articles.items.length > 0"
15+
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
16+
<NuxtLink v-for="article in articles.items" :key="article.slug" :to="`/blog/${article.slug}`"
17+
class="group bg-fill-secondary border border-separator-primary rounded-xl p-6 hover:border-accent-primary transition-colors">
18+
<h2 class="text-xl font-semibold text-accent-primary group-hover:underline mb-3">
19+
{{ article.title }}
20+
</h2>
21+
<p class="text-sm text-label-tertiary">
22+
{{ formatDate(article.createdAt) }}
23+
</p>
24+
</NuxtLink>
25+
</div>
26+
27+
<div v-else class="text-center py-12">
28+
<p class="text-label-secondary">Статей поки немає</p>
29+
</div>
30+
</div>
31+
</div>
32+
</template>
33+
34+
<script setup lang="ts">
35+
import { definePageMeta, useFetch } from '#imports'
36+
37+
definePageMeta({
38+
layout: 'default',
39+
})
40+
41+
interface ArticleListItem {
42+
slug: string
43+
title: string
44+
createdAt: string
45+
updatedAt: string
46+
}
47+
48+
interface ArticlesResponse {
49+
items: ArticleListItem[]
50+
page: number
51+
pageSize: number
52+
total: number
53+
}
54+
55+
const { data: articles, pending, error } = await useFetch<ArticlesResponse>('/api/content')
56+
57+
function formatDate(dateString: string): string {
58+
return new Date(dateString).toLocaleDateString('uk-UA', {
59+
year: 'numeric',
60+
month: 'long',
61+
day: 'numeric',
62+
})
63+
}
64+
</script>

app/pages/index.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
<MainBadge v-if="!electricityLoading && electricityStatus" :variant="electricityBadgeVariant"
66
:label="electricityBadgeLabel" class="w-full sm:w-auto mb-4 sm:mb-0" />
77
</div>
8+
<NuxtLink to="/blog" class="text-label-primary hover:text-accent-primary transition-colors font-medium">
9+
Блог
10+
</NuxtLink>
811
<div class="flex w-full sm:w-auto items-center justify-end gap-4 flex-row">
912
<MainButton buttonStyle="primary" size="M" icon="ic:baseline-discord" label="Вхід для резедентів"
1013
:link="data?.redirectUri" class="w-full sm:w-auto" />

server/api/content/index.get.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { defineEventHandler, getQuery, useNitroApp } from '#imports'
2+
3+
export default defineEventHandler(async (event) => {
4+
const { page = '1', pageSize = '20' } = getQuery(event) as Record<string, string>
5+
const p = Math.max(parseInt(page, 10) || 1, 1)
6+
const ps = Math.min(Math.max(parseInt(pageSize, 10) || 20, 1), 100)
7+
8+
const db = useNitroApp().db
9+
const cursor = db
10+
.collection('blogPosts')
11+
.find(
12+
{ status: 'published' },
13+
{ projection: { _id: 0, slug: 1, title: 1, createdAt: 1, updatedAt: 1 } }
14+
)
15+
.sort({ createdAt: -1 })
16+
.skip((p - 1) * ps)
17+
.limit(ps)
18+
19+
const [items, total] = await Promise.all([
20+
cursor.toArray(),
21+
db.collection('blogPosts').countDocuments({ status: 'published' }),
22+
])
23+
24+
return { items, page: p, pageSize: ps, total }
25+
})

0 commit comments

Comments
 (0)