Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a85d5a8c73 | |||
| 80cebe3357 | |||
| 451a5fbf0f | |||
| aeb6d1164b | |||
| 2f14cd82dd | |||
| faedd0bc7a | |||
| 1d207fae64 | |||
| a71f345f66 | |||
| 7cc6c1197f | |||
| 115bbf7366 |
@@ -1,6 +1,5 @@
|
|||||||
.git
|
.git
|
||||||
/bin/
|
/bin/
|
||||||
/.shards/
|
/.shards/
|
||||||
/bruno/
|
|
||||||
/spec/
|
/spec/
|
||||||
/sqlite/
|
/sqlite/
|
||||||
|
|||||||
@@ -39,6 +39,15 @@ jobs:
|
|||||||
VERSION=$(echo $VERSION | tr -d '\n\r')
|
VERSION=$(echo $VERSION | tr -d '\n\r')
|
||||||
echo "RELEASE_TAG=$VERSION" >> $GITHUB_ENV
|
echo "RELEASE_TAG=$VERSION" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Set tags
|
||||||
|
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
|
- name: Build and push image
|
||||||
id: push
|
id: push
|
||||||
uses: docker/build-push-action@v5.0.0
|
uses: docker/build-push-action@v5.0.0
|
||||||
@@ -46,9 +55,7 @@ jobs:
|
|||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
tags: |
|
tags: sjdonado/bit:${{ env.TAGS }}
|
||||||
sjdonado/bit:latest
|
|
||||||
${{ github.event_name == 'release' && env.RELEASE_TAG && 'sjdonado/bit:${{ env.RELEASE_TAG }}' || '' }}
|
|
||||||
|
|
||||||
- name: Attest
|
- name: Attest
|
||||||
uses: actions/attest-build-provenance@v1
|
uses: actions/attest-build-provenance@v1
|
||||||
|
|||||||
+19
-9
@@ -1,5 +1,6 @@
|
|||||||
FROM alpine:edge as base
|
FROM alpine:edge AS build
|
||||||
|
|
||||||
|
ENV ENV=production
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
RUN apk update && apk add --no-cache \
|
RUN apk update && apk add --no-cache \
|
||||||
@@ -9,20 +10,29 @@ RUN apk update && apk add --no-cache \
|
|||||||
sqlite-dev \
|
sqlite-dev \
|
||||||
openssl-dev
|
openssl-dev
|
||||||
|
|
||||||
FROM base AS build
|
COPY . .
|
||||||
ENV ENV=production
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
RUN shards install
|
RUN shards install
|
||||||
RUN shards build --progress
|
RUN shards build --release --no-debug
|
||||||
|
|
||||||
|
FROM alpine:edge AS runtime
|
||||||
|
|
||||||
|
ENV ENV=production
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
gc \
|
||||||
|
pcre2 \
|
||||||
|
libevent \
|
||||||
|
yaml \
|
||||||
|
sqlite-libs \
|
||||||
|
openssl
|
||||||
|
|
||||||
|
RUN mkdir -p sqlite
|
||||||
|
|
||||||
FROM base AS release
|
|
||||||
RUN mkdir -p /usr/src/app/sqlite
|
|
||||||
COPY --from=build /usr/src/app/db db
|
COPY --from=build /usr/src/app/db db
|
||||||
COPY --from=build /usr/src/app/data data
|
COPY --from=build /usr/src/app/data data
|
||||||
COPY --from=build /usr/src/app/bin /usr/local/bin
|
COPY --from=build /usr/src/app/bin /usr/local/bin
|
||||||
COPY --from=build /usr/src/app/data /usr/local/data
|
|
||||||
|
|
||||||
EXPOSE 4000/tcp
|
EXPOSE 4000/tcp
|
||||||
CMD ["bit"]
|
CMD ["bit"]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[](https://hub.docker.com/repository/docker/sjdonado/bit/general)
|
[](https://hub.docker.com/repository/docker/sjdonado/bit)
|
||||||
[](https://hub.docker.com/repository/docker/sjdonado/bit/general)
|
[](https://hub.docker.com/repository/docker/sjdonado/bit)
|
||||||
[](https://hub.docker.com/repository/docker/sjdonado/bit/general)
|
[](https://hub.docker.com/repository/docker/sjdonado/bit)
|
||||||
|
|
||||||
# Benchmark
|
# Benchmark
|
||||||
|
|
||||||
@@ -39,16 +39,16 @@ Average Response Time: 12.37 µs
|
|||||||
|
|
||||||
# Self-hosted
|
# Self-hosted
|
||||||
|
|
||||||
- Run via docker-compose
|
## Run via docker-compose
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker-compose up
|
docker-compose up
|
||||||
|
|
||||||
docker-compose exec -it app migrate
|
# Generate an api key
|
||||||
docker-compose exec -it app cli --create-user=Admin
|
docker-compose exec -it app cli --create-user=Admin
|
||||||
```
|
```
|
||||||
|
|
||||||
- Run via docker cli
|
## Run via docker cli
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run \
|
docker run \
|
||||||
@@ -59,11 +59,10 @@ docker run \
|
|||||||
-e APP_URL="http://localhost:4000" \
|
-e APP_URL="http://localhost:4000" \
|
||||||
sjdonado/bit
|
sjdonado/bit
|
||||||
|
|
||||||
docker exec -it bit migrate
|
|
||||||
docker exec -it bit cli --create-user=Admin
|
docker exec -it bit cli --create-user=Admin
|
||||||
```
|
```
|
||||||
|
|
||||||
- Dokku
|
## Dokku
|
||||||
|
|
||||||
```dockerfile
|
```dockerfile
|
||||||
FROM sjdonado/bit
|
FROM sjdonado/bit
|
||||||
@@ -83,7 +82,6 @@ dokku config:set bit DATABASE_URL="sqlite3://./sqlite/data.db?journal_mode=wal&s
|
|||||||
dokku ports:add bit http:80:4000
|
dokku ports:add bit http:80:4000
|
||||||
dokku ports:add bit https:443:4000
|
dokku ports:add bit https:443:4000
|
||||||
|
|
||||||
dokku run bit migrate
|
|
||||||
dokku run bit cli --create-user=Admin
|
dokku run bit cli --create-user=Admin
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -93,11 +91,9 @@ dokku run bit cli --create-user=Admin
|
|||||||
|
|
||||||
1. **Ping the API**
|
1. **Ping the API**
|
||||||
|
|
||||||
- **Endpoint**: `/api/ping`
|
- Endpoint: `GET /api/ping`
|
||||||
- **HTTP Method**: GET
|
- Payload: None
|
||||||
- **Description**: Ping the API to check if it's running
|
- Response Example
|
||||||
- **Payload**: -
|
|
||||||
- **Response Example**:
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"message": "pong"
|
"message": "pong"
|
||||||
@@ -106,12 +102,10 @@ dokku run bit cli --create-user=Admin
|
|||||||
|
|
||||||
2. **Retrieve a link by its slug**
|
2. **Retrieve a link by its slug**
|
||||||
|
|
||||||
- **Endpoint**: `/:slug`
|
- Endpoint: `GET /:slug`
|
||||||
- **HTTP Method**: GET
|
- Headers: `X-Api-Key`
|
||||||
- **Description**: Retrieve a link by its slug
|
- Payload: None
|
||||||
- **Payload**: -
|
- Response Example
|
||||||
- **Headers**: `X-Api-Key`
|
|
||||||
- **Response Example**:
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
@@ -135,12 +129,10 @@ dokku run bit cli --create-user=Admin
|
|||||||
|
|
||||||
3. **Retrieve all links**
|
3. **Retrieve all links**
|
||||||
|
|
||||||
- **Endpoint**: `/api/links`
|
- Endpoint: `GET /api/links`
|
||||||
- **HTTP Method**: GET
|
- Headers: `X-Api-Key`
|
||||||
- **Description**: Retrieve all links
|
- Payload: None
|
||||||
- **Payload**: -
|
- Response Example
|
||||||
- **Headers**: `X-Api-Key`
|
|
||||||
- **Response Example**:
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"data": [
|
"data": [
|
||||||
@@ -166,12 +158,10 @@ dokku run bit cli --create-user=Admin
|
|||||||
|
|
||||||
4. **Retrieve a link by its ID**
|
4. **Retrieve a link by its ID**
|
||||||
|
|
||||||
- **Endpoint**: `/api/links/:id`
|
- Endpoint: `GET /api/links/:id`
|
||||||
- **HTTP Method**: GET
|
- Headers: `X-Api-Key`
|
||||||
- **Description**: Retrieve a link by its ID
|
- Payload: None
|
||||||
- **Payload**: -
|
- Response Example
|
||||||
- **Headers**: `X-Api-Key`
|
|
||||||
- **Response Example**:
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
@@ -195,17 +185,15 @@ dokku run bit cli --create-user=Admin
|
|||||||
|
|
||||||
5. **Create a new link**
|
5. **Create a new link**
|
||||||
|
|
||||||
- **Endpoint**: `/api/links`
|
- Endpoint\*\*: `POST /api/links`
|
||||||
- **HTTP Method**: POST
|
- Payload:
|
||||||
- **Description**: Create a new link
|
|
||||||
- **Payload**:
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"url": "https://example.com"
|
"url": "https://example.com"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- **Headers**: `X-Api-Key`
|
- Headers: `X-Api-Key`
|
||||||
- **Response Example**:
|
- Response Example:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
@@ -219,17 +207,15 @@ dokku run bit cli --create-user=Admin
|
|||||||
|
|
||||||
6. **Update an existing link by its ID**
|
6. **Update an existing link by its ID**
|
||||||
|
|
||||||
- **Endpoint**: `/api/links/:id`
|
- Endpoint: `PUT /api/links/:id`
|
||||||
- **HTTP Method**: PUT
|
- Payload:
|
||||||
- **Description**: Update an existing link by its ID
|
|
||||||
- **Payload**:
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"url": "https://newexample.com"
|
"url": "https://newexample.com"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- **Headers**: `X-Api-Key`
|
- Headers: `X-Api-Key`
|
||||||
- **Response Example**:
|
- Response Example:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
@@ -243,12 +229,10 @@ dokku run bit cli --create-user=Admin
|
|||||||
|
|
||||||
7. **Delete a link by its ID**
|
7. **Delete a link by its ID**
|
||||||
|
|
||||||
- **Endpoint**: `/api/links/:id`
|
- Endpoint: `DELETE /api/links/:id`
|
||||||
- **HTTP Method**: DELETE
|
- Payload: None
|
||||||
- **Description**: Delete a link by its ID
|
- Headers: `X-Api-Key`
|
||||||
- **Payload**: -
|
- Response Example:
|
||||||
- **Headers**: `X-Api-Key`
|
|
||||||
- **Response Example**:
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"message": "Link deleted"
|
"message": "Link deleted"
|
||||||
@@ -267,7 +251,7 @@ Options:
|
|||||||
|
|
||||||
# Development
|
# Development
|
||||||
|
|
||||||
1. **Installation**
|
## Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
brew tap amberframework/micrate
|
brew tap amberframework/micrate
|
||||||
@@ -275,23 +259,22 @@ brew install micrate
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
shards run migrate
|
|
||||||
shards run bit
|
shards run bit
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Generate the `X-Api-Key`**
|
## Generate the `X-Api-Key`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
shards run cli -- --create-user=Admin
|
shards run cli -- --create-user=Admin
|
||||||
```
|
```
|
||||||
|
|
||||||
# Run tests
|
## Run tests
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ENV=test crystal spec
|
ENV=test crystal spec
|
||||||
```
|
```
|
||||||
|
|
||||||
# Contributing
|
## Contributing
|
||||||
|
|
||||||
1. Fork it (<https://github.com/sjdonado/bit/fork>)
|
1. Fork it (<https://github.com/sjdonado/bit/fork>)
|
||||||
2. Create your feature branch (`git checkout -b my-new-feature`)
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
|
require "log"
|
||||||
|
|
||||||
ENV["ENV"] ||= "development"
|
ENV["ENV"] ||= "development"
|
||||||
|
ENV["APP_URL"] ||= "http://localhost:4000"
|
||||||
|
ENV["DATABASE_URL"] ||= "sqlite3://./sqlite/data.db?journal_mode=wal&synchronous=normal&foreign_keys=true
|
||||||
|
"
|
||||||
|
|
||||||
{% if env("ENV") != "production" %}
|
{% if env("ENV") != "production" %}
|
||||||
require "dotenv"
|
require "dotenv"
|
||||||
Dotenv.load ".env.#{ENV["ENV"]}" # File must exist in non-production!
|
Dotenv.load ".env.#{ENV["ENV"]}" # File must exist in non-production!
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
|
{% if env("ENV") == "production" %}
|
||||||
|
Log.setup(:error)
|
||||||
|
{% end %}
|
||||||
|
|||||||
@@ -27,12 +27,14 @@ module App::Controllers::Link
|
|||||||
link.url = url
|
link.url = url
|
||||||
link.user = user
|
link.user = user
|
||||||
|
|
||||||
|
attempts = 0
|
||||||
loop do
|
loop do
|
||||||
slug = Random::Secure.urlsafe_base64(4).gsub(/[^a-zA-Z0-9]/, "")
|
slug = Random::Secure.urlsafe_base64(attempts >= 2 ? 6 : 5).gsub(/[^a-zA-Z0-9]/, "")
|
||||||
if !Database.get_by(Link, slug: slug)
|
unless Database.get_by(Link, slug: slug)
|
||||||
link.slug = slug
|
link.slug = slug
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
attempts += 1
|
||||||
end
|
end
|
||||||
|
|
||||||
changeset = Database.insert(link)
|
changeset = Database.insert(link)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
require "sqlite3"
|
require "sqlite3"
|
||||||
require "crecto"
|
require "crecto"
|
||||||
|
require"micrate"
|
||||||
|
|
||||||
module App::Lib
|
module App::Lib
|
||||||
class Database
|
class Database
|
||||||
@@ -14,5 +15,12 @@ module App::Lib
|
|||||||
if ENV["ENV"] == "development"
|
if ENV["ENV"] == "development"
|
||||||
Crecto::DbLogger.set_handler(STDOUT)
|
Crecto::DbLogger.set_handler(STDOUT)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.run_migrations
|
||||||
|
Micrate::DB.connection_url = ENV["DATABASE_URL"]
|
||||||
|
Micrate::Cli.run_up
|
||||||
|
end
|
||||||
|
|
||||||
|
run_migrations
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
require "kemal"
|
require "kemal"
|
||||||
|
|
||||||
module App
|
module App
|
||||||
|
class InternalServerErrorException < Kemal::Exceptions::CustomException
|
||||||
|
def initialize(context)
|
||||||
|
context.response.content_type = "application/json"
|
||||||
|
context.response.status_code = 500
|
||||||
|
context.response.print({ "error" => "Internal Server Error" }.to_json)
|
||||||
|
super(context)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class BadRequestException < Kemal::Exceptions::CustomException
|
class BadRequestException < Kemal::Exceptions::CustomException
|
||||||
def initialize(context, message : String)
|
def initialize(context, message : String)
|
||||||
|
context.response.content_type = "application/json"
|
||||||
context.response.status_code = 400
|
context.response.status_code = 400
|
||||||
context.response.print({ "error" => message }.to_json)
|
context.response.print({ "error" => message }.to_json)
|
||||||
super(context)
|
super(context)
|
||||||
@@ -11,13 +21,16 @@ module App
|
|||||||
|
|
||||||
class UnauthorizedException < Kemal::Exceptions::CustomException
|
class UnauthorizedException < Kemal::Exceptions::CustomException
|
||||||
def initialize(context)
|
def initialize(context)
|
||||||
|
context.response.content_type = "application/json"
|
||||||
context.response.status_code = 401
|
context.response.status_code = 401
|
||||||
|
context.response.print({ "error" => "Unauthorized access" }.to_json)
|
||||||
super(context)
|
super(context)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class ForbiddenException < Kemal::Exceptions::CustomException
|
class ForbiddenException < Kemal::Exceptions::CustomException
|
||||||
def initialize(context)
|
def initialize(context)
|
||||||
|
context.response.content_type = "application/json"
|
||||||
context.response.status_code = 403
|
context.response.status_code = 403
|
||||||
context.response.print({ "error" => "Access not allowed" }.to_json)
|
context.response.print({ "error" => "Access not allowed" }.to_json)
|
||||||
super(context)
|
super(context)
|
||||||
@@ -26,16 +39,28 @@ module App
|
|||||||
|
|
||||||
class NotFoundException < Kemal::Exceptions::CustomException
|
class NotFoundException < Kemal::Exceptions::CustomException
|
||||||
def initialize(context)
|
def initialize(context)
|
||||||
|
context.response.content_type = "application/json"
|
||||||
context.response.status_code = 404
|
context.response.status_code = 404
|
||||||
|
context.response.print({ "error" => "Resource not found" }.to_json)
|
||||||
super(context)
|
super(context)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class UnprocessableEntityException < Kemal::Exceptions::CustomException
|
class UnprocessableEntityException < Kemal::Exceptions::CustomException
|
||||||
def initialize(context, message : Hash(String, Array(String)))
|
def initialize(context, message : Hash(String, Array(String)))
|
||||||
|
context.response.content_type = "application/json"
|
||||||
context.response.status_code = 422
|
context.response.status_code = 422
|
||||||
context.response.print({ "errors" => message }.to_json)
|
context.response.print({ "errors" => message }.to_json)
|
||||||
super(context)
|
super(context)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
error 500 do |env|
|
||||||
|
App::InternalServerErrorException.new(env)
|
||||||
|
""
|
||||||
|
end
|
||||||
|
|
||||||
|
error 404 do |env|
|
||||||
|
""
|
||||||
|
end
|
||||||
|
|||||||
+1
-1
@@ -17,6 +17,6 @@ module App::Models
|
|||||||
unique_constraint :slug
|
unique_constraint :slug
|
||||||
|
|
||||||
validate_required [:slug, :url]
|
validate_required [:slug, :url]
|
||||||
validate_format :url, /\A(?:https?:\/\/)?(?:[\w-]+\.)+[\w-]+(?:\/\S*)?/
|
validate_format :url, /\Ahttps?:\/\/(?:[\w.-]+)(?::\d+)?(?:[\/?#]\S*)?\z/i
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ module App
|
|||||||
env.response.headers["Access-Control-Allow-Origin"] = "*"
|
env.response.headers["Access-Control-Allow-Origin"] = "*"
|
||||||
env.response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
|
env.response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
|
||||||
env.response.headers["Access-Control-Allow-Headers"] = "Content-Type, Accept, Origin, X-Api-Key"
|
env.response.headers["Access-Control-Allow-Headers"] = "Content-Type, Accept, Origin, X-Api-Key"
|
||||||
|
env.response.headers.delete("X-Powered-By")
|
||||||
end
|
end
|
||||||
|
|
||||||
after_all do |env|
|
after_all do |env|
|
||||||
|
|||||||
@@ -134,12 +134,6 @@ if [ $? -ne 0 ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
docker-compose exec -T app migrate
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "Failed to run database migrations."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create a new user and capture the API key
|
# Create a new user and capture the API key
|
||||||
output=$(docker-compose exec -T app cli --create-user=Admin)
|
output=$(docker-compose exec -T app cli --create-user=Admin)
|
||||||
api_key=$(echo "$output" | awk -F' ' '/X-Api-Key:/{print $NF}')
|
api_key=$(echo "$output" | awk -F' ' '/X-Api-Key:/{print $NF}')
|
||||||
|
|||||||
@@ -11,8 +11,4 @@ require "./app/routes"
|
|||||||
add_context_storage_type(App::Models::User)
|
add_context_storage_type(App::Models::User)
|
||||||
add_handler(App::Middlewares::Auth.new)
|
add_handler(App::Middlewares::Auth.new)
|
||||||
|
|
||||||
error 500 { |env| {"error" => "Internal Server Error" }.to_json}
|
|
||||||
error 401 { |env| {"error" => "Unauthorized" }.to_json}
|
|
||||||
error 404 { |env| {"error" => "Not Found" }.to_json}
|
|
||||||
|
|
||||||
Kemal.run
|
Kemal.run
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
-- +micrate Up
|
||||||
|
-- SQL in section 'Up' is executed when this migration is applied
|
||||||
|
|
||||||
|
-- Step 1: Create a new table with the desired column type
|
||||||
|
CREATE TABLE links_new (
|
||||||
|
id TEXT PRIMARY KEY NOT NULL,
|
||||||
|
user_id TEXT NOT NULL,
|
||||||
|
slug VARCHAR(8) UNIQUE NOT NULL,
|
||||||
|
url TEXT NOT NULL,
|
||||||
|
created_at INTEGER DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
updated_at INTEGER DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Step 2: Copy data from the old table to the new table
|
||||||
|
INSERT INTO links_new (id, user_id, slug, url, created_at, updated_at)
|
||||||
|
SELECT id, user_id, slug, url, created_at, updated_at FROM links;
|
||||||
|
|
||||||
|
-- Step 3: Drop the old table
|
||||||
|
DROP TABLE links;
|
||||||
|
|
||||||
|
-- Step 4: Rename the new table to the old table's name
|
||||||
|
ALTER TABLE links_new RENAME TO links;
|
||||||
|
|
||||||
|
-- +micrate Down
|
||||||
|
-- SQL section 'Down' is executed when this migration is rolled back
|
||||||
|
|
||||||
|
-- Step 1: Create a new table with the original column type
|
||||||
|
CREATE TABLE links_old (
|
||||||
|
id TEXT PRIMARY KEY NOT NULL,
|
||||||
|
user_id TEXT NOT NULL,
|
||||||
|
slug VARCHAR(4) UNIQUE NOT NULL,
|
||||||
|
url TEXT NOT NULL,
|
||||||
|
created_at INTEGER DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
updated_at INTEGER DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Step 2: Copy data from the current table to the old table
|
||||||
|
INSERT INTO links_old (id, user_id, slug, url, created_at, updated_at)
|
||||||
|
SELECT id, user_id, substr(slug, 1, 4), url, created_at, updated_at FROM links;
|
||||||
|
|
||||||
|
-- Step 3: Drop the current table
|
||||||
|
DROP TABLE links;
|
||||||
|
|
||||||
|
-- Step 4: Rename the old table to the current table's name
|
||||||
|
ALTER TABLE links_old RENAME TO links;
|
||||||
+6
-3
@@ -3,7 +3,10 @@ services:
|
|||||||
build: .
|
build: .
|
||||||
environment:
|
environment:
|
||||||
ENV: production
|
ENV: production
|
||||||
DATABASE_URL: sqlite3://./sqlite/data.db?journal_mode=wal&synchronous=normal&foreign_keys=true
|
|
||||||
APP_URL: http://0.0.0.0:4001
|
|
||||||
ports:
|
ports:
|
||||||
- 4001:4000
|
- 4000:4000
|
||||||
|
volumes:
|
||||||
|
- sqlite_data:/app/sqlite
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
sqlite_data:
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
require "sqlite3"
|
|
||||||
require"micrate"
|
|
||||||
|
|
||||||
require "../app/config/env"
|
|
||||||
|
|
||||||
Micrate::DB.connection_url = ENV["DATABASE_URL"]
|
|
||||||
Micrate::Cli.run_up
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
name: bit
|
name: bit
|
||||||
version: 1.1.0
|
version: 1.2.0
|
||||||
|
|
||||||
authors:
|
authors:
|
||||||
- Juan Rodriguez <sjdonado@icloud.com>
|
- Juan Rodriguez <sjdonado@icloud.com>
|
||||||
@@ -9,8 +9,6 @@ targets:
|
|||||||
main: bit.cr
|
main: bit.cr
|
||||||
cli:
|
cli:
|
||||||
main: scripts/cli.cr
|
main: scripts/cli.cr
|
||||||
migrate:
|
|
||||||
main: scripts/migrate.cr
|
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
kemal:
|
kemal:
|
||||||
|
|||||||
Reference in New Issue
Block a user