chore: import upstream snapshot with attribution
CI / compile_and_lint (push) Failing after 0s
CI / docker_build (linux/amd64, -linux-amd64-duckdb, duckdb) (push) Failing after 0s
CI / docker_build (linux/arm64, -linux-arm64, minimal) (push) Failing after 2s
CI / docker_build (linux/arm64, -linux-arm64-duckdb, duckdb) (push) Failing after 1s
CI / docker_build (linux/amd64, minimal) (push) Failing after 1s
CI / test (, sqlite, sqlite::memory:) (push) Has been skipped
CI / test (mssql, mssql, mssql://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / test (mysql, mysql, mysql://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / test (oracle, oracle, Driver=Oracle 21 ODBC driver;Dbq=//127.0.0.1:1521/FREEPDB1;Uid=root;Pwd=Password123!) (push) Has been skipped
CI / test (postgres, odbc, Driver=PostgreSQL Unicode;Server=127.0.0.1;Port=5432;Database=sqlpage;UID=root;PWD=Password123!, true) (push) Has been skipped
CI / test (postgres, postgres, postgres://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / playwright (push) Has been skipped
CI / docker_build (linux/arm/v7, -linux-arm-v7, minimal) (push) Failing after 0s
CI / hurl_examples (push) Failing after 8s
deploy website / deploy_official_site (push) Failing after 1s
CI / hurl (${{ matrix.example }}) (push) Has been skipped
CI / docker_push (duckdb) (push) Has been cancelled
CI / docker_push (minimal) (push) Has been cancelled
CI / windows_test (push) Has been cancelled
CI / compile_and_lint (push) Failing after 0s
CI / docker_build (linux/amd64, -linux-amd64-duckdb, duckdb) (push) Failing after 0s
CI / docker_build (linux/arm64, -linux-arm64, minimal) (push) Failing after 2s
CI / docker_build (linux/arm64, -linux-arm64-duckdb, duckdb) (push) Failing after 1s
CI / docker_build (linux/amd64, minimal) (push) Failing after 1s
CI / test (, sqlite, sqlite::memory:) (push) Has been skipped
CI / test (mssql, mssql, mssql://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / test (mysql, mysql, mysql://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / test (oracle, oracle, Driver=Oracle 21 ODBC driver;Dbq=//127.0.0.1:1521/FREEPDB1;Uid=root;Pwd=Password123!) (push) Has been skipped
CI / test (postgres, odbc, Driver=PostgreSQL Unicode;Server=127.0.0.1;Port=5432;Database=sqlpage;UID=root;PWD=Password123!, true) (push) Has been skipped
CI / test (postgres, postgres, postgres://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / playwright (push) Has been skipped
CI / docker_build (linux/arm/v7, -linux-arm-v7, minimal) (push) Failing after 0s
CI / hurl_examples (push) Failing after 8s
deploy website / deploy_official_site (push) Failing after 1s
CI / hurl (${{ matrix.example }}) (push) Has been skipped
CI / docker_push (duckdb) (push) Has been cancelled
CI / docker_push (minimal) (push) Has been cancelled
CI / windows_test (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1 @@
|
||||
images/
|
||||
@@ -0,0 +1,14 @@
|
||||
# Image gallery
|
||||
|
||||
This example shows how to create an image gallery with user uploads.
|
||||
|
||||

|
||||
|
||||
Users can log in (default login is `admin`/`admin`) and upload images.
|
||||
Uploaded images are stored in the database as data urls.
|
||||
Using data urls allows us to store the images in the database, without having to set up a separate file server,
|
||||
which is the simplest way to get started. However, this is not a good idea if you want to store large images,
|
||||
as data urls are not very efficient, and will need to be transferred in full from the database to sqlpage to the user's browser. One can use `sqlpage.exec` to move the images to a separate file server, and store only the file urls in the database.
|
||||
|
||||

|
||||

|
||||
@@ -0,0 +1,19 @@
|
||||
-- redirect to the login page if the password is not correct
|
||||
SELECT 'authentication' AS component,
|
||||
'login.sql?error' AS link,
|
||||
(select password_hash from user where username = :Username) AS password_hash,
|
||||
:Password AS password;
|
||||
|
||||
-- code after this line will only be executed if the user is authenticated
|
||||
-- (i.e. if the password that they sent matches the password hash that we have stored for them)
|
||||
|
||||
insert into session (id, username)
|
||||
values (sqlpage.random_string(32), :Username)
|
||||
returning
|
||||
'cookie' AS component,
|
||||
'session_token' AS name,
|
||||
id AS value;
|
||||
|
||||
-- The user browser will now have a cookie named `session_token` that we can check later
|
||||
-- to see if the user is logged in.
|
||||
select 'redirect' as component, '/' as link;
|
||||
@@ -0,0 +1,17 @@
|
||||
services:
|
||||
init-uploads:
|
||||
image: alpine
|
||||
command: ["sh", "-c", "mkdir -p /var/www/images && chmod 777 /var/www/images"]
|
||||
volumes:
|
||||
- .:/var/www
|
||||
|
||||
web:
|
||||
image: lovasoa/sqlpage:main
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- .:/var/www
|
||||
- ./sqlpage:/etc/sqlpage
|
||||
depends_on:
|
||||
init-uploads:
|
||||
condition: service_completed_successfully
|
||||
@@ -0,0 +1,23 @@
|
||||
select 'shell' as component,
|
||||
'My image gallery' as title,
|
||||
(
|
||||
case when sqlpage.cookie('session_token') is null then 'login'
|
||||
else 'logout' end
|
||||
) as menu_item;
|
||||
|
||||
select 'card' as component,
|
||||
'My image gallery' as title;
|
||||
|
||||
select title, description, image_url as top_image
|
||||
from image;
|
||||
|
||||
select 'Your gallery is empty' as title,
|
||||
'You have not uploaded any images yet. Click the button below to upload a new image.' as description
|
||||
where not exists (select 1 from image);
|
||||
|
||||
select 'button' as component;
|
||||
select
|
||||
'Upload a new image' as title,
|
||||
'upload_form.sql' as link,
|
||||
'plus' as icon,
|
||||
'primary' as color;
|
||||
@@ -0,0 +1,12 @@
|
||||
select 'shell' as component, 'My image gallery' as title;
|
||||
|
||||
select 'form' as component, 'Login' as title, 'create_session.sql' as action;
|
||||
select 'text' as type, 'Username' as name, true as required;
|
||||
select 'password' as type, 'Password' as name, true as required;
|
||||
|
||||
|
||||
select 'alert' as component,
|
||||
'danger' as color,
|
||||
'You are not logged in' as title,
|
||||
'Sorry, we could not log you in. Please try again.' as description
|
||||
where $error is not null;
|
||||
@@ -0,0 +1,6 @@
|
||||
select
|
||||
'cookie' AS component,
|
||||
'session_token' AS name,
|
||||
true AS remove;
|
||||
|
||||
select 'redirect' as component, '/login.sql' as link -- redirect to the login page after the user logs out
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 778 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
@@ -0,0 +1,8 @@
|
||||
-- a sqlite table that will hold the image URLs together with a title and a description
|
||||
CREATE TABLE image (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
image_url TEXT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
@@ -0,0 +1,14 @@
|
||||
create table user (
|
||||
username text primary key,
|
||||
password_hash text not null
|
||||
);
|
||||
|
||||
create table session (
|
||||
id text primary key,
|
||||
username text not null references user(username),
|
||||
created_at timestamp not null default current_timestamp
|
||||
);
|
||||
|
||||
-- Creates an initial user with the username `admin` and the password `admin` (hashed using sqlpage.hash_password('admin'))
|
||||
insert into user (username, password_hash)
|
||||
values ('admin', '$argon2id$v=19$m=19456,t=2,p=1$4lu3hSvaqXK0dMCPZLOIPg$PUFJSB6L3J5eZ33z9WX7y0nOH6KawV2FdW0abMuPE7o');
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"max_uploaded_file_size": 5000000
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
# Public gallery is reachable without a session.
|
||||
GET http://localhost:8080
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
header "Content-Type" contains "text/html"
|
||||
body contains "My image gallery"
|
||||
body contains "Upload a new image"
|
||||
body not contains "An error occurred"
|
||||
|
||||
# Upload form is protected.
|
||||
GET http://localhost:8080/upload_form.sql
|
||||
HTTP 302
|
||||
[Asserts]
|
||||
header "Location" == "/login.sql"
|
||||
|
||||
GET http://localhost:8080/login.sql
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
body contains "Login"
|
||||
body contains "Username"
|
||||
body contains "Password"
|
||||
body not contains "An error occurred"
|
||||
|
||||
# Demo credentials from the example migration: admin/admin.
|
||||
POST http://localhost:8080/create_session.sql
|
||||
[Form]
|
||||
Username: admin
|
||||
Password: admin
|
||||
HTTP 302
|
||||
[Asserts]
|
||||
header "Set-Cookie" contains "session_token="
|
||||
header "Location" == "/"
|
||||
|
||||
# Hurl's cookie store reuses the session_token set above.
|
||||
GET http://localhost:8080/upload_form.sql
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
body contains "Upload a new image"
|
||||
body contains "Title"
|
||||
body contains "Description"
|
||||
body contains "Image"
|
||||
body not contains "An error occurred"
|
||||
|
||||
POST http://localhost:8080/upload.sql
|
||||
[Multipart]
|
||||
Title: Hurl test image
|
||||
Description: Uploaded by Hurl
|
||||
Image: file,screenshots/upload.png; image/png
|
||||
HTTP 302
|
||||
[Captures]
|
||||
location: header "Location"
|
||||
[Asserts]
|
||||
header "Location" matches "^/\\?created_id=\\d+$"
|
||||
|
||||
GET http://localhost:8080{{location}}
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
body contains "Hurl test image"
|
||||
body contains "Uploaded by Hurl"
|
||||
body contains "/images/"
|
||||
body not contains "An error occurred"
|
||||
@@ -0,0 +1,32 @@
|
||||
-- important: we do not accept file uploads from unauthenticated users
|
||||
select 'redirect' as component, '/login.sql' as link -- redirect to the login page if the user is not logged in
|
||||
where not exists (
|
||||
select true from session
|
||||
where
|
||||
sqlpage.cookie('session_token') = id and
|
||||
created_at > datetime('now', '-1 day') -- require the user to log in again after 1 day
|
||||
);
|
||||
|
||||
-- Redirect the user back to the form if no file was uploaded
|
||||
select 'redirect' as component, '/upload_form.sql' as link
|
||||
where sqlpage.uploaded_file_mime_type('Image') NOT LIKE 'image/%';
|
||||
|
||||
insert or ignore into image (title, description, image_url)
|
||||
values (
|
||||
:Title,
|
||||
:Description,
|
||||
-- Persist the uploaded file to the local "images" folder at the root of the website and return the path
|
||||
sqlpage.persist_uploaded_file('Image', 'images', 'jpg,jpeg,png,gif')
|
||||
-- alternatively, if the images are small, you could store them in the database directly with the following line
|
||||
-- sqlpage.read_file_as_data_url(sqlpage.uploaded_file_path('Image'))
|
||||
)
|
||||
returning 'redirect' as component,
|
||||
format('/?created_id=%d', id) as link;
|
||||
|
||||
-- If the insert failed, warn the user
|
||||
select 'alert' as component,
|
||||
'red' as color,
|
||||
'alert-triangle' as icon,
|
||||
'Failed to upload image' as title,
|
||||
'Please try again with a smaller picture. Maximum allowed file size is 500Kb.' as description
|
||||
;
|
||||
@@ -0,0 +1,7 @@
|
||||
select 'redirect' as component, '/login.sql' as link -- redirect to the login page if the user is not logged in
|
||||
where not exists (select true from session where sqlpage.cookie('session_token') = id and created_at > datetime('now', '-1 day')); -- require the user to log in again after 1 day
|
||||
|
||||
select 'form' as component, 'Upload a new image' as title, 'upload.sql' as action;
|
||||
select 'text' as type, 'Title' as name, true as required;
|
||||
select 'text' as type, 'Description' as name;
|
||||
select 'file' as type, 'Image' as name, 'image/*' as accept;
|
||||
Reference in New Issue
Block a user