fix: redis version on ci

This commit is contained in:
Reinaldy Rafli 2023-08-12 12:36:08 +07:00
parent e949b11ab9
commit 9aedbc6648
Signed by: aldy505
GPG Key ID: A3F8A7E23DA2AD94
20 changed files with 726 additions and 697 deletions

View File

@ -3,17 +3,17 @@ root = true
[*] [*]
charset = utf-8 charset = utf-8
end_of_line = lf end_of_line = lf
indent_size = 4 indent_size = 2
indent_style = space indent_style = space
insert_final_newline = false insert_final_newline = false
max_line_length = 120 max_line_length = 120
tab_width = 4 tab_width = 2
ij_continuation_indent_size = 8 ij_continuation_indent_size = 4
ij_formatter_off_tag = @formatter:off ij_formatter_off_tag = @formatter:off
ij_formatter_on_tag = @formatter:on ij_formatter_on_tag = @formatter:on
ij_formatter_tags_enabled = true ij_formatter_tags_enabled = true
ij_smart_tabs = false ij_smart_tabs = false
ij_visual_guides = ij_visual_guides =
ij_wrap_on_typing = false ij_wrap_on_typing = false
[*.css] [*.css]
@ -30,7 +30,7 @@ ij_css_hex_color_upper_case = false
ij_css_keep_blank_lines_in_code = 2 ij_css_keep_blank_lines_in_code = 2
ij_css_keep_indents_on_empty_lines = false ij_css_keep_indents_on_empty_lines = false
ij_css_keep_single_line_blocks = false ij_css_keep_single_line_blocks = false
ij_css_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow ij_css_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow
ij_css_space_after_colon = true ij_css_space_after_colon = true
ij_css_space_before_opening_brace = true ij_css_space_before_opening_brace = true
ij_css_use_double_quotes = true ij_css_use_double_quotes = true
@ -60,7 +60,7 @@ ij_sass_keep_indents_on_empty_lines = false
ij_sass_keep_single_line_blocks = false ij_sass_keep_single_line_blocks = false
ij_sass_line_comment_add_space = false ij_sass_line_comment_add_space = false
ij_sass_line_comment_at_first_column = false ij_sass_line_comment_at_first_column = false
ij_sass_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow ij_sass_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow
ij_sass_space_after_colon = true ij_sass_space_after_colon = true
ij_sass_space_before_opening_brace = true ij_sass_space_before_opening_brace = true
ij_sass_use_double_quotes = true ij_sass_use_double_quotes = true
@ -114,7 +114,7 @@ ij_typescript_array_initializer_wrap = off
ij_typescript_assignment_wrap = off ij_typescript_assignment_wrap = off
ij_typescript_binary_operation_sign_on_next_line = false ij_typescript_binary_operation_sign_on_next_line = false
ij_typescript_binary_operation_wrap = off ij_typescript_binary_operation_wrap = off
ij_typescript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** ij_typescript_blacklist_imports = rxjs/Rx, node_modules/**, **/node_modules/**, @angular/material, @angular/material/typings/**
ij_typescript_blank_lines_after_imports = 1 ij_typescript_blank_lines_after_imports = 1
ij_typescript_blank_lines_around_class = 1 ij_typescript_blank_lines_around_class = 1
ij_typescript_blank_lines_around_field = 0 ij_typescript_blank_lines_around_field = 0
@ -183,7 +183,7 @@ ij_typescript_prefer_explicit_types_function_expression_returns = false
ij_typescript_prefer_explicit_types_function_returns = false ij_typescript_prefer_explicit_types_function_returns = false
ij_typescript_prefer_explicit_types_vars_fields = false ij_typescript_prefer_explicit_types_vars_fields = false
ij_typescript_prefer_parameters_wrap = false ij_typescript_prefer_parameters_wrap = false
ij_typescript_property_prefix = ij_typescript_property_prefix =
ij_typescript_reformat_c_style_comments = false ij_typescript_reformat_c_style_comments = false
ij_typescript_space_after_colon = true ij_typescript_space_after_colon = true
ij_typescript_space_after_comma = true ij_typescript_space_after_comma = true
@ -297,7 +297,7 @@ ij_javascript_array_initializer_wrap = off
ij_javascript_assignment_wrap = off ij_javascript_assignment_wrap = off
ij_javascript_binary_operation_sign_on_next_line = false ij_javascript_binary_operation_sign_on_next_line = false
ij_javascript_binary_operation_wrap = off ij_javascript_binary_operation_wrap = off
ij_javascript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** ij_javascript_blacklist_imports = rxjs/Rx, node_modules/**, **/node_modules/**, @angular/material, @angular/material/typings/**
ij_javascript_blank_lines_after_imports = 1 ij_javascript_blank_lines_after_imports = 1
ij_javascript_blank_lines_around_class = 1 ij_javascript_blank_lines_around_class = 1
ij_javascript_blank_lines_around_field = 0 ij_javascript_blank_lines_around_field = 0
@ -362,7 +362,7 @@ ij_javascript_prefer_explicit_types_function_expression_returns = false
ij_javascript_prefer_explicit_types_function_returns = false ij_javascript_prefer_explicit_types_function_returns = false
ij_javascript_prefer_explicit_types_vars_fields = false ij_javascript_prefer_explicit_types_vars_fields = false
ij_javascript_prefer_parameters_wrap = false ij_javascript_prefer_parameters_wrap = false
ij_javascript_property_prefix = ij_javascript_property_prefix =
ij_javascript_reformat_c_style_comments = false ij_javascript_reformat_c_style_comments = false
ij_javascript_space_after_colon = true ij_javascript_space_after_colon = true
ij_javascript_space_after_comma = true ij_javascript_space_after_comma = true
@ -460,7 +460,7 @@ ij_go_group_stdlib_imports = true
ij_go_import_sorting = gofmt ij_go_import_sorting = gofmt
ij_go_keep_indents_on_empty_lines = false ij_go_keep_indents_on_empty_lines = false
ij_go_local_group_mode = project ij_go_local_group_mode = project
ij_go_local_package_prefixes = ij_go_local_package_prefixes =
ij_go_move_all_imports_in_one_declaration = true ij_go_move_all_imports_in_one_declaration = true
ij_go_move_all_stdlib_imports_in_one_group = true ij_go_move_all_stdlib_imports_in_one_group = true
ij_go_remove_redundant_import_aliases = false ij_go_remove_redundant_import_aliases = false

View File

@ -2,7 +2,7 @@ name: CI
on: on:
push: push:
branches: ["master"] branches: [ "master" ]
jobs: jobs:
api-build: api-build:
@ -29,7 +29,7 @@ jobs:
volumes: volumes:
- minio-data:/data - minio-data:/data
redis: redis:
image: redis:7.0.2-bookworm image: redis:7.0.12-bookworm
ports: ports:
- 6379:6379 - 6379:6379
defaults: defaults:

View File

@ -2,7 +2,7 @@ name: PR
on: on:
pull_request: pull_request:
branches: ["*"] branches: [ "*" ]
jobs: jobs:
client-build: client-build:
@ -18,7 +18,7 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Installling dependencies - name: Install dependencies
run: npm install run: npm install
- name: Lint - name: Lint
@ -66,7 +66,7 @@ jobs:
volumes: volumes:
- minio-data:/data - minio-data:/data
redis: redis:
image: redis:7.0.2-bookworm image: redis:7.0.12-bookworm
ports: ports:
- 6379:6379 - 6379:6379
defaults: defaults:

View File

@ -1,75 +1,87 @@
# Contributing Guide # Contributing Guide
First of all. Thank you for considering to contribute on Jokes Bapak2 API project. I hope this project will get better and we will become more bapak2 than ever. First of all. Thank you for considering to contribute on Jokes Bapak2 API project. I hope this project will get better
and we will become more bapak2 than ever.
This project is a monorepo, meaning the backend, frontend, and Github CI are all in one place (one repository). Before you do anything, if you're going to do some breaking change or you'll write (or remove) large numbers of LOC (line of codes), please open an issue first and let us know about it. So that our work won't bother you and you'll have a breeze on developing this.
This project is a monorepo, meaning the backend, frontend, and Github CI are all in one place (one repository). Before
## Project Prerequisites && Setup you do anything, if you're going to do some breaking change or you'll write (or remove) large numbers of LOC (line of
codes), please open an issue first and let us know about it. So that our work won't bother you and you'll have a breeze
### Front End (`./client`) on developing this.
You'll have to install: ## Project Prerequisites && Setup
* Node.js LTS (preferably with [fnm](https://github.com/Schniz/fnm) or [nvm](https://github.com/nvm-sh/nvm))
* Yarn v1 ### Front End (`./client`)
See the [README](./client/README.md) on client for detailed project setup. You'll have to install:
### Back End (`./api`) * Node.js LTS (preferably with [fnm](https://github.com/Schniz/fnm) or [nvm](https://github.com/nvm-sh/nvm))
* Yarn v1
You'll have to install:
* Go v1.16.x See the [README](./client/README.md) on client for detailed project setup.
* (Optional) [Fiber CLI](https://github.com/gofiber/cli) for ease of development
### Back End (`./api`)
See the [README](./api/README.md) on client for detailed project setup.
You'll have to install:
### With Docker Compose
* Go v1.16.x
If you're just developing the front end and too lazy installing Go and such (or the other way around), you can use `docker-compose` file specified on the main page. * (Optional) [Fiber CLI](https://github.com/gofiber/cli) for ease of development
You'll have to install: See the [README](./api/README.md) on client for detailed project setup.
* Docker (preferably with Docker Desktop if you're on Windows or Mac)
* Docker Compose ### With Docker Compose
```bash If you're just developing the front end and too lazy installing Go and such (or the other way around), you can
# Create a docker container but don't start it yet. use `docker-compose` file specified on the main page.
$ docker-compose up --no-start
You'll have to install:
# Or if you want to create the docker container and start it right away
$ docker-compose up * Docker (preferably with Docker Desktop if you're on Windows or Mac)
* Docker Compose
# If you want to have it running in the background
$ docker-compose up --detach ```bash
# Create a docker container but don't start it yet.
# Start existing container $ docker-compose up --no-start
$ docker-compose start
# Or if you want to create the docker container and start it right away
# Stop running container $ docker-compose up
$ docker-compose stop
# If you want to have it running in the background
# Destroy current container $ docker-compose up --detach
$ docker-compose down
``` # Start existing container
$ docker-compose start
## Before submitting PR
# Stop running container
### Front End (`./client`) $ docker-compose stop
Please run these: # Destroy current container
* `yarn lint` $ docker-compose down
* `yarn format` ```
* `yarn build`
## Before submitting PR
If those command didn't pass, please fix the problem first. Please recheck your changes, make sure NOT to leave any secret token/keys behind.
### Front End (`./client`)
### Back End (`./api`)
Please run these:
Please run these:
* `go fmt` * `yarn lint`
* `go build main.go` * `yarn format`
* `go test -v -race -coverprofile=coverage.out -covermode=atomic ./...` * `yarn build`
If those command didn't pass, please fix the problem first. Please recheck your changes, make sure NOT to leave any secret token/keys behind. If those command didn't pass, please fix the problem first. Please recheck your changes, make sure NOT to leave any
secret token/keys behind.
## One more thing..
### Back End (`./api`)
Please run these:
* `go fmt`
* `go build main.go`
* `go test -v -race -coverprofile=coverage.out -covermode=atomic ./...`
If those command didn't pass, please fix the problem first. Please recheck your changes, make sure NOT to leave any
secret token/keys behind.
## One more thing..
Oh my God, thank you so much!!! Working on an open source project is interesting right?? 😆 Oh my God, thank you so much!!! Working on an open source project is interesting right?? 😆

View File

@ -7,15 +7,19 @@
<br> <br>
</h1> </h1>
👋 Hey there! Always a work in progress, if you'd like to contribute this while this repo is still growing, that would be so great! 👋 Hey there! Always a work in progress, if you'd like to contribute this while this repo is still growing, that would be
so great!
ou can access the front facing web on [jokesbapak2.reinaldyrafli.com](http://jokesbapak2.reinaldyrafli.com/). ou can access the front facing web on [jokesbapak2.reinaldyrafli.com](http://jokesbapak2.reinaldyrafli.com/).
## Brief explanation of what is this ## Brief explanation of what is this
Jokes Bapak2 is an image API that you can use for free! I've been seeing lots and lots of Indonesian dad jokes on Twitter, Facebook and Instagram on early 2020. In a month, I made a Discord bot that provides the jokes. But I thought, why not make it as an API? Jokes Bapak2 is an image API that you can use for free! I've been seeing lots and lots of Indonesian dad jokes on
Twitter, Facebook and Instagram on early 2020. In a month, I made a Discord bot that provides the jokes. But I thought,
why not make it as an API?
This is some kind of [icanhazdadjokes](https://icanhazdadjoke.com/) but it's Indonesian and it's not text, it's images. Dad jokes in Indonesia is somewhat a bit different than in US/UK because, I guess, here, it's a lot dumber. This is some kind of [icanhazdadjokes](https://icanhazdadjoke.com/) but it's Indonesian and it's not text, it's images.
Dad jokes in Indonesia is somewhat a bit different than in US/UK because, I guess, here, it's a lot dumber.
## Project Directories ## Project Directories
@ -24,29 +28,31 @@ This is some kind of [icanhazdadjokes](https://icanhazdadjoke.com/) but it's Ind
You can consume this API via a website (linked in the front facing web) with a few endpoints: You can consume this API via a website (linked in the front facing web) with a few endpoints:
* `/` - Random jokes bapak2 * `/` - Random jokes bapak2
* `/id/{number}` - Jokes bapak2 based on ID * `/id/{number}` - Jokes bapak2 based on ID
* `/today` - Jokes bapak2 of the day * `/today` - Jokes bapak2 of the day
* `/total` - Total available jokes bapak2 * `/total` - Total available jokes bapak2
Currently I'm (still) searching for an alternative for AWS S3 that I can use for free. Currently I'm (still) searching for an alternative for AWS S3 that I can use for free.
## Tech stacks ## Tech stacks
* Go (for `api` / back end) * Go (for `api` / back end)
* Node.js (for `client` / front end) * Node.js (for `client` / front end)
* Postgres * Postgres
* Redis * Redis
That's it. That's it.
## Development ## Development
Two ways of doing this: Two ways of doing this:
1. Install all the tech stack on your local machine
2. Using docker-compose
See [CONTRIBUTING](./CONTRIBUTING.md) or README files on each project directory for further instruction on how to run the development environment. 1. Install all the tech stack on your local machine
2. Using docker-compose
See [CONTRIBUTING](./CONTRIBUTING.md) or README files on each project directory for further instruction on how to run
the development environment.
## Thanks to ## Thanks to

View File

@ -1,78 +1,79 @@
# Jokes Bapak2 API # Jokes Bapak2 API
Still work in progress Still work in progress
## Development ## Development
```bash ```bash
# Install modules # Install modules
$ go mod download $ go mod download
# or # or
$ go mod vendor $ go mod vendor
# run the local server # run the local server
$ go run main.go $ go run main.go
# build everything # build everything
$ go build main.go $ go build main.go
``` ```
There is a placeholder data ready for you to query it manually in `/platform/database/placeholder.sql`. Have a good time developing! There is a placeholder data ready for you to query it manually in `/platform/database/placeholder.sql`. Have a good time
developing!
## Used packages
## Used packages
| Name | Version | Type |
| --- | --- | --- | | Name | Version | Type |
| [gofiber/fiber](https://github.com/gofiber/fiber) | `v2.21.0` | Framework | |------------------------------------------------------------------|--------------------------------------|-----------|
| [jackc/pgx](https://github.com/jackc/pgx) | `v4.13.0` | Database | | [gofiber/fiber](https://github.com/gofiber/fiber) | `v2.21.0` | Framework |
| [go-redis/redis](https://github.com/go-redis/redis) | `v8.11.4` | Cache | | [jackc/pgx](https://github.com/jackc/pgx) | `v4.13.0` | Database |
| [allegro/bigcache](https://github.com/allegro/bigcache) | `v3.0.1` | Cache | | [go-redis/redis](https://github.com/go-redis/redis) | `v8.11.4` | Cache |
| [joho/godotenv](https://github.com/joho/godotenv) | `v1.4.0` | Config | | [allegro/bigcache](https://github.com/allegro/bigcache) | `v3.0.1` | Cache |
| [getsentry/sentry-go](https://github.com/getsentry/sentry-go) | `v0.11.0` | Logging | | [joho/godotenv](https://github.com/joho/godotenv) | `v1.4.0` | Config |
| [aldy505/phc-crypto](https://github.com/aldy505/phc-crypto) | `v1.1.0` | Utils | | [getsentry/sentry-go](https://github.com/getsentry/sentry-go) | `v0.11.0` | Logging |
| [Masterminds/squirrel](https://github.com/Masterminds/squirrel ) | `v1.5.1` | Utils | | [aldy505/phc-crypto](https://github.com/aldy505/phc-crypto) | `v1.1.0` | Utils |
| [aldy505/bob](https://github.com/aldy505/bob) | `v0.0.4` | Utils | | [Masterminds/squirrel](https://github.com/Masterminds/squirrel ) | `v1.5.1` | Utils |
| [gojek/heimdall](https://github.com/gojek/heimdall) | `v7.0.2` | Utils | | [aldy505/bob](https://github.com/aldy505/bob) | `v0.0.4` | Utils |
| [georgysavva/scany](https://github.com/georgysavva/scany) | `v0.2.9` | Utils | | [gojek/heimdall](https://github.com/gojek/heimdall) | `v7.0.2` | Utils |
| [pquerna/ffjson](https://github.com/pquerna/ffjson) | `v0.0.0-20190930134022-aa0246cd15f7` | Utils | | [georgysavva/scany](https://github.com/georgysavva/scany) | `v0.2.9` | Utils |
| [pquerna/ffjson](https://github.com/pquerna/ffjson) | `v0.0.0-20190930134022-aa0246cd15f7` | Utils |
## Directory structure
## Directory structure
```
. ```
├── core - Pure business logic .
│ ├── administrator ├── core - Pure business logic
│ ├── joke │ ├── administrator
│ ├── schema │ ├── joke
│ ├── submit │ ├── schema
│ └── validator │ ├── submit
├── Dockerfile - Docker image for API │ └── validator
├── documentation.json - Swagger documentation ├── Dockerfile - Docker image for API
├── documentation.yaml - Swagger documentation ├── documentation.json - Swagger documentation
├── favicon.png ├── documentation.yaml - Swagger documentation
├── go.mod - Module declaration ├── favicon.png
├── go.sum - Checksum for modules ├── go.mod - Module declaration
├── handler - Route handlers ├── go.sum - Checksum for modules
│ ├── health ├── handler - Route handlers
│ ├── joke │ ├── health
│ └── submit │ ├── joke
├── main.go - Application entry point │ └── submit
├── middleware - Route middlewares ├── main.go - Application entry point
├── platform - Third party packages ├── middleware - Route middlewares
│ └── database ├── platform - Third party packages
├── README.md - You are here │ └── database
├── routes - Route definitions ├── README.md - You are here
└── utils - Utility functions ├── routes - Route definitions
``` └── utils - Utility functions
```
## `.env` configuration
## `.env` configuration
```ini
ENV=development ```ini
PORT=5000 ENV=development
PORT=5000
DATABASE_URL=postgres://postgres:password@localhost:5432/jokesbapak2
REDIS_URL=redis://@localhost:6379 DATABASE_URL=postgres://postgres:password@localhost:5432/jokesbapak2
REDIS_URL=redis://@localhost:6379
SENTRY_DSN=
SENTRY_DSN=
``` ```

View File

@ -2,9 +2,10 @@ package joke_test
import ( import (
"context" "context"
"jokes-bapak2-api/core/joke"
"testing" "testing"
"time" "time"
"jokes-bapak2-api/core/joke"
) )
func TestGetRandomJoke(t *testing.T) { func TestGetRandomJoke(t *testing.T) {

View File

@ -2,9 +2,10 @@ package joke_test
import ( import (
"context" "context"
"jokes-bapak2-api/core/joke"
"testing" "testing"
"time" "time"
"jokes-bapak2-api/core/joke"
) )
func TestListJokeFromBucket(t *testing.T) { func TestListJokeFromBucket(t *testing.T) {

View File

@ -2,9 +2,10 @@ package joke_test
import ( import (
"context" "context"
"jokes-bapak2-api/core/joke"
"testing" "testing"
"time" "time"
"jokes-bapak2-api/core/joke"
) )
func TestGetTodaysJoke(t *testing.T) { func TestGetTodaysJoke(t *testing.T) {

View File

@ -2,9 +2,10 @@ package joke_test
import ( import (
"context" "context"
"jokes-bapak2-api/core/joke"
"testing" "testing"
"time" "time"
"jokes-bapak2-api/core/joke"
) )
func TestGetTotalJoke(t *testing.T) { func TestGetTotalJoke(t *testing.T) {

View File

@ -1,321 +1,321 @@
openapi: 3.0.0 openapi: 3.0.0
info: info:
title: Jokesbapak2 Image API title: Jokesbapak2 Image API
description: > description: >
Jokes Bapak2 is an image API that you can use for free! I've been seeing lots and lots of Indonesian dad jokes on Twitter, Jokes Bapak2 is an image API that you can use for free! I've been seeing lots and lots of Indonesian dad jokes on Twitter,
Facebook and Instagram on early 2020. In a month, I made a Discord bot that provides the jokes. Facebook and Instagram on early 2020. In a month, I made a Discord bot that provides the jokes.
But I thought, why not make it as an API? But I thought, why not make it as an API?
version: 0.0.1 version: 0.0.1
contact: contact:
name: Reinaldy Rafli name: Reinaldy Rafli
url: https://github.com/aldy505 url: https://github.com/aldy505
email: aldy505@tutanota.com email: aldy505@tutanota.com
license: license:
name: GNU General Public License v3.0 name: GNU General Public License v3.0
url: https://github.com/aldy505/jokes-bapak2/blob/master/LICENSE url: https://github.com/aldy505/jokes-bapak2/blob/master/LICENSE
servers: servers:
- url: "https://jokesbapak2.reinaldyrafli.com/api/v1" - url: "https://jokesbapak2.reinaldyrafli.com/api/v1"
description: Production description: Production
- url: "https://jokesbapak2.reinaldyrafli.com/api" - url: "https://jokesbapak2.reinaldyrafli.com/api"
description: Production description: Production
- url: "http://localhost:5000" - url: "http://localhost:5000"
description: Development description: Development
paths: paths:
/: /:
get: get:
tags: tags:
- Jokes - Jokes
summary: Get random Jokes Bapak2 image summary: Get random Jokes Bapak2 image
description: Returns a different image (PNG, JPG, or GIF) for every call. description: Returns a different image (PNG, JPG, or GIF) for every call.
responses: responses:
200: 200:
description: Image data description: Image data
content: content:
"image/gif": {} "image/gif": { }
"image/png": {} "image/png": { }
"image/jpeg": {} "image/jpeg": { }
put: put:
summary: Add a new joke into database summary: Add a new joke into database
description: asd description: asd
tags: tags:
- Jokes - Jokes
requestBody: requestBody:
description: asds description: asds
required: true required: true
content: content:
application/json: application/json:
schema: schema:
allOf: allOf:
- $ref: "#/components/schemas/request.auth" - $ref: "#/components/schemas/request.auth"
- $ref: "#/components/schemas/request.joke" - $ref: "#/components/schemas/request.joke"
responses: responses:
201: 201:
description: Image has been added description: Image has been added
content: content:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/request.joke" $ref: "#/components/schemas/request.joke"
example: example:
link: https://link.to/image.jpg link: https://link.to/image.jpg
400: 400:
description: Bad request description: Bad request
content: content:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/response.error" $ref: "#/components/schemas/response.error"
example: example:
error: URL provided is not a valid image error: URL provided is not a valid image
403: 403:
description: Must be authenticated to submit a joke description: Must be authenticated to submit a joke
content: content:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/response.error" $ref: "#/components/schemas/response.error"
/id/{id}: /id/{id}:
parameters: parameters:
- in: path - in: path
name: id name: id
schema: schema:
type: number type: number
required: true required: true
description: A number that represents image's ID description: A number that represents image's ID
get: get:
summary: Get random Jokes Bapak2 image by ID summary: Get random Jokes Bapak2 image by ID
description: Returns consistent image for every call. description: Returns consistent image for every call.
tags: tags:
- Jokes - Jokes
responses: responses:
200: 200:
description: Image data description: Image data
content: content:
"image/jpeg": {} "image/jpeg": { }
"image/png": {} "image/png": { }
"image/gif": {} "image/gif": { }
404: 404:
description: Provided image ID was not found description: Provided image ID was not found
content: content:
text/plain: text/plain:
schema: schema:
type: string type: string
example: Requested ID was not found. example: Requested ID was not found.
patch: patch:
summary: Update a Joke with certain image ID summary: Update a Joke with certain image ID
description: Returns consistent image for every call. description: Returns consistent image for every call.
tags: tags:
- Jokes - Jokes
responses: responses:
200: 200:
description: Sucessfully updated an image item description: Sucessfully updated an image item
content: content:
application/json: application/json:
schema: schema:
allOf: allOf:
- $ref: "#/components/schemas/response.confirmation" - $ref: "#/components/schemas/response.confirmation"
- $ref: "#/components/schemas/request.joke" - $ref: "#/components/schemas/request.joke"
400: 400:
description: Link provided is not a valid image description: Link provided is not a valid image
content: content:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/response.error" $ref: "#/components/schemas/response.error"
403: 403:
description: Must be authenticated to submit a joke description: Must be authenticated to submit a joke
content: content:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/response.error" $ref: "#/components/schemas/response.error"
406: 406:
description: If the Joke ID does not exists description: If the Joke ID does not exists
content: content:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/response.error" $ref: "#/components/schemas/response.error"
delete: delete:
summary: Delete a Joke with certain image ID summary: Delete a Joke with certain image ID
description: hi description: hi
tags: tags:
- Jokes - Jokes
responses: responses:
200: 200:
description: Sucessfully deleted an image item description: Sucessfully deleted an image item
content: content:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/response.confirmation" $ref: "#/components/schemas/response.confirmation"
403: 403:
description: Must be authenticated to submit a joke description: Must be authenticated to submit a joke
content: content:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/response.error" $ref: "#/components/schemas/response.error"
406: 406:
description: If the Joke ID does not exists description: If the Joke ID does not exists
content: content:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/response.error" $ref: "#/components/schemas/response.error"
/today: /today:
get: get:
summary: Get the joke of the day summary: Get the joke of the day
description: A joke a day makes more of a dad out of you. description: A joke a day makes more of a dad out of you.
tags: tags:
- Jokes - Jokes
responses: responses:
200: 200:
description: Image data description: Image data
content: content:
"image/jpeg": {} "image/jpeg": { }
"image/png": {} "image/png": { }
"image/gif": {} "image/gif": { }
/total: /total:
get: get:
summary: Get total amount of jokes in database summary: Get total amount of jokes in database
tags: tags:
- Jokes - Jokes
responses: responses:
200: 200:
description: Total jokes description: Total jokes
content: content:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/response.confirmation" $ref: "#/components/schemas/response.confirmation"
example: example:
message: "154" message: "154"
/submit: /submit:
get: get:
summary: Get submitted Jokes summary: Get submitted Jokes
tags: tags:
- Submission - Submission
parameters: parameters:
- name: author - name: author
in: query in: query
required: false required: false
description: Author to be queried description: Author to be queried
schema: schema:
type: string type: string
- name: approved - name: approved
in: query in: query
required: false required: false
description: Whether query just approved jokes or not description: Whether query just approved jokes or not
schema: schema:
type: boolean type: boolean
- name: limit - name: limit
in: query in: query
required: false required: false
schema: schema:
type: number type: number
- name: page - name: page
in: query in: query
required: false required: false
schema: schema:
type: number type: number
responses: responses:
200: 200:
description: asd description: asd
content: content:
application/json: application/json:
schema: schema:
type: object type: object
properties: properties:
count: count:
type: number type: number
jokes: jokes:
type: array type: array
items: items:
$ref: "#/components/schemas/response.submission" $ref: "#/components/schemas/response.submission"
post: post:
summary: Submit a joke summary: Submit a joke
description: > description: >
Must be in multipart/form-data format. Must be in multipart/form-data format.
Author must be in the format of "Name &lt;email&gt;". Author must be in the format of "Name &lt;email&gt;".
tags: tags:
- Submission - Submission
requestBody: requestBody:
content: content:
multipart/form-data: multipart/form-data:
schema: schema:
properties: properties:
link: link:
description: Image link description: Image link
type: string type: string
image: image:
description: Image data description: Image data
type: string type: string
author: author:
description: Person who submitted this description: Person who submitted this
type: string type: string
required: required:
- author - author
- image - image
- link - link
responses: responses:
201: 201:
description: Joke successfully submitted description: Joke successfully submitted
content: content:
application/json: application/json:
schema: schema:
allOf: allOf:
- $ref: "#/components/schemas/response.confirmation" - $ref: "#/components/schemas/response.confirmation"
- type: object - type: object
properties: properties:
data: data:
$ref: "#/components/schemas/response.submission" $ref: "#/components/schemas/response.submission"
400: 400:
description: Invalid data was sent description: Invalid data was sent
content: content:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/response.error" $ref: "#/components/schemas/response.error"
/health: /health:
get: get:
summary: Health check summary: Health check
description: Ping the databases to make sure everything's alright description: Ping the databases to make sure everything's alright
tags: tags:
- Health - Health
responses: responses:
200: 200:
description: Everything is okay description: Everything is okay
403: 403:
description: Something is not okay description: Something is not okay
content: content:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/response.error" $ref: "#/components/schemas/response.error"
components: components:
schemas: schemas:
request.auth: request.auth:
type: object type: object
properties: properties:
key: key:
type: string type: string
token: token:
type: string type: string
request.joke: request.joke:
type: object type: object
properties: properties:
link: link:
type: string type: string
response.confirmation: response.confirmation:
type: object type: object
properties: properties:
message: message:
type: string type: string
response.error: response.error:
type: object type: object
properties: properties:
error: error:
type: string type: string
response.submission: response.submission:
type: object type: object
properties: properties:
id: id:
type: number type: number
link: link:
type: string type: string
created_at: created_at:
type: string type: string
author: author:
type: string type: string
status: status:
type: number type: number

View File

@ -1,10 +1,11 @@
package joke package joke
import ( import (
core "jokes-bapak2-api/core/joke"
"net/http" "net/http"
"strconv" "strconv"
core "jokes-bapak2-api/core/joke"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
) )

View File

@ -1,9 +1,10 @@
package joke package joke
import ( import (
core "jokes-bapak2-api/core/joke"
"net/http" "net/http"
"strconv" "strconv"
core "jokes-bapak2-api/core/joke"
) )
// TotalJokes provides a HTTP handler for acquiring total jokes // TotalJokes provides a HTTP handler for acquiring total jokes

View File

@ -1,182 +1,181 @@
package main package main
import ( import (
"errors" "context"
"log" "errors"
"net" "log"
"net/http" "net"
"os" "net/http"
"os/signal" "os"
"os/signal"
"context" "time"
"jokes-bapak2-api/core/joke"
"jokes-bapak2-api/routes" "jokes-bapak2-api/core/joke"
"jokes-bapak2-api/routes"
"github.com/go-redis/redis/v8"
"github.com/minio/minio-go/v7" "github.com/go-redis/redis/v8"
"github.com/minio/minio-go/v7/pkg/credentials" "github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"time"
"github.com/allegro/bigcache/v3"
"github.com/allegro/bigcache/v3" "github.com/getsentry/sentry-go"
"github.com/getsentry/sentry-go" "github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5" "github.com/rs/cors"
"github.com/rs/cors" )
)
func main() {
func main() { redisURL, ok := os.LookupEnv("REDIS_URL")
redisURL, ok := os.LookupEnv("REDIS_URL") if !ok {
if !ok { redisURL = "redis://@localhost:6379"
redisURL = "redis://@localhost:6379" }
}
minioHost, ok := os.LookupEnv("MINIO_HOST")
minioHost, ok := os.LookupEnv("MINIO_HOST") if !ok {
if !ok { minioHost = "localhost:9000"
minioHost = "localhost:9000" }
}
minioRegion, ok := os.LookupEnv("MINIO_REGION")
minioRegion, ok := os.LookupEnv("MINIO_REGION") if !ok {
if !ok { minioRegion = ""
minioRegion = "" }
}
minioSecure, ok := os.LookupEnv("MINIO_SECURE")
minioSecure, ok := os.LookupEnv("MINIO_SECURE") if !ok {
if !ok { minioSecure = "false"
minioSecure = "false" }
}
minioID, ok := os.LookupEnv("MINIO_ACCESS_ID")
minioID, ok := os.LookupEnv("MINIO_ACCESS_ID") if !ok {
if !ok { minioID = "minio"
minioID = "minio" }
}
minioSecret, ok := os.LookupEnv("MINIO_SECRET_KEY")
minioSecret, ok := os.LookupEnv("MINIO_SECRET_KEY") if !ok {
if !ok { minioSecret = "password"
minioSecret = "password" }
}
minioToken, ok := os.LookupEnv("MINIO_TOKEN")
minioToken, ok := os.LookupEnv("MINIO_TOKEN") if !ok {
if !ok { minioToken = ""
minioToken = "" }
}
sentryDsn, ok := os.LookupEnv("SENTRY_DSN")
sentryDsn, ok := os.LookupEnv("SENTRY_DSN") if !ok {
if !ok { sentryDsn = ""
sentryDsn = "" }
}
port, ok := os.LookupEnv("PORT")
port, ok := os.LookupEnv("PORT") if !ok {
if !ok { port = "5000"
port = "5000" }
}
hostname, ok := os.LookupEnv("HOSTNAME")
hostname, ok := os.LookupEnv("HOSTNAME") if !ok {
if !ok { hostname = "127.0.0.1"
hostname = "127.0.0.1" }
}
environment, ok := os.LookupEnv("ENVIRONMENT")
environment, ok := os.LookupEnv("ENVIRONMENT") if !ok {
if !ok { environment = "development"
environment = "development" }
}
// Setup In Memory
// Setup In Memory memory, err := bigcache.NewBigCache(bigcache.DefaultConfig(10 * time.Minute))
memory, err := bigcache.NewBigCache(bigcache.DefaultConfig(10 * time.Minute)) if err != nil {
if err != nil { log.Panicln(err)
log.Panicln(err) }
} defer memory.Close()
defer memory.Close()
// Setup MinIO
// Setup MinIO minioClient, err := minio.New(minioHost, &minio.Options{
minioClient, err := minio.New(minioHost, &minio.Options{ Creds: credentials.NewStaticV4(minioID, minioSecret, minioToken),
Creds: credentials.NewStaticV4(minioID, minioSecret, minioToken), Region: minioRegion,
Region: minioRegion, Secure: minioSecure == "true",
Secure: minioSecure == "true", })
}) if err != nil {
if err != nil { log.Fatalf("setting up minio client: %s", err.Error())
log.Fatalf("setting up minio client: %s", err.Error()) return
return }
}
parsedRedisURL, err := redis.ParseURL(redisURL)
parsedRedisURL, err := redis.ParseURL(redisURL) if err != nil {
if err != nil { log.Fatalf("parsing redis url: %s", err.Error())
log.Fatalf("parsing redis url: %s", err.Error()) return
return }
}
redisClient := redis.NewClient(parsedRedisURL)
redisClient := redis.NewClient(parsedRedisURL) defer func() {
defer func() { err := redisClient.Close()
err := redisClient.Close() if err != nil {
if err != nil { log.Printf("closing redis client: %s", err.Error())
log.Printf("closing redis client: %s", err.Error()) }
} }()
}()
// Setup Sentry
// Setup Sentry err = sentry.Init(sentry.ClientOptions{
err = sentry.Init(sentry.ClientOptions{ Dsn: sentryDsn,
Dsn: sentryDsn, Environment: environment,
Environment: environment, AttachStacktrace: true,
AttachStacktrace: true, // Enable printing of SDK debug messages.
// Enable printing of SDK debug messages. // Useful when getting started or trying to figure something out.
// Useful when getting started or trying to figure something out. Debug: environment != "production",
Debug: environment != "production", })
}) if err != nil {
if err != nil { log.Fatalf("setting up sentry: %s", err.Error())
log.Fatalf("setting up sentry: %s", err.Error()) return
return }
} defer sentry.Flush(2 * time.Second)
defer sentry.Flush(2 * time.Second)
setupCtx, setupCancel := context.WithDeadline(context.Background(), time.Now().Add(time.Minute*4))
setupCtx, setupCancel := context.WithDeadline(context.Background(), time.Now().Add(time.Minute*4)) defer setupCancel()
defer setupCancel()
_, _, err = joke.GetTodaysJoke(setupCtx, minioClient, redisClient, memory)
_, _, err = joke.GetTodaysJoke(setupCtx, minioClient, redisClient, memory) if err != nil {
if err != nil { log.Fatalf("getting initial joke data: %s", err.Error())
log.Fatalf("getting initial joke data: %s", err.Error()) return
return }
}
healthRouter := routes.Health(minioClient, redisClient)
healthRouter := routes.Health(minioClient, redisClient) jokeRouter := routes.Joke(minioClient, redisClient, memory)
jokeRouter := routes.Joke(minioClient, redisClient, memory)
router := chi.NewRouter()
router := chi.NewRouter()
router.Use(cors.New(cors.Options{
router.Use(cors.New(cors.Options{ AllowedMethods: []string{http.MethodGet},
AllowedMethods: []string{http.MethodGet}, AllowCredentials: false,
AllowCredentials: false, MaxAge: int(60 * 60 * 24 * 365),
MaxAge: int(60 * 60 * 24 * 365), Debug: false,
Debug: false, }).Handler)
}).Handler)
router.Mount("/health", healthRouter)
router.Mount("/health", healthRouter) router.Mount("/", jokeRouter)
router.Mount("/", jokeRouter)
server := &http.Server{
server := &http.Server{ Handler: router,
Handler: router, Addr: net.JoinHostPort(hostname, port),
Addr: net.JoinHostPort(hostname, port), ReadTimeout: time.Minute,
ReadTimeout: time.Minute, WriteTimeout: time.Minute,
WriteTimeout: time.Minute, IdleTimeout: time.Second * 30,
IdleTimeout: time.Second * 30, ReadHeaderTimeout: time.Minute,
ReadHeaderTimeout: time.Minute, }
}
exitSignal := make(chan os.Signal, 1)
exitSignal := make(chan os.Signal, 1) signal.Notify(exitSignal, os.Interrupt)
signal.Notify(exitSignal, os.Interrupt)
go func() {
go func() { err := server.ListenAndServe()
err := server.ListenAndServe() if err != nil && !errors.Is(err, http.ErrServerClosed) {
if err != nil && !errors.Is(err, http.ErrServerClosed) { log.Fatalf("listening http server: %v", err)
log.Fatalf("listening http server: %v", err) }
} }()
}()
<-exitSignal
<-exitSignal
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), time.Second*30)
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), time.Second*30) defer shutdownCancel()
defer shutdownCancel()
err = server.Shutdown(shutdownCtx)
err = server.Shutdown(shutdownCtx) if err != nil {
if err != nil { log.Printf("shutting down http server: %v", err)
log.Printf("shutting down http server: %v", err) }
} }
}

View File

@ -1,8 +1,9 @@
package utils_test package utils_test
import ( import (
"jokes-bapak2-api/utils"
"testing" "testing"
"jokes-bapak2-api/utils"
) )
func TestIsIn_True(t *testing.T) { func TestIsIn_True(t *testing.T) {

View File

@ -1,9 +1,10 @@
package utils_test package utils_test
import ( import (
"jokes-bapak2-api/utils"
"testing" "testing"
"time" "time"
"jokes-bapak2-api/utils"
) )
func TestIsToday_Today(t *testing.T) { func TestIsToday_Today(t *testing.T) {

View File

@ -1,9 +1,10 @@
package utils_test package utils_test
import ( import (
"jokes-bapak2-api/utils"
"strings" "strings"
"testing" "testing"
"jokes-bapak2-api/utils"
) )
func TestParseToJSONBody(t *testing.T) { func TestParseToJSONBody(t *testing.T) {

View File

@ -1,8 +1,9 @@
package utils_test package utils_test
import ( import (
"jokes-bapak2-api/utils"
"testing" "testing"
"jokes-bapak2-api/utils"
) )
func TestRandomString_Valid(t *testing.T) { func TestRandomString_Valid(t *testing.T) {

View File

@ -1,9 +1,10 @@
package utils_test package utils_test
import ( import (
"jokes-bapak2-api/utils"
"net/http" "net/http"
"testing" "testing"
"jokes-bapak2-api/utils"
) )
func TestRequest_Get(t *testing.T) { func TestRequest_Get(t *testing.T) {

View File

@ -14,6 +14,6 @@ $ yarn start
## Used package ## Used package
| Name | Version | Type | | Name | Version | Type |
| --- | --- | --- | |---------------------------------------------------------------|----------|-------------------|
| [mcollina/autocannon](https://github.com/mcollina/autocannon) | `v7.4.0` | Benchmarking Tool | | [mcollina/autocannon](https://github.com/mcollina/autocannon) | `v7.4.0` | Benchmarking Tool |