Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 30fb539289 | |||
| 66fd6db3c2 | |||
| 93a91cd76e | |||
| 1d1444234b | |||
| 49ac63210e | |||
| 702491cb39 | |||
| d55dbe0471 | |||
| 55969b03b5 | |||
| 70a036e158 | |||
| 6ade7d295b | |||
| e6ae133449 | |||
| d3706a8778 |
@@ -1,4 +1,4 @@
|
|||||||
name: Publish Docker image
|
name: Publish Docker images
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -8,21 +8,20 @@ on:
|
|||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
push_to_registry:
|
build-platforms:
|
||||||
name: Push Docker image to Docker Hub
|
name: Build Platforms
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform: [linux/amd64, linux/arm64]
|
||||||
permissions:
|
permissions:
|
||||||
packages: write
|
|
||||||
contents: read
|
contents: read
|
||||||
attestations: write
|
packages: write
|
||||||
id-token: write
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v2
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
@@ -32,33 +31,42 @@ jobs:
|
|||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Extract version from shard.yml
|
- name: Extract version
|
||||||
id: extract_version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
VERSION=$(grep '^version:' shard.yml | cut -d ' ' -f 2)
|
VERSION=$(grep '^version:' shard.yml | cut -d ' ' -f 2)
|
||||||
echo "RELEASE_TAG=$VERSION" >> $GITHUB_ENV
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Set tags
|
- name: Build and push platform image
|
||||||
id: set_tags
|
|
||||||
run: |
|
|
||||||
if [[ "${{ github.event_name }}" == "release" ]]; then
|
|
||||||
echo "TAGS=latest,${{ env.RELEASE_TAG }}" >> $GITHUB_ENV
|
|
||||||
else
|
|
||||||
echo "TAGS=latest" >> $GITHUB_ENV
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Build and push image
|
|
||||||
id: push
|
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
|
env:
|
||||||
|
CRYSTAL_WORKERS: ${{ matrix.platform == 'linux/amd64' && 4 || 2 }}
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
|
platforms: ${{ matrix.platform }}
|
||||||
push: true
|
push: true
|
||||||
platforms: linux/amd64,linux/arm64
|
tags: |
|
||||||
tags: sjdonado/bit:${{ env.TAGS }}
|
sjdonado/bit:${{ github.event_name == 'release' && steps.version.outputs.version || 'latest' }}-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
|
||||||
|
build-args: |
|
||||||
|
TARGETARCH=${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
- name: Attest
|
create-manifest:
|
||||||
uses: actions/attest-build-provenance@v1
|
name: Create Manifest
|
||||||
id: attest
|
runs-on: ubuntu-latest
|
||||||
|
needs: build-platforms
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
subject-name: sjdonado/bit
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
subject-digest: ${{ steps.push.outputs.digest }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Create manifest
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create \
|
||||||
|
-t sjdonado/bit:${{ github.event_name == 'release' && needs.build-platforms.outputs.version || 'latest' }} \
|
||||||
|
sjdonado/bit:${{ github.event_name == 'release' && needs.build-platforms.outputs.version || 'latest' }}-{amd64,arm64}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
/docs/
|
|
||||||
/lib/
|
/lib/
|
||||||
/bin/
|
/bin/
|
||||||
/.shards/
|
/.shards/
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, religion, or sexual identity
|
||||||
|
and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
* Demonstrating empathy and kindness toward other people
|
||||||
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
* Giving and gracefully accepting constructive feedback
|
||||||
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
* Focusing on what is best not just for us as individuals, but for the
|
||||||
|
overall community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery, and sexual attention or
|
||||||
|
advances of any kind
|
||||||
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or email
|
||||||
|
address, without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official email address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community leaders responsible for enforcement at
|
||||||
|
[INSERT CONTACT METHOD].
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series
|
||||||
|
of actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or
|
||||||
|
permanent ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within
|
||||||
|
the community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 2.0, available at
|
||||||
|
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by
|
||||||
|
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
|
||||||
|
at [https://www.contributor-covenant.org/translations][translations].
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
|
||||||
|
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||||
|
[FAQ]: https://www.contributor-covenant.org/faq
|
||||||
|
[translations]: https://www.contributor-covenant.org/translations
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
# Contributing Guidelines
|
||||||
|
|
||||||
|
We welcome contributions from the community! Please follow these guidelines to help maintain consistency and quality in the project.
|
||||||
|
|
||||||
|
## Code of Conduct
|
||||||
|
This project adheres to the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md). By participating, you agree to uphold its terms.
|
||||||
|
|
||||||
|
## How to Contribute
|
||||||
|
|
||||||
|
### 1. Fork the Repository
|
||||||
|
Click the "Fork" button at the top-right of the [repository page](https://github.com/sjdonado/bit).
|
||||||
|
|
||||||
|
### 2. Clone Your Fork
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/YOUR_USERNAME/bit.git
|
||||||
|
cd bit
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Create a Feature Branch
|
||||||
|
```bash
|
||||||
|
git checkout -b feat/your-feature-name
|
||||||
|
```
|
||||||
|
|
||||||
|
### or for bug fixes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout -b fix/issue-description
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Develop Your Changes
|
||||||
|
- Check [Local Development](docs/SETUP.md#local-development) guidelines
|
||||||
|
- Ensure changes match the project scope
|
||||||
|
- Write clear commit messages
|
||||||
|
- Include tests for new functionality
|
||||||
|
- Update documentation when applicable
|
||||||
|
|
||||||
|
### 5. Commit Changes
|
||||||
|
```bash
|
||||||
|
git commit -am 'Add descriptive commit message'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Push to GitHub
|
||||||
|
```bash
|
||||||
|
git push origin your-branch-name
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Create a Pull Request
|
||||||
|
1. Go to the [original repository](https://github.com/sjdonado/bit)
|
||||||
|
2. Click "New Pull Request"
|
||||||
|
3. Select your fork and branch
|
||||||
|
4. Add a clear description including:
|
||||||
|
- Purpose of changes
|
||||||
|
- Related issues (if applicable)
|
||||||
|
- Testing performed
|
||||||
|
|
||||||
|
## Pull Request Guidelines
|
||||||
|
- Keep PRs focused on a single feature/bugfix
|
||||||
|
- Ensure all tests pass
|
||||||
|
- Update documentation in the same PR
|
||||||
|
- Use descriptive titles (e.g., "Add URL validation" not "Update code")
|
||||||
|
- Reference related issues using #issue-number
|
||||||
|
|
||||||
|
## Reporting Issues
|
||||||
|
When opening an issue, please include:
|
||||||
|
1. Description of the problem
|
||||||
|
2. Steps to reproduce
|
||||||
|
3. Expected vs actual behavior
|
||||||
|
4. Environment details (OS, Crystal version, etc)
|
||||||
|
|
||||||
|
For feature requests:
|
||||||
|
- Explain the problem you're trying to solve
|
||||||
|
- Suggest potential implementations
|
||||||
|
|
||||||
|
## License
|
||||||
|
By contributing, you agree that your contributions will be licensed under the [license](LICENSE).
|
||||||
+25
-16
@@ -1,32 +1,41 @@
|
|||||||
FROM alpine:edge AS build
|
FROM debian:bookworm-slim AS build
|
||||||
|
|
||||||
|
ARG TARGETARCH
|
||||||
ENV ENV=production
|
ENV ENV=production
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
RUN apk update && apk add --no-cache \
|
RUN apt-get update && apt-get install -y \
|
||||||
|
curl \
|
||||||
|
gnupg \
|
||||||
|
ca-certificates \
|
||||||
|
&& mkdir -p /etc/apt/keyrings \
|
||||||
|
&& curl -fsSL https://packagecloud.io/84codes/crystal/gpgkey | gpg --dearmor > /etc/apt/trusted.gpg.d/84codes_crystal.gpg \
|
||||||
|
&& echo "deb [signed-by=/etc/apt/trusted.gpg.d/84codes_crystal.gpg] https://packagecloud.io/84codes/crystal/debian/ bookworm main" > /etc/apt/sources.list.d/84codes_crystal.list
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
crystal \
|
crystal \
|
||||||
shards \
|
libssl-dev \
|
||||||
yaml-dev \
|
libyaml-dev \
|
||||||
sqlite-dev \
|
libsqlite3-dev \
|
||||||
openssl-dev
|
libevent-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN shards install
|
RUN shards install --production
|
||||||
RUN shards build --release --no-debug
|
RUN shards build --release --no-debug --progress --stats
|
||||||
|
|
||||||
FROM alpine:edge AS runtime
|
FROM debian:bookworm-slim AS runtime
|
||||||
|
|
||||||
ENV ENV=production
|
ENV ENV=production
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
RUN apk add --no-cache \
|
RUN apt-get update && apt-get install -y \
|
||||||
gc \
|
libssl3 \
|
||||||
pcre2 \
|
libyaml-0-2 \
|
||||||
libevent \
|
libsqlite3-0 \
|
||||||
yaml \
|
libevent-2.1-7 \
|
||||||
sqlite-libs \
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
openssl
|
|
||||||
|
|
||||||
RUN mkdir -p sqlite
|
RUN mkdir -p sqlite
|
||||||
|
|
||||||
|
|||||||
@@ -1,296 +1,28 @@
|
|||||||
[](https://hub.docker.com/repository/docker/sjdonado/bit)
|
[](https://hub.docker.com/r/sjdonado/bit)
|
||||||
[](https://hub.docker.com/repository/docker/sjdonado/bit)
|
[](https://hub.docker.com/r/sjdonado/bit)
|
||||||
[](https://hub.docker.com/repository/docker/sjdonado/bit)
|
[](https://hub.docker.com/r/sjdonado/bit)
|
||||||
|
|
||||||
## API Endpoints
|
# Bit URL Shortener
|
||||||
|
|
||||||
1. **Ping the API**
|
Lightweight URL shortener service with minimal resource requirements. Average memory consumption is **20MB RAM** with container disk space under **50MB**.
|
||||||
|
|
||||||
- Endpoint: `GET /api/ping`
|
Bit is highly performant, achieving over 850 requests per second with an average latency of just 118ms. For detailed benchmark results, see [benchmark](docs/SETUP.md#benchmark).
|
||||||
- Payload: None
|
|
||||||
- Response Example
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"message": "pong"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Retrieve a link by its slug**
|
Images available on [Docker Hub](https://hub.docker.com/r/sjdonado/bit/tags).
|
||||||
|
|
||||||
- Endpoint: `GET /:slug`
|
|
||||||
- Headers: `X-Api-Key`
|
|
||||||
- Payload: None
|
|
||||||
- Response Example
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"data": {
|
|
||||||
"id": "84f0c7a4-8c4e-4665-b676-cb9c5e40f1db",
|
|
||||||
"refer": "http://localhost:4000/3wP4BQ",
|
|
||||||
"origin": "https://monocuco.donado.co",
|
|
||||||
"clicks": [
|
|
||||||
{
|
|
||||||
"id": "730e2202-58f9-478c-a24c-f1c561df6716",
|
|
||||||
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:127.0) Gecko/20100101 Firefox/127.0",
|
|
||||||
"language": "en-US",
|
|
||||||
"browser": "Firefox",
|
|
||||||
"os": "Mac OS X",
|
|
||||||
"source": "Unknown",
|
|
||||||
"created_at": "2024-07-12T19:25:22Z"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Retrieve all links**
|
|
||||||
|
|
||||||
- Endpoint: `GET /api/links`
|
|
||||||
- Headers: `X-Api-Key`
|
|
||||||
- Payload: None
|
|
||||||
- Response Example
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"id": "84f0c7a4-8c4e-4665-b676-cb9c5e40f1db",
|
|
||||||
"refer": "http://localhost:4000/3wP4BQ",
|
|
||||||
"origin": "https://monocuco.donado.co",
|
|
||||||
"clicks": [
|
|
||||||
{
|
|
||||||
"id": "730e2202-58f9-478c-a24c-f1c561df6716",
|
|
||||||
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:127.0) Gecko/20100101 Firefox/127.0",
|
|
||||||
"language": "en-US",
|
|
||||||
"browser": "Firefox",
|
|
||||||
"os": "Mac OS X",
|
|
||||||
"source": "Unknown",
|
|
||||||
"created_at": "2024-07-12T19:25:22Z"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Retrieve a link by its ID**
|
|
||||||
|
|
||||||
- Endpoint: `GET /api/links/:id`
|
|
||||||
- Headers: `X-Api-Key`
|
|
||||||
- Payload: None
|
|
||||||
- Response Example
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"data": {
|
|
||||||
"id": "84f0c7a4-8c4e-4665-b676-cb9c5e40f1db",
|
|
||||||
"refer": "http://localhost:4000/3wP4BQ",
|
|
||||||
"origin": "https://monocuco.donado.co",
|
|
||||||
"clicks": [
|
|
||||||
{
|
|
||||||
"id": "730e2202-58f9-478c-a24c-f1c561df6716",
|
|
||||||
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:127.0) Gecko/20100101 Firefox/127.0",
|
|
||||||
"language": "en-US",
|
|
||||||
"browser": "Firefox",
|
|
||||||
"os": "Mac OS X",
|
|
||||||
"source": "Unknown",
|
|
||||||
"created_at": "2024-07-12T19:25:22Z"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
5. **Create a new link**
|
|
||||||
|
|
||||||
- Endpoint\*\*: `POST /api/links`
|
|
||||||
- Payload:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"url": "https://example.com"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
- Headers: `X-Api-Key`
|
|
||||||
- Response Example:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"data": {
|
|
||||||
"id": "84f0c7a4-8c4e-4665-b676-cb9c5e40f1db",
|
|
||||||
"refer": "http://localhost:4000/3wP4BQ",
|
|
||||||
"origin": "https://monocuco.donado.co/test",
|
|
||||||
"clicks": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
6. **Update an existing link by its ID**
|
|
||||||
|
|
||||||
- Endpoint: `PUT /api/links/:id`
|
|
||||||
- Payload:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"url": "https://newexample.com"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
- Headers: `X-Api-Key`
|
|
||||||
- Response Example:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"data": {
|
|
||||||
"id": "84f0c7a4-8c4e-4665-b676-cb9c5e40f1db",
|
|
||||||
"refer": "http://localhost:4000/3wP4BQ",
|
|
||||||
"origin": "https://newexample.com",
|
|
||||||
"clicks": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
7. **Delete a link by its ID**
|
|
||||||
|
|
||||||
- Endpoint: `DELETE /api/links/:id`
|
|
||||||
- Payload: None
|
|
||||||
- Headers: `X-Api-Key`
|
|
||||||
- Response Example:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"message": "Link deleted"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Self-hosted
|
|
||||||
|
|
||||||
### Run via docker-compose
|
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
```bash
|
```bash
|
||||||
docker-compose up
|
docker run -p 4000:4000 -e ADMIN_API_KEY=$(openssl rand -base64 32) sjdonado/bit:latest
|
||||||
|
|
||||||
# Optional: Generate an api key
|
|
||||||
# docker-compose exec -it app cli --create-user=Admin
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Run via docker cli
|
## Minimum Requirements
|
||||||
|
- 50MB disk space
|
||||||
|
- 50MB RAM (20MB avg usage)
|
||||||
|
- x86_64 or ARM64 architecture
|
||||||
|
|
||||||
```bash
|
## Documentation
|
||||||
docker run \
|
- [API Reference](docs/API.md)
|
||||||
--name bit \
|
- [Advanced Setup](docs/SETUP.md)
|
||||||
-p 4000:4000 \
|
|
||||||
-e ENV="production" \
|
|
||||||
-e DATABASE_URL="sqlite3://./sqlite/data.db?journal_mode=wal&synchronous=normal&foreign_keys=true" \
|
|
||||||
-e APP_URL="http://localhost:4000" \
|
|
||||||
-e ADMIN_NAME="Admin" \
|
|
||||||
-e ADMIN_API_KEY=$(openssl rand -base64 32) \
|
|
||||||
sjdonado/bit
|
|
||||||
|
|
||||||
# Optional: Generate an api key
|
|
||||||
# docker exec -it bit cli --create-user=Admin
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dokku
|
|
||||||
|
|
||||||
```dockerfile
|
|
||||||
FROM sjdonado/bit
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
dokku apps:create bit
|
|
||||||
|
|
||||||
dokku domains:set bit bit.donado.co
|
|
||||||
dokku letsencrypt:enable bit
|
|
||||||
|
|
||||||
dokku storage:ensure-directory bit-sqlite
|
|
||||||
dokku storage:mount bit /var/lib/dokku/data/storage/bit-sqlite:/usr/src/app/sqlite/
|
|
||||||
|
|
||||||
dokku config:set bit DATABASE_URL="sqlite3://./sqlite/data.db?journal_mode=wal&synchronous=normal&foreign_keys=true" APP_URL=https://bit.donado.co ADMIN_NAME=Admin ADMIN_API_KEY=$(openssl rand -base64 32)
|
|
||||||
|
|
||||||
dokku ports:add bit http:80:4000
|
|
||||||
dokku ports:add bit https:443:4000
|
|
||||||
|
|
||||||
# Optional: Generate an api key
|
|
||||||
# dokku run bit cli --create-user=Admin
|
|
||||||
```
|
|
||||||
|
|
||||||
## CLI
|
|
||||||
|
|
||||||
```
|
|
||||||
Usage: ./cli [options]
|
|
||||||
Options:
|
|
||||||
--create-user=NAME Create a new user with the given name
|
|
||||||
--list-users List all users
|
|
||||||
--delete-user=USER_ID Delete a user by ID
|
|
||||||
```
|
|
||||||
|
|
||||||
## Benchmark
|
|
||||||
|
|
||||||
```
|
|
||||||
$ ./benchmark.sh
|
|
||||||
Setting up...
|
|
||||||
[+] Running 3/3
|
|
||||||
✔ Network bit_default Created 0.0s
|
|
||||||
✔ Volume "bit_sqlite_data" Created 0.0s
|
|
||||||
✔ Container bit Started 0.1s
|
|
||||||
Captured API Key: aHOCnZSuo2kOHy2mDa-iOA
|
|
||||||
Waiting for the application to be ready...
|
|
||||||
HTTP/1.1 200 OK
|
|
||||||
Connection: keep-alive
|
|
||||||
Content-Type: application/json
|
|
||||||
Date: Sun, 27 Oct 2024 11:52:33 GMT
|
|
||||||
Access-Control-Allow-Origin: *
|
|
||||||
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
|
|
||||||
Access-Control-Allow-Headers: Content-Type, Accept, Origin, X-Api-Key
|
|
||||||
Content-Length: 13
|
|
||||||
|
|
||||||
Starting resource usage monitoring...
|
|
||||||
Creating 10000 short links with 100 conrurrent requests...
|
|
||||||
Link creation complete: 10000 links created.
|
|
||||||
Fetching all created links from /api/links...
|
|
||||||
Selected link for benchmarking: http://localhost:4000/UaVZjA
|
|
||||||
Starting benchmark with Bombardier...
|
|
||||||
Bombarding http://localhost:4000/oEKLAg with 10000 request(s) using 100 connection(s)
|
|
||||||
10000 / 10000 [===============================================================================] 100.00% 830/s 12s
|
|
||||||
Done!
|
|
||||||
Statistics Avg Stdev Max
|
|
||||||
Reqs/sec 853.89 1625.49 8942.54
|
|
||||||
Latency 118.48ms 11.52ms 142.58ms
|
|
||||||
HTTP codes:
|
|
||||||
1xx - 0, 2xx - 0, 3xx - 10000, 4xx - 0, 5xx - 0
|
|
||||||
others - 0
|
|
||||||
Throughput: 360.02KB/s
|
|
||||||
Benchmark completed.
|
|
||||||
Analyzing resource usage...
|
|
||||||
**** Results ****
|
|
||||||
Average CPU Usage: 40.68%
|
|
||||||
Average Memory Usage: 28.62 MiB
|
|
||||||
./benchmark.sh: line 135: 61567 Terminated: 15 monitor_resource_usage
|
|
||||||
[+] Running 2/2
|
|
||||||
✔ Container bit Removed 10.1s
|
|
||||||
✔ Network bit_default Removed
|
|
||||||
```
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
- Setup
|
|
||||||
```bash
|
|
||||||
brew tap amberframework/micrate
|
|
||||||
brew install micrate
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
shards run bit
|
|
||||||
```
|
|
||||||
|
|
||||||
- Generate the `X-Api-Key`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
shards run cli -- --create-user=Admin
|
|
||||||
```
|
|
||||||
|
|
||||||
- Run tests
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ENV=test crystal spec
|
|
||||||
```
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
Found an issue or have a suggestion? Please follow our [contribution guidelines](CONTRIBUTING.md).
|
||||||
1. Fork it (<https://github.com/sjdonado/bit/fork>)
|
|
||||||
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
||||||
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
||||||
4. Push to the branch (`git push origin my-new-feature`)
|
|
||||||
5. Create a new Pull Request
|
|
||||||
|
|||||||
+152
@@ -0,0 +1,152 @@
|
|||||||
|
# API Reference
|
||||||
|
|
||||||
|
1. **Ping the API**
|
||||||
|
|
||||||
|
- Endpoint: `GET /api/ping`
|
||||||
|
- Payload: None
|
||||||
|
- Response Example
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "pong"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Redirect by Slug**
|
||||||
|
|
||||||
|
- Endpoint: `GET /:slug`
|
||||||
|
- Headers: `X-Api-Key`
|
||||||
|
- Payload: None
|
||||||
|
- Response Example
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"id": "84f0c7a4-8c4e-4665-b676-cb9c5e40f1db",
|
||||||
|
"refer": "http://localhost:4000/3wP4BQ",
|
||||||
|
"origin": "https://monocuco.donado.co",
|
||||||
|
"clicks": [
|
||||||
|
{
|
||||||
|
"id": "730e2202-58f9-478c-a24c-f1c561df6716",
|
||||||
|
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:127.0) Gecko/20100101 Firefox/127.0",
|
||||||
|
"language": "en-US",
|
||||||
|
"browser": "Firefox",
|
||||||
|
"os": "Mac OS X",
|
||||||
|
"source": "Unknown",
|
||||||
|
"created_at": "2024-07-12T19:25:22Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **List All Links**
|
||||||
|
|
||||||
|
- Endpoint: `GET /api/links`
|
||||||
|
- Headers: `X-Api-Key`
|
||||||
|
- Payload: None
|
||||||
|
- Response Example
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "84f0c7a4-8c4e-4665-b676-cb9c5e40f1db",
|
||||||
|
"refer": "http://localhost:4000/3wP4BQ",
|
||||||
|
"origin": "https://monocuco.donado.co",
|
||||||
|
"clicks": [
|
||||||
|
{
|
||||||
|
"id": "730e2202-58f9-478c-a24c-f1c561df6716",
|
||||||
|
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:127.0) Gecko/20100101 Firefox/127.0",
|
||||||
|
"language": "en-US",
|
||||||
|
"browser": "Firefox",
|
||||||
|
"os": "Mac OS X",
|
||||||
|
"source": "Unknown",
|
||||||
|
"created_at": "2024-07-12T19:25:22Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **List link by ID**
|
||||||
|
|
||||||
|
- Endpoint: `GET /api/links/:id`
|
||||||
|
- Headers: `X-Api-Key`
|
||||||
|
- Payload: None
|
||||||
|
- Response Example
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"id": "84f0c7a4-8c4e-4665-b676-cb9c5e40f1db",
|
||||||
|
"refer": "http://localhost:4000/3wP4BQ",
|
||||||
|
"origin": "https://monocuco.donado.co",
|
||||||
|
"clicks": [
|
||||||
|
{
|
||||||
|
"id": "730e2202-58f9-478c-a24c-f1c561df6716",
|
||||||
|
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:127.0) Gecko/20100101 Firefox/127.0",
|
||||||
|
"language": "en-US",
|
||||||
|
"browser": "Firefox",
|
||||||
|
"os": "Mac OS X",
|
||||||
|
"source": "Unknown",
|
||||||
|
"created_at": "2024-07-12T19:25:22Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Create new link**
|
||||||
|
|
||||||
|
- Endpoint\*\*: `POST /api/links`
|
||||||
|
- Payload:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"url": "https://example.com"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Headers: `X-Api-Key`
|
||||||
|
- Response Example:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"id": "84f0c7a4-8c4e-4665-b676-cb9c5e40f1db",
|
||||||
|
"refer": "http://localhost:4000/3wP4BQ",
|
||||||
|
"origin": "https://monocuco.donado.co/test",
|
||||||
|
"clicks": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Update an existing link by ID**
|
||||||
|
|
||||||
|
- Endpoint: `PUT /api/links/:id`
|
||||||
|
- Payload:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"url": "https://newexample.com"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Headers: `X-Api-Key`
|
||||||
|
- Response Example:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"id": "84f0c7a4-8c4e-4665-b676-cb9c5e40f1db",
|
||||||
|
"refer": "http://localhost:4000/3wP4BQ",
|
||||||
|
"origin": "https://newexample.com",
|
||||||
|
"clicks": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
7. **Delete a link by ID**
|
||||||
|
|
||||||
|
- Endpoint: `DELETE /api/links/:id`
|
||||||
|
- Payload: None
|
||||||
|
- Headers: `X-Api-Key`
|
||||||
|
- Response Example:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Link deleted"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
+147
@@ -0,0 +1,147 @@
|
|||||||
|
## CLI
|
||||||
|
|
||||||
|
```
|
||||||
|
Usage: ./cli [options]
|
||||||
|
Options:
|
||||||
|
--create-user=NAME Create a new user with the given name
|
||||||
|
--list-users List all users
|
||||||
|
--delete-user=USER_ID Delete a user by ID
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run It Anywhere
|
||||||
|
|
||||||
|
### Docker Compose
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up
|
||||||
|
|
||||||
|
# Optional: Generate an api key
|
||||||
|
# docker-compose exec -it app cli --create-user=Admin
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker CLI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run \
|
||||||
|
--name bit \
|
||||||
|
-p 4000:4000 \
|
||||||
|
-e ENV="production" \
|
||||||
|
-e DATABASE_URL="sqlite3://./sqlite/data.db?journal_mode=wal&synchronous=normal&foreign_keys=true" \
|
||||||
|
-e APP_URL="http://localhost:4000" \
|
||||||
|
-e ADMIN_NAME="Admin" \
|
||||||
|
-e ADMIN_API_KEY=$(openssl rand -base64 32) \
|
||||||
|
sjdonado/bit
|
||||||
|
|
||||||
|
# Optional: Generate an api key
|
||||||
|
# docker exec -it bit cli --create-user=Admin
|
||||||
|
```
|
||||||
|
|
||||||
|
### Self-Hosted with Dokku
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
FROM sjdonado/bit
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dokku apps:create bit
|
||||||
|
|
||||||
|
dokku domains:set bit bit.donado.co
|
||||||
|
dokku letsencrypt:enable bit
|
||||||
|
|
||||||
|
dokku storage:ensure-directory bit-sqlite
|
||||||
|
dokku storage:mount bit /var/lib/dokku/data/storage/bit-sqlite:/usr/src/app/sqlite/
|
||||||
|
|
||||||
|
dokku config:set bit DATABASE_URL="sqlite3://./sqlite/data.db?journal_mode=wal&synchronous=normal&foreign_keys=true" APP_URL=https://bit.donado.co ADMIN_NAME=Admin ADMIN_API_KEY=$(openssl rand -base64 32)
|
||||||
|
|
||||||
|
dokku ports:add bit http:80:4000
|
||||||
|
dokku ports:add bit https:443:4000
|
||||||
|
|
||||||
|
# Optional: Generate an api key
|
||||||
|
# dokku run bit cli --create-user=Admin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Local Development
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
- Crystal 1.12+
|
||||||
|
- Shards package manager
|
||||||
|
- SQLite3
|
||||||
|
|
||||||
|
### Install Dependencies
|
||||||
|
- linux
|
||||||
|
```bash
|
||||||
|
sudo apt-get update && sudo apt-get install -y crystal libssl-dev libsqlite3-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
- macos
|
||||||
|
```bash
|
||||||
|
brew tap amberframework/micrate
|
||||||
|
brew install micrate
|
||||||
|
```
|
||||||
|
|
||||||
|
### Install Shards and Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
shards run bit
|
||||||
|
```
|
||||||
|
|
||||||
|
- Generate the `X-Api-Key`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
shards run cli -- --create-user=Admin
|
||||||
|
```
|
||||||
|
|
||||||
|
- Run tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ENV=test crystal spec
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benchmark
|
||||||
|
|
||||||
|
Conducted on a MacBook Air M2 with 16GB RAM.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./benchmark.sh
|
||||||
|
Setting up...
|
||||||
|
[+] Running 3/3
|
||||||
|
✔ Network bit_default Created 0.0s
|
||||||
|
✔ Volume "bit_sqlite_data" Created 0.0s
|
||||||
|
✔ Container bit Started 0.1s
|
||||||
|
Captured API Key: aHOCnZSuo2kOHy2mDa-iOA
|
||||||
|
Waiting for the application to be ready...
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Connection: keep-alive
|
||||||
|
Content-Type: application/json
|
||||||
|
Date: Sun, 27 Oct 2024 11:52:33 GMT
|
||||||
|
Access-Control-Allow-Origin: *
|
||||||
|
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
|
||||||
|
Access-Control-Allow-Headers: Content-Type, Accept, Origin, X-Api-Key
|
||||||
|
Content-Length: 13
|
||||||
|
|
||||||
|
Starting resource usage monitoring...
|
||||||
|
Creating 10000 short links with 100 conrurrent requests...
|
||||||
|
Link creation complete: 10000 links created.
|
||||||
|
Fetching all created links from /api/links...
|
||||||
|
Selected link for benchmarking: http://localhost:4000/UaVZjA
|
||||||
|
Starting benchmark with Bombardier...
|
||||||
|
Bombarding http://localhost:4000/oEKLAg with 10000 request(s) using 100 connection(s)
|
||||||
|
10000 / 10000 [===============================================================================] 100.00% 830/s 12s
|
||||||
|
Done!
|
||||||
|
Statistics Avg Stdev Max
|
||||||
|
Reqs/sec 853.89 1625.49 8942.54
|
||||||
|
Latency 118.48ms 11.52ms 142.58ms
|
||||||
|
HTTP codes:
|
||||||
|
1xx - 0, 2xx - 0, 3xx - 10000, 4xx - 0, 5xx - 0
|
||||||
|
others - 0
|
||||||
|
Throughput: 360.02KB/s
|
||||||
|
Benchmark completed.
|
||||||
|
Analyzing resource usage...
|
||||||
|
**** Results ****
|
||||||
|
Average CPU Usage: 40.68%
|
||||||
|
Average Memory Usage: 28.62 MiB
|
||||||
|
./benchmark.sh: line 135: 61567 Terminated: 15 monitor_resource_usage
|
||||||
|
[+] Running 2/2
|
||||||
|
✔ Container bit Removed 10.1s
|
||||||
|
✔ Network bit_default Removed
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user