move settings directly into user table + added qr logo upload

main
TZGyn 1 year ago
parent bcbe7a68f0
commit 4d245ff5fb
Signed by: TZGyn
GPG Key ID: 122EAF77AE81FD4A

@ -0,0 +1,4 @@
ALTER TABLE "project" ADD COLUMN "qr_image_base64" text;--> statement-breakpoint
ALTER TABLE "user" ADD COLUMN "qr_background" varchar(7) DEFAULT '#fff' NOT NULL;--> statement-breakpoint
ALTER TABLE "user" ADD COLUMN "qr_foreground" varchar(7) DEFAULT '#000' NOT NULL;--> statement-breakpoint
ALTER TABLE "user" ADD COLUMN "qr_image_base64" text;

@ -0,0 +1,11 @@
-- Custom SQL migration file, put you code below! --
-- copy qr background setting
UPDATE "user"
SET qr_background = setting.qr_background
FROM setting
WHERE setting.user_id = "user".id;
-- copy qr foreground setting
UPDATE "user"
SET qr_foreground = setting.qr_foreground
FROM setting
WHERE setting.user_id = "user".id;

@ -0,0 +1,486 @@
{
"id": "cc527753-148c-4459-ab86-7a6c95be8151",
"prevId": "956fa062-8556-4889-b3bb-49539bb7256f",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.email_verification_token": {
"name": "email_verification_token",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(255)",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.project": {
"name": "project",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"uuid": {
"name": "uuid",
"type": "uuid",
"primaryKey": false,
"notNull": false,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"qr_background": {
"name": "qr_background",
"type": "varchar(7)",
"primaryKey": false,
"notNull": true,
"default": "'#ffffff'"
},
"qr_foreground": {
"name": "qr_foreground",
"type": "varchar(7)",
"primaryKey": false,
"notNull": true,
"default": "'#000000'"
},
"domain_status": {
"name": "domain_status",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"default": "'verified'"
},
"enable_custom_domain": {
"name": "enable_custom_domain",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"custom_ip": {
"name": "custom_ip",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"custom_domain_id": {
"name": "custom_domain_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"custom_domain": {
"name": "custom_domain",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"qr_corner_square_style": {
"name": "qr_corner_square_style",
"type": "varchar",
"primaryKey": false,
"notNull": true,
"default": "'square'"
},
"qr_dot_style": {
"name": "qr_dot_style",
"type": "varchar",
"primaryKey": false,
"notNull": true,
"default": "'square'"
},
"qr_image_base64": {
"name": "qr_image_base64",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.session": {
"name": "session",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(255)",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.setting": {
"name": "setting",
"schema": "",
"columns": {
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"qr_background": {
"name": "qr_background",
"type": "varchar(7)",
"primaryKey": false,
"notNull": false
},
"qr_foreground": {
"name": "qr_foreground",
"type": "varchar(7)",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.shortener": {
"name": "shortener",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"link": {
"name": "link",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"ios": {
"name": "ios",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"ios_link": {
"name": "ios_link",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"android": {
"name": "android",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"android_link": {
"name": "android_link",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"code": {
"name": "code",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"active": {
"name": "active",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"project_id": {
"name": "project_id",
"type": "integer",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"shortener_code_unique": {
"name": "shortener_code_unique",
"nullsNotDistinct": false,
"columns": [
"code"
]
}
}
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"uuid": {
"name": "uuid",
"type": "uuid",
"primaryKey": false,
"notNull": false,
"default": "gen_random_uuid()"
},
"email_verified": {
"name": "email_verified",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"google_id": {
"name": "google_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"username": {
"name": "username",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"password": {
"name": "password",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"plan": {
"name": "plan",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"default": "'free'"
},
"stripe_customer_id": {
"name": "stripe_customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"qr_background": {
"name": "qr_background",
"type": "varchar(7)",
"primaryKey": false,
"notNull": true,
"default": "'#fff'"
},
"qr_foreground": {
"name": "qr_foreground",
"type": "varchar(7)",
"primaryKey": false,
"notNull": true,
"default": "'#000'"
},
"qr_corner_square_style": {
"name": "qr_corner_square_style",
"type": "varchar",
"primaryKey": false,
"notNull": true,
"default": "'square'"
},
"qr_dot_style": {
"name": "qr_dot_style",
"type": "varchar",
"primaryKey": false,
"notNull": true,
"default": "'square'"
},
"qr_image_base64": {
"name": "qr_image_base64",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_email_unique": {
"name": "user_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
}
},
"public.visitor": {
"name": "visitor",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"shortener_id": {
"name": "shortener_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"country_code": {
"name": "country_code",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"country": {
"name": "country",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"city": {
"name": "city",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"device_type": {
"name": "device_type",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"default": "''"
},
"device_vendor": {
"name": "device_vendor",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"default": "''"
},
"os": {
"name": "os",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"default": "''"
},
"browser": {
"name": "browser",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"default": "''"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {},
"schemas": {},
"sequences": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

@ -0,0 +1,486 @@
{
"id": "dbe99c19-b436-46dd-af9d-2d3f7f33c0bd",
"prevId": "cc527753-148c-4459-ab86-7a6c95be8151",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.email_verification_token": {
"name": "email_verification_token",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(255)",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.project": {
"name": "project",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"uuid": {
"name": "uuid",
"type": "uuid",
"primaryKey": false,
"notNull": false,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"qr_background": {
"name": "qr_background",
"type": "varchar(7)",
"primaryKey": false,
"notNull": true,
"default": "'#ffffff'"
},
"qr_foreground": {
"name": "qr_foreground",
"type": "varchar(7)",
"primaryKey": false,
"notNull": true,
"default": "'#000000'"
},
"domain_status": {
"name": "domain_status",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"default": "'verified'"
},
"enable_custom_domain": {
"name": "enable_custom_domain",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"custom_ip": {
"name": "custom_ip",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"custom_domain_id": {
"name": "custom_domain_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"custom_domain": {
"name": "custom_domain",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"qr_corner_square_style": {
"name": "qr_corner_square_style",
"type": "varchar",
"primaryKey": false,
"notNull": true,
"default": "'square'"
},
"qr_dot_style": {
"name": "qr_dot_style",
"type": "varchar",
"primaryKey": false,
"notNull": true,
"default": "'square'"
},
"qr_image_base64": {
"name": "qr_image_base64",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.session": {
"name": "session",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(255)",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.setting": {
"name": "setting",
"schema": "",
"columns": {
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"qr_background": {
"name": "qr_background",
"type": "varchar(7)",
"primaryKey": false,
"notNull": false
},
"qr_foreground": {
"name": "qr_foreground",
"type": "varchar(7)",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.shortener": {
"name": "shortener",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"link": {
"name": "link",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"ios": {
"name": "ios",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"ios_link": {
"name": "ios_link",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"android": {
"name": "android",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"android_link": {
"name": "android_link",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"code": {
"name": "code",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"active": {
"name": "active",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"project_id": {
"name": "project_id",
"type": "integer",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"shortener_code_unique": {
"name": "shortener_code_unique",
"columns": [
"code"
],
"nullsNotDistinct": false
}
}
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"uuid": {
"name": "uuid",
"type": "uuid",
"primaryKey": false,
"notNull": false,
"default": "gen_random_uuid()"
},
"email_verified": {
"name": "email_verified",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"google_id": {
"name": "google_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"username": {
"name": "username",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"password": {
"name": "password",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"plan": {
"name": "plan",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"default": "'free'"
},
"stripe_customer_id": {
"name": "stripe_customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"qr_background": {
"name": "qr_background",
"type": "varchar(7)",
"primaryKey": false,
"notNull": true,
"default": "'#fff'"
},
"qr_foreground": {
"name": "qr_foreground",
"type": "varchar(7)",
"primaryKey": false,
"notNull": true,
"default": "'#000'"
},
"qr_corner_square_style": {
"name": "qr_corner_square_style",
"type": "varchar",
"primaryKey": false,
"notNull": true,
"default": "'square'"
},
"qr_dot_style": {
"name": "qr_dot_style",
"type": "varchar",
"primaryKey": false,
"notNull": true,
"default": "'square'"
},
"qr_image_base64": {
"name": "qr_image_base64",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_email_unique": {
"name": "user_email_unique",
"columns": [
"email"
],
"nullsNotDistinct": false
}
}
},
"public.visitor": {
"name": "visitor",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"shortener_id": {
"name": "shortener_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"country_code": {
"name": "country_code",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"country": {
"name": "country",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"city": {
"name": "city",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"device_type": {
"name": "device_type",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"default": "''"
},
"device_vendor": {
"name": "device_vendor",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"default": "''"
},
"os": {
"name": "os",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"default": "''"
},
"browser": {
"name": "browser",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"default": "''"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {},
"schemas": {},
"sequences": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

@ -0,0 +1,458 @@
{
"id": "74a897dc-352a-4a82-8966-5a47a6719602",
"prevId": "dbe99c19-b436-46dd-af9d-2d3f7f33c0bd",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.email_verification_token": {
"name": "email_verification_token",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(255)",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.project": {
"name": "project",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"uuid": {
"name": "uuid",
"type": "uuid",
"primaryKey": false,
"notNull": false,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"qr_background": {
"name": "qr_background",
"type": "varchar(7)",
"primaryKey": false,
"notNull": true,
"default": "'#ffffff'"
},
"qr_foreground": {
"name": "qr_foreground",
"type": "varchar(7)",
"primaryKey": false,
"notNull": true,
"default": "'#000000'"
},
"domain_status": {
"name": "domain_status",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"default": "'verified'"
},
"enable_custom_domain": {
"name": "enable_custom_domain",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"custom_ip": {
"name": "custom_ip",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"custom_domain_id": {
"name": "custom_domain_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"custom_domain": {
"name": "custom_domain",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"qr_corner_square_style": {
"name": "qr_corner_square_style",
"type": "varchar",
"primaryKey": false,
"notNull": true,
"default": "'square'"
},
"qr_dot_style": {
"name": "qr_dot_style",
"type": "varchar",
"primaryKey": false,
"notNull": true,
"default": "'square'"
},
"qr_image_base64": {
"name": "qr_image_base64",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.session": {
"name": "session",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "varchar(255)",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.shortener": {
"name": "shortener",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"link": {
"name": "link",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"ios": {
"name": "ios",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"ios_link": {
"name": "ios_link",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"android": {
"name": "android",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"android_link": {
"name": "android_link",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"code": {
"name": "code",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"active": {
"name": "active",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"project_id": {
"name": "project_id",
"type": "integer",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"shortener_code_unique": {
"name": "shortener_code_unique",
"nullsNotDistinct": false,
"columns": [
"code"
]
}
}
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"uuid": {
"name": "uuid",
"type": "uuid",
"primaryKey": false,
"notNull": false,
"default": "gen_random_uuid()"
},
"email_verified": {
"name": "email_verified",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"google_id": {
"name": "google_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"username": {
"name": "username",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"password": {
"name": "password",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"plan": {
"name": "plan",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"default": "'free'"
},
"stripe_customer_id": {
"name": "stripe_customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"qr_background": {
"name": "qr_background",
"type": "varchar(7)",
"primaryKey": false,
"notNull": true,
"default": "'#fff'"
},
"qr_foreground": {
"name": "qr_foreground",
"type": "varchar(7)",
"primaryKey": false,
"notNull": true,
"default": "'#000'"
},
"qr_corner_square_style": {
"name": "qr_corner_square_style",
"type": "varchar",
"primaryKey": false,
"notNull": true,
"default": "'square'"
},
"qr_dot_style": {
"name": "qr_dot_style",
"type": "varchar",
"primaryKey": false,
"notNull": true,
"default": "'square'"
},
"qr_image_base64": {
"name": "qr_image_base64",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_email_unique": {
"name": "user_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
}
},
"public.visitor": {
"name": "visitor",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"shortener_id": {
"name": "shortener_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"country_code": {
"name": "country_code",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"country": {
"name": "country",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"city": {
"name": "city",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"device_type": {
"name": "device_type",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"default": "''"
},
"device_vendor": {
"name": "device_vendor",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"default": "''"
},
"os": {
"name": "os",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"default": "''"
},
"browser": {
"name": "browser",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"default": "''"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {},
"schemas": {},
"sequences": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

@ -148,6 +148,27 @@
"when": 1725037845646,
"tag": "0020_warm_santa_claus",
"breakpoints": true
},
{
"idx": 21,
"version": "7",
"when": 1725167182706,
"tag": "0021_faulty_ironclad",
"breakpoints": true
},
{
"idx": 22,
"version": "7",
"when": 1725167233901,
"tag": "0022_cynical_emma_frost",
"breakpoints": true
},
{
"idx": 23,
"version": "7",
"when": 1725167580022,
"tag": "0023_dashing_radioactive_man",
"breakpoints": true
}
]
}

@ -6,6 +6,7 @@ import {
integer,
uuid,
boolean,
text,
} from 'drizzle-orm/pg-core'
import { relations } from 'drizzle-orm'
@ -25,6 +26,12 @@ export const user = pgTable('user', {
.$type<'free' | 'pro' | 'owner'>()
.default('free'),
stripeCustomerId: varchar('stripe_customer_id', { length: 255 }),
qrBackground: varchar('qr_background', { length: 7 })
.notNull()
.default('#fff'),
qrForeground: varchar('qr_foreground', { length: 7 })
.notNull()
.default('#000'),
qrCornerSquareStyle: varchar('qr_corner_square_style')
.$type<'dot' | 'square' | 'extra-rounded'>()
.notNull()
@ -33,6 +40,7 @@ export const user = pgTable('user', {
.$type<'square' | 'rounded'>()
.notNull()
.default('square'),
qrImageBase64: text('qr_image_base64'),
})
export const shortener = pgTable('shortener', {
@ -82,6 +90,7 @@ export const project = pgTable('project', {
.$type<'square' | 'rounded'>()
.notNull()
.default('square'),
qrImageBase64: text('qr_image_base64'),
})
export const visitor = pgTable('visitor', {
@ -114,12 +123,6 @@ export const session = pgTable('session', {
}).notNull(),
})
export const setting = pgTable('setting', {
userId: integer('user_id').notNull(),
qr_background: varchar('qr_background', { length: 7 }),
qr_foreground: varchar('qr_foreground', { length: 7 }),
})
export const emailVerificationToken = pgTable(
'email_verification_token',
{
@ -136,7 +139,6 @@ export const emailVerificationToken = pgTable(
// relations
export const userRelations = relations(user, ({ one, many }) => ({
shortener: many(shortener),
setting: one(setting),
}))
export const shortenerRelations = relations(
@ -178,10 +180,3 @@ export const sessionRelations = relations(session, ({ one }) => ({
references: [user.id],
}),
}))
export const settingRelations = relations(setting, ({ one }) => ({
user: one(user, {
fields: [setting.userId],
references: [user.id],
}),
}))

@ -1,7 +1,6 @@
import type { InferSelectModel } from 'drizzle-orm'
import { project, setting, shortener, user } from './schema'
import { project, shortener, user } from './schema'
export type User = InferSelectModel<typeof user>
export type Shortener = InferSelectModel<typeof shortener>
export type Project = InferSelectModel<typeof project>
export type Setting = InferSelectModel<typeof setting>

@ -117,15 +117,10 @@ export const load = (async (event) => {
where: (project, { eq }) => eq(project.userId, user.id),
})
const settings = db.query.setting.findFirst({
where: (settings, { eq }) => eq(settings.userId, user.id),
})
return {
shorteners,
projects: await projects,
selected_project,
settings: await settings,
page,
perPage,
search,

@ -12,6 +12,7 @@
export let cornerSquareStyle: 'dot' | 'square' | 'extra-rounded' =
'square'
export let dotStyle: 'square' | 'rounded' = 'square'
export let existingQrImage: string | null = null
let image = ''
@ -43,9 +44,9 @@
data: value,
width: 300,
height: 300,
margin: 10,
margin: 1,
qrOptions: {
errorCorrectionLevel: 'L',
errorCorrectionLevel: 'M',
typeNumber: 0,
},
backgroundOptions: {
@ -58,6 +59,11 @@
cornersSquareOptions: {
type: cornerSquareStyle,
},
image: existingQrImage || undefined,
imageOptions: {
imageSize: 0.7,
margin: 8,
},
})
const blob = await qrcodestyling.getRawData()
if (!blob) return

@ -18,9 +18,5 @@ export const load = (async (event) => {
redirect(300, `/dashboard/links`)
}
const settings = await db.query.setting.findFirst({
where: (settings, { eq }) => eq(settings.userId, user.id),
})
return { shortener, settings }
return { shortener }
}) satisfies PageServerLoad

@ -11,20 +11,28 @@
<ScrollArea>
<div class="max-w-2xl px-10 py-4">
<QR
value={data.shortener_url + '/' + data.shortener.code}
value={'https://' +
data.shortener_url +
'/' +
data.shortener.code}
code={data.shortener.code}
background={data.settings?.qr_background || '#fff'}
color={data.settings?.qr_foreground || '#000'}
background={data.user.qrBackground}
color={data.user.qrForeground}
cornerSquareStyle={data.user.qrCornerSquareStyle}
dotStyle={data.user.qrDotStyle} />
dotStyle={data.user.qrDotStyle}
existingQrImage={data.user.qrImageBase64} />
</div>
</ScrollArea>
{:else}
<QR
value={data.shortener_url + '/' + data.shortener.code}
value={'https://' +
data.shortener_url +
'/' +
data.shortener.code}
code={data.shortener.code}
background={data.settings?.qr_background || '#fff'}
color={data.settings?.qr_foreground || '#000'}
background={data.user.qrBackground}
color={data.user.qrForeground}
cornerSquareStyle={data.user.qrCornerSquareStyle}
dotStyle={data.user.qrDotStyle} />
dotStyle={data.user.qrDotStyle}
existingQrImage={data.user.qrImageBase64} />
{/if}

@ -7,7 +7,7 @@
import * as Avatar from '$lib/components/ui/avatar'
import { Badge } from '$lib/components/ui/badge'
import { ScrollArea } from '$lib/components/ui/scroll-area'
import type { Shortener, Project, Setting } from '$lib/db/types'
import type { Shortener, Project } from '$lib/db/types'
import {
BarChart,
EditIcon,
@ -27,7 +27,6 @@
visitorCount: number
}
export let shortener_url: string
export let settings: Setting | undefined
export let selected_project: Project
let deleteDialogOpen = false

@ -88,14 +88,9 @@ export const load = (async (event) => {
),
)
const settings = db.query.setting.findFirst({
where: (settings, { eq }) => eq(settings.userId, user.id),
})
return {
selectedProject,
shorteners,
settings: await settings,
page,
perPage,
search,

@ -184,8 +184,7 @@
<ShortenerCard
{shortener}
selected_project={data.selectedProject}
shortener_url={data.shortener_url}
settings={data.settings} />
shortener_url={data.shortener_url} />
{/each}
</div>
</ScrollArea>

@ -12,6 +12,7 @@
export let cornerSquareStyle: 'dot' | 'square' | 'extra-rounded' =
'square'
export let dotStyle: 'square' | 'rounded' = 'square'
export let existingQrImage: string | null = null
let image = ''
@ -43,9 +44,9 @@
data: value,
width: 300,
height: 300,
margin: 10,
margin: 1,
qrOptions: {
errorCorrectionLevel: 'L',
errorCorrectionLevel: 'M',
typeNumber: 0,
},
backgroundOptions: {
@ -58,6 +59,11 @@
cornersSquareOptions: {
type: cornerSquareStyle,
},
image: existingQrImage || undefined,
imageOptions: {
imageSize: 0.7,
margin: 8,
},
})
const blob = await qrcodestyling.getRawData()
if (!blob) return

@ -16,20 +16,22 @@
<ScrollArea>
<div class="max-w-2xl px-10 py-4">
<QR
value={url + '/' + data.shortener.code}
value={'https://' + url + '/' + data.shortener.code}
code={data.shortener.code}
background={data.project.qr_background}
color={data.project.qr_foreground}
cornerSquareStyle={data.project.qrCornerSquareStyle}
dotStyle={data.project.qrDotStyle} />
dotStyle={data.project.qrDotStyle}
existingQrImage={data.project.qrImageBase64} />
</div>
</ScrollArea>
{:else}
<QR
value={url + '/' + data.shortener.code}
value={'https://' + url + '/' + data.shortener.code}
code={data.shortener.code}
background={data.project.qr_background}
color={data.project.qr_foreground}
cornerSquareStyle={data.project.qrCornerSquareStyle}
dotStyle={data.project.qrDotStyle} />
dotStyle={data.project.qrDotStyle}
existingQrImage={data.project.qrImageBase64} />
{/if}

@ -8,6 +8,8 @@
export let cornerSquareStyle: 'dot' | 'square' | 'extra-rounded' =
'square'
export let dotStyle: 'square' | 'rounded' = 'square'
export let existingQrImage: string | null = null
export let qrImage: File | null = null
let image = ''
@ -17,15 +19,19 @@
}
try {
const qrImageDataUrl = qrImage
? URL.createObjectURL(qrImage)
: existingQrImage || undefined
const qrcodestyling = new (
await import('qr-code-styling')
).default({
data: value,
width: 300,
height: 300,
margin: 10,
margin: 1,
qrOptions: {
errorCorrectionLevel: 'L',
errorCorrectionLevel: 'M',
typeNumber: 0,
},
backgroundOptions: {
@ -38,6 +44,11 @@
cornersSquareOptions: {
type: cornerSquareStyle,
},
image: qrImageDataUrl,
imageOptions: {
imageSize: 0.7,
margin: 8,
},
})
const blob = await qrcodestyling.getRawData()
if (!blob) return
@ -52,6 +63,7 @@
color &&
cornerSquareStyle &&
dotStyle &&
(qrImage === null || qrImage) &&
generateQrCode()
onMount(() => {

@ -15,6 +15,7 @@
import { cn } from '$lib/utils'
export let data: SuperValidated<Infer<FormSchema>>
export let qrImageBase64: string | null = null
export let isPro: boolean = false
const form = superForm(data, {
@ -37,6 +38,7 @@
<form
method="POST"
use:enhance
enctype="multipart/form-data"
class="flex flex-col gap-4"
action="?/update">
<Form.Field {form} name="name" class="flex flex-col gap-2">
@ -52,7 +54,9 @@
background={$formData.qr_background}
color={$formData.qr_foreground}
cornerSquareStyle={$formData.qrCornerSquareStyle}
dotStyle={$formData.qrDotStyle} />
dotStyle={$formData.qrDotStyle}
existingQrImage={qrImageBase64}
qrImage={$formData.qrImage} />
</div>
<Form.Field {form} name="qr_background" class="flex flex-col gap-2">
<Form.Control let:attrs>
@ -70,94 +74,112 @@
<Form.Description>QR Code foreground color</Form.Description>
<Form.FieldErrors />
</Form.Field>
{#if isPro}
<Form.Field {form} name="qrCornerSquareStyle">
<Form.Control let:attrs>
<Form.Label>
Corner Square Style <span class="text-brand">(Pro)</span>
</Form.Label>
<Input
{...attrs}
class="hidden"
hidden
aria-hidden
bind:value={$formData.qrCornerSquareStyle} />
</Form.Control>
<div class="flex gap-4">
<Button
on:click={() => ($formData.qrCornerSquareStyle = 'square')}
class={cn(
'flex flex-col gap-3 border p-12',
$formData.qrCornerSquareStyle === 'square'
? 'bg-primary-foreground'
: 'bg-transparent',
)}>
<div class="border-brand border-4 p-4"></div>
Square
</Button>
<Button
on:click={() => ($formData.qrCornerSquareStyle = 'dot')}
class={cn(
'flex flex-col gap-3 border p-12',
$formData.qrCornerSquareStyle === 'dot'
? 'bg-primary-foreground'
: 'bg-transparent',
)}>
<div class="border-brand rounded-full border-4 p-4"></div>
Dot
</Button>
<Button
on:click={() =>
($formData.qrCornerSquareStyle = 'extra-rounded')}
class={cn(
'flex flex-col gap-3 border p-12',
$formData.qrCornerSquareStyle === 'extra-rounded'
? 'bg-primary-foreground'
: 'bg-transparent',
)}>
<div class="border-brand rounded-lg border-4 p-4"></div>
Rounded
</Button>
</div>
</Form.Field>
<Form.Field {form} name="qrDotStyle">
<Form.Control let:attrs>
<Form.Label>
Dot Style <span class="text-brand">(Pro)</span>
</Form.Label>
<Input
{...attrs}
class="hidden"
hidden
aria-hidden
bind:value={$formData.qrDotStyle} />
</Form.Control>
<div class="flex gap-4">
<Button
on:click={() => ($formData.qrDotStyle = 'square')}
class={cn(
'flex flex-col gap-3 border p-12',
$formData.qrDotStyle === 'square'
? 'bg-primary-foreground'
: 'bg-transparent',
)}>
<div class="border-brand border-4"></div>
Square
</Button>
<Button
on:click={() => ($formData.qrDotStyle = 'rounded')}
class={cn(
'flex flex-col gap-3 border p-12',
$formData.qrDotStyle === 'rounded'
? 'bg-primary-foreground'
: 'bg-transparent',
)}>
<div class="border-brand w-4 rounded-lg border-4"></div>
Rounded
</Button>
</div>
</Form.Field>
{/if}
<Form.Field {form} name="qrImage">
<Form.Control let:attrs>
<Form.Label>
Image <span class="text-brand">(Pro)</span>
</Form.Label>
<Input
{...attrs}
accept="image/png, image/jpeg"
type="file"
disabled={!isPro}
on:input={(e) =>
($formData.qrImage = e.currentTarget.files?.item(0))} />
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<Form.Field {form} name="qrCornerSquareStyle">
<Form.Control let:attrs>
<Form.Label>
Corner Square Style <span class="text-brand">(Pro)</span>
</Form.Label>
<Input
{...attrs}
class="hidden"
hidden
aria-hidden
bind:value={$formData.qrCornerSquareStyle} />
</Form.Control>
<div class="flex gap-4">
<Button
disabled={!isPro}
on:click={() => ($formData.qrCornerSquareStyle = 'square')}
class={cn(
'flex flex-col gap-3 border p-12',
$formData.qrCornerSquareStyle === 'square'
? 'bg-primary-foreground'
: 'bg-transparent',
)}>
<div class="border-brand border-4 p-4"></div>
Square
</Button>
<Button
disabled={!isPro}
on:click={() => ($formData.qrCornerSquareStyle = 'dot')}
class={cn(
'flex flex-col gap-3 border p-12',
$formData.qrCornerSquareStyle === 'dot'
? 'bg-primary-foreground'
: 'bg-transparent',
)}>
<div class="border-brand rounded-full border-4 p-4"></div>
Dot
</Button>
<Button
disabled={!isPro}
on:click={() =>
($formData.qrCornerSquareStyle = 'extra-rounded')}
class={cn(
'flex flex-col gap-3 border p-12',
$formData.qrCornerSquareStyle === 'extra-rounded'
? 'bg-primary-foreground'
: 'bg-transparent',
)}>
<div class="border-brand rounded-lg border-4 p-4"></div>
Rounded
</Button>
</div>
</Form.Field>
<Form.Field {form} name="qrDotStyle">
<Form.Control let:attrs>
<Form.Label>
Dot Style <span class="text-brand">(Pro)</span>
</Form.Label>
<Input
{...attrs}
class="hidden"
hidden
aria-hidden
bind:value={$formData.qrDotStyle} />
</Form.Control>
<div class="flex gap-4">
<Button
disabled={!isPro}
on:click={() => ($formData.qrDotStyle = 'square')}
class={cn(
'flex flex-col gap-3 border p-12',
$formData.qrDotStyle === 'square'
? 'bg-primary-foreground'
: 'bg-transparent',
)}>
<div class="border-brand border-4"></div>
Square
</Button>
<Button
disabled={!isPro}
on:click={() => ($formData.qrDotStyle = 'rounded')}
class={cn(
'flex flex-col gap-3 border p-12',
$formData.qrDotStyle === 'rounded'
? 'bg-primary-foreground'
: 'bg-transparent',
)}>
<div class="border-brand w-4 rounded-lg border-4"></div>
Rounded
</Button>
</div>
</Form.Field>
<Form.Button class="w-fit">
{#if $submitting}
<LoaderCircle class="animate-spin" />

@ -6,6 +6,7 @@ import {
setMessage,
superValidate,
fail,
withFiles,
} from 'sveltekit-superforms'
import {
formSchema,
@ -56,6 +57,7 @@ export const load = (async (event) => {
},
zod(formSchema),
),
qrImageBase64: project.qrImageBase64,
enableCustomDomainForm: await superValidate(
{ enableDomain: project.custom_domain || '' },
zod(enableCustomDomainFormSchema),
@ -81,12 +83,25 @@ export const actions: Actions = {
update: async (event) => {
const form = await superValidate(event, zod(formSchema))
if (!form.valid) {
return fail(400, {
form,
})
return fail(
400,
withFiles({
form,
}),
)
}
const user = event.locals.user
const qrImage = form.data.qrImage
const qrImageType = qrImage ? qrImage.type : undefined
const qrImageBlob = qrImage
? await qrImage.arrayBuffer()
: undefined
const qrImageBase64 = qrImageBlob
? Buffer.from(qrImageBlob).toString('base64')
: undefined
await db
.update(projectTable)
.set({
@ -99,6 +114,12 @@ export const actions: Actions = {
: undefined,
qrDotStyle:
user.plan !== 'free' ? form.data.qrDotStyle : undefined,
qrImageBase64:
user.plan !== 'free'
? qrImage
? `data:${qrImageType};base64,${qrImageBase64}`
: undefined
: undefined,
})
.where(
and(
@ -107,9 +128,9 @@ export const actions: Actions = {
),
)
return {
return withFiles({
form,
}
})
},
enable_custom_domain: async (event) => {
const form = await superValidate(

@ -309,7 +309,10 @@
</div>
<Separator />
<EditForm data={data.form} isPro={data.user.plan !== 'free'} />
<EditForm
data={data.form}
isPro={data.user.plan !== 'free'}
qrImageBase64={data.qrImageBase64} />
<Separator />

@ -12,6 +12,17 @@ export const formSchema = z.object({
.max(7),
qrCornerSquareStyle: z.custom<'dot' | 'square' | 'extra-rounded'>(),
qrDotStyle: z.custom<'square' | 'rounded'>(),
qrImage: z
.instanceof(File, { message: 'Please upload a file' })
.refine(
(file) =>
file.type === 'image/jpeg' || file.type === 'image/png',
{
message: 'Only JPEG or PNG files are allowed',
},
)
.optional()
.nullable(),
})
export const enableCustomDomainFormSchema = z.object({

@ -4,10 +4,12 @@
export let background = '#fff'
export let color = '#000'
export let value = 'example.com/abcdefgh'
export let value = 'abcdefghajskdadsj'
export let cornerSquareStyle: 'dot' | 'square' | 'extra-rounded' =
'square'
export let dotStyle: 'square' | 'rounded' = 'square'
export let existingQrImage: string | null = null
export let qrImage: File | null = null
let image = ''
@ -17,15 +19,19 @@
}
try {
const qrImageDataUrl = qrImage
? URL.createObjectURL(qrImage)
: existingQrImage || undefined
const qrcodestyling = new (
await import('qr-code-styling')
).default({
data: value,
width: 300,
height: 300,
margin: 10,
margin: 1,
qrOptions: {
errorCorrectionLevel: 'L',
errorCorrectionLevel: 'M',
typeNumber: 0,
},
backgroundOptions: {
@ -38,6 +44,11 @@
cornersSquareOptions: {
type: cornerSquareStyle,
},
image: qrImageDataUrl,
imageOptions: {
imageSize: 0.7,
margin: 8,
},
})
const blob = await qrcodestyling.getRawData()
if (!blob) return
@ -52,6 +63,7 @@
color &&
cornerSquareStyle &&
dotStyle &&
(qrImage === null || qrImage) &&
generateQrCode()
onMount(() => {

@ -1,25 +1,20 @@
import { db } from '$lib/db'
import type { PageServerLoad, Actions } from './$types'
import { fail } from '@sveltejs/kit'
import { superValidate } from 'sveltekit-superforms'
import { superValidate, withFiles } from 'sveltekit-superforms'
import { formSchema } from './schema'
import { zod } from 'sveltekit-superforms/adapters'
import { setting } from '$lib/db/schema'
import { eq } from 'drizzle-orm'
import { user as userTable } from '$lib/db/schema'
export const load = (async (event) => {
const user = event.locals.user
const settings = await db.query.setting.findFirst({
where: (setting, { eq }) =>
eq(setting.userId, event.locals.user.id),
})
const qr_background = settings?.qr_background || '#000'
const qr_foreground = settings?.qr_foreground || '#fff'
const qr_background = user.qrBackground
const qr_foreground = user.qrForeground
return {
settings,
qrImageBase64: user.qrImageBase64,
form: await superValidate(
{
qr_background,
@ -28,6 +23,7 @@ export const load = (async (event) => {
qrDotStyle: user.qrDotStyle,
},
zod(formSchema),
{ errors: false },
),
}
}) satisfies PageServerLoad
@ -36,40 +32,56 @@ export const actions: Actions = {
default: async (event) => {
const form = await superValidate(event, zod(formSchema))
if (!form.valid) {
return fail(400, {
form,
})
return fail(
400,
withFiles({
form,
}),
)
}
const user = event.locals.user
const userId = event.locals.user.id
const settings = await db.query.setting.findFirst({
where: (settingData, { eq }) => eq(settingData.userId, userId),
})
if (!settings) {
await db.insert(setting).values({ userId })
}
const {
qr_background,
qr_foreground,
qrCornerSquareStyle,
qrDotStyle,
qrImage,
} = form.data
const qrImageType = qrImage ? qrImage.type : undefined
const qrImageBlob = qrImage
? await qrImage.arrayBuffer()
: undefined
const qrImageBase64 = qrImageBlob
? Buffer.from(qrImageBlob).toString('base64')
: undefined
await db
.update(setting)
.set({ qr_background, qr_foreground })
.where(eq(setting.userId, userId))
.update(userTable)
.set({
qrBackground: qr_background,
qrForeground: qr_foreground,
})
.where(eq(userTable.id, userId))
if (user.plan !== 'free') {
await db
.update(userTable)
.set({ qrCornerSquareStyle, qrDotStyle })
.set({
qrCornerSquareStyle,
qrDotStyle,
qrImageBase64: qrImage
? `data:${qrImageType};base64,${qrImageBase64}`
: undefined,
})
.where(eq(userTable.id, userId))
}
return {
return withFiles({
form,
}
})
},
}

@ -45,10 +45,16 @@
background={$formData.qr_background}
color={$formData.qr_foreground}
cornerSquareStyle={$formData.qrCornerSquareStyle}
dotStyle={$formData.qrDotStyle} />
dotStyle={$formData.qrDotStyle}
existingQrImage={data.qrImageBase64}
qrImage={$formData.qrImage} />
</div>
<form method="POST" use:enhance class="flex flex-col gap-6">
<form
method="POST"
use:enhance
enctype="multipart/form-data"
class="flex flex-col gap-6">
<Form.Field {form} name="qr_background">
<Form.Control let:attrs>
<Form.Label>Background Color</Form.Label>
@ -65,95 +71,112 @@
<Form.Description>QR Code foreground color</Form.Description>
<Form.FieldErrors />
</Form.Field>
{#if data.user.plan !== 'free'}
<Form.Field {form} name="qrCornerSquareStyle">
<Form.Control let:attrs>
<Form.Label>
Corner Square Style <span class="text-brand">(Pro)</span>
</Form.Label>
<Input
{...attrs}
class="hidden"
hidden
aria-hidden
bind:value={$formData.qrCornerSquareStyle} />
</Form.Control>
<div class="flex gap-4">
<Button
on:click={() =>
($formData.qrCornerSquareStyle = 'square')}
class={cn(
'flex flex-col gap-3 border p-12',
$formData.qrCornerSquareStyle === 'square'
? 'bg-primary-foreground'
: 'bg-transparent',
)}>
<div class="border-brand border-4 p-4"></div>
Square
</Button>
<Button
on:click={() => ($formData.qrCornerSquareStyle = 'dot')}
class={cn(
'flex flex-col gap-3 border p-12',
$formData.qrCornerSquareStyle === 'dot'
? 'bg-primary-foreground'
: 'bg-transparent',
)}>
<div class="border-brand rounded-full border-4 p-4"></div>
Dot
</Button>
<Button
on:click={() =>
($formData.qrCornerSquareStyle = 'extra-rounded')}
class={cn(
'flex flex-col gap-3 border p-12',
$formData.qrCornerSquareStyle === 'extra-rounded'
? 'bg-primary-foreground'
: 'bg-transparent',
)}>
<div class="border-brand rounded-lg border-4 p-4"></div>
Rounded
</Button>
</div>
</Form.Field>
<Form.Field {form} name="qrDotStyle">
<Form.Control let:attrs>
<Form.Label>
Dot Style <span class="text-brand">(Pro)</span>
</Form.Label>
<Input
{...attrs}
class="hidden"
hidden
aria-hidden
bind:value={$formData.qrDotStyle} />
</Form.Control>
<div class="flex gap-4">
<Button
on:click={() => ($formData.qrDotStyle = 'square')}
class={cn(
'flex flex-col gap-3 border p-12',
$formData.qrDotStyle === 'square'
? 'bg-primary-foreground'
: 'bg-transparent',
)}>
<div class="border-brand border-4 p-4"></div>
Square
</Button>
<Button
on:click={() => ($formData.qrDotStyle = 'rounded')}
class={cn(
'flex flex-col gap-3 border p-12',
$formData.qrDotStyle === 'rounded'
? 'bg-primary-foreground'
: 'bg-transparent',
)}>
<div class="border-brand rounded-full border-4 p-4"></div>
Rounded
</Button>
</div>
</Form.Field>
{/if}
<Form.Field {form} name="qrImage">
<Form.Control let:attrs>
<Form.Label>
Image <span class="text-brand">(Pro)</span>
</Form.Label>
<Input
{...attrs}
accept="image/png, image/jpeg"
type="file"
disabled={data.user.plan === 'free'}
on:input={(e) =>
($formData.qrImage = e.currentTarget.files?.item(0))} />
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<Form.Field {form} name="qrCornerSquareStyle">
<Form.Control let:attrs>
<Form.Label>
Corner Square Style <span class="text-brand">(Pro)</span>
</Form.Label>
<Input
{...attrs}
class="hidden"
hidden
aria-hidden
bind:value={$formData.qrCornerSquareStyle} />
</Form.Control>
<div class="flex gap-4">
<Button
disabled={data.user.plan === 'free'}
on:click={() => ($formData.qrCornerSquareStyle = 'square')}
class={cn(
'flex flex-col gap-3 border p-12',
$formData.qrCornerSquareStyle === 'square'
? 'bg-primary-foreground'
: 'bg-transparent',
)}>
<div class="border-brand border-4 p-4"></div>
Square
</Button>
<Button
disabled={data.user.plan === 'free'}
on:click={() => ($formData.qrCornerSquareStyle = 'dot')}
class={cn(
'flex flex-col gap-3 border p-12',
$formData.qrCornerSquareStyle === 'dot'
? 'bg-primary-foreground'
: 'bg-transparent',
)}>
<div class="border-brand rounded-full border-4 p-4"></div>
Dot
</Button>
<Button
disabled={data.user.plan === 'free'}
on:click={() =>
($formData.qrCornerSquareStyle = 'extra-rounded')}
class={cn(
'flex flex-col gap-3 border p-12',
$formData.qrCornerSquareStyle === 'extra-rounded'
? 'bg-primary-foreground'
: 'bg-transparent',
)}>
<div class="border-brand rounded-lg border-4 p-4"></div>
Rounded
</Button>
</div>
</Form.Field>
<Form.Field {form} name="qrDotStyle">
<Form.Control let:attrs>
<Form.Label>
Dot Style <span class="text-brand">(Pro)</span>
</Form.Label>
<Input
{...attrs}
class="hidden"
hidden
aria-hidden
bind:value={$formData.qrDotStyle} />
</Form.Control>
<div class="flex gap-4">
<Button
disabled={data.user.plan === 'free'}
on:click={() => ($formData.qrDotStyle = 'square')}
class={cn(
'flex flex-col gap-3 border p-12',
$formData.qrDotStyle === 'square'
? 'bg-primary-foreground'
: 'bg-transparent',
)}>
<div class="border-brand border-4"></div>
Square
</Button>
<Button
disabled={data.user.plan === 'free'}
on:click={() => ($formData.qrDotStyle = 'rounded')}
class={cn(
'flex flex-col gap-3 border p-12',
$formData.qrDotStyle === 'rounded'
? 'bg-primary-foreground'
: 'bg-transparent',
)}>
<div class="border-brand w-4 rounded-lg border-4"></div>
Rounded
</Button>
</div>
</Form.Field>
<Form.Button class="w-fit">
{#if $submitting}

@ -11,4 +11,15 @@ export const formSchema = z.object({
.max(7),
qrCornerSquareStyle: z.custom<'dot' | 'square' | 'extra-rounded'>(),
qrDotStyle: z.custom<'square' | 'rounded'>(),
qrImage: z
.instanceof(File, { message: 'Please upload a file' })
.refine(
(file) =>
file.type === 'image/jpeg' || file.type === 'image/png',
{
message: 'Only JPEG or PNG files are allowed',
},
)
.optional()
.nullable(),
})

@ -12,11 +12,7 @@ export const load = (async (event) => {
const shortener = await db.query.shortener.findFirst({
where: (shortener, { eq }) => eq(shortener.code, id),
with: {
user: {
with: {
setting: true,
},
},
user: true,
project: true,
},
})
@ -25,29 +21,32 @@ export const load = (async (event) => {
redirect(301, `/dashboard/links`)
}
let colorSetting: {
let setting: {
color: { background: string | null; foreground: string | null }
cornerSquareType: 'square' | 'dot' | 'extra-rounded'
dotStyle: 'square' | 'rounded'
image: string | null
} | null = null
if (color === 'true') {
if (shortener.project) {
colorSetting = {
setting = {
color: {
background: shortener.project.qr_background,
foreground: shortener.project.qr_foreground,
},
cornerSquareType: shortener.project.qrCornerSquareStyle,
dotStyle: shortener.project.qrDotStyle,
image: shortener.project.qrImageBase64,
}
} else if (shortener.user.setting) {
colorSetting = {
} else if (shortener.user) {
setting = {
color: {
background: shortener.user.setting.qr_background,
foreground: shortener.user.setting.qr_foreground,
background: shortener.user.qrBackground,
foreground: shortener.user.qrForeground,
},
cornerSquareType: shortener.user.qrCornerSquareStyle,
dotStyle: shortener.user.qrDotStyle,
image: shortener.user.qrImageBase64,
}
}
}
@ -57,5 +56,5 @@ export const load = (async (event) => {
? shortener.project.custom_domain || shortenerUrl
: shortenerUrl
return { shortener, url, colorSetting, shortenerId: id }
return { shortener, url, setting, shortenerId: id }
}) satisfies PageServerLoad

@ -34,23 +34,28 @@
const qrcodestyling = new (
await import('qr-code-styling')
).default({
data: data.url + '/' + data.shortenerId,
data: 'https://' + data.url + '/' + data.shortenerId,
width: 300,
height: 300,
margin: 10,
margin: 1,
qrOptions: {
errorCorrectionLevel: 'L',
errorCorrectionLevel: 'M',
typeNumber: 0,
},
backgroundOptions: {
color: data.colorSetting?.color.background || '#fff',
color: data.setting?.color.background || '#fff',
},
dotsOptions: {
color: data.colorSetting?.color.foreground || '#000',
type: data.colorSetting?.dotStyle || 'square',
color: data.setting?.color.foreground || '#000',
type: data.setting?.dotStyle || 'square',
},
cornersSquareOptions: {
type: data.colorSetting?.cornerSquareType || 'square',
type: data.setting?.cornerSquareType || 'square',
},
image: data.setting?.image || undefined,
imageOptions: {
imageSize: 0.7,
margin: 8,
},
})
const blob = await qrcodestyling.getRawData()

Loading…
Cancel
Save