Ported the project from sqlite to postgres
This commit is contained in:
parent
d397b5c1ed
commit
29c1fc8f82
23 changed files with 566 additions and 409 deletions
|
@ -25,6 +25,9 @@ RUN cargo build --target x86_64-unknown-linux-musl --release
|
||||||
RUN rm src/*.rs
|
RUN rm src/*.rs
|
||||||
ADD server /build-container
|
ADD server /build-container
|
||||||
|
|
||||||
|
# Make sure sqlx reads from .sqlx directory
|
||||||
|
ENV SQLX_OFFLINE true
|
||||||
|
|
||||||
RUN cargo build --target x86_64-unknown-linux-musl --release
|
RUN cargo build --target x86_64-unknown-linux-musl --release
|
||||||
|
|
||||||
# Final stage, copy the server binary and the frontend build
|
# Final stage, copy the server binary and the frontend build
|
||||||
|
|
|
@ -22,6 +22,9 @@ RUN cargo build --target x86_64-unknown-linux-musl
|
||||||
RUN rm src/*.rs
|
RUN rm src/*.rs
|
||||||
ADD server /build-container
|
ADD server /build-container
|
||||||
|
|
||||||
|
# Make sure sqlx reads from .sqlx directory
|
||||||
|
ENV SQLX_OFFLINE true
|
||||||
|
|
||||||
# Note that '--release' is missing here, so we build in debug mode
|
# Note that '--release' is missing here, so we build in debug mode
|
||||||
RUN cargo build --target x86_64-unknown-linux-musl
|
RUN cargo build --target x86_64-unknown-linux-musl
|
||||||
|
|
||||||
|
|
72
justfile
72
justfile
|
@ -1,76 +1,60 @@
|
||||||
runtime := "podman"
|
|
||||||
|
|
||||||
# Builds a debug container and runs it
|
# Builds a debug container and runs it
|
||||||
dev: start-debug
|
dev: start-debug
|
||||||
@echo "Cd into client and run 'npm run dev' to start the client in dev mode."
|
@echo "Cd into client and run 'npm run dev' to start the client in dev mode."
|
||||||
|
|
||||||
[private]
|
|
||||||
npm-install directory:
|
|
||||||
cd {{directory}} && npm install
|
|
||||||
|
|
||||||
# Builds the client with npm (result in client/dist)
|
|
||||||
[private]
|
|
||||||
npm-build directory: (npm-install directory)
|
|
||||||
cd {{directory}} && npm run build
|
|
||||||
@echo "Built client at {{directory}}/dist"
|
|
||||||
|
|
||||||
# Builds a debug container
|
# Builds a debug container
|
||||||
[private]
|
[private]
|
||||||
build-container-server-debug:
|
build-container-server-debug:
|
||||||
{{runtime}} build -t fb-server-debug -f container/ContainerfileDebug .
|
podman build -t fb-server-debug -f container/ContainerfileDebug .
|
||||||
|
|
||||||
# Builds a debug container and runs it
|
# Builds a debug container and runs it
|
||||||
[private]
|
[private]
|
||||||
start-debug: build-container-server-debug remove-podman-containers
|
start-debug: start-postgres-dev clean-podman init-sqlx build-container-server-debug
|
||||||
{{runtime}} run -d -e DATABASE_URL=sqlite:debug.db -p 8080:8080 --name frostbyte-debug fb-server-debug
|
podman network create fb_network --ignore
|
||||||
|
podman run -d --network fb_network -e DATABASE_URL=postgres://postgres:password@postgres:5432/frostbyte -p 8080:8080 --name frostbyte-debug fb-server-debug
|
||||||
@echo "Debug server started."
|
@echo "Debug server started."
|
||||||
|
|
||||||
# Builds a release container
|
# Builds a release container
|
||||||
[private]
|
[private]
|
||||||
build-container-release:
|
build-container-release:
|
||||||
{{runtime}} build -t fb-server -f container/Containerfile .
|
podman build -t fb-server -f container/Containerfile .
|
||||||
|
|
||||||
# Builds a release container and runs it
|
# Builds a release container and runs it
|
||||||
start-release: build-container-release remove-podman-containers
|
start-release: start-postgres-dev clean-podman init-sqlx build-container-release
|
||||||
{{runtime}} network create fb_network --ignore
|
podman network create fb_network --ignore
|
||||||
{{runtime}} run -d --network fb_network -e DATABASE_URL=sqlite:release.db -p 8080:8080 --name frostbyte fb-server
|
podman run -d --network fb_network -e DATABASE_URL=postgres://postgres:password@postgres:5432/frostbyte -p 8080:8080 --name frostbyte fb-server
|
||||||
|
|
||||||
|
# Initializes the database, runs migrations and then prepares sqlx
|
||||||
init-sqlx:
|
init-sqlx:
|
||||||
echo "DATABASE_URL=sqlite:debug.db" > server/.env
|
echo "DATABASE_URL=postgres://postgres:password@localhost:5432/frostbyte" > server/.env
|
||||||
cd server && sqlx database create
|
cd server && sqlx database create --connect-timeout 40 # Postgres takes a while to start up
|
||||||
cd server && sqlx migrate run
|
cd server && sqlx migrate run --source migrations_pg
|
||||||
cd server && cargo sqlx prepare
|
cd server && cargo sqlx prepare
|
||||||
|
|
||||||
# Removes and stops any containers related to the project
|
# Starts a postgres container for development
|
||||||
[private]
|
[private]
|
||||||
remove-podman-containers:
|
start-postgres-dev:
|
||||||
{{runtime}} network rm -f fb_network
|
podman rm -f postgres
|
||||||
{{runtime}} container rm -f frostbyte
|
podman run --network fb_network --name postgres -e POSTGRES_PASSWORD=password -d -p 5432:5432 docker.io/postgres:16.1-alpine
|
||||||
{{runtime}} container rm -f frostbyte-debug
|
|
||||||
|
|
||||||
# Deletes everything podman related (even unrelated to the project)
|
# Forcefully stops and removes the frostbyte container
|
||||||
[private]
|
[private]
|
||||||
prune-podman:
|
clean-podman:
|
||||||
{{runtime}} stop -a
|
podman container rm -f frostbyte
|
||||||
{{runtime}} rm -af
|
podman container rm -f frostbyte-debug
|
||||||
{{runtime}} image rm -af
|
|
||||||
{{runtime}} system prune -af
|
# Forcefully removes the frostbyte images
|
||||||
{{runtime}} system reset --force
|
[private]
|
||||||
|
clean-images:
|
||||||
|
podman image rm -f fb-server
|
||||||
|
podman image rm -f fb-server-debug
|
||||||
|
|
||||||
# Cleans up everything related to the project
|
# Cleans up everything related to the project
|
||||||
clean:
|
clean: clean-podman clean-images
|
||||||
{{runtime}} container rm -f frostbyte
|
|
||||||
{{runtime}} container rm -f frostbyte-debug
|
|
||||||
{{runtime}} image rm -f fb-server
|
|
||||||
{{runtime}} image rm -f fb-server-debug
|
|
||||||
rm -rf client/dist
|
rm -rf client/dist
|
||||||
rm -rf client/node_modules
|
rm -rf client/node_modules
|
||||||
rm -rf client-solid/dist
|
rm -rf client-solid/dist
|
||||||
rm -rf client-solid/node_modules
|
rm -rf client-solid/node_modules
|
||||||
rm -rf server/public
|
rm -rf server/public
|
||||||
rm -rf server/target
|
rm -rf server/target
|
||||||
@echo "Cleaned up! Make sure to run 'just nuke' to nuke everything podman related."
|
@echo "Cleaned up! Make sure to clean up podman volumes and networks."
|
||||||
|
|
||||||
# Nukes everything. No mercy. Leave no trace.
|
|
||||||
nuke: clean prune-podman
|
|
||||||
@echo "Nuked everything! You're starting from scratch now."
|
|
|
@ -1,20 +1,22 @@
|
||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "PostgreSQL",
|
||||||
"query": "SELECT username FROM users WHERE username = ?",
|
"query": "SELECT username FROM users WHERE username = $1",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
"name": "username",
|
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
|
"name": "username",
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Right": 1
|
"Left": [
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"nullable": [
|
"nullable": [
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "fdbb1cd2873a5c866faaf3a60185d1f98e14198655aa536c2227ef8d2c6b88e1"
|
"hash": "16e84d577155f3c47fcb736bbad4dcaf05b21c79d47fe008e209191157f5697e"
|
||||||
}
|
}
|
|
@ -1,46 +1,46 @@
|
||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "PostgreSQL",
|
||||||
"query": "SELECT * FROM posts WHERE id = (SELECT MAX(id) FROM posts)",
|
"query": "SELECT * FROM posts WHERE id = (SELECT MAX(id) FROM posts)",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
"name": "id",
|
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Int64"
|
"name": "id",
|
||||||
|
"type_info": "Int8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "user_id",
|
|
||||||
"ordinal": 1,
|
"ordinal": 1,
|
||||||
"type_info": "Int64"
|
"name": "user_id",
|
||||||
|
"type_info": "Int8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "content",
|
|
||||||
"ordinal": 2,
|
"ordinal": 2,
|
||||||
|
"name": "content",
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "upvotes",
|
|
||||||
"ordinal": 3,
|
"ordinal": 3,
|
||||||
"type_info": "Int64"
|
"name": "upvotes",
|
||||||
|
"type_info": "Int4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "downvotes",
|
|
||||||
"ordinal": 4,
|
"ordinal": 4,
|
||||||
"type_info": "Int64"
|
"name": "downvotes",
|
||||||
|
"type_info": "Int4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "created_at",
|
|
||||||
"ordinal": 5,
|
"ordinal": 5,
|
||||||
"type_info": "Datetime"
|
"name": "created_at",
|
||||||
|
"type_info": "Timestamp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "updated_at",
|
|
||||||
"ordinal": 6,
|
"ordinal": 6,
|
||||||
"type_info": "Datetime"
|
"name": "updated_at",
|
||||||
|
"type_info": "Timestamp"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Right": 0
|
"Left": []
|
||||||
},
|
},
|
||||||
"nullable": [
|
"nullable": [
|
||||||
false,
|
false,
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "SELECT id FROM users WHERE username = ?",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Int64"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 1
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
false
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"hash": "2e61cd30a6cd3e0937dd096b4f94493e8bcb8c10687d0f8c0592fe38ed956fa6"
|
|
||||||
}
|
|
|
@ -1,36 +1,38 @@
|
||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "PostgreSQL",
|
||||||
"query": "SELECT * FROM users WHERE username = ?",
|
"query": "SELECT * FROM users WHERE username = $1",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
"name": "id",
|
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Int64"
|
"name": "id",
|
||||||
|
"type_info": "Int8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "username",
|
|
||||||
"ordinal": 1,
|
"ordinal": 1,
|
||||||
|
"name": "username",
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "password",
|
|
||||||
"ordinal": 2,
|
"ordinal": 2,
|
||||||
|
"name": "password",
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "created_at",
|
|
||||||
"ordinal": 3,
|
"ordinal": 3,
|
||||||
"type_info": "Datetime"
|
"name": "created_at",
|
||||||
|
"type_info": "Timestamp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "updated_at",
|
|
||||||
"ordinal": 4,
|
"ordinal": 4,
|
||||||
"type_info": "Datetime"
|
"name": "updated_at",
|
||||||
|
"type_info": "Timestamp"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Right": 1
|
"Left": [
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"nullable": [
|
"nullable": [
|
||||||
false,
|
false,
|
||||||
|
@ -40,5 +42,5 @@
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "98f4c0bfff04e07f5d0a46d48a31d24655826eebdf09c7f9f45d770df02035d3"
|
"hash": "606364c79e0990deb07dfbe6c32b3d302d083ec5333f3a5ce04113c38a041100"
|
||||||
}
|
}
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "INSERT INTO users (username, password) VALUES (?, ?)",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 2
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "78ecd3bc5a999dc227a86556f0ce41065974104b735d96b3c5785480023bb60a"
|
|
||||||
}
|
|
|
@ -1,10 +1,10 @@
|
||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "PostgreSQL",
|
||||||
"query": "INSERT INTO posts (user_id, content) VALUES (1, 'Hello world! The demo username is user and the password is pass.')",
|
"query": "INSERT INTO posts (user_id, content) VALUES (1, 'Hello world! The demo username is user and the password is pass.')",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Right": 0
|
"Left": []
|
||||||
},
|
},
|
||||||
"nullable": []
|
"nullable": []
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "INSERT INTO posts (user_id, content) VALUES ($1, $2)",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8",
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "a2835289cba16d38401e5324876508b8397ef7fbb9eb521ac3c5e57206eeecf7"
|
||||||
|
}
|
|
@ -1,46 +1,46 @@
|
||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "PostgreSQL",
|
||||||
"query": "SELECT * FROM posts WHERE id = 1",
|
"query": "SELECT * FROM posts WHERE id = 1",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
"name": "id",
|
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Int64"
|
"name": "id",
|
||||||
|
"type_info": "Int8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "user_id",
|
|
||||||
"ordinal": 1,
|
"ordinal": 1,
|
||||||
"type_info": "Int64"
|
"name": "user_id",
|
||||||
|
"type_info": "Int8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "content",
|
|
||||||
"ordinal": 2,
|
"ordinal": 2,
|
||||||
|
"name": "content",
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "upvotes",
|
|
||||||
"ordinal": 3,
|
"ordinal": 3,
|
||||||
"type_info": "Int64"
|
"name": "upvotes",
|
||||||
|
"type_info": "Int4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "downvotes",
|
|
||||||
"ordinal": 4,
|
"ordinal": 4,
|
||||||
"type_info": "Int64"
|
"name": "downvotes",
|
||||||
|
"type_info": "Int4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "created_at",
|
|
||||||
"ordinal": 5,
|
"ordinal": 5,
|
||||||
"type_info": "Datetime"
|
"name": "created_at",
|
||||||
|
"type_info": "Timestamp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "updated_at",
|
|
||||||
"ordinal": 6,
|
"ordinal": 6,
|
||||||
"type_info": "Datetime"
|
"name": "updated_at",
|
||||||
|
"type_info": "Timestamp"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Right": 0
|
"Left": []
|
||||||
},
|
},
|
||||||
"nullable": [
|
"nullable": [
|
||||||
false,
|
false,
|
||||||
|
|
|
@ -1,46 +1,48 @@
|
||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "PostgreSQL",
|
||||||
"query": "SELECT * FROM posts WHERE id = ?",
|
"query": "SELECT * FROM posts WHERE id = $1",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
"name": "id",
|
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Int64"
|
"name": "id",
|
||||||
|
"type_info": "Int8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "user_id",
|
|
||||||
"ordinal": 1,
|
"ordinal": 1,
|
||||||
"type_info": "Int64"
|
"name": "user_id",
|
||||||
|
"type_info": "Int8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "content",
|
|
||||||
"ordinal": 2,
|
"ordinal": 2,
|
||||||
|
"name": "content",
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "upvotes",
|
|
||||||
"ordinal": 3,
|
"ordinal": 3,
|
||||||
"type_info": "Int64"
|
"name": "upvotes",
|
||||||
|
"type_info": "Int4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "downvotes",
|
|
||||||
"ordinal": 4,
|
"ordinal": 4,
|
||||||
"type_info": "Int64"
|
"name": "downvotes",
|
||||||
|
"type_info": "Int4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "created_at",
|
|
||||||
"ordinal": 5,
|
"ordinal": 5,
|
||||||
"type_info": "Datetime"
|
"name": "created_at",
|
||||||
|
"type_info": "Timestamp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "updated_at",
|
|
||||||
"ordinal": 6,
|
"ordinal": 6,
|
||||||
"type_info": "Datetime"
|
"name": "updated_at",
|
||||||
|
"type_info": "Timestamp"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Right": 1
|
"Left": [
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"nullable": [
|
"nullable": [
|
||||||
false,
|
false,
|
||||||
|
@ -52,5 +54,5 @@
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "da280dfbdfe992918eb4f25ca61c08fc01474c3753a63e05b02051f5c066abc2"
|
"hash": "b6019471ff1989ef2f0658b0b34e683fdc706751e2bb69043544c9a4d08b5ba0"
|
||||||
}
|
}
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "INSERT INTO posts (user_id, content) VALUES (?, ?)",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 2
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "c2804e9c7bbd92dcddc34dd6f9d95a2598bb69984d6023e38eeea54454887b90"
|
|
||||||
}
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "INSERT INTO users (username, password) VALUES ($1, $2)",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text",
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "c936f44864dafe4660a736babd5f93050b7d35c66c0fe0c86f7b2dcdb7a1e3eb"
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "SELECT id FROM users WHERE username = $1",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "dd99e48b1572e25db38f03da95984fda1072913b29bb6b3753a0d351583dfff6"
|
||||||
|
}
|
|
@ -1,46 +1,49 @@
|
||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "PostgreSQL",
|
||||||
"query": "SELECT * FROM posts ORDER BY created_at DESC LIMIT ? OFFSET ?",
|
"query": "SELECT * FROM posts ORDER BY created_at DESC LIMIT $1 OFFSET $2",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
"name": "id",
|
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Int64"
|
"name": "id",
|
||||||
|
"type_info": "Int8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "user_id",
|
|
||||||
"ordinal": 1,
|
"ordinal": 1,
|
||||||
"type_info": "Int64"
|
"name": "user_id",
|
||||||
|
"type_info": "Int8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "content",
|
|
||||||
"ordinal": 2,
|
"ordinal": 2,
|
||||||
|
"name": "content",
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "upvotes",
|
|
||||||
"ordinal": 3,
|
"ordinal": 3,
|
||||||
"type_info": "Int64"
|
"name": "upvotes",
|
||||||
|
"type_info": "Int4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "downvotes",
|
|
||||||
"ordinal": 4,
|
"ordinal": 4,
|
||||||
"type_info": "Int64"
|
"name": "downvotes",
|
||||||
|
"type_info": "Int4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "created_at",
|
|
||||||
"ordinal": 5,
|
"ordinal": 5,
|
||||||
"type_info": "Datetime"
|
"name": "created_at",
|
||||||
|
"type_info": "Timestamp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "updated_at",
|
|
||||||
"ordinal": 6,
|
"ordinal": 6,
|
||||||
"type_info": "Datetime"
|
"name": "updated_at",
|
||||||
|
"type_info": "Timestamp"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Right": 2
|
"Left": [
|
||||||
|
"Int8",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"nullable": [
|
"nullable": [
|
||||||
false,
|
false,
|
||||||
|
@ -52,5 +55,5 @@
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "8f5e9d8d0c7c33d31f02a9cbd9886ed945d788caeb704b2ee5f743f7bf5fac88"
|
"hash": "f68cd95363d7da716b14f430118176ed4da34e450fc07b812f6bf77073cc2128"
|
||||||
}
|
}
|
492
server/Cargo.lock
generated
492
server/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -19,7 +19,7 @@ log = "0.4.20"
|
||||||
serde = { version = "1.0.188", features = ["derive"] }
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
serde_json = "1.0.107"
|
serde_json = "1.0.107"
|
||||||
sled = { version = "0.34.7" }
|
sled = { version = "0.34.7" }
|
||||||
sqlx = { version = "0.7.2", features = ["sqlite", "runtime-tokio", "chrono", "uuid"] }
|
sqlx = { version = "0.7.2", features = ["runtime-tokio", "chrono", "uuid", "postgres", "tls-rustls"] }
|
||||||
uuid = { version = "1.4.1", features = ["serde", "v4"] }
|
uuid = { version = "1.4.1", features = ["serde", "v4"] }
|
||||||
|
|
||||||
[profile.dev.package.sqlx-macros]
|
[profile.dev.package.sqlx-macros]
|
||||||
|
|
38
server/migrations_pg/0001_users_table.sql
Normal file
38
server/migrations_pg/0001_users_table.sql
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
username TEXT NOT NULL UNIQUE,
|
||||||
|
password TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Create a function to set created_at and updated_at on INSERT
|
||||||
|
CREATE OR REPLACE FUNCTION set_timestamps_on_insert() RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
NEW.created_at = NOW();
|
||||||
|
NEW.updated_at = NOW();
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- Create a trigger to call the function after INSERT
|
||||||
|
CREATE TRIGGER set_timestamps_on_insert
|
||||||
|
BEFORE INSERT ON users
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION set_timestamps_on_insert();
|
||||||
|
|
||||||
|
-- Create a function to set updated_at on UPDATE
|
||||||
|
CREATE OR REPLACE FUNCTION set_updated_at() RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
NEW.updated_at = NOW();
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- Create a trigger to call the function after UPDATE
|
||||||
|
CREATE TRIGGER set_updated_at
|
||||||
|
BEFORE UPDATE ON users
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION set_updated_at();
|
||||||
|
|
||||||
|
CREATE INDEX users_username_index ON users (username);
|
43
server/migrations_pg/0002_posts_table.sql
Normal file
43
server/migrations_pg/0002_posts_table.sql
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS posts (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
upvotes INTEGER NOT NULL DEFAULT 0,
|
||||||
|
downvotes INTEGER NOT NULL DEFAULT 0,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Create a function to set created_at and updated_at on INSERT
|
||||||
|
CREATE OR REPLACE FUNCTION set_timestamps_on_insert() RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
NEW.created_at = NOW();
|
||||||
|
NEW.updated_at = NOW();
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- Create a trigger to call the function after INSERT
|
||||||
|
CREATE TRIGGER set_timestamps_on_insert
|
||||||
|
BEFORE INSERT ON posts
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION set_timestamps_on_insert();
|
||||||
|
|
||||||
|
-- Create a function to set updated_at on UPDATE
|
||||||
|
CREATE OR REPLACE FUNCTION set_updated_at() RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
NEW.updated_at = NOW();
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- Create a trigger to call the function after UPDATE
|
||||||
|
CREATE TRIGGER set_updated_at
|
||||||
|
BEFORE UPDATE ON posts
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION set_updated_at();
|
||||||
|
|
||||||
|
CREATE INDEX posts_user_id_index ON posts (user_id);
|
||||||
|
CREATE INDEX posts_id_index ON posts (id);
|
||||||
|
CREATE INDEX idx_created_at_desc ON posts (created_at DESC);
|
|
@ -4,13 +4,13 @@ use argon2::{
|
||||||
Argon2, PasswordHasher, PasswordVerifier,
|
Argon2, PasswordHasher, PasswordVerifier,
|
||||||
};
|
};
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
use sqlx::SqlitePool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
// Gets the latest posts from the database, ordered by created_at
|
// Gets the latest posts from the database, ordered by created_at
|
||||||
pub async fn db_get_latest_posts(pool: &SqlitePool, limit: i64, offset: i64) -> Vec<Post> {
|
pub async fn db_get_latest_posts(pool: &PgPool, limit: i64, offset: i64) -> Vec<Post> {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
Post,
|
Post,
|
||||||
"SELECT * FROM posts ORDER BY created_at DESC LIMIT ? OFFSET ?",
|
"SELECT * FROM posts ORDER BY created_at DESC LIMIT $1 OFFSET $2",
|
||||||
limit,
|
limit,
|
||||||
offset
|
offset
|
||||||
)
|
)
|
||||||
|
@ -20,19 +20,19 @@ pub async fn db_get_latest_posts(pool: &SqlitePool, limit: i64, offset: i64) ->
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the post with id from the database
|
// Gets the post with id from the database
|
||||||
pub async fn db_get_post(id: i64, pool: &SqlitePool) -> Option<Post> {
|
pub async fn db_get_post(id: i64, pool: &PgPool) -> Option<Post> {
|
||||||
sqlx::query_as!(Post, "SELECT * FROM posts WHERE id = ?", id)
|
sqlx::query_as!(Post, "SELECT * FROM posts WHERE id = $1", id)
|
||||||
.fetch_one(pool)
|
.fetch_one(pool)
|
||||||
.await
|
.await
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inserts a new post to the database
|
// Inserts a new post to the database
|
||||||
pub async fn db_new_post(userid: i64, content: &str, pool: &SqlitePool) -> Option<Post> {
|
pub async fn db_new_post(userid: i64, content: &str, pool: &PgPool) -> Option<Post> {
|
||||||
info!("User with id {} submitted a post", userid);
|
info!("User with id {} submitted a post", userid);
|
||||||
|
|
||||||
let insert_query = sqlx::query!(
|
let insert_query = sqlx::query!(
|
||||||
"INSERT INTO posts (user_id, content) VALUES (?, ?)",
|
"INSERT INTO posts (user_id, content) VALUES ($1, $2)",
|
||||||
userid,
|
userid,
|
||||||
content
|
content
|
||||||
)
|
)
|
||||||
|
@ -57,8 +57,8 @@ pub async fn db_new_post(userid: i64, content: &str, pool: &SqlitePool) -> Optio
|
||||||
Some(post)
|
Some(post)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn db_user_exists(username: String, pool: &SqlitePool) -> bool {
|
pub async fn db_user_exists(username: String, pool: &PgPool) -> bool {
|
||||||
let exists = sqlx::query!("SELECT username FROM users WHERE username = ?", username)
|
let exists = sqlx::query!("SELECT username FROM users WHERE username = $1", username)
|
||||||
.fetch_one(pool)
|
.fetch_one(pool)
|
||||||
.await
|
.await
|
||||||
.ok()
|
.ok()
|
||||||
|
@ -67,9 +67,9 @@ pub async fn db_user_exists(username: String, pool: &SqlitePool) -> bool {
|
||||||
exists.is_some()
|
exists.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn db_user_login(username: String, password: String, pool: &SqlitePool) -> Option<User> {
|
pub async fn db_user_login(username: String, password: String, pool: &PgPool) -> Option<User> {
|
||||||
let username = username.clone();
|
let username = username.clone();
|
||||||
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE username = ?", username)
|
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE username = $1", username)
|
||||||
.fetch_one(pool)
|
.fetch_one(pool)
|
||||||
.await
|
.await
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
@ -95,7 +95,7 @@ pub async fn db_user_login(username: String, password: String, pool: &SqlitePool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn db_new_user(username: String, password: String, pool: &SqlitePool) -> Option<User> {
|
pub async fn db_new_user(username: String, password: String, pool: &PgPool) -> Option<User> {
|
||||||
// First check if the user already exists
|
// First check if the user already exists
|
||||||
match db_user_exists(username.clone(), pool).await {
|
match db_user_exists(username.clone(), pool).await {
|
||||||
true => {
|
true => {
|
||||||
|
@ -113,7 +113,7 @@ pub async fn db_new_user(username: String, password: String, pool: &SqlitePool)
|
||||||
|
|
||||||
// Insert our new user into the database
|
// Insert our new user into the database
|
||||||
let insert_query = sqlx::query!(
|
let insert_query = sqlx::query!(
|
||||||
"INSERT INTO users (username, password) VALUES (?, ?)",
|
"INSERT INTO users (username, password) VALUES ($1, $2)",
|
||||||
username,
|
username,
|
||||||
phc_hash
|
phc_hash
|
||||||
)
|
)
|
||||||
|
@ -123,7 +123,7 @@ pub async fn db_new_user(username: String, password: String, pool: &SqlitePool)
|
||||||
match insert_query {
|
match insert_query {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
info!("User: {} registered", username);
|
info!("User: {} registered", username);
|
||||||
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE username = ?", username)
|
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE username = $1", username)
|
||||||
.fetch_one(pool)
|
.fetch_one(pool)
|
||||||
.await
|
.await
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
|
|
@ -79,7 +79,7 @@ pub async fn new_post(new_post: Json<NewPost>, state: Data<ServerState>) -> Resu
|
||||||
let username = claims.sub.clone();
|
let username = claims.sub.clone();
|
||||||
|
|
||||||
// This one is avoidable if we just store the user id in the token
|
// This one is avoidable if we just store the user id in the token
|
||||||
let userid = sqlx::query!("SELECT id FROM users WHERE username = ?", username)
|
let userid = sqlx::query!("SELECT id FROM users WHERE username = $1", username)
|
||||||
.fetch_one(&state.pool)
|
.fetch_one(&state.pool)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
|
@ -4,10 +4,8 @@ use std::sync::Mutex;
|
||||||
|
|
||||||
use log::error;
|
use log::error;
|
||||||
use log::info;
|
use log::info;
|
||||||
use sqlx::migrate::MigrateDatabase;
|
use sqlx::postgres::PgPoolOptions;
|
||||||
use sqlx::Pool;
|
use sqlx::PgPool;
|
||||||
use sqlx::Sqlite;
|
|
||||||
use sqlx::{self, sqlite};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CaptchaState {
|
pub struct CaptchaState {
|
||||||
|
@ -24,7 +22,7 @@ impl CaptchaState {
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ServerState {
|
pub struct ServerState {
|
||||||
pub pool: Pool<Sqlite>,
|
pub pool: PgPool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerState {
|
impl ServerState {
|
||||||
|
@ -38,17 +36,13 @@ impl ServerState {
|
||||||
|
|
||||||
info!("Using db_url: {}", &db_url);
|
info!("Using db_url: {}", &db_url);
|
||||||
|
|
||||||
if !sqlx::Sqlite::database_exists(&db_url).await.unwrap() {
|
let pool = PgPoolOptions::new()
|
||||||
sqlx::Sqlite::create_database(&db_url).await.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let pool = sqlite::SqlitePoolOptions::new()
|
|
||||||
.max_connections(5)
|
.max_connections(5)
|
||||||
.connect(&db_url)
|
.connect(&db_url)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
sqlx::migrate!("./migrations").run(&pool).await.unwrap();
|
sqlx::migrate!("./migrations_pg").run(&pool).await.unwrap();
|
||||||
|
|
||||||
match crate::db::db_new_user("imbus".to_string(), "kartellen1234".to_string(), &pool).await
|
match crate::db::db_new_user("imbus".to_string(), "kartellen1234".to_string(), &pool).await
|
||||||
{
|
{
|
||||||
|
@ -63,13 +57,10 @@ impl ServerState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
use sqlx::SqlitePool;
|
|
||||||
|
|
||||||
// Inserts a bunch of dummy data into the database
|
// Inserts a bunch of dummy data into the database
|
||||||
// Mostly useful for debugging new posts, as we need to satisfy foreign key constraints.
|
// Mostly useful for debugging new posts, as we need to satisfy foreign key constraints.
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
async fn debug_setup(pool: &SqlitePool) -> Result<(), sqlx::Error> {
|
async fn debug_setup(pool: &PgPool) -> Result<(), sqlx::Error> {
|
||||||
use sqlx::query;
|
use sqlx::query;
|
||||||
|
|
||||||
use crate::db::db_new_user;
|
use crate::db::db_new_user;
|
||||||
|
|
Loading…
Reference in a new issue