Skip to content

Commit

Permalink
Merge pull request #12 from doutorfinancas/prodrigues/create_ability_…
Browse files Browse the repository at this point in the history
…to_redirect_by_a_number_of_times

feat: #1 Restrict the access to a shorty URL by the number of redirects
  • Loading branch information
pedrodsrodrigues authored May 11, 2023
2 parents de98aa4 + 02ad68a commit f845171
Show file tree
Hide file tree
Showing 13 changed files with 284 additions and 92 deletions.
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ DB_USERNAME=your-user
DB_PASSWORD=your-pun-chword
DB_NAME=pun_sho
DB_URL=something-somewhere-over-the-rainbow
DB_PORT=1337
DB_PORT=26257
DB_ADAPTOR=cockroach
QR_PNG_LOGO=./img/logo_df.png
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/.idea
/dist
.env
tmp
pun-sho
.env
http-requests/http-client.private.env.json
13 changes: 10 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,23 @@ hook/setup:
pre-commit install

.PHONY: test
test:
TEST_MODE=full go test -v ./...
go vet -printf=false ./...
test: test/go test/http-requests

.PHONY: test/cover
test/cover:
TEST_MODE=full go test -coverprofile=c.out -v ./...
go tool cover -html=c.out -o coverage.html
go vet -printf=false ./...

.PHONY: test/go
test/go:
TEST_MODE=full go test -v ./...
go vet -printf=false ./...

.PHONY: test/http-requests
test/http-requests:
docker run --rm -i -t -v ./http-requests:/workdir jetbrains/intellij-http-client --env development --env-file http-client.env.json --private-env-file http-client.private.env.json -D rest-api.http

.PHONY: migration/create
migration/create:
@read -p "Enter migration name: " MIGRATION_NAME; \
Expand Down
96 changes: 53 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,61 +19,33 @@ Spelled pan‧cho - ˈpãnʲ.t͡ʃo

props to [XKCD](https://xkcd.com/927/)

We decided that we need something that doesn't exist on every other project (mix all of them
, and you would have it).
We decided that we need something that doesn't exist on every other project (mix all of them, and you would have it).

So, we decided to make yet another URL shortener.

## Usage
you can clone this repo or use one of the precompiled binaries available in the release section
You can clone this repo or use one of the precompiled binaries available in the release section

you can also use docker, pre-made images are available for you at `docker pull ghcr.io/doutorfinancas/pun-sho:latest`
or you can
You can also use docker, pre-made images are available for you at `docker pull ghcr.io/doutorfinancas/pun-sho:latest`
or you can:
```bash
# this API_PORT is defined in .env file or put it in env itself
export API_PORT=8080
docker run --env-file=.env -p 8080:${API_PORT} -t ghcr.io/doutorfinancas/pun-sho:latest pun-sho
```

you should also copy the `.env.example` to `.env` and fill the values for the database.
You should also copy the `.env.example` to `.env` and fill the values for the database.
you can use either `cockroach` or `postgres` as value for the `DB_ADAPTOR`

if you use `cockroach`, you can create a free account [here](https://cockroachlabs.cloud/)

## DB migrations
This project uses database migrations.
For any changes on the DB structure to be dealt with or replicated or rolled-back we use a [migration tool](https://github.com/golang-migrate/migrate)

### Install golang-migrate
```shell
# cockroach
go install -tags 'cockroachdb' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
# postgres
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
```

### Create new migration files
```shell
make migration/create
```

### Migrate
To go versions up:
```shell
make migration/up
```

To go versions down:
```shell
make migration/clean
```
If you want to use `cockroach`, you can create a free account [here](https://cockroachlabs.cloud/)

### Create a short link
```bash
read -r -d '' BODY <<EOF
{
"link": "https://www.google.pt/",
"TTL": "2023-03-25T23:59:59Z",
"redirection_limit": 5,
"qr_code": {
"create": true,
"width" : 50,
Expand All @@ -90,17 +62,18 @@ EOF
# it will overlay the logo on qrcode center

curl -XPOST https://yourdomain.something/api/v1/short \
-H 'token: Whatever_Token_you_put_in_your_env' \
-H 'token: ThisIsA5uper$ecureAPIToken' \
-H 'Content-Type: application/json' \
-d $BODY
-d $BODY
```

this would render an answer like:
This would render an answer like:
```json
{
"id":"4b677dfe-e17a-46e7-9cd2-25a45e8cb19c",
"link":"https://www.google.pt/",
"TTL":"2023-03-25T23:59:59Z",
"redirection_limit": 5,
"created_at":"2023-03-20T10:50:38.399449Z",
"deleted_at":null,
"accesses":null,
Expand All @@ -112,15 +85,16 @@ this would render an answer like:

### Get statistics from a visited link
```bash
curl -H 'token: ThisIsA5uper$ecureAPIToken' https://yourdomain.something/api/v1/short/c62cbe57-7e45-4e87-a7c1-11cfb006870b
curl -H 'token: ThisIsA5uper$ecureAPIToken' http://localhost:8080/api/v1/short/c62cbe57-7e45-4e87-a7c1-11cfb006870b
```

this would render an answer like:
This would render an answer like ("visits" and "redirects" will only be equal to 1 if you access the link once):
```json
{
"id":"c62cbe57-7e45-4e87-a7c1-11cfb006870b",
"link":"https://www.google.pt/",
"TTL":"2023-03-25T23:59:59Z",
"redirection_limit": 5,
"created_at":"2023-03-19T18:56:06.8404Z",
"deleted_at":null,
"accesses": [
Expand Down Expand Up @@ -154,14 +128,50 @@ this would render an answer like:

### Get a list of links
```bash
curl -H 'token: ThisIsA5uper$ecureAPIToken' https://yourdomain.something/api/v1/short/?limit=20&offset=0
curl -H 'token: ThisIsA5uper$ecureAPIToken' http://localhost:8080/api/v1/short/?limit=20&offset=0
```

will return a list of short links, using pagination
Which will return a list of short links, using pagination.

### Deleting a link to make it inaccessible
```bash
curl -H 'token: ThisIsA5uper$ecureAPIToken' -XDELETE https://yourdomain.something/api/v1/short/c62cbe57-7e45-4e87-a7c1-11cfb006870b
curl -H 'token: ThisIsA5uper$ecureAPIToken' -XDELETE http://localhost:8080/api/v1/short/c62cbe57-7e45-4e87-a7c1-11cfb006870b
```

## Tests
You can execute all the tests of the application by using `make test`.

If you want to only execute one of the types we have, then you can run:
- `make test/go` for Go tests
- `make test/http-requests` for http-requests tests
(uses Docker but you can, locally, execute them through Intellij IDEA or through [http-client cli](https://www.jetbrains.com/help/idea/http-client-cli.html)).

## DB migrations
This project uses database migrations.
For any changes on the DB structure to be dealt with or replicated or rolled-back we use a [migration tool](https://github.com/golang-migrate/migrate).

### Install golang-migrate
```shell
# cockroach
go install -tags 'cockroachdb' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
# postgres
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
```

### Create new migration files
```shell
make migration/create
```

### Migrate
To go versions up:
```shell
make migration/up
```

To go versions down:
```shell
make migration/clean
```

## Releases
Expand Down
7 changes: 4 additions & 3 deletions api/request/create_shorty.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import (
)

type CreateShorty struct {
Link string `json:"link"`
TTL *time.Time `json:"TTL"`
QRCode *QRCode `json:"qr_code"`
Link string `json:"link"`
TTL *time.Time `json:"TTL"`
RedirectionLimit *int `json:"redirection_limit"`
QRCode *QRCode `json:"qr_code"`
}

type QRCode struct {
Expand Down
2 changes: 2 additions & 0 deletions db_migrations/000004_add_redirection_number_limit.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE pun_sho.shorties
DROP COLUMN redirection_limit;
2 changes: 2 additions & 0 deletions db_migrations/000004_add_redirection_number_limit.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE pun_sho.shorties
ADD COLUMN redirection_limit INT DEFAULT 0;
23 changes: 12 additions & 11 deletions entity/shorty_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@ import (
)

type Shorty struct {
ID uuid.UUID `json:"id" gorm:"column:id;type:uuid;default:uuid_generate_v4()"`
PublicID string `json:"-" gorm:"column:public_id"`
Link string `json:"link" gorm:"column:link"`
TTL *time.Time `json:"TTL" gorm:"column:ttl"`
CreatedAt *time.Time `json:"created_at" gorm:"column:created_at"`
DeletedAt *time.Time `json:"deleted_at" gorm:"column:deleted_at"`
ShortyAccesses []ShortyAccess `json:"accesses" gorm:"-"`
ShortLink string `json:"short_link" gorm:"-"`
Visits int `json:"visits" gorm:"-"`
RedirectCount int `json:"redirects" gorm:"-"`
QRCode string `json:"qr_code,omitempty" gorm:"column:qr_code"`
ID uuid.UUID `json:"id" gorm:"column:id;type:uuid;default:uuid_generate_v4()"`
PublicID string `json:"-" gorm:"column:public_id"`
Link string `json:"link" gorm:"column:link"`
TTL *time.Time `json:"TTL" gorm:"column:ttl"`
RedirectionLimit *int `json:"redirection_limit" gorm:"column:redirection_limit"`
CreatedAt *time.Time `json:"created_at" gorm:"column:created_at"`
DeletedAt *time.Time `json:"deleted_at" gorm:"column:deleted_at"`
ShortyAccesses []ShortyAccess `json:"accesses" gorm:"-"`
ShortLink string `json:"short_link" gorm:"-"`
Visits int `json:"visits" gorm:"-"`
RedirectCount int `json:"redirects" gorm:"-"`
QRCode string `json:"qr_code,omitempty" gorm:"column:qr_code"`
}

func (*Shorty) TableName() string {
Expand Down
5 changes: 5 additions & 0 deletions http-requests/http-client.env.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"development": {
"host": "http://localhost:8080"
}
}
5 changes: 5 additions & 0 deletions http-requests/http-client.private.env.json.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"development": {
"token": "ThisIsA5uper$ecureAPIToken"
}
}
74 changes: 74 additions & 0 deletions http-requests/rest-api.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
### Create a short link

# curl -XPOST {{host}}/api/v1/short
# -H 'token: {{token}}'
# -H 'Content-Type: application/json'
# -d $BODY
POST {{host}}/api/v1/short
token: {{token}}
Content-Type: application/json

{
"link": "https://www.brave.com/",
"TTL": "2023-05-25T23:59:59Z",
"redirection_limit": 5,
"qr_code": {
"create": true,
"width" : 50,
"height": 50,
"foreground_color": "#000000",
"background_color": "#ffffff",
"shape": "circle"
}
}

> {%
client.test("Status", function() {
client.assert(response.status === 201, "Response status is not 201");
});

client.test("Content-Type", function() {
const contentType = response.contentType.mimeType
client.assert(contentType === "application/json", "Response Content-Type is not 'application/json'");
});

client.global.set('shorty_id', response.body.id);
%}

### Get statistics from a visited link

# curl -H 'token: {{token}}' {{host}}/api/v1/short/{{shorty_id}}
# * {{shorty_id}} being what was created in the previous test
GET {{host}}/api/v1/short/{{shorty_id}}
token: {{token}}

> {%
client.test("Status", function() {
client.assert(response.status === 200, "Response status is not 200");
});

client.test("Content-Type", function() {
const contentType = response.contentType.mimeType
client.assert(contentType === "application/json", "Response Content-Type is not 'application/json'");
});
%}

### Get a list of links

# curl -H 'token: {{token}}' {{host}}/api/v1/short/?limit=20&offset=0
GET {{host}}/api/v1/short/?limit=20&offset=0
token: {{token}}

> {%
client.test("Status", function() {
client.assert(response.status === 200, "Response status is not 200");
});

client.test("Content-Type", function() {
const contentType = response.contentType.mimeType
client.assert(contentType === "application/json", "Response Content-Type is not 'application/json'");
});
%}

###

Loading

0 comments on commit f845171

Please sign in to comment.