├── .github
└── workflows
│ └── main.yml
├── Dockerfile
├── Makefile
├── README.md
├── client
├── .gitignore
├── README.md
├── babel.config.js
├── cypress.json
├── package.json
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── App.vue
│ ├── assets
│ │ └── logo.png
│ ├── locales
│ │ ├── en.json
│ │ └── tr.json
│ ├── main.js
│ ├── mixins
│ │ └── messages.js
│ ├── plugins
│ │ └── i18n.js
│ ├── router
│ │ └── index.js
│ ├── store
│ │ └── index.js
│ └── views
│ │ ├── AlarmCreate.vue
│ │ └── AlarmList.vue
├── tests
│ ├── e2e
│ │ ├── plugins
│ │ │ └── index.js
│ │ ├── specs
│ │ │ └── test.js
│ │ └── support
│ │ │ ├── commands.js
│ │ │ └── index.js
│ └── unit
│ │ └── example.spec.js
├── vue.config.js
└── yarn.lock
├── config
├── config.go
├── config_dev.go
└── config_prod.go
├── controllers
├── alarm_controller.go
├── alarm_controller_setup_test.go
├── alarm_controller_test.go
├── base_controller.go
├── test.png
├── test_helper.go
├── token_controller.go
└── token_controller_test.go
├── database
└── init.go
├── go.mod
├── go.sum
├── infra
├── awsclient
│ └── aws.go
├── cronclient
│ └── cron.go
└── telegramclient
│ └── telegram.go
├── main.go
├── models
├── job.go
└── user.go
├── repository
├── jobrepo
│ ├── job_repository.go
│ └── job_repository_test.go
├── test_helpers.go
└── userrepo
│ ├── user_repository.go
│ └── user_repository_test.go
├── services
├── jobservice
│ └── job_service.go
└── userservice
│ └── user_service.go
├── test.sh
└── utils
├── string.go
└── string_test.go
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Deploy to EC2
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | workflow_dispatch:
7 |
8 | jobs:
9 | build:
10 | runs-on: cron-job-ec2
11 |
12 | steps:
13 | - uses: actions/checkout@v2
14 |
15 | - name: Prune
16 | run: docker system prune -f
17 |
18 | - name: Stop
19 | run: docker stop cjvg || true
20 |
21 | - name: Make envfile
22 | uses: SpicyPizza/create-envfile@v1
23 | with:
24 | envkey_TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
25 | envkey_MONGODB_DATABASE: ${{ secrets.MONGODB_DATABASE }}
26 | envkey_MONGODB_URI: ${{ secrets.MONGODB_URI }}
27 | envkey_RM_AWS_ACCESS_KEY: ${{ secrets.RM_AWS_ACCESS_KEY }}
28 | envkey_RM_AWS_SECRET_KEY: ${{ secrets.RM_AWS_SECRET_KEY }}
29 | envkey_RM_AWS_REGION: ${{ secrets.RM_AWS_REGION }}
30 | envkey_RM_AWS_BUCKET_NAME: ${{ secrets.RM_AWS_BUCKET_NAME }}
31 |
32 | - name: Build
33 | run: docker build -t cron-job-vue-go .
34 |
35 | - name: Run
36 | run: docker run --name=cjvg --rm -d -p 3000:3000 --dns 8.8.8.8 cron-job-vue-go
37 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:lts-alpine as VueBuilder
2 | COPY client/ client/
3 | RUN cd client && yarn install --frozen-lockfile && yarn build
4 |
5 | FROM golang:alpine AS GoBuilder
6 | WORKDIR /app
7 | COPY go.sum go.mod ./
8 | RUN go mod download
9 | COPY --from=VueBuilder client/dist client/dist
10 | COPY . .
11 | RUN CGO_ENABLED=0 GOOS=linux go build --tags prod -o main main.go
12 |
13 | FROM alpine:3
14 | RUN apk update \
15 | && apk upgrade
16 | RUN apk add --no-cache ca-certificates tzdata \
17 | && update-ca-certificates 2>/dev/null || true
18 | COPY --from=GoBuilder /app/.env .
19 | COPY --from=GoBuilder /app/main .
20 | EXPOSE 3000
21 | CMD ["./main"]
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | docker build -t cron-job-vue-go .
3 |
4 | run:
5 | docker run --rm -i -t -p 3000:3000 --dns 8.8.8.8 cron-job-vue-go
6 |
7 | all: build run
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ```bash
2 | go run --tags dev main.go
3 | ```
4 |
5 | ```bash
6 | cd client && yarn serve
7 | ```
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | /tests/e2e/videos/
6 | /tests/e2e/screenshots/
7 |
8 |
9 | # local env files
10 | .env.local
11 | .env.*.local
12 |
13 | # Log files
14 | npm-debug.log*
15 | yarn-debug.log*
16 | yarn-error.log*
17 | pnpm-debug.log*
18 |
19 | # Editor directories and files
20 | .idea
21 | .vscode
22 | *.suo
23 | *.ntvs*
24 | *.njsproj
25 | *.sln
26 | *.sw?
27 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # client
2 |
3 | ## Project setup
4 | ```
5 | yarn install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | yarn serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | yarn build
16 | ```
17 |
18 | ### Run your unit tests
19 | ```
20 | yarn test:unit
21 | ```
22 |
23 | ### Run your end-to-end tests
24 | ```
25 | yarn test:e2e
26 | ```
27 |
28 | ### Customize configuration
29 | See [Configuration Reference](https://cli.vuejs.org/config/).
30 |
--------------------------------------------------------------------------------
/client/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/client/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "pluginsFile": "tests/e2e/plugins/index.js"
3 | }
4 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "test:unit": "vue-cli-service test:unit",
9 | "test:e2e": "vue-cli-service test:e2e"
10 | },
11 | "dependencies": {
12 | "bootstrap": "^4.6.0",
13 | "bootstrap-vue": "^2.21.2",
14 | "core-js": "^3.6.5",
15 | "js-cookie": "^2.2.1",
16 | "vue": "^2.6.12",
17 | "vue-i18n": "^8.24.4",
18 | "vue-router": "^3.2.0",
19 | "vuex": "^3.4.0"
20 | },
21 | "devDependencies": {
22 | "@vue/cli-plugin-babel": "~4.5.0",
23 | "@vue/cli-plugin-e2e-cypress": "~4.5.0",
24 | "@vue/cli-plugin-router": "~4.5.0",
25 | "@vue/cli-plugin-unit-jest": "~4.5.0",
26 | "@vue/cli-plugin-vuex": "~4.5.0",
27 | "@vue/cli-service": "~4.5.0",
28 | "@vue/test-utils": "^1.0.3",
29 | "sass": "^1.26.5",
30 | "sass-loader": "^8.0.2",
31 | "vue-template-compiler": "^2.6.11"
32 | },
33 | "browserslist": [
34 | "> 1%",
35 | "last 2 versions",
36 | "not dead"
37 | ],
38 | "jest": {
39 | "preset": "@vue/cli-plugin-unit-jest"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdulsametileri/cron-job-vue-go/8f41c7f31d6cbd0795e6bd7b60881a404f09549c/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Telegram Reminder
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/client/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | TR
8 | EN
9 |
10 |
11 |
12 | {{ $t("listAlarm") }}
13 | {{ $t("createAlarm") }}
14 |
15 |
16 |
17 |
18 |
19 | {{ $t('telegramBotMsg') }}
20 | Bot Link
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | OK
29 |
30 |
31 |
32 |
33 |
34 |
78 |
79 |
84 |
--------------------------------------------------------------------------------
/client/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdulsametileri/cron-job-vue-go/8f41c7f31d6cbd0795e6bd7b60881a404f09549c/client/src/assets/logo.png
--------------------------------------------------------------------------------
/client/src/locales/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "createAlarm": "Create Alarm",
3 | "listAlarm": "List Alarms",
4 | "telegramBotMsg": "",
5 | "fileUploadPlaceholder": "",
6 | "fileDropPlaceholder": "",
7 | "image": "Image",
8 | "documentName": "Document Name",
9 | "time": "Time",
10 | "timePickerPlaceHolder": "Choose time",
11 | "repeatOptions": "Repeat Options",
12 | "createAlarmBtn": "Create Alarm",
13 | "resetAlarmForm": "Reset Alarm Form",
14 | "weekDays": {
15 | "default": "Choose week day",
16 | "sunday": "Sunday",
17 | "monday": "Monday",
18 | "tuesday": "Tuesday",
19 | "wednesday": "Wednesday",
20 | "thursday": "Thursday",
21 | "friday": "Friday",
22 | "saturday": "Saturday",
23 | "all": "All day in week",
24 | "twoWeekSunday": "Sunday in Every two week",
25 | "twoWeekMonday": "Monday in Every two week",
26 | "twoWeekTuesday": "Tuesday in Every two week",
27 | "twoWeekWednesday": "Wednesday in Every two week",
28 | "twoWeekThursday": "Thursday in Every two week",
29 | "twoWeekFriday": "Friday in Every two week",
30 | "twoWeekSaturday": "Saturday in Every two week"
31 | },
32 | "formError": {
33 | "name": "Name cannot be empty.",
34 | "imgFile": "Image file cannot be empty.",
35 | "time": "Time cannot leave blank.",
36 | "repeatType": "Repeat type cannot be empty"
37 | },
38 | "operationSuccess": "Operation has been made successfully",
39 | "delete": "Delete",
40 | "jobsEmpty": "You haven't created alarm yet. So the list is empty"
41 | }
--------------------------------------------------------------------------------
/client/src/locales/tr.json:
--------------------------------------------------------------------------------
1 | {
2 | "createAlarm": "Alarm Oluştur",
3 | "listAlarm": "Alarmları Listele",
4 | "telegramBotMsg": "Tokeni almak için aşağıdaki linkten botun adresine tıklayıp. /token yazın. Sonrasında botun gönderdiği tokeni alttaki 'Token' bölümüne yazın",
5 | "fileUploadPlaceholder": "Resim Seç",
6 | "fileDropPlaceholder": "Buraya bırakın",
7 | "image": "Resim",
8 | "documentName": "Dökümanın İsmi",
9 | "time": "Saat ve dakika",
10 | "timePickerPlaceHolder": "Saat seçin",
11 | "repeatOptions": "Tekrarlama Ayarı",
12 | "createAlarmBtn": "Alarmı Oluştur",
13 | "resetAlarmForm": "Formu Resetle",
14 | "weekDays": {
15 | "default": "Seçiniz",
16 | "sunday": "Pazar",
17 | "monday": "Pazartesi",
18 | "tuesday": "Salı",
19 | "wednesday": "Çarşamba",
20 | "thursday": "Perşembe",
21 | "friday": "Cuma",
22 | "saturday": "Cumartesi",
23 | "all": "Haftanın Her Günü",
24 | "twoWeekSunday": "2 Haftada Bir Pazar",
25 | "twoWeekMonday": "2 Haftada Bir Pazartesi",
26 | "twoWeekTuesday": "2 Haftada Bir Salı",
27 | "twoWeekWednesday": "2 Haftada Bir Çarşamba",
28 | "twoWeekThursday": "2 Haftada Bir Perşembe",
29 | "twoWeekFriday": "2 Haftada Bir Cuma",
30 | "twoWeekSaturday": "2 Haftada Bir Cumartesi"
31 | },
32 | "formError": {
33 | "name": "İsim boş bırakılamaz.",
34 | "imgFile": "Dosya bölümü boş bırakılamaz.",
35 | "time": "Saat boş bırakılamaz.",
36 | "repeatType": "Tekrarlama aralığı boş bırakılamaz."
37 | },
38 | "operationSuccess": "İşleminiz başarılı bir şekilde yapıldı.",
39 | "delete": "Sil",
40 | "jobsEmpty": "Henüz bir alarm oluşturmadığınızdan bomboş bir liste sayfası görüyorsunuz."
41 | }
--------------------------------------------------------------------------------
/client/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import i18n from './plugins/i18n';
3 |
4 | import {BootstrapVue, IconsPlugin} from 'bootstrap-vue'
5 | import 'bootstrap/dist/css/bootstrap.css'
6 | import 'bootstrap-vue/dist/bootstrap-vue.css'
7 | import App from './App.vue'
8 | import router from './router'
9 | import store from './store'
10 |
11 | import messages from '@/mixins/messages'
12 |
13 | Vue.use(BootstrapVue)
14 | Vue.use(IconsPlugin)
15 |
16 | i18n.locale = 'tr';
17 |
18 | Vue.config.productionTip = false
19 |
20 | Vue.mixin({
21 | data() {
22 | return {
23 | isDevelopment: process.env.NODE_ENV === "development",
24 | repeatTypes: [
25 | {value: -1, text: i18n.tc('weekDays.default')},
26 | {value: 1, text: i18n.tc('weekDays.monday')},
27 | {value: 2, text: i18n.tc('weekDays.tuesday')},
28 | {value: 3, text: i18n.tc('weekDays.wednesday')},
29 | {value: 4, text: i18n.tc('weekDays.thursday')},
30 | {value: 5, text: i18n.tc('weekDays.friday')},
31 | {value: 6, text: i18n.tc('weekDays.saturday')},
32 | {value: 0, text: i18n.tc('weekDays.sunday')},
33 | {value: 7, text: i18n.tc('weekDays.all')},
34 | {value: 8, text: i18n.tc('weekDays.twoWeekMonday')},
35 | {value: 9, text: i18n.tc('weekDays.twoWeekTuesday')},
36 | {value: 10, text: i18n.tc('weekDays.twoWeekWednesday')},
37 | {value: 11, text: i18n.tc('weekDays.twoWeekThursday')},
38 | {value: 12, text: i18n.tc('weekDays.twoWeekFriday')},
39 | {value: 13, text: i18n.tc('weekDays.twoWeekSaturday')},
40 | {value: 14, text: i18n.tc('weekDays.twoWeekSunday')}
41 | ],
42 | indexStrToWeekDay: {
43 | "-1": i18n.tc('weekDays.default'),
44 | "1": i18n.tc('weekDays.monday'),
45 | "2": i18n.tc('weekDays.tuesday'),
46 | "3": i18n.tc('weekDays.wednesday'),
47 | "4": i18n.tc('weekDays.thursday'),
48 | "5": i18n.tc('weekDays.friday'),
49 | "6": i18n.tc('weekDays.saturday'),
50 | "0": i18n.tc('weekDays.sunday'),
51 | "7": i18n.tc('weekDays.all'),
52 | "8": i18n.tc('weekDays.twoWeekMonday'),
53 | "9": i18n.tc('weekDays.twoWeekTuesday'),
54 | "10": i18n.tc('weekDays.twoWeekWednesday'),
55 | "11": i18n.tc('weekDays.twoWeekThursday'),
56 | "12": i18n.tc('weekDays.twoWeekFriday'),
57 | "13": i18n.tc('weekDays.twoWeekSaturday'),
58 | "14": i18n.tc('weekDays.twoWeekSunday'),
59 | }
60 | };
61 | },
62 | });
63 |
64 | Vue.mixin(messages)
65 |
66 | new Vue({
67 | router,
68 | store,
69 | i18n,
70 | render: h => h(App)
71 | }).$mount('#app')
72 |
--------------------------------------------------------------------------------
/client/src/mixins/messages.js:
--------------------------------------------------------------------------------
1 | export default {
2 | methods: {
3 | async showErrorMessage(msg = "", title = "Error") {
4 | await this.$bvModal.msgBoxOk(msg, {
5 | title: title,
6 | headerBgVariant: 'danger',
7 | headerTextVariant: 'light',
8 | size: 'md',
9 | buttonSize: 'md',
10 | okVariant: 'danger',
11 | headerClass: 'p-2 border-bottom-0',
12 | bodyClass: 'modalCustomBody',
13 | footerClass: 'p-2 border-top-0',
14 | });
15 | },
16 | async showSucessMessage(msg = this.$t('operationSuccess'), title = 'Success') {
17 | await this.$bvModal.msgBoxOk(msg, {
18 | title: title,
19 | headerBgVariant: 'success',
20 | headerTextVariant: 'light',
21 | size: 'md',
22 | buttonSize: 'md',
23 | okVariant: 'success',
24 | headerClass: 'p-2 border-bottom-0',
25 | bodyClass: 'modalCustomBody',
26 | footerClass: 'p-2 border-top-0',
27 | });
28 | },
29 | }
30 | }
--------------------------------------------------------------------------------
/client/src/plugins/i18n.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import VueI18n from 'vue-i18n';
3 |
4 | Vue.use(VueI18n);
5 |
6 | function loadLocaleMessages() {
7 | const locales = require.context('../locales', true, /[A-Za-z0-9-_,\s]+\.json$/i);
8 | const messages = {};
9 | locales.keys().forEach((key) => {
10 | const matched = key.match(/([A-Za-z0-9-_]+)\./i);
11 | if (matched && matched.length > 1) {
12 | const locale = matched[1];
13 | messages[locale] = locales(key);
14 | }
15 | });
16 | return messages;
17 | }
18 |
19 | export default new VueI18n({
20 | locale: 'tr',
21 | fallback: 'en',
22 | messages: loadLocaleMessages(),
23 | });
--------------------------------------------------------------------------------
/client/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from 'vue-router'
3 |
4 | Vue.use(VueRouter)
5 |
6 | const routes = [
7 | {
8 | path: '/',
9 | name: 'AlarmList',
10 | component: () => import('@/views/AlarmList')
11 | },
12 | {
13 | path: '/create-alarm',
14 | name: 'AlarmCreate',
15 | component: () => import('@/views/AlarmCreate')
16 | }
17 | ]
18 |
19 | const router = new VueRouter({
20 | mode: 'hash',
21 | base: process.env.BASE_URL,
22 | routes
23 | })
24 |
25 | export default router
26 |
--------------------------------------------------------------------------------
/client/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 |
4 | Vue.use(Vuex)
5 |
6 | export default new Vuex.Store({
7 | state: {
8 | },
9 | mutations: {
10 | },
11 | actions: {
12 | },
13 | modules: {
14 | }
15 | })
16 |
--------------------------------------------------------------------------------
/client/src/views/AlarmCreate.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
19 |
24 |
25 |
26 |
27 |
29 |
30 |
31 | {{ $t('createAlarmBtn') }}
32 | {{ $t('resetAlarmForm') }}
33 |
34 |
35 |
36 |
37 |
117 |
118 |
--------------------------------------------------------------------------------
/client/src/views/AlarmList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
{{ $t('jobsEmpty') }}
6 |
7 |
8 |
9 |
10 |
11 | Alarm: {{ job.name }}
12 |
13 |
14 |
15 |
16 | {{ $t('delete') }}
17 |
18 |
19 |
20 |
21 |
22 |
23 | {{ indexStrToWeekDay[job.repeatType] }} ~/~ {{ job.time }}
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
33 |
34 |
35 |
36 |
37 |
38 |
83 |
84 |
--------------------------------------------------------------------------------
/client/tests/e2e/plugins/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable arrow-body-style */
2 | // https://docs.cypress.io/guides/guides/plugins-guide.html
3 |
4 | // if you need a custom webpack configuration you can uncomment the following import
5 | // and then use the `file:preprocessor` event
6 | // as explained in the cypress docs
7 | // https://docs.cypress.io/api/plugins/preprocessors-api.html#Examples
8 |
9 | // /* eslint-disable import/no-extraneous-dependencies, global-require */
10 | // const webpack = require('@cypress/webpack-preprocessor')
11 |
12 | module.exports = (on, config) => {
13 | // on('file:preprocessor', webpack({
14 | // webpackOptions: require('@vue/cli-service/webpack.config'),
15 | // watchOptions: {}
16 | // }))
17 |
18 | return Object.assign({}, config, {
19 | fixturesFolder: 'tests/e2e/fixtures',
20 | integrationFolder: 'tests/e2e/specs',
21 | screenshotsFolder: 'tests/e2e/screenshots',
22 | videosFolder: 'tests/e2e/videos',
23 | supportFile: 'tests/e2e/support/index.js'
24 | })
25 | }
26 |
--------------------------------------------------------------------------------
/client/tests/e2e/specs/test.js:
--------------------------------------------------------------------------------
1 | // https://docs.cypress.io/api/introduction/api.html
2 |
3 | describe('My First Test', () => {
4 | it('Visits the app root url', () => {
5 | cy.visit('/')
6 | cy.contains('h1', 'Welcome to Your Vue.js App')
7 | })
8 | })
9 |
--------------------------------------------------------------------------------
/client/tests/e2e/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This is will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/client/tests/e2e/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/client/tests/unit/example.spec.js:
--------------------------------------------------------------------------------
1 | import { shallowMount } from '@vue/test-utils'
2 |
3 | describe('HelloWorld.vue', () => {
4 | it('renders props.msg when passed', () => {
5 | /*const msg = 'new message'
6 | const wrapper = shallowMount(HelloWorld, {
7 | propsData: { msg }
8 | })
9 | expect(wrapper.text()).toMatch(msg)*/
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/client/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | devServer: {
3 | proxy: "http://localhost:3000"
4 | }
5 | }
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/spf13/viper"
5 | "log"
6 | )
7 |
8 | func Setup() {
9 | if IsDebug {
10 | viper.SetConfigFile("config_dev.json")
11 | } else {
12 | viper.SetConfigFile(".env")
13 | }
14 |
15 | if err := viper.ReadInConfig(); err != nil {
16 | log.Fatalf("Error while reading config file %s", err)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/config/config_dev.go:
--------------------------------------------------------------------------------
1 | // +build dev
2 |
3 | package config
4 |
5 | const (
6 | IsDebug = true
7 | )
8 |
--------------------------------------------------------------------------------
/config/config_prod.go:
--------------------------------------------------------------------------------
1 | // +build prod
2 |
3 | package config
4 |
5 | const (
6 | IsDebug = false
7 | )
8 |
--------------------------------------------------------------------------------
/controllers/alarm_controller.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "github.com/Abdulsametileri/cron-job-vue-go/infra/awsclient"
7 | "github.com/Abdulsametileri/cron-job-vue-go/infra/cronclient"
8 | "github.com/Abdulsametileri/cron-job-vue-go/infra/telegramclient"
9 | "github.com/Abdulsametileri/cron-job-vue-go/models"
10 | "github.com/Abdulsametileri/cron-job-vue-go/services/jobservice"
11 | "github.com/Abdulsametileri/cron-job-vue-go/services/userservice"
12 | "github.com/google/uuid"
13 | "net/http"
14 | )
15 |
16 | var (
17 | ErrMethodNotAllowed = errors.New("Method not allowed")
18 | ErrFieldNotFound = func(field string) error { return fmt.Errorf("You have to specify %s", field) }
19 | ErrReadingFile = errors.New("Error while reading image file")
20 | ErrDb = errors.New("DB error occured")
21 | ErrTokenDoesNotExistInUrl = errors.New("Token does not exist")
22 | ErrS3Upload = errors.New("Error uploading file to s3")
23 | ErrDeleteFileS3 = errors.New("Error deleting file in s3")
24 | ErrAddingJob = errors.New("Error adding job to Db")
25 | ErrGettingJob = errors.New("Error getting job in DB")
26 | ErrJobAlreadyExist = errors.New("Error you already create your job before")
27 | ErrJobDelete = errors.New("Error deleting the speficied tag job in db")
28 | ErrUserDoesNotExist = errors.New("Error user does not exist")
29 | ErrGettingJobList = errors.New("Error getting the job list")
30 | ErrTagDoesNotExistInUrl = errors.New("Tag does not exist in the url")
31 | )
32 |
33 | type AlarmController interface {
34 | CreateAlarm(http.ResponseWriter, *http.Request)
35 | ListAlarm(http.ResponseWriter, *http.Request)
36 | DeleteAlarm(http.ResponseWriter, *http.Request)
37 | }
38 |
39 | type alarmController struct {
40 | bc BaseController
41 | us userservice.UserService
42 | js jobservice.JobService
43 | aws awsclient.AwsClient
44 | tc telegramclient.TelegramClient
45 | cc cronclient.CronClient
46 | }
47 |
48 | func NewAlarmController(
49 | bc BaseController,
50 | us userservice.UserService, js jobservice.JobService, aws awsclient.AwsClient,
51 | tc telegramclient.TelegramClient,
52 | cc cronclient.CronClient) AlarmController {
53 | return &alarmController{
54 | bc: bc,
55 | us: us,
56 | js: js,
57 | aws: aws,
58 | tc: tc,
59 | cc: cc,
60 | }
61 | }
62 |
63 | func (ac alarmController) CreateAlarm(w http.ResponseWriter, r *http.Request) {
64 | if r.Method != http.MethodPost {
65 | ac.bc.Error(w, http.StatusNotFound, ErrMethodNotAllowed)
66 | return
67 | }
68 |
69 | token := r.FormValue("token")
70 | name := r.FormValue("name")
71 | gettime := r.FormValue("time")
72 | repeatType := r.FormValue("repeatType")
73 | uploadedFile, _, errFile := r.FormFile("file")
74 | uploadedFileName := r.FormValue("fileName")
75 | uploadedFileType := r.FormValue("fileType")
76 |
77 | if token == "" {
78 | ac.bc.Error(w, http.StatusBadRequest, ErrFieldNotFound("token"))
79 | return
80 | }
81 |
82 | if name == "" {
83 | ac.bc.Error(w, http.StatusBadRequest, ErrFieldNotFound("name"))
84 | return
85 | }
86 |
87 | if gettime == "" {
88 | ac.bc.Error(w, http.StatusBadRequest, ErrFieldNotFound("time"))
89 | return
90 | }
91 |
92 | if repeatType == "" || repeatType == "-1" {
93 | ac.bc.Error(w, http.StatusBadRequest, ErrFieldNotFound("repeat time"))
94 | return
95 | }
96 |
97 | if errFile != nil {
98 | ac.bc.Error(w, http.StatusBadRequest, ErrReadingFile)
99 | return
100 | }
101 |
102 | user, err := ac.us.GetUserByToken(token)
103 | if err != nil {
104 | fmt.Println(err)
105 | ac.bc.Error(w, http.StatusBadRequest, ErrDb)
106 | return
107 | }
108 |
109 | if user.TelegramId == 0 {
110 | ac.bc.Error(w, http.StatusBadRequest, ErrTokenDoesNotExistInUrl)
111 | return
112 | }
113 |
114 | searchByFields := make(map[string]interface{})
115 | searchByFields["userTelegramId"] = user.TelegramId
116 | searchByFields["imageUrl"] = ac.aws.DetermineS3ImageUrl(user.TelegramId, uploadedFileName)
117 | searchByFields["repeatType"] = repeatType
118 | searchByFields["time"] = gettime
119 |
120 | job, err := ac.js.GetJobByFields(searchByFields)
121 | if err != nil {
122 | fmt.Println(err)
123 | ac.bc.Error(w, http.StatusBadRequest, ErrGettingJob)
124 | return
125 | }
126 |
127 | if job.Tag != "" {
128 | ac.bc.Error(w, http.StatusBadRequest, ErrJobAlreadyExist)
129 | return
130 | }
131 |
132 | filePathOnS3, err := ac.aws.UploadToS3(user.TelegramId, uploadedFileName, uploadedFileType, uploadedFile)
133 | if err != nil {
134 | ac.bc.Error(w, http.StatusBadRequest, ErrS3Upload)
135 | return
136 | }
137 |
138 | addedJob := models.Job{
139 | Tag: uuid.New().String(),
140 | Name: name,
141 | UserTelegramId: user.TelegramId,
142 | UserToken: user.Token,
143 | ImageUrl: filePathOnS3,
144 | RepeatType: repeatType,
145 | Time: gettime,
146 | Status: models.JobValid,
147 | }
148 | err = ac.js.AddJob(addedJob)
149 |
150 | if err != nil {
151 | err = ac.aws.DeleteFileInS3(filePathOnS3)
152 | if err != nil {
153 | ac.bc.Error(w, http.StatusBadRequest, ErrDeleteFileS3)
154 | return
155 | }
156 | ac.bc.Error(w, http.StatusBadRequest, ErrAddingJob)
157 | return
158 | }
159 |
160 | _ = ac.cc.Schedule(addedJob)
161 |
162 | ac.bc.Data(w, http.StatusOK, nil, "")
163 | }
164 |
165 | func (ac alarmController) ListAlarm(w http.ResponseWriter, r *http.Request) {
166 | if r.Method != http.MethodGet {
167 | ac.bc.Error(w, http.StatusNotFound, ErrMethodNotAllowed)
168 | return
169 | }
170 |
171 | token := r.URL.Query().Get("token")
172 | if token == "" {
173 | ac.bc.Error(w, http.StatusBadRequest, ErrTokenDoesNotExistInUrl)
174 | return
175 | }
176 |
177 | user, err := ac.us.GetUserByToken(token)
178 | if err != nil {
179 | ac.bc.Error(w, http.StatusBadRequest, err)
180 | return
181 | }
182 | if user.TelegramId == 0 {
183 | ac.bc.Error(w, http.StatusBadRequest, ErrUserDoesNotExist)
184 | return
185 | }
186 |
187 | jobs, err := ac.js.ListAllValidJobsByToken(token)
188 | if err != nil {
189 | ac.bc.Error(w, http.StatusBadRequest, ErrGettingJobList)
190 | return
191 | }
192 |
193 | ac.bc.Data(w, http.StatusOK, jobs, "")
194 | }
195 |
196 | func (ac alarmController) DeleteAlarm(w http.ResponseWriter, r *http.Request) {
197 | if r.Method != http.MethodPost {
198 | ac.bc.Error(w, http.StatusNotFound, ErrMethodNotAllowed)
199 | return
200 | }
201 |
202 | tag := r.URL.Query().Get("tag")
203 | if tag == "" {
204 | ac.bc.Error(w, http.StatusBadRequest, ErrTagDoesNotExistInUrl)
205 | return
206 | }
207 |
208 | err := ac.js.DeleteJobByTag(tag)
209 | if err != nil {
210 | ac.bc.Error(w, http.StatusBadRequest, ErrJobDelete)
211 | return
212 | }
213 |
214 | err = ac.cc.RemoveJobByTag(tag)
215 | if err != nil {
216 | ac.tc.SendMessageForDebug(fmt.Sprintf("error removing job by tag %s %s", tag, err.Error()))
217 | return
218 | }
219 |
220 | ac.bc.Data(w, http.StatusOK, nil, "")
221 | }
222 |
--------------------------------------------------------------------------------
/controllers/alarm_controller_setup_test.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "github.com/Abdulsametileri/cron-job-vue-go/models"
5 | "mime/multipart"
6 | )
7 |
8 | type userSvc struct{}
9 |
10 | func (uc *userSvc) AddUser(user models.User) error {
11 | return nil
12 | }
13 |
14 | func (uc *userSvc) GetUserByTelegramId(telegramId int64) (models.User, error) {
15 | return models.User{}, nil
16 | }
17 |
18 | func (uc *userSvc) GetUserByToken(token string) (models.User, error) {
19 | if token == "db-err" {
20 | return models.User{}, ErrDb
21 | }
22 |
23 | if token == "not-exist-token" {
24 | return models.User{}, nil
25 | }
26 |
27 | if token == "job-already-exist-db-error" {
28 | return models.User{TelegramId: -1}, nil
29 | }
30 |
31 | if token == "job-already-exist" {
32 | return models.User{
33 | Token: "job-already-exist",
34 | TelegramId: 123,
35 | TelegramDisplayName: "ileri4s",
36 | }, nil
37 | }
38 |
39 | return models.User{
40 | Token: "x1pnwjjkhj3o",
41 | TelegramId: 12314,
42 | TelegramDisplayName: "ileri4s",
43 | }, nil
44 | }
45 |
46 | type jobSvc struct{}
47 |
48 | func (js *jobSvc) ListAllValidJobsByToken(token string) ([]models.Job, error) {
49 | if token == "job-list-err" {
50 | return nil, ErrGettingJobList
51 | }
52 | if token == "three-job-list-item" {
53 | return make([]models.Job, 3), nil
54 | }
55 | return make([]models.Job, 0), nil
56 | }
57 |
58 | func (js *jobSvc) DeleteJobByTag(tag string) error {
59 | if tag == "error-tag" {
60 | return ErrJobDelete
61 | }
62 | return nil
63 | }
64 |
65 | func (js *jobSvc) ListAllValidJobs() ([]models.Job, error) {
66 | return make([]models.Job, 0), nil
67 | }
68 |
69 | func (js *jobSvc) AddJob(job models.Job) error {
70 | if job.ImageUrl == "error-scenario-with-s3" {
71 | return ErrAddingJob
72 | }
73 | if job.ImageUrl == "error-scenario-job" {
74 | return ErrAddingJob
75 | }
76 | return nil
77 | }
78 |
79 | func (js *jobSvc) GetJobByFields(fields map[string]interface{}) (models.Job, error) {
80 | if fields["userTelegramId"].(int64) == -1 {
81 | return models.Job{}, ErrGettingJob
82 | }
83 | if fields["userTelegramId"].(int64) == 123 {
84 | return models.Job{
85 | Tag: "arbitrary",
86 | }, nil
87 | }
88 | return models.Job{}, nil
89 | }
90 |
91 | type awsClient struct{}
92 |
93 | func (client awsClient) UploadToS3(userId int64, fileName, fileType string, file multipart.File) (string, error) {
94 | if fileName == "badFileName" {
95 | return "", ErrS3Upload
96 | }
97 | return fileName, nil
98 | }
99 |
100 | func (client awsClient) DeleteFileInS3(fileUrl string) error {
101 | if fileUrl == "error-scenario-with-s3" {
102 | return ErrDeleteFileS3
103 | }
104 | return nil
105 | }
106 |
107 | func (client awsClient) DetermineS3ImageUrl(userId int64, fileName string) string {
108 | return "https://remindercron.s3.eu-central-1.amazonaws.com/Screen+Shot+2021-05-12+at+09.39.43.png"
109 | }
110 |
111 | type telegramClient struct{}
112 |
113 | func (tc *telegramClient) SendMessageForDebug(msg string) {
114 |
115 | }
116 |
117 | func (tc *telegramClient) GetMessages() {
118 |
119 | }
120 |
121 | func (tc *telegramClient) SendImage(telegramId int64, imageUrl string) error {
122 | return nil
123 | }
124 |
--------------------------------------------------------------------------------
/controllers/alarm_controller_test.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "github.com/magiconair/properties/assert"
5 | "net/http"
6 | "testing"
7 | )
8 |
9 | func TestAlarmController(t *testing.T) {
10 | bc := NewBaseController()
11 | userService := &userSvc{}
12 | jobService := &jobSvc{}
13 | awsClient := &awsClient{}
14 | telegramClient := &telegramClient{}
15 |
16 | alarmCtrl := NewAlarmController(bc, userService, jobService, awsClient, telegramClient, nil)
17 |
18 | t.Run("CreateAlarm", func(t *testing.T) {
19 | t.Run("Is Get Not Allowed", func(t *testing.T) {
20 | w, req := createHttpReq(http.MethodGet, "/api/create-alarm", nil)
21 | alarmCtrl.CreateAlarm(w, req)
22 |
23 | res := parseBody(w)
24 |
25 | assert.Equal(t, res.Code, http.StatusNotFound)
26 | assert.Equal(t, res.Message, ErrMethodNotAllowed.Error())
27 | })
28 | t.Run("Getting Token error", func(t *testing.T) {
29 | w, req := createHttpReq(http.MethodPost, "/api/create-alarm", nil)
30 | alarmCtrl.CreateAlarm(w, req)
31 |
32 | res := parseBody(w)
33 |
34 | assert.Equal(t, res.Code, http.StatusBadRequest)
35 | assert.Equal(t, res.Message, ErrFieldNotFound("token").Error())
36 | })
37 | t.Run("Non empty token but getting empty name error", func(t *testing.T) {
38 | w, req := createHttpReq(http.MethodPost, "/api/create-alarm", nil)
39 | req.Form.Set("token", "token")
40 |
41 | alarmCtrl.CreateAlarm(w, req)
42 |
43 | res := parseBody(w)
44 |
45 | assert.Equal(t, res.Code, http.StatusBadRequest)
46 | assert.Equal(t, res.Message, ErrFieldNotFound("name").Error())
47 | })
48 | t.Run("Non empty {token, name} but getting empty time error", func(t *testing.T) {
49 | w, req := createHttpReq(http.MethodPost, "/api/create-alarm", nil)
50 | req.Form.Set("token", "token")
51 | req.Form.Set("name", "name")
52 |
53 | alarmCtrl.CreateAlarm(w, req)
54 |
55 | res := parseBody(w)
56 |
57 | assert.Equal(t, res.Code, http.StatusBadRequest)
58 | assert.Equal(t, res.Message, ErrFieldNotFound("time").Error())
59 | })
60 |
61 | t.Run("Non empty {token, name, time} but getting empty repeatType error", func(t *testing.T) {
62 | w, req := createHttpReq(http.MethodPost, "/api/create-alarm", nil)
63 | req.Form.Set("token", "token")
64 | req.Form.Set("name", "name")
65 | req.Form.Set("time", "23:14")
66 |
67 | alarmCtrl.CreateAlarm(w, req)
68 |
69 | res := parseBody(w)
70 |
71 | assert.Equal(t, res.Code, http.StatusBadRequest)
72 | assert.Equal(t, res.Message, ErrFieldNotFound("repeat time").Error())
73 | })
74 | t.Run("Non empty {token,name,time,repeatType} but getting reading image file err", func(t *testing.T) {
75 | w, req := createHttpReq(http.MethodPost, "/api/create-alarm", nil)
76 | req.Form.Set("token", "token")
77 | req.Form.Set("name", "name")
78 | req.Form.Set("time", "23:14")
79 | req.Form.Set("repeatType", "5")
80 |
81 | alarmCtrl.CreateAlarm(w, req)
82 |
83 | res := parseBody(w)
84 |
85 | assert.Equal(t, res.Code, http.StatusBadRequest)
86 | assert.Equal(t, res.Message, ErrReadingFile.Error())
87 | })
88 | t.Run("Getting token err occured in db", func(t *testing.T) {
89 | body, contentType := fileUploadRequest()
90 |
91 | w, req := createHttpReq(http.MethodPost, "/api/create-alarm", body)
92 | req.Form.Set("token", "db-err")
93 | req.Form.Set("name", "name")
94 | req.Form.Set("time", "23:14")
95 | req.Form.Set("repeatType", "5")
96 | req.Form.Set("fileName", "test")
97 | req.Form.Set("fileType", "image/png")
98 |
99 | req.Header.Add("Content-Type", contentType)
100 |
101 | alarmCtrl.CreateAlarm(w, req)
102 |
103 | res := parseBody(w)
104 |
105 | assert.Equal(t, res.Code, http.StatusBadRequest)
106 | assert.Equal(t, res.Message, ErrDb.Error())
107 | })
108 |
109 | t.Run("when job exist db error", func(t *testing.T) {
110 | body, contentType := fileUploadRequest()
111 |
112 | w, req := createHttpReq(http.MethodPost, "/api/create-alarm", body)
113 | req.Form.Set("token", "job-already-exist-db-error")
114 | req.Form.Set("name", "name")
115 | req.Form.Set("time", "23:14")
116 | req.Form.Set("repeatType", "5")
117 | req.Form.Set("fileName", "arbitrary-name")
118 | req.Form.Set("fileType", "image/png")
119 |
120 | req.Header.Add("Content-Type", contentType)
121 |
122 | alarmCtrl.CreateAlarm(w, req)
123 |
124 | res := parseBody(w)
125 |
126 | assert.Equal(t, res.Code, http.StatusBadRequest)
127 | assert.Equal(t, res.Message, ErrGettingJob.Error())
128 | })
129 | t.Run("when job already exist error", func(t *testing.T) {
130 | body, contentType := fileUploadRequest()
131 |
132 | w, req := createHttpReq(http.MethodPost, "/api/create-alarm", body)
133 | req.Form.Set("token", "job-already-exist")
134 | req.Form.Set("name", "name")
135 | req.Form.Set("time", "23:14")
136 | req.Form.Set("repeatType", "5")
137 | req.Form.Set("fileName", "arbitrary-name")
138 | req.Form.Set("fileType", "image/png")
139 |
140 | req.Header.Add("Content-Type", contentType)
141 |
142 | alarmCtrl.CreateAlarm(w, req)
143 |
144 | res := parseBody(w)
145 |
146 | assert.Equal(t, res.Code, http.StatusBadRequest)
147 | assert.Equal(t, res.Message, ErrJobAlreadyExist.Error())
148 | })
149 |
150 | t.Run("Getting non exist token error", func(t *testing.T) {
151 | body, contentType := fileUploadRequest()
152 |
153 | w, req := createHttpReq(http.MethodPost, "/api/create-alarm", body)
154 | req.Form.Set("token", "not-exist-token")
155 | req.Form.Set("name", "name")
156 | req.Form.Set("time", "23:14")
157 | req.Form.Set("repeatType", "5")
158 | req.Form.Set("fileName", "test")
159 | req.Form.Set("fileType", "image/png")
160 |
161 | req.Header.Add("Content-Type", contentType)
162 |
163 | alarmCtrl.CreateAlarm(w, req)
164 |
165 | res := parseBody(w)
166 |
167 | assert.Equal(t, res.Code, http.StatusBadRequest)
168 | assert.Equal(t, res.Message, ErrTokenDoesNotExistInUrl.Error())
169 | })
170 | t.Run("Getting s3 upload error", func(t *testing.T) {
171 | body, contentType := fileUploadRequest()
172 |
173 | w, req := createHttpReq(http.MethodPost, "/api/create-alarm", body)
174 | req.Form.Set("token", "sametintokeni")
175 | req.Form.Set("name", "name")
176 | req.Form.Set("time", "23:14")
177 | req.Form.Set("repeatType", "5")
178 | req.Form.Set("fileName", "badFileName")
179 | req.Form.Set("fileType", "image/png")
180 |
181 | req.Header.Add("Content-Type", contentType)
182 |
183 | alarmCtrl.CreateAlarm(w, req)
184 |
185 | res := parseBody(w)
186 |
187 | assert.Equal(t, res.Code, http.StatusBadRequest)
188 | assert.Equal(t, res.Message, ErrS3Upload.Error())
189 | })
190 |
191 | t.Run("When job created, job err occured, delete uploaded file in s3 also occured", func(t *testing.T) {
192 | body, contentType := fileUploadRequest()
193 |
194 | w, req := createHttpReq(http.MethodPost, "/api/create-alarm", body)
195 | req.Form.Set("token", "sametintokeni")
196 | req.Form.Set("name", "name")
197 | req.Form.Set("time", "23:14")
198 | req.Form.Set("repeatType", "5")
199 | req.Form.Set("fileName", "error-scenario-with-s3")
200 | req.Form.Set("fileType", "image/png")
201 |
202 | req.Header.Add("Content-Type", contentType)
203 |
204 | alarmCtrl.CreateAlarm(w, req)
205 |
206 | res := parseBody(w)
207 |
208 | assert.Equal(t, res.Code, http.StatusBadRequest)
209 | assert.Equal(t, res.Message, ErrDeleteFileS3.Error())
210 | })
211 | t.Run("When job created, job err occured, delete uploaded file in s3 is success return add job error", func(t *testing.T) {
212 | body, contentType := fileUploadRequest()
213 |
214 | w, req := createHttpReq(http.MethodPost, "/api/create-alarm", body)
215 | req.Form.Set("token", "sametintokeni")
216 | req.Form.Set("name", "name")
217 | req.Form.Set("time", "23:14")
218 | req.Form.Set("repeatType", "5")
219 | req.Form.Set("fileName", "error-scenario-job")
220 | req.Form.Set("fileType", "image/png")
221 |
222 | req.Header.Add("Content-Type", contentType)
223 |
224 | alarmCtrl.CreateAlarm(w, req)
225 |
226 | res := parseBody(w)
227 |
228 | assert.Equal(t, res.Code, http.StatusBadRequest)
229 | assert.Equal(t, res.Message, ErrAddingJob.Error())
230 | })
231 | })
232 |
233 | t.Run("ListAlarm", func(t *testing.T) {
234 | t.Run("Is Post not allowed", func(t *testing.T) {
235 | w, req := createHttpReq(http.MethodPost, "/api/list-alarm", nil)
236 | alarmCtrl.ListAlarm(w, req)
237 |
238 | res := parseBody(w)
239 |
240 | assert.Equal(t, res.Code, http.StatusNotFound)
241 | assert.Equal(t, res.Message, ErrMethodNotAllowed.Error())
242 | })
243 | t.Run("Error when token not specified", func(t *testing.T) {
244 | w, req := createHttpReq(http.MethodGet, "/api/list-alarm", nil)
245 | alarmCtrl.ListAlarm(w, req)
246 |
247 | res := parseBody(w)
248 |
249 | assert.Equal(t, res.Code, http.StatusBadRequest)
250 | assert.Equal(t, res.Message, ErrTokenDoesNotExistInUrl.Error())
251 | })
252 |
253 | t.Run("Error occured in db when validating the token", func(t *testing.T) {
254 | w, req := createHttpReq(http.MethodGet, "/api/list-alarm/?token=db-err", nil)
255 | alarmCtrl.ListAlarm(w, req)
256 |
257 | res := parseBody(w)
258 |
259 | assert.Equal(t, res.Code, http.StatusBadRequest)
260 | assert.Equal(t, res.Message, ErrDb.Error())
261 | })
262 |
263 | t.Run("Error user cannot exist given token", func(t *testing.T) {
264 | w, req := createHttpReq(http.MethodGet, "/api/list-alarm/?token=not-exist-token", nil)
265 | alarmCtrl.ListAlarm(w, req)
266 |
267 | res := parseBody(w)
268 |
269 | assert.Equal(t, res.Code, http.StatusBadRequest)
270 | assert.Equal(t, res.Message, ErrUserDoesNotExist.Error())
271 | })
272 |
273 | t.Run("Error getting the job list", func(t *testing.T) {
274 | w, req := createHttpReq(http.MethodGet, "/api/list-alarm/?token=job-list-err", nil)
275 | alarmCtrl.ListAlarm(w, req)
276 |
277 | res := parseBody(w)
278 | assert.Equal(t, res.Code, http.StatusBadRequest)
279 | assert.Equal(t, res.Message, ErrGettingJobList.Error())
280 | })
281 |
282 | t.Run("Getting job list empty", func(t *testing.T) {
283 | w, req := createHttpReq(http.MethodGet, "/api/list-alarm/?token=123", nil)
284 | alarmCtrl.ListAlarm(w, req)
285 |
286 | res := parseBody(w)
287 | assert.Equal(t, res.Code, http.StatusOK)
288 |
289 | val, _ := res.Data.([]interface{})
290 | lenItems := len(val)
291 |
292 | assert.Equal(t, lenItems, 0)
293 | })
294 |
295 | t.Run("Getting the job list item", func(t *testing.T) {
296 | w, req := createHttpReq(http.MethodGet, "/api/list-alarm/?token=three-job-list-item", nil)
297 | alarmCtrl.ListAlarm(w, req)
298 |
299 | res := parseBody(w)
300 | assert.Equal(t, res.Code, http.StatusOK)
301 |
302 | val, _ := res.Data.([]interface{})
303 | lenItems := len(val)
304 |
305 | assert.Equal(t, lenItems, 3)
306 | })
307 | })
308 |
309 | t.Run("DeleteAlarm", func(t *testing.T) {
310 | t.Run("Is Get Not Allowed", func(t *testing.T) {
311 | w, req := createHttpReq(http.MethodGet, "/api/delete-alarm", nil)
312 | alarmCtrl.DeleteAlarm(w, req)
313 | res := parseBody(w)
314 |
315 | assert.Equal(t, res.Code, http.StatusNotFound)
316 | assert.Equal(t, res.Message, ErrMethodNotAllowed.Error())
317 | })
318 | t.Run("Is tag is speficied in url", func(t *testing.T) {
319 | w, req := createHttpReq(http.MethodPost, "/api/delete-alarm", nil)
320 | alarmCtrl.DeleteAlarm(w, req)
321 | res := parseBody(w)
322 |
323 | assert.Equal(t, res.Code, http.StatusBadRequest)
324 | assert.Equal(t, res.Message, ErrTagDoesNotExistInUrl.Error())
325 | })
326 | t.Run("Error occured when deleting the job in db with specified tag", func(t *testing.T) {
327 | w, req := createHttpReq(http.MethodPost, "/api/delete-alarm?tag=error-tag", nil)
328 | alarmCtrl.DeleteAlarm(w, req)
329 | res := parseBody(w)
330 |
331 | assert.Equal(t, res.Code, http.StatusBadRequest)
332 | assert.Equal(t, res.Message, ErrJobDelete.Error())
333 | })
334 | })
335 | }
336 |
--------------------------------------------------------------------------------
/controllers/base_controller.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 | )
7 |
8 | type Props struct {
9 | Code int `json:"code"`
10 | Data interface{} `json:"data"`
11 | Message string `json:"message"`
12 | }
13 |
14 | type BaseController interface {
15 | Data(w http.ResponseWriter, code int, data interface{}, message string)
16 | Error(w http.ResponseWriter, code int, err error)
17 | }
18 |
19 | type baseController struct {
20 | }
21 |
22 | func NewBaseController() BaseController {
23 | return &baseController{}
24 | }
25 |
26 | func (bc *baseController) Data(w http.ResponseWriter, code int, data interface{}, message string) {
27 | ret := &Props{
28 | Code: code,
29 | Data: data,
30 | Message: message,
31 | }
32 | w.Header().Set("Content-Type", "application/json")
33 | jData, _ := json.Marshal(&ret)
34 | w.Write(jData)
35 | }
36 |
37 | func (bc *baseController) Error(w http.ResponseWriter, code int, friendlyErrorForClient error) {
38 | ret := &Props{
39 | Code: code,
40 | Data: nil,
41 | Message: friendlyErrorForClient.Error(),
42 | }
43 | w.Header().Set("Content-Type", "application/json")
44 | jData, _ := json.Marshal(&ret)
45 | w.Write(jData)
46 | }
47 |
--------------------------------------------------------------------------------
/controllers/test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abdulsametileri/cron-job-vue-go/8f41c7f31d6cbd0795e6bd7b60881a404f09549c/controllers/test.png
--------------------------------------------------------------------------------
/controllers/test_helper.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "io"
8 | "io/ioutil"
9 | "mime/multipart"
10 | "net/http"
11 | "net/http/httptest"
12 | "net/url"
13 | "os"
14 | )
15 |
16 | func writeErrorMsg(err error) string {
17 | return err.Error() + "\n"
18 | }
19 |
20 | func parseBody(w *httptest.ResponseRecorder) Props {
21 | resp := w.Result()
22 | body, _ := ioutil.ReadAll(resp.Body)
23 | defer resp.Body.Close()
24 |
25 | props := Props{}
26 | _ = json.Unmarshal(body, &props)
27 |
28 | return props
29 | }
30 |
31 | func createHttpReq(method string, endpoint string, body *bytes.Buffer) (w *httptest.ResponseRecorder, req *http.Request) {
32 | if body == nil {
33 | body = bytes.NewBuffer(make([]byte, 512))
34 | }
35 | req = httptest.NewRequest(method, endpoint, body)
36 | w = httptest.NewRecorder()
37 | req.Form = url.Values{}
38 | return
39 | }
40 |
41 | func fileUploadRequest() (body *bytes.Buffer, contentType string) {
42 | file, err := os.Open("./test.png")
43 | if err != nil {
44 | fmt.Println(err)
45 | }
46 | defer file.Close()
47 | body = new(bytes.Buffer)
48 | writer := multipart.NewWriter(body)
49 | part, err := writer.CreateFormFile("file", "test")
50 | if err != nil {
51 | fmt.Println(err)
52 | }
53 | _, err = io.Copy(part, file)
54 | if err != nil {
55 | fmt.Println(err)
56 | }
57 | err = writer.Close()
58 | if err != nil {
59 | fmt.Println(err)
60 | }
61 | return body, writer.FormDataContentType()
62 | }
63 |
--------------------------------------------------------------------------------
/controllers/token_controller.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "github.com/Abdulsametileri/cron-job-vue-go/services/userservice"
5 | "net/http"
6 | )
7 |
8 | type TokenController interface {
9 | ValidateToken(http.ResponseWriter, *http.Request)
10 | }
11 |
12 | type tokenController struct {
13 | bc BaseController
14 | us userservice.UserService
15 | }
16 |
17 | func NewTokenController(bc BaseController, us userservice.UserService) TokenController {
18 | return &tokenController{
19 | bc: bc,
20 | us: us,
21 | }
22 | }
23 |
24 | func (t tokenController) ValidateToken(w http.ResponseWriter, r *http.Request) {
25 | if r.Method != http.MethodGet {
26 | t.bc.Error(w, http.StatusNotFound, ErrMethodNotAllowed)
27 | return
28 | }
29 |
30 | token := r.URL.Query().Get("token")
31 | if token == "" {
32 | t.bc.Error(w, http.StatusBadRequest, ErrFieldNotFound("token"))
33 | return
34 | }
35 |
36 | user, err := t.us.GetUserByToken(token)
37 | if err != nil || user.TelegramId == 0 {
38 | t.bc.Error(w, http.StatusBadRequest, ErrTokenDoesNotExistInUrl)
39 | return
40 | }
41 |
42 | t.bc.Data(w, http.StatusOK, nil, "")
43 | }
44 |
--------------------------------------------------------------------------------
/controllers/token_controller_test.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/stretchr/testify/assert"
6 | "io/ioutil"
7 | "net/http"
8 | "testing"
9 | )
10 |
11 | func TestTokenController(t *testing.T) {
12 | us := &userSvc{}
13 | bc := NewBaseController()
14 | tokenCtrl := NewTokenController(bc, us)
15 |
16 | t.Run("Any method except get is not allowed", func(t *testing.T) {
17 | w, req := createHttpReq(http.MethodPost, "/api/validate-token", nil)
18 | tokenCtrl.ValidateToken(w, req)
19 |
20 | resp := w.Result()
21 | body, _ := ioutil.ReadAll(resp.Body)
22 | defer resp.Body.Close()
23 |
24 | props := &Props{}
25 | _ = json.Unmarshal(body, props)
26 |
27 | assert.Equal(t, props.Code, http.StatusNotFound)
28 | assert.Equal(t, props.Message, ErrMethodNotAllowed.Error())
29 | })
30 | t.Run("Token does not exist error", func(t *testing.T) {
31 | w, req := createHttpReq(http.MethodGet, "/api/validate-token", nil)
32 | tokenCtrl.ValidateToken(w, req)
33 |
34 | resp := w.Result()
35 | body, _ := ioutil.ReadAll(resp.Body)
36 | defer resp.Body.Close()
37 |
38 | props := &Props{}
39 | _ = json.Unmarshal(body, props)
40 |
41 | assert.Equal(t, props.Code, http.StatusBadRequest)
42 | assert.Equal(t, props.Message, ErrFieldNotFound("token").Error())
43 | })
44 | t.Run("user does not exist with specified token", func(t *testing.T) {
45 | w, req := createHttpReq(http.MethodGet, "/api/validate-token/?token=not-exist-token", nil)
46 | tokenCtrl.ValidateToken(w, req)
47 |
48 | resp := w.Result()
49 | body, _ := ioutil.ReadAll(resp.Body)
50 | defer resp.Body.Close()
51 |
52 | props := &Props{}
53 | _ = json.Unmarshal(body, props)
54 |
55 | assert.Equal(t, props.Code, http.StatusBadRequest)
56 | assert.Equal(t, props.Message, ErrTokenDoesNotExistInUrl.Error())
57 | })
58 | }
59 |
--------------------------------------------------------------------------------
/database/init.go:
--------------------------------------------------------------------------------
1 | package database
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/spf13/viper"
7 | "go.mongodb.org/mongo-driver/mongo"
8 | "go.mongodb.org/mongo-driver/mongo/options"
9 | "log"
10 | "time"
11 | )
12 |
13 | func Setup() *mongo.Client {
14 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
15 | defer cancel()
16 | client, err := mongo.Connect(ctx, options.Client().ApplyURI(viper.GetString("MONGODB_URI")))
17 | if err != nil {
18 | log.Fatalf("Error when trying to connect mongodb database %v", err)
19 | }
20 | fmt.Println(err)
21 | return client
22 | }
23 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/Abdulsametileri/cron-job-vue-go
2 |
3 | go 1.16
4 |
5 | require (
6 | github.com/aws/aws-sdk-go v1.38.40 // indirect
7 | github.com/go-co-op/gocron v1.5.0
8 | github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
9 | github.com/google/go-cmp v0.5.5 // indirect
10 | github.com/google/uuid v1.2.0
11 | github.com/magiconair/properties v1.8.1
12 | github.com/spf13/viper v1.7.1
13 | github.com/stretchr/testify v1.7.0
14 | github.com/technoweenie/multipartstreamer v1.0.1 // indirect
15 | go.mongodb.org/mongo-driver v1.5.2
16 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect
17 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 // indirect
18 | golang.org/x/text v0.3.6 // indirect
19 | gopkg.in/yaml.v2 v2.4.0 // indirect
20 | )
21 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
8 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
9 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
10 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
11 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
12 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
13 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
14 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
15 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
16 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
17 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
18 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
19 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
20 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
21 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
22 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
23 | github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk=
24 | github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
25 | github.com/aws/aws-sdk-go v1.38.40 h1:VVqBFV24tGgXR11tFXPjmR+0ItbnUepbuQjdmhgu3U0=
26 | github.com/aws/aws-sdk-go v1.38.40/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
27 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
28 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
29 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
30 | github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
31 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
32 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
33 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
34 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
35 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
36 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
37 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
38 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
39 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
40 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
41 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
42 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
43 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
44 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
45 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
46 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
47 | github.com/go-co-op/gocron v1.5.0 h1:tIiwAPwKGcazVFJTNmGe0wE73UpZSEHovoahqGGx9+c=
48 | github.com/go-co-op/gocron v1.5.0/go.mod h1:7MgKum7jD7YgIRj7Zx7K1iJKAf1MlSIsEieRl18+KyU=
49 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
50 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
51 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
52 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
53 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
54 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
55 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
56 | github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU=
57 | github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
58 | github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
59 | github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
60 | github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
61 | github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
62 | github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
63 | github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs=
64 | github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
65 | github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
66 | github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
67 | github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28=
68 | github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo=
69 | github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk=
70 | github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw=
71 | github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
72 | github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg=
73 | github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE=
74 | github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
75 | github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
76 | github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
77 | github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
78 | github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
79 | github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
80 | github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
81 | github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
82 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
83 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
84 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
85 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
86 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
87 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
88 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
89 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
90 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
91 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
92 | github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
93 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
94 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
95 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
96 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
97 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
98 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
99 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
100 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
101 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
102 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
103 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
104 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
105 | github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
106 | github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
107 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
108 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
109 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
110 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
111 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
112 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
113 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
114 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
115 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
116 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
117 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
118 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
119 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
120 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
121 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
122 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
123 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
124 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
125 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
126 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
127 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
128 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
129 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
130 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
131 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
132 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
133 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
134 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
135 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
136 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
137 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
138 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
139 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
140 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
141 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
142 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
143 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
144 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
145 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
146 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
147 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
148 | github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
149 | github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
150 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
151 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
152 | github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M=
153 | github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
154 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
155 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
156 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
157 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
158 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
159 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
160 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
161 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
162 | github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
163 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
164 | github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
165 | github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
166 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
167 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
168 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
169 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
170 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
171 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
172 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
173 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
174 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
175 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
176 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
177 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
178 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
179 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
180 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
181 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
182 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
183 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
184 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
185 | github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
186 | github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
187 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
188 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
189 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
190 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
191 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
192 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
193 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
194 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
195 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
196 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
197 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
198 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
199 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
200 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
201 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
202 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
203 | github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
204 | github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
205 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
206 | github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
207 | github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
208 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
209 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
210 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
211 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
212 | github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
213 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
214 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
215 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
216 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
217 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
218 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
219 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
220 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
221 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
222 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
223 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
224 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
225 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
226 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
227 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
228 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
229 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
230 | github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
231 | github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
232 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
233 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
234 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
235 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
236 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
237 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
238 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
239 | github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
240 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
241 | github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
242 | github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
243 | github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
244 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
245 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
246 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
247 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
248 | github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w=
249 | github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
250 | github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc=
251 | github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
252 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
253 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
254 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
255 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
256 | go.mongodb.org/mongo-driver v1.5.2 h1:AsxOLoJTgP6YNM0fXWw4OjdluYmWzQYp+lFJL7xu9fU=
257 | go.mongodb.org/mongo-driver v1.5.2/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw=
258 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
259 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
260 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
261 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
262 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
263 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
264 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
265 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
266 | golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
267 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
268 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
269 | golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
270 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
271 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
272 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
273 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
274 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
275 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
276 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
277 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
278 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
279 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
280 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
281 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
282 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
283 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
284 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
285 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
286 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
287 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
288 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
289 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
290 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
291 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
292 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
293 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
294 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
295 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
296 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
297 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
298 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
299 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
300 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
301 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
302 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
303 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
304 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
305 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
306 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
307 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
308 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
309 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
310 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
311 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
312 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
313 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
314 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
315 | golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
316 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
317 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
318 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
319 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
320 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
321 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
322 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
323 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
324 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
325 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
326 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
327 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
328 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
329 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
330 | golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
331 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
332 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
333 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
334 | golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
335 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
336 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
337 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
338 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
339 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E=
340 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
341 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
342 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
343 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
344 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
345 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
346 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
347 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
348 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
349 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
350 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
351 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
352 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
353 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
354 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
355 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
356 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
357 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
358 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
359 | golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
360 | golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
361 | golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
362 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
363 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
364 | golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
365 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
366 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
367 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
368 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
369 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
370 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
371 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
372 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
373 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
374 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
375 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
376 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
377 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
378 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
379 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
380 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
381 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
382 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
383 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
384 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
385 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
386 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
387 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
388 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
389 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
390 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
391 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
392 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
393 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
394 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
395 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
396 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
397 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
398 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
399 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
400 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
401 | gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
402 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
403 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
404 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
405 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
406 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
407 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
408 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
409 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
410 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
411 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
412 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
413 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
414 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
415 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
416 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
417 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
418 |
--------------------------------------------------------------------------------
/infra/awsclient/aws.go:
--------------------------------------------------------------------------------
1 | package awsclient
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "github.com/aws/aws-sdk-go/aws"
7 | "github.com/aws/aws-sdk-go/aws/credentials"
8 | "github.com/aws/aws-sdk-go/aws/session"
9 | "github.com/aws/aws-sdk-go/service/s3"
10 | "github.com/aws/aws-sdk-go/service/s3/s3manager"
11 | "github.com/spf13/viper"
12 | "log"
13 | "mime/multipart"
14 | "net/url"
15 | )
16 |
17 | type AwsClient interface {
18 | DeleteFileInS3(fileName string) error
19 | UploadToS3(userId int64, fileName, fileType string, file multipart.File) (string, error)
20 | DetermineS3ImageUrl(userId int64, fileName string) string
21 | }
22 |
23 | type awsClient struct {
24 | session *session.Session
25 | }
26 |
27 | func NewAwsClient() AwsClient {
28 | sess, err := session.NewSession(
29 | &aws.Config{
30 | Region: aws.String(viper.GetString("RM_AWS_REGION")),
31 | Credentials: credentials.NewStaticCredentials(
32 | viper.GetString("RM_AWS_ACCESS_KEY"),
33 | viper.GetString("RM_AWS_SECRET_KEY"),
34 | "", // a token will be created when the session it's used.
35 | ),
36 | })
37 | if err != nil {
38 | log.Fatal("Error when trying to connect aws")
39 | }
40 |
41 | return awsClient{session: sess}
42 | }
43 |
44 | func (client awsClient) DeleteFileInS3(fileUrl string) error {
45 | svc := s3.New(client.session)
46 |
47 | input := &s3.DeleteObjectInput{
48 | Bucket: aws.String(viper.GetString("RM_AWS_BUCKET_NAME")),
49 | Key: aws.String(fileUrl),
50 | }
51 | _, err := svc.DeleteObject(input)
52 | if err != nil {
53 | fmt.Println(err.Error())
54 | }
55 | return err
56 | }
57 |
58 | func (client awsClient) UploadToS3(userId int64, fileName, fileType string, file multipart.File) (string, error) {
59 | uploader := s3manager.NewUploader(client.session)
60 |
61 | _, err := uploader.Upload(&s3manager.UploadInput{
62 | Bucket: aws.String(viper.GetString("RM_AWS_BUCKET_NAME")),
63 | ACL: aws.String("public-read"),
64 | Key: aws.String(fileKey(userId, fileName)),
65 | Body: file,
66 | ContentType: aws.String(fileType),
67 | })
68 | fmt.Println(err)
69 | if err != nil {
70 | return "", errors.New(fmt.Sprintf("Failed to upload image on s3 %v", err))
71 | }
72 |
73 | filePath := client.DetermineS3ImageUrl(userId, fileName)
74 | return filePath, nil
75 | }
76 |
77 | func (client awsClient) DetermineS3ImageUrl(userId int64, fileName string) string {
78 | bucketName := viper.GetString("RM_AWS_BUCKET_NAME")
79 | region := viper.GetString("RM_AWS_REGION")
80 |
81 | filePath := fmt.Sprintf("https://%s.s3.%s.amazonaws.com/%d/%s", bucketName, region, userId, url.QueryEscape(fileName))
82 | return filePath
83 | }
84 |
85 | func fileKey(userId int64, fileName string) string {
86 | return fmt.Sprintf("%d/%s", userId, fileName)
87 | }
88 |
--------------------------------------------------------------------------------
/infra/cronclient/cron.go:
--------------------------------------------------------------------------------
1 | package cronclient
2 |
3 | import (
4 | "github.com/Abdulsametileri/cron-job-vue-go/infra/telegramclient"
5 | "github.com/Abdulsametileri/cron-job-vue-go/models"
6 | "github.com/Abdulsametileri/cron-job-vue-go/services/jobservice"
7 | "github.com/Abdulsametileri/cron-job-vue-go/utils"
8 | "github.com/go-co-op/gocron"
9 | "strconv"
10 | "time"
11 | )
12 |
13 | var IndexToWeekDay = map[int]time.Weekday{
14 | 0: time.Sunday,
15 | 1: time.Monday,
16 | 2: time.Tuesday,
17 | 3: time.Wednesday,
18 | 4: time.Thursday,
19 | 5: time.Friday,
20 | 6: time.Saturday,
21 |
22 | 8: time.Monday,
23 | 9: time.Tuesday,
24 | 10: time.Wednesday,
25 | 11: time.Thursday,
26 | 12: time.Friday,
27 | 13: time.Saturday,
28 | 14: time.Sunday,
29 | }
30 |
31 | type CronClient interface {
32 | Schedule(job models.Job) error
33 | RemoveJobByTag(tag string) error
34 | }
35 |
36 | type cronClient struct {
37 | js jobservice.JobService
38 | tc telegramclient.TelegramClient
39 | sch *gocron.Scheduler
40 | }
41 |
42 | func NewCronClient(js jobservice.JobService, tc telegramclient.TelegramClient) CronClient {
43 | location, _ := time.LoadLocation("Europe/Istanbul")
44 | scheduleClient := gocron.NewScheduler(location)
45 | scheduleClient.StartAsync()
46 |
47 | cronClient := &cronClient{
48 | js: js,
49 | tc: tc,
50 | sch: scheduleClient,
51 | }
52 |
53 | jobs, _ := js.ListAllValidJobs()
54 | if len(jobs) > 0 {
55 | for _, job := range jobs {
56 | _ = cronClient.Schedule(job)
57 | }
58 | }
59 |
60 | return cronClient
61 | }
62 |
63 | func (c cronClient) RemoveJobByTag(tag string) error {
64 | return c.sch.RemoveByTag(tag)
65 | }
66 |
67 | func (c cronClient) Schedule(job models.Job) error {
68 | repeatTypeInt, _ := strconv.Atoi(job.RepeatType)
69 |
70 | val, ok := IndexToWeekDay[repeatTypeInt]
71 | if ok {
72 | if repeatTypeInt <= 6 {
73 | c.sch.Every(1)
74 | } else {
75 | c.sch.Every(2)
76 | }
77 | c.sch.Day().Weekday(val)
78 | } else {
79 | c.sch.Every(1).Days()
80 | }
81 | c.sch.At(job.Time)
82 |
83 | scheduledJob, err := c.sch.Do(func() {
84 | err := c.tc.SendImage(job.UserTelegramId, job.ImageUrl)
85 | if err != nil {
86 | c.tc.SendMessageForDebug("Schedule Job Telegram Send Image" + err.Error())
87 | }
88 | })
89 |
90 | if err != nil {
91 | prettyScheduledJob, _ := utils.PrettyPrint(scheduledJob)
92 | prettyDbJob, _ := utils.PrettyPrint(job)
93 | c.tc.SendMessageForDebug("Cron Schedule " + prettyScheduledJob + " models.Job=" + prettyDbJob + err.Error())
94 | return err
95 | }
96 | scheduledJob.Tag(job.Tag)
97 |
98 | return nil
99 | }
100 |
--------------------------------------------------------------------------------
/infra/telegramclient/telegram.go:
--------------------------------------------------------------------------------
1 | package telegramclient
2 |
3 | import (
4 | "fmt"
5 | "github.com/Abdulsametileri/cron-job-vue-go/models"
6 | "github.com/Abdulsametileri/cron-job-vue-go/services/userservice"
7 | tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
8 | "github.com/google/uuid"
9 | "github.com/spf13/viper"
10 | "log"
11 | )
12 |
13 | type TelegramClient interface {
14 | GetMessages()
15 | SendImage(telegramId int64, imageUrl string) error
16 | SendMessageForDebug(msg string)
17 | }
18 |
19 | type telegramClient struct {
20 | bot *tgbotapi.BotAPI
21 | us userservice.UserService
22 | }
23 |
24 | func NewTelegramClient(us userservice.UserService) TelegramClient {
25 | bot, err := tgbotapi.NewBotAPI(viper.GetString("TELEGRAM_BOT_TOKEN"))
26 | if err != nil {
27 | log.Fatalf("Error initializing telegram %v", err)
28 | }
29 | return &telegramClient{
30 | bot: bot,
31 | us: us,
32 | }
33 | }
34 |
35 | func (t telegramClient) SendMessageForDebug(text string) {
36 | msg := tgbotapi.NewMessage(513873156, text)
37 | _, _ = t.bot.Send(msg)
38 | }
39 |
40 | func (t telegramClient) SendImage(telegramId int64, imageUrl string) error {
41 | msg := tgbotapi.NewPhotoShare(telegramId, imageUrl)
42 | _, err := t.bot.Send(msg)
43 | return err
44 | }
45 |
46 | func (t telegramClient) GetMessages() {
47 | u := tgbotapi.NewUpdate(0)
48 | u.Timeout = 60
49 |
50 | updates, err := t.bot.GetUpdatesChan(u)
51 | log.Println(err)
52 |
53 | for update := range updates {
54 | if update.Message == nil {
55 | continue
56 | }
57 | userTelegramId := int64(update.Message.From.ID)
58 | userTelegramName := update.Message.From.UserName
59 | chatId := update.Message.Chat.ID
60 |
61 | if update.Message.Text == "/token" {
62 | user, err := t.us.GetUserByTelegramId(userTelegramId)
63 | if err != nil {
64 | _, _ = t.bot.Send(tgbotapi.NewMessage(chatId, err.Error()))
65 | continue
66 | }
67 | if user.TelegramId != 0 {
68 | _, _ = t.bot.Send(tgbotapi.NewMessage(chatId, fmt.Sprintf("You have already token. %s", user.Token)))
69 | continue
70 | }
71 |
72 | token := uuid.New()
73 | tokenMsg := tgbotapi.NewMessage(chatId, fmt.Sprintf("%s", token))
74 | tokenMsg.ReplyToMessageID = update.Message.MessageID
75 |
76 | err = t.us.AddUser(models.User{
77 | Token: token.String(),
78 | TelegramId: userTelegramId,
79 | TelegramDisplayName: userTelegramName,
80 | })
81 |
82 | _, _ = t.bot.Send(tokenMsg)
83 | _, _ = t.bot.Send(tgbotapi.NewMessage(chatId, "You have to give this token on our site."))
84 | } else {
85 | _, _ = t.bot.Send(tgbotapi.NewMessage(chatId, "Invalid command"))
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "embed"
6 | "fmt"
7 | "github.com/Abdulsametileri/cron-job-vue-go/config"
8 | "github.com/Abdulsametileri/cron-job-vue-go/controllers"
9 | "github.com/Abdulsametileri/cron-job-vue-go/database"
10 | "github.com/Abdulsametileri/cron-job-vue-go/infra/awsclient"
11 | "github.com/Abdulsametileri/cron-job-vue-go/infra/cronclient"
12 | "github.com/Abdulsametileri/cron-job-vue-go/infra/telegramclient"
13 | "github.com/Abdulsametileri/cron-job-vue-go/repository/jobrepo"
14 | "github.com/Abdulsametileri/cron-job-vue-go/repository/userrepo"
15 | "github.com/Abdulsametileri/cron-job-vue-go/services/jobservice"
16 | "github.com/Abdulsametileri/cron-job-vue-go/services/userservice"
17 | "github.com/spf13/viper"
18 | "io/fs"
19 | "log"
20 | "net/http"
21 | "os"
22 | "os/signal"
23 | "time"
24 | )
25 |
26 | //go:embed client/dist
27 | var clientFS embed.FS
28 |
29 | func main() {
30 | config.Setup()
31 |
32 | distFS, err := fs.Sub(clientFS, "client/dist")
33 | if err != nil {
34 | log.Fatalf("error dist %v", err)
35 | }
36 |
37 | mongoClient := database.Setup()
38 | mongodb := mongoClient.Database(viper.GetString("MONGODB_DATABASE"))
39 | userCollection := mongodb.Collection("users")
40 | jobCollection := mongodb.Collection("jobs")
41 | userRepo := userrepo.NewUserRepository(userCollection)
42 | jobRepo := jobrepo.NewJobRepository(jobCollection)
43 |
44 | userService := userservice.NewUserService(userRepo)
45 | jobService := jobservice.NewJobService(jobRepo)
46 |
47 | awsClient := awsclient.NewAwsClient()
48 |
49 | telegramClient := telegramclient.NewTelegramClient(userService)
50 | if !config.IsDebug {
51 | go telegramClient.GetMessages()
52 | }
53 |
54 | cronClient := cronclient.NewCronClient(jobService, telegramClient)
55 |
56 | baseController := controllers.NewBaseController()
57 | tokenController := controllers.NewTokenController(baseController, userService)
58 | alarmController := controllers.NewAlarmController(baseController, userService, jobService, awsClient, telegramClient, cronClient)
59 |
60 | http.Handle("/", http.FileServer(http.FS(distFS)))
61 |
62 | http.HandleFunc("/api/validate-token", tokenController.ValidateToken)
63 | http.HandleFunc("/api/create-alarm", alarmController.CreateAlarm)
64 | http.HandleFunc("/api/list-alarm", alarmController.ListAlarm)
65 | http.HandleFunc("/api/delete-alarm", alarmController.DeleteAlarm)
66 |
67 | srv := &http.Server{
68 | Addr: ":3000",
69 | }
70 | fmt.Printf("Server is starting at %s \n", srv.Addr)
71 | go func() {
72 | if err := srv.ListenAndServe(); err != nil {
73 | log.Fatal(err.Error())
74 | }
75 | }()
76 |
77 | c := make(chan os.Signal, 1)
78 |
79 | signal.Notify(c, os.Interrupt)
80 | signal.Notify(c, os.Kill)
81 |
82 | sig := <-c
83 | log.Println("Got signal:", sig)
84 | ctx, _ := context.WithTimeout(context.Background(), 15*time.Second)
85 |
86 | _ = srv.Shutdown(ctx)
87 | }
88 |
--------------------------------------------------------------------------------
/models/job.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type JobStatus int
4 |
5 | const (
6 | JobValid = iota + 1
7 | JobDeleted
8 | )
9 |
10 | type Job struct {
11 | Tag string `json:"tag" bson:"tag"`
12 | Name string `json:"name" bson:"name"`
13 | UserTelegramId int64 `json:"userTelegramId" bson:"userTelegramId"`
14 | UserToken string `json:"userToken" bson:"userToken"`
15 | ImageUrl string `json:"imageUrl" bson:"imageUrl"`
16 | RepeatType string `json:"repeatType" bson:"repeatType"`
17 | Time string `json:"time" bson:"time"`
18 | Status JobStatus `json:"status" bson:"status"`
19 | }
20 |
--------------------------------------------------------------------------------
/models/user.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type User struct {
4 | Token string `json:"token" bson:"token"`
5 | TelegramId int64 `json:"telegramId" bson:"telegramId"`
6 | TelegramDisplayName string `json:"telegramDisplayName" bson:"telegramDisplayName"`
7 | }
8 |
--------------------------------------------------------------------------------
/repository/jobrepo/job_repository.go:
--------------------------------------------------------------------------------
1 | package jobrepo
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "github.com/Abdulsametileri/cron-job-vue-go/models"
7 | "go.mongodb.org/mongo-driver/bson"
8 | "go.mongodb.org/mongo-driver/mongo"
9 | "go.mongodb.org/mongo-driver/mongo/options"
10 | "time"
11 | )
12 |
13 | type Repo interface {
14 | ListAllValidJobs() ([]models.Job, error)
15 | ListAllValidJobsByToken(token string) ([]models.Job, error)
16 | AddJob(models.Job) error
17 | GetJobByFields(map[string]interface{}) (models.Job, error)
18 | DeleteJobByTag(tag string) error
19 | }
20 |
21 | type jobRepository struct {
22 | collection *mongo.Collection
23 | }
24 |
25 | func NewJobRepository(collection *mongo.Collection) Repo {
26 | return &jobRepository{collection: collection}
27 | }
28 |
29 | func (j jobRepository) ListAllValidJobs() ([]models.Job, error) {
30 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
31 | defer cancel()
32 |
33 | cur, err := j.collection.Find(ctx, bson.M{
34 | "status": models.JobValid,
35 | })
36 | if err != nil {
37 | return make([]models.Job, 0), err
38 | }
39 | defer cur.Close(ctx)
40 | var jobs []models.Job
41 | if err = cur.All(ctx, &jobs); err != nil {
42 | return make([]models.Job, 0), err
43 | }
44 | if jobs == nil {
45 | return make([]models.Job, 0), nil
46 | }
47 | return jobs, nil
48 | }
49 |
50 | func (j jobRepository) ListAllValidJobsByToken(token string) ([]models.Job, error) {
51 | ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
52 | defer cancel()
53 |
54 | findOptions := options.Find()
55 | findOptions.SetSort(bson.D{{"_id", -1}})
56 | filter := bson.M{"userToken": token, "status": models.JobValid}
57 | cur, err := j.collection.Find(ctx, filter, findOptions)
58 | if err != nil {
59 | return make([]models.Job, 0), err
60 | }
61 | defer cur.Close(ctx)
62 | var jobs []models.Job
63 | if err = cur.All(ctx, &jobs); err != nil {
64 | return make([]models.Job, 0), err
65 | }
66 | if jobs == nil {
67 | return make([]models.Job, 0), nil
68 | }
69 | return jobs, nil
70 | }
71 |
72 | func (j jobRepository) AddJob(job models.Job) error {
73 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
74 | defer cancel()
75 | _, err := j.collection.InsertOne(ctx, job)
76 | return err
77 | }
78 |
79 | func (j jobRepository) GetJobByFields(fields map[string]interface{}) (models.Job, error) {
80 | filter := bson.M{}
81 | for key, val := range fields {
82 | filter[key] = val
83 | }
84 |
85 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
86 | defer cancel()
87 |
88 | var job models.Job
89 | err := j.collection.FindOne(ctx, filter).Decode(&job)
90 | if err == mongo.ErrNoDocuments {
91 | return models.Job{}, nil
92 | }
93 |
94 | return job, err
95 | }
96 |
97 | func (j jobRepository) DeleteJobByTag(tag string) error {
98 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
99 | defer cancel()
100 | filter := bson.M{
101 | "tag": tag,
102 | }
103 | update := bson.D{{"$set", bson.D{{"status", models.JobDeleted}}}}
104 |
105 | up, err := j.collection.UpdateOne(ctx, filter, update)
106 | if up != nil && up.MatchedCount == 0 {
107 | return errors.New("Job does not found with the given tag")
108 | }
109 | return err
110 | }
111 |
--------------------------------------------------------------------------------
/repository/jobrepo/job_repository_test.go:
--------------------------------------------------------------------------------
1 | package jobrepo
2 |
3 | import (
4 | "context"
5 | "github.com/Abdulsametileri/cron-job-vue-go/models"
6 | "github.com/Abdulsametileri/cron-job-vue-go/repository"
7 | "github.com/google/uuid"
8 | "github.com/magiconair/properties/assert"
9 | "github.com/stretchr/testify/require"
10 | "go.mongodb.org/mongo-driver/bson"
11 | "go.mongodb.org/mongo-driver/mongo"
12 | "testing"
13 | )
14 |
15 | func cleanCollection(t *testing.T, jobCollection *mongo.Collection) {
16 | errDrop := jobCollection.Drop(context.Background())
17 | require.NoError(t, errDrop)
18 | }
19 |
20 | func TestJobRepository_ListAllValidJobs(t *testing.T) {
21 | client, errSetupDB := repository.SetupDB()
22 | require.NoError(t, errSetupDB)
23 |
24 | jobCollection, errCollection := repository.SetupCollection(client, "jobs")
25 | require.NoError(t, errCollection)
26 |
27 | defer cleanCollection(t, jobCollection)
28 |
29 | job1 := models.Job{
30 | Tag: uuid.NewString(),
31 | UserTelegramId: 123,
32 | UserToken: "samet",
33 | ImageUrl: "http://test",
34 | RepeatType: "2",
35 | Time: "18:55",
36 | Status: models.JobValid,
37 | }
38 | _, errAddJob := jobCollection.InsertOne(context.Background(), job1)
39 | require.NoError(t, errAddJob)
40 |
41 | job1.Time = "13:55"
42 | job1.RepeatType = "4"
43 | job1.Tag = uuid.NewString()
44 | _, errAddJob = jobCollection.InsertOne(context.Background(), job1)
45 | require.NoError(t, errAddJob)
46 |
47 | job1.Time = "16:55"
48 | job1.Tag = uuid.NewString()
49 | job1.Status = models.JobDeleted
50 | _, errAddJob = jobCollection.InsertOne(context.Background(), job1)
51 | require.NoError(t, errAddJob)
52 |
53 | jobRepo := NewJobRepository(jobCollection)
54 | jobs, err := jobRepo.ListAllValidJobs()
55 | require.NoError(t, err)
56 | require.Equal(t, len(jobs), 2)
57 | }
58 |
59 | func TestJobRepository_ListAllValidJobsByToken_with2Items(t *testing.T) {
60 | client, errSetupDB := repository.SetupDB()
61 | require.NoError(t, errSetupDB)
62 |
63 | jobCollection, errCollection := repository.SetupCollection(client, "jobs")
64 | require.NoError(t, errCollection)
65 |
66 | defer cleanCollection(t, jobCollection)
67 |
68 | job1 := models.Job{
69 | Tag: uuid.NewString(),
70 | UserTelegramId: 123,
71 | UserToken: "samet",
72 | ImageUrl: "http://test",
73 | RepeatType: "1",
74 | Time: "11:55",
75 | Status: models.JobValid,
76 | }
77 | _, errAddJob := jobCollection.InsertOne(context.Background(), job1)
78 | require.NoError(t, errAddJob)
79 |
80 | job1.Time = "13:55"
81 | job1.Tag = uuid.NewString()
82 | job1.Status = models.JobValid
83 | _, errAddJob = jobCollection.InsertOne(context.Background(), job1)
84 | require.NoError(t, errAddJob)
85 |
86 | job1.Time = "16:55"
87 | job1.Tag = uuid.NewString()
88 | job1.Status = models.JobDeleted
89 | _, errAddJob = jobCollection.InsertOne(context.Background(), job1)
90 | require.NoError(t, errAddJob)
91 |
92 | jobRepo := NewJobRepository(jobCollection)
93 | jobs, err := jobRepo.ListAllValidJobsByToken(job1.UserToken)
94 | require.NoError(t, err)
95 | require.Equal(t, len(jobs), 2)
96 | }
97 |
98 | func TestJobRepository_ListJobsByToken_withNoItem(t *testing.T) {
99 | client, errSetupDB := repository.SetupDB()
100 | require.NoError(t, errSetupDB)
101 |
102 | jobCollection, errCollection := repository.SetupCollection(client, "jobs")
103 | require.NoError(t, errCollection)
104 |
105 | defer cleanCollection(t, jobCollection)
106 |
107 | jobRepo := NewJobRepository(jobCollection)
108 | jobs, err := jobRepo.ListAllValidJobsByToken("test")
109 | require.NoError(t, err)
110 | require.Equal(t, len(jobs), 0)
111 | }
112 |
113 | func TestJobRepository_AddJob(t *testing.T) {
114 | client, errSetupDB := repository.SetupDB()
115 | require.NoError(t, errSetupDB)
116 |
117 | jobCollection, errCollection := repository.SetupCollection(client, "jobs")
118 | require.NoError(t, errCollection)
119 |
120 | defer cleanCollection(t, jobCollection)
121 |
122 | jobToAdd := models.Job{
123 | Tag: uuid.New().String(),
124 | UserTelegramId: 123,
125 | ImageUrl: "http://test",
126 | RepeatType: "1",
127 | Time: "11:55",
128 | }
129 |
130 | jobRepo := NewJobRepository(jobCollection)
131 | err := jobRepo.AddJob(jobToAdd)
132 | require.NoError(t, err)
133 | }
134 |
135 | func TestJobRepository_GetJobByFields(t *testing.T) {
136 | client, errSetupDB := repository.SetupDB()
137 | require.NoError(t, errSetupDB)
138 |
139 | jobCollection, errCollection := repository.SetupCollection(client, "jobs")
140 | require.NoError(t, errCollection)
141 |
142 | defer cleanCollection(t, jobCollection)
143 |
144 | addedJob := models.Job{
145 | Tag: uuid.New().String(),
146 | UserTelegramId: 123,
147 | ImageUrl: "http://test",
148 | RepeatType: "1",
149 | Time: "11:55",
150 | }
151 | _, errAddJob := jobCollection.InsertOne(context.Background(), addedJob)
152 | require.NoError(t, errAddJob)
153 |
154 | jobRepo := NewJobRepository(jobCollection)
155 |
156 | fields := make(map[string]interface{}, 0)
157 | fields["userTelegramId"] = addedJob.UserTelegramId
158 | fields["imageUrl"] = addedJob.ImageUrl
159 | fields["repeatType"] = addedJob.RepeatType
160 | fields["time"] = addedJob.Time
161 |
162 | job, err := jobRepo.GetJobByFields(fields)
163 | require.NoError(t, err)
164 |
165 | assert.Equal(t, job.Tag, addedJob.Tag)
166 | assert.Equal(t, job.UserTelegramId, addedJob.UserTelegramId)
167 | assert.Equal(t, job.ImageUrl, addedJob.ImageUrl)
168 | assert.Equal(t, job.RepeatType, addedJob.RepeatType)
169 | assert.Equal(t, job.Time, addedJob.Time)
170 | }
171 |
172 | func TestJobRepository_GetJobByFields_NotExistedJob(t *testing.T) {
173 | client, errSetupDB := repository.SetupDB()
174 | require.NoError(t, errSetupDB)
175 |
176 | jobCollection, errCollection := repository.SetupCollection(client, "jobs")
177 | require.NoError(t, errCollection)
178 |
179 | defer cleanCollection(t, jobCollection)
180 |
181 | jobRepo := NewJobRepository(jobCollection)
182 |
183 | fields := make(map[string]interface{}, 0)
184 | fields["userTelegramId"] = 123
185 | fields["repeatType"] = "1"
186 | fields["time"] = "11:55"
187 |
188 | job, err := jobRepo.GetJobByFields(fields)
189 | require.NoError(t, err)
190 |
191 | emptyJob := models.Job{}
192 | assert.Equal(t, job.Tag, emptyJob.Tag)
193 | assert.Equal(t, job.UserTelegramId, emptyJob.UserTelegramId)
194 | assert.Equal(t, job.ImageUrl, emptyJob.ImageUrl)
195 | assert.Equal(t, job.RepeatType, emptyJob.RepeatType)
196 | assert.Equal(t, job.Time, emptyJob.Time)
197 | }
198 |
199 | func TestJobRepository_DeleteJobByTag(t *testing.T) {
200 | client, errSetupDB := repository.SetupDB()
201 | require.NoError(t, errSetupDB)
202 |
203 | jobCollection, errCollection := repository.SetupCollection(client, "jobs")
204 | require.NoError(t, errCollection)
205 |
206 | defer cleanCollection(t, jobCollection)
207 |
208 | addedJob := models.Job{
209 | Tag: "123",
210 | Name: "test",
211 | UserTelegramId: 123,
212 | UserToken: "123",
213 | ImageUrl: "http://s3..",
214 | RepeatType: "1",
215 | Time: "12:38",
216 | Status: models.JobValid,
217 | }
218 |
219 | _, err := jobCollection.InsertOne(context.Background(), addedJob)
220 | require.NoError(t, err)
221 |
222 | jobRepository := NewJobRepository(jobCollection)
223 |
224 | err = jobRepository.DeleteJobByTag(addedJob.Tag)
225 | require.NoError(t, err)
226 |
227 | var jobFromDb models.Job
228 | jobCollection.FindOne(context.Background(), bson.M{"tag": addedJob.Tag}).Decode(&jobFromDb)
229 |
230 | require.Equal(t, jobFromDb.Tag, addedJob.Tag)
231 | require.Equal(t, int(jobFromDb.Status), models.JobDeleted)
232 | }
233 |
--------------------------------------------------------------------------------
/repository/test_helpers.go:
--------------------------------------------------------------------------------
1 | package repository
2 |
3 | import (
4 | "context"
5 | "go.mongodb.org/mongo-driver/mongo"
6 | "go.mongodb.org/mongo-driver/mongo/options"
7 | )
8 |
9 | func SetupDB() (*mongo.Client, error) {
10 | client, err := mongo.
11 | Connect(context.Background(),
12 | options.Client().
13 | ApplyURI("mongodb://localhost:27017/testReminder"))
14 |
15 | return client, err
16 | }
17 |
18 | func SetupCollection(client *mongo.Client, collectionName string) (*mongo.Collection, error) {
19 | collection := client.Database("testReminder").Collection(collectionName)
20 | errDrop := collection.Drop(context.Background())
21 | return collection, errDrop
22 | }
23 |
--------------------------------------------------------------------------------
/repository/userrepo/user_repository.go:
--------------------------------------------------------------------------------
1 | package userrepo
2 |
3 | import (
4 | "context"
5 | "github.com/Abdulsametileri/cron-job-vue-go/models"
6 | "go.mongodb.org/mongo-driver/bson"
7 | "go.mongodb.org/mongo-driver/mongo"
8 | "time"
9 | )
10 |
11 | type Repo interface {
12 | AddUser(user models.User) error
13 | GetUserByTelegramId(telegramId int64) (models.User, error)
14 | GetUserByToken(token string) (models.User, error)
15 | }
16 |
17 | type userRepo struct {
18 | collection *mongo.Collection
19 | }
20 |
21 | func NewUserRepository(col *mongo.Collection) Repo {
22 | return &userRepo{
23 | collection: col,
24 | }
25 | }
26 |
27 | func (u userRepo) AddUser(user models.User) error {
28 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
29 | defer cancel()
30 | _, err := u.collection.InsertOne(ctx, user)
31 | return err
32 | }
33 |
34 | func (u userRepo) GetUserByToken(token string) (models.User, error) {
35 | filter := bson.M{"token": token}
36 | return u.GetUserBySpecifiedFilter(filter)
37 | }
38 |
39 | func (u userRepo) GetUserByTelegramId(telegramId int64) (models.User, error) {
40 | filter := bson.M{"telegramId": telegramId}
41 | return u.GetUserBySpecifiedFilter(filter)
42 | }
43 |
44 | func (u userRepo) GetUserBySpecifiedFilter(filter bson.M) (models.User, error) {
45 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
46 | defer cancel()
47 |
48 | var user models.User
49 | err := u.collection.FindOne(ctx, filter).Decode(&user)
50 | if err == mongo.ErrNoDocuments {
51 | return models.User{}, nil
52 | }
53 |
54 | return user, err
55 | }
56 |
--------------------------------------------------------------------------------
/repository/userrepo/user_repository_test.go:
--------------------------------------------------------------------------------
1 | package userrepo
2 |
3 | import (
4 | "context"
5 | "github.com/Abdulsametileri/cron-job-vue-go/models"
6 | "github.com/Abdulsametileri/cron-job-vue-go/repository"
7 | "github.com/stretchr/testify/assert"
8 | "github.com/stretchr/testify/require"
9 | "go.mongodb.org/mongo-driver/mongo"
10 | "testing"
11 | )
12 |
13 | func cleanCollection(t *testing.T, userCollection *mongo.Collection) {
14 | errDrop := userCollection.Drop(context.Background())
15 | require.NoError(t, errDrop)
16 | }
17 |
18 | func TestUserRepo_AddUser(t *testing.T) {
19 | client, errSetupDB := repository.SetupDB()
20 | require.NoError(t, errSetupDB)
21 |
22 | userCollection, errCollection := repository.SetupCollection(client, "users")
23 | require.NoError(t, errCollection)
24 |
25 | defer cleanCollection(t, userCollection)
26 |
27 | user := models.User{
28 | Token: "123",
29 | TelegramId: 123,
30 | TelegramDisplayName: "Test",
31 | }
32 | userRepo := NewUserRepository(userCollection)
33 | errAddUser := userRepo.AddUser(user)
34 | require.NoError(t, errAddUser)
35 | }
36 |
37 | func TestUserRepo_GetUserByTelegramId(t *testing.T) {
38 | client, errSetupDB := repository.SetupDB()
39 | require.NoError(t, errSetupDB)
40 |
41 | userCollection, errCollection := repository.SetupCollection(client, "users")
42 | require.NoError(t, errCollection)
43 |
44 | defer cleanCollection(t, userCollection)
45 |
46 | user := models.User{
47 | Token: "123",
48 | TelegramId: 123,
49 | TelegramDisplayName: "Test",
50 | }
51 |
52 | _, errAddUser := userCollection.InsertOne(context.Background(), user)
53 | require.NoError(t, errAddUser)
54 |
55 | userRepo := NewUserRepository(userCollection)
56 | foundedUser, errGetUser := userRepo.GetUserByTelegramId(user.TelegramId)
57 | require.NoError(t, errGetUser)
58 |
59 | assert.Equal(t, user.Token, foundedUser.Token)
60 | assert.Equal(t, user.TelegramId, foundedUser.TelegramId)
61 | assert.Equal(t, user.TelegramDisplayName, foundedUser.TelegramDisplayName)
62 | }
63 |
64 | func TestUserRepo_GetUserByTelegramId_NotExistUser(t *testing.T) {
65 | client, errSetupDB := repository.SetupDB()
66 | require.NoError(t, errSetupDB)
67 |
68 | userCollection, errCollection := repository.SetupCollection(client, "users")
69 | require.NoError(t, errCollection)
70 |
71 | defer cleanCollection(t, userCollection)
72 |
73 | userRepo := NewUserRepository(userCollection)
74 | foundedUser, errGetUser := userRepo.GetUserByTelegramId(978)
75 | require.NoError(t, errGetUser)
76 |
77 | emptyUser := models.User{}
78 | assert.Equal(t, emptyUser.Token, foundedUser.Token)
79 | assert.Equal(t, emptyUser.TelegramId, foundedUser.TelegramId)
80 | assert.Equal(t, emptyUser.TelegramDisplayName, foundedUser.TelegramDisplayName)
81 | }
82 |
83 | func TestUserRepo_GetUserByToken(t *testing.T) {
84 | client, errSetupDB := repository.SetupDB()
85 | require.NoError(t, errSetupDB)
86 |
87 | userCollection, errCollection := repository.SetupCollection(client, "users")
88 | require.NoError(t, errCollection)
89 |
90 | defer cleanCollection(t, userCollection)
91 |
92 | user := models.User{
93 | Token: "123",
94 | TelegramId: 123,
95 | TelegramDisplayName: "Test",
96 | }
97 |
98 | _, errAddUser := userCollection.InsertOne(context.Background(), user)
99 | require.NoError(t, errAddUser)
100 |
101 | userRepo := NewUserRepository(userCollection)
102 | foundedUser, errGetUser := userRepo.GetUserByToken(user.Token)
103 | require.NoError(t, errGetUser)
104 |
105 | assert.Equal(t, user.Token, foundedUser.Token)
106 | assert.Equal(t, user.TelegramId, foundedUser.TelegramId)
107 | assert.Equal(t, user.TelegramDisplayName, foundedUser.TelegramDisplayName)
108 | }
109 |
110 | func TestUserRepo_GetUserByToken_NotExistUser(t *testing.T) {
111 | client, errSetupDB := repository.SetupDB()
112 | require.NoError(t, errSetupDB)
113 |
114 | userCollection, errCollection := repository.SetupCollection(client, "users")
115 | require.NoError(t, errCollection)
116 |
117 | defer cleanCollection(t, userCollection)
118 |
119 | userRepo := NewUserRepository(userCollection)
120 | foundedUser, errGetUser := userRepo.GetUserByToken("notexistedtoken")
121 | require.NoError(t, errGetUser)
122 |
123 | emptyUser := models.User{}
124 | assert.Equal(t, emptyUser.Token, foundedUser.Token)
125 | assert.Equal(t, emptyUser.TelegramId, foundedUser.TelegramId)
126 | assert.Equal(t, emptyUser.TelegramDisplayName, foundedUser.TelegramDisplayName)
127 | }
128 |
--------------------------------------------------------------------------------
/services/jobservice/job_service.go:
--------------------------------------------------------------------------------
1 | package jobservice
2 |
3 | import (
4 | "github.com/Abdulsametileri/cron-job-vue-go/models"
5 | "github.com/Abdulsametileri/cron-job-vue-go/repository/jobrepo"
6 | )
7 |
8 | type JobService interface {
9 | ListAllValidJobsByToken(token string) ([]models.Job, error)
10 | ListAllValidJobs() ([]models.Job, error)
11 | AddJob(job models.Job) error
12 | GetJobByFields(map[string]interface{}) (models.Job, error)
13 | DeleteJobByTag(tag string) error
14 | }
15 |
16 | type jobService struct {
17 | Repo jobrepo.Repo
18 | }
19 |
20 | func NewJobService(jRepo jobrepo.Repo) JobService {
21 | return &jobService{
22 | Repo: jRepo,
23 | }
24 | }
25 |
26 | func (j jobService) ListAllValidJobs() ([]models.Job, error) {
27 | return j.Repo.ListAllValidJobs()
28 | }
29 |
30 | func (j jobService) ListAllValidJobsByToken(token string) ([]models.Job, error) {
31 | return j.Repo.ListAllValidJobsByToken(token)
32 | }
33 |
34 | func (j jobService) AddJob(job models.Job) error {
35 | return j.Repo.AddJob(job)
36 | }
37 |
38 | func (j jobService) GetJobByFields(m map[string]interface{}) (models.Job, error) {
39 | return j.Repo.GetJobByFields(m)
40 | }
41 |
42 | func (j jobService) DeleteJobByTag(tag string) error {
43 | return j.Repo.DeleteJobByTag(tag)
44 | }
45 |
--------------------------------------------------------------------------------
/services/userservice/user_service.go:
--------------------------------------------------------------------------------
1 | package userservice
2 |
3 | import (
4 | "github.com/Abdulsametileri/cron-job-vue-go/models"
5 | "github.com/Abdulsametileri/cron-job-vue-go/repository/userrepo"
6 | )
7 |
8 | type UserService interface {
9 | AddUser(user models.User) error
10 | GetUserByTelegramId(telegramId int64) (models.User, error)
11 | GetUserByToken(token string) (models.User, error)
12 | }
13 |
14 | type userService struct {
15 | Repo userrepo.Repo
16 | }
17 |
18 | func NewUserService(usrRepo userrepo.Repo) UserService {
19 | return &userService{Repo: usrRepo}
20 | }
21 |
22 | func (u userService) AddUser(user models.User) error {
23 | return u.Repo.AddUser(user)
24 | }
25 |
26 | func (u userService) GetUserByTelegramId(telegramId int64) (models.User, error) {
27 | return u.Repo.GetUserByTelegramId(telegramId)
28 | }
29 |
30 | func (u userService) GetUserByToken(token string) (models.User, error) {
31 | return u.Repo.GetUserByToken(token)
32 | }
33 |
--------------------------------------------------------------------------------
/test.sh:
--------------------------------------------------------------------------------
1 | go test -tags dev --cover ./...
--------------------------------------------------------------------------------
/utils/string.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "encoding/json"
4 |
5 | func PrettyPrint(i interface{}) (string, error) {
6 | s, err := json.MarshalIndent(i, "", "\t")
7 | return string(s), err
8 | }
9 |
--------------------------------------------------------------------------------
/utils/string_test.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "fmt"
5 | "github.com/Abdulsametileri/cron-job-vue-go/models"
6 | "github.com/stretchr/testify/require"
7 | "testing"
8 | )
9 |
10 | func TestPrettyPrint(t *testing.T) {
11 | t.Run("Write job pretty", func(t *testing.T) {
12 | job := models.Job{
13 | Tag: "tag",
14 | Name: "name",
15 | UserTelegramId: 123,
16 | UserToken: "token",
17 | ImageUrl: "http://etc.",
18 | RepeatType: "1",
19 | Time: "22:10",
20 | Status: models.JobValid,
21 | }
22 | msg, err := PrettyPrint(job)
23 | fmt.Println(msg)
24 | require.NoError(t, err)
25 | })
26 | }
27 |
--------------------------------------------------------------------------------