diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..23c7cff --- /dev/null +++ b/.env.example @@ -0,0 +1,12 @@ +# Server +HTTP_PORT=8000 + +# Database +POSTGRES_USER=root +POSTGRES_PASSWORD=root +POSTGRES_HOST=localhost +POSTGRES_PORT=5432 +POSTGRES_DB=root +AUTO_MIGRATE=True + +BOT_KEY=botkey \ No newline at end of file diff --git a/.github/workflows/cd-build-push-deploy.yaml b/.github/workflows/cd-build-push-deploy.yaml new file mode 100644 index 0000000..55a46e8 --- /dev/null +++ b/.github/workflows/cd-build-push-deploy.yaml @@ -0,0 +1,49 @@ +name: Publish Docker image + +on: + workflow_run: + workflows: ["CI"] + types: + - completed + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'main' }} + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + attestations: write + id-token: write + steps: + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Set short git commit SHA + id: vars + run: | + calculatedSha=$(git rev-parse --short ${{ github.sha }}) + echo "COMMIT_SHORT_SHA=$calculatedSha" >> $GITHUB_ENV + + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and push Docker image + id: push + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + push: true + tags: | + shampiniony/shampsdev-server:latest + shampiniony/shampsdev-server:${{ env.COMMIT_SHORT_SHA }} + + - name: Trigger watchtower to update container(s) + shell: bash + run: | + curl -H "Authorization: Bearer ${{ secrets.WATCHTOWER_HTTP_API_TOKEN }}" ${{ secrets.WATCHTOWER_UPDATE_ENDPOINT }} \ No newline at end of file diff --git a/.github/workflows/ci-tests-build.yaml b/.github/workflows/ci-tests-build.yaml new file mode 100644 index 0000000..d799e6e --- /dev/null +++ b/.github/workflows/ci-tests-build.yaml @@ -0,0 +1,22 @@ +name: CI + +on: + push: + branches: [ "main", "dev" ] + pull_request: + branches: [ "main", "dev" ] + +jobs: + + build: + + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Build docker image + run: docker compose build \ No newline at end of file diff --git a/.gitignore b/.gitignore index 516b8f9..81d21bc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules dist *.sqlite -.DS_Store \ No newline at end of file +.DS_Store +.env \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b7637c1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +# Base image +FROM node:18 + +# Create app directory +WORKDIR /usr/src/app + +# A wildcard is used to ensure both package.json AND package-lock.json are copied +COPY package*.json ./ + +# Install app dependencies +RUN npm install + +# Bundle app source +COPY . . + +# Creates a "dist" folder with the production build +RUN npm run build + +# Start the server using the production build +CMD [ "node", "dist/main.js" ] diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..d8d102f --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,24 @@ +services: + database: + restart: unless-stopped + image: postgis/postgis:16-3.4 + container_name: database + volumes: + - ~/.pg/shampsdev-db:/var/lib/postgresql/data + env_file: + - .env + ports: + - "5432:5432" + backend: + container_name: backend + build: . + env_file: + - .env + environment: + HTTP_PORT: 8000 + POSTGRES_HOST: database + depends_on: + - database + ports: + - "8000:8000" + restart: unless-stopped \ No newline at end of file diff --git a/package.json b/package.json index 6bac8bf..79b1bc9 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@apollo/server": "^4.11.0", "@nestjs/apollo": "^12.2.0", "@nestjs/common": "^10.3.2", + "@nestjs/config": "^3.2.3", "@nestjs/core": "^10.3.2", "@nestjs/graphql": "^12.2.0", "@nestjs/platform-express": "^10.3.2", @@ -31,6 +32,7 @@ "graphql-subscriptions": "^2.0.0", "graphql-tools": "^9.0.1", "nestjs-telegram": "^1.2.0", + "pg": "^8.13.0", "reflect-metadata": "^0.2.1", "rxjs": "^7.8.1", "sqlite3": "^5.1.7", diff --git a/src/app.controller.spec.ts b/src/app.controller.spec.ts index b7bb716..dc8b5aa 100644 --- a/src/app.controller.spec.ts +++ b/src/app.controller.spec.ts @@ -11,11 +11,4 @@ describe('AppController', () => { providers: [AppService], }).compile(); }); - - describe('getHello', () => { - it('should return "Hello World!"', () => { - const appController = app.get(AppController); - expect(appController.getHello()).toBe('Hello World!'); - }); - }); }); diff --git a/src/app.module.ts b/src/app.module.ts index cfa9d51..f8d28ef 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -8,23 +8,29 @@ import { join } from 'path'; import { TypeOrmModule } from '@nestjs/typeorm'; import { TelegramModule } from 'nestjs-telegram'; import { TelegramStatsService } from './telegram-stats/telegram-stats.service'; +import { ConfigModule } from '@nestjs/config'; @Module({ imports: [ + ConfigModule.forRoot(), GraphQLModule.forRoot({ driver: ApolloDriver, - autoSchemaFile: join(process.cwd(), 'src/shema.gql'), + autoSchemaFile: join(process.cwd(), 'src/schema.gql'), sortSchema: true, installSubscriptionHandlers: true, }), TypeOrmModule.forRoot({ - type: 'sqlite', - database: 'database.sqlite', + type: 'postgres', + host: process.env.POSTGRES_HOST, + port: parseInt(process.env.POSTGRES_PORT, 10), + username: process.env.POSTGRES_USER, + password: process.env.POSTGRES_PASSWORD, + database: process.env.POSTGRES_DB, entities: ['dist/**/*.entity{.ts,.js}'], - synchronize: true, + synchronize: process.env.AUTO_MIGRATE === 'True', }), TelegramModule.forRoot({ - botKey: 'YourBotApiToken', + botKey: process.env.BOT_KEY, }), StatsModule, ], diff --git a/src/main.ts b/src/main.ts index 13cad38..1da32f3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,6 +3,6 @@ import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); - await app.listen(3000); + await app.listen(process.env.HTTP_PORT); } bootstrap(); diff --git a/src/shema.gql b/src/shema.gql deleted file mode 100644 index 4c8f02a..0000000 --- a/src/shema.gql +++ /dev/null @@ -1,34 +0,0 @@ -# ------------------------------------------------------ -# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) -# ------------------------------------------------------ - -input CreateStatInput { - count: Int! - name: String! - stat_id: String! -} - -""" -A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date-time format. -""" -scalar DateTime - -type Mutation { - createStat(createStatInput: CreateStatInput!): Stat! -} - -type Query { - stats: [Stat!]! -} - -type Stat { - count: Float! - id: Int! - name: String! - stat_id: String! - timestamp: DateTime! -} - -type Subscription { - statCreated: [Stat!]! -} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 5bf4830..2327f62 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1226,6 +1226,15 @@ tslib "1.11.1" uuid "7.0.1" +"@nestjs/config@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@nestjs/config/-/config-3.2.3.tgz#569888a33ada50b0f182002015e152e054990016" + integrity sha512-p6yv/CvoBewJ72mBq4NXgOAi2rSQNWx3a+IMJLVKS2uiwFCOQQuiIatGwq6MRjXV3Jr+B41iUO8FIf4xBrZ4/w== + dependencies: + dotenv "16.4.5" + dotenv-expand "10.0.0" + lodash "4.17.21" + "@nestjs/core@^10.3.2": version "10.4.4" resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-10.4.4.tgz#12cb1110da6d76e12ceccf0e92f6f5220fe27525" @@ -3327,7 +3336,12 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dotenv@^16.0.3: +dotenv-expand@10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-10.0.0.tgz#12605d00fb0af6d0a592e6558585784032e4ef37" + integrity sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A== + +dotenv@16.4.5, dotenv@^16.0.3: version "16.4.5" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== @@ -5999,6 +6013,62 @@ peek-readable@^5.1.3: resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-5.2.0.tgz#7458f18126217c154938c32a185f5d05f3df3710" integrity sha512-U94a+eXHzct7vAd19GH3UQ2dH4Satbng0MyYTMaQatL0pvYYL5CTPR25HBhKtecl+4bfu1/i3vC6k0hydO5Vcw== +pg-cloudflare@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98" + integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q== + +pg-connection-string@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.7.0.tgz#f1d3489e427c62ece022dba98d5262efcb168b37" + integrity sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA== + +pg-int8@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" + integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== + +pg-pool@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.7.0.tgz#d4d3c7ad640f8c6a2245adc369bafde4ebb8cbec" + integrity sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g== + +pg-protocol@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.7.0.tgz#ec037c87c20515372692edac8b63cf4405448a93" + integrity sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ== + +pg-types@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" + integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== + dependencies: + pg-int8 "1.0.1" + postgres-array "~2.0.0" + postgres-bytea "~1.0.0" + postgres-date "~1.0.4" + postgres-interval "^1.1.0" + +pg@^8.13.0: + version "8.13.0" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.13.0.tgz#e3d245342eb0158112553fcc1890a60720ae2a3d" + integrity sha512-34wkUTh3SxTClfoHB3pQ7bIMvw9dpFU1audQQeZG837fmHfHpr14n/AELVDoOYVDW2h5RDWU78tFjkD+erSBsw== + dependencies: + pg-connection-string "^2.7.0" + pg-pool "^3.7.0" + pg-protocol "^1.7.0" + pg-types "^2.1.0" + pgpass "1.x" + optionalDependencies: + pg-cloudflare "^1.1.1" + +pgpass@1.x: + version "1.0.5" + resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" + integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== + dependencies: + split2 "^4.1.0" + picocolors@^1.0.0, picocolors@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" @@ -6043,6 +6113,28 @@ pluralize@8.0.0: resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== +postgres-array@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" + integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== + +postgres-bytea@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" + integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== + +postgres-date@~1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" + integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== + +postgres-interval@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" + integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== + dependencies: + xtend "^4.0.0" + prebuild-install@^7.1.1: version "7.1.2" resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.2.tgz#a5fd9986f5a6251fbc47e1e5c65de71e68c0a056" @@ -6629,6 +6721,11 @@ source-map@^0.6.0, source-map@^0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +split2@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" + integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== + sprintf-js@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a"