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

This commit is contained in:
wehub-resource-sync
2026-07-13 12:31:57 +08:00
commit d718c5a372
986 changed files with 74597 additions and 0 deletions
@@ -0,0 +1 @@
images/
@@ -0,0 +1,14 @@
# Image gallery
This example shows how to create an image gallery with user uploads.
![Screenshot of the image gallery](./screenshots/homepage.png)
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.
![Screenshot of the upload page](./screenshots/upload.png)
![Screenshot of the login page](./screenshots/login.png)
@@ -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;