added update image route

This commit is contained in:
2024-04-20 14:52:43 +07:00
parent ed8c0e77f2
commit ee9963c3b6
11 changed files with 914 additions and 35 deletions

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
1. `pnpm install`
2. `pnpm initialize_data`
3. `docker compose up`

View File

@@ -1,8 +1,12 @@
import { db } from "./src/db"; import { db } from "./src/db";
import { group, opinion, zone, province } from "./src/schema.ts"; import { group, opinion, zone, province } from "./src/schema";
import { Groups, Opinions, Provinces, Districts } from "./initialData.ts"; import { Groups, Opinions, Provinces, Districts } from "./initialData";
import { createBucket, createClient } from "./src/minio";
async function main() { async function main() {
let mc = createClient();
await createBucket(mc);
const isInitialized = await db.query.group const isInitialized = await db.query.group
.findMany() .findMany()
.then((groups) => groups.length > 0); .then((groups) => groups.length > 0);

View File

@@ -0,0 +1,10 @@
CREATE TABLE `image_to_user` (
`user_id` integer PRIMARY KEY NOT NULL,
`image_name` text NOT NULL,
`created_on` integer DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
);
--> statement-breakpoint
DROP TABLE `phone_tokens`;--> statement-breakpoint
ALTER TABLE users ADD `image` text;--> statement-breakpoint
CREATE INDEX `image_idx` ON `users` (`image`);

View File

@@ -0,0 +1,484 @@
{
"version": "5",
"dialect": "sqlite",
"id": "2cf2acb7-cc98-4f28-8ead-5916b87b7683",
"prevId": "58f80520-7300-4bc4-943d-87568666e42d",
"tables": {
"groups": {
"name": "groups",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"groups_name_unique": {
"name": "groups_name_unique",
"columns": [
"name"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"image_to_user": {
"name": "image_to_user",
"columns": {
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"image_name": {
"name": "image_name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"created_on": {
"name": "created_on",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP"
}
},
"indexes": {},
"foreignKeys": {
"image_to_user_user_id_users_id_fk": {
"name": "image_to_user_user_id_users_id_fk",
"tableFrom": "image_to_user",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"opinions": {
"name": "opinions",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'3Choice'"
}
},
"indexes": {
"opinions_name_unique": {
"name": "opinions_name_unique",
"columns": [
"name"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"provinces": {
"name": "provinces",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"provinces_name_unique": {
"name": "provinces_name_unique",
"columns": [
"name"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"users": {
"name": "users",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"firstName": {
"name": "firstName",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"lastName": {
"name": "lastName",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"cid": {
"name": "cid",
"type": "text(13)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"age": {
"name": "age",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"phone": {
"name": "phone",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"public_phone": {
"name": "public_phone",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"facebook": {
"name": "facebook",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"twitter": {
"name": "twitter",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"tiktok": {
"name": "tiktok",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"other_social": {
"name": "other_social",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"image": {
"name": "image",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"job": {
"name": "job",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"education": {
"name": "education",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"vision": {
"name": "vision",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"reason": {
"name": "reason",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"group_id": {
"name": "group_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"zone_id": {
"name": "zone_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"users_cid_unique": {
"name": "users_cid_unique",
"columns": [
"cid"
],
"isUnique": true
},
"users_phone_unique": {
"name": "users_phone_unique",
"columns": [
"phone"
],
"isUnique": true
},
"phone_idx": {
"name": "phone_idx",
"columns": [
"phone"
],
"isUnique": false
},
"image_idx": {
"name": "image_idx",
"columns": [
"image"
],
"isUnique": false
}
},
"foreignKeys": {
"users_group_id_groups_id_fk": {
"name": "users_group_id_groups_id_fk",
"tableFrom": "users",
"tableTo": "groups",
"columnsFrom": [
"group_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"users_zone_id_zones_id_fk": {
"name": "users_zone_id_zones_id_fk",
"tableFrom": "users",
"tableTo": "zones",
"columnsFrom": [
"zone_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"user_opinions": {
"name": "user_opinions",
"columns": {
"user_id": {
"name": "user_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"opinion_id": {
"name": "opinion_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"choice": {
"name": "choice",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": "'deciding'"
}
},
"indexes": {},
"foreignKeys": {
"user_opinions_user_id_users_id_fk": {
"name": "user_opinions_user_id_users_id_fk",
"tableFrom": "user_opinions",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"user_opinions_opinion_id_opinions_id_fk": {
"name": "user_opinions_opinion_id_opinions_id_fk",
"tableFrom": "user_opinions",
"tableTo": "opinions",
"columnsFrom": [
"opinion_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"user_opinions_user_id_opinion_id_pk": {
"columns": [
"opinion_id",
"user_id"
],
"name": "user_opinions_user_id_opinion_id_pk"
}
},
"uniqueConstraints": {}
},
"zones": {
"name": "zones",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"province_id": {
"name": "province_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"zones_name_province_id_unique": {
"name": "zones_name_province_id_unique",
"columns": [
"name",
"province_id"
],
"isUnique": true
}
},
"foreignKeys": {
"zones_province_id_provinces_id_fk": {
"name": "zones_province_id_provinces_id_fk",
"tableFrom": "zones",
"tableTo": "provinces",
"columnsFrom": [
"province_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
}
}

View File

@@ -8,6 +8,13 @@
"when": 1713548458041, "when": 1713548458041,
"tag": "0000_right_nebula", "tag": "0000_right_nebula",
"breakpoints": true "breakpoints": true
},
{
"idx": 1,
"version": "5",
"when": 1713599233997,
"tag": "0001_chilly_bullseye",
"breakpoints": true
} }
] ]
} }

View File

@@ -22,6 +22,7 @@
"drizzle-zod": "^0.5.1", "drizzle-zod": "^0.5.1",
"express": "^4.19.2", "express": "^4.19.2",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"minio": "^7.1.3",
"trpc-playground": "^1.0.4", "trpc-playground": "^1.0.4",
"zod": "^3.22.4" "zod": "^3.22.4"
}, },

205
pnpm-lock.yaml generated
View File

@@ -29,6 +29,9 @@ dependencies:
jsonwebtoken: jsonwebtoken:
specifier: ^9.0.2 specifier: ^9.0.2
version: 9.0.2 version: 9.0.2
minio:
specifier: ^7.1.3
version: 7.1.3
trpc-playground: trpc-playground:
specifier: ^1.0.4 specifier: ^1.0.4
version: 1.0.4(@trpc/server@10.45.2)(@types/node@20.12.7)(express@4.19.2)(typescript@5.4.5)(zod@3.22.4) version: 1.0.4(@trpc/server@10.45.2)(@types/node@20.12.7)(express@4.19.2)(typescript@5.4.5)(zod@3.22.4)
@@ -898,6 +901,12 @@ packages:
'@types/send': 0.17.4 '@types/send': 0.17.4
dev: true dev: true
/@zxing/text-encoding@0.9.0:
resolution: {integrity: sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==}
requiresBuild: true
dev: false
optional: true
/abbrev@1.1.1: /abbrev@1.1.1:
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
dev: true dev: true
@@ -941,6 +950,17 @@ packages:
resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
dev: false dev: false
/async@3.2.5:
resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==}
dev: false
/available-typed-arrays@1.0.7:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
dependencies:
possible-typed-array-names: 1.0.0
dev: false
/balanced-match@1.0.2: /balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true dev: true
@@ -1001,6 +1021,12 @@ packages:
readable-stream: 3.6.2 readable-stream: 3.6.2
dev: false dev: false
/block-stream2@2.1.0:
resolution: {integrity: sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==}
dependencies:
readable-stream: 3.6.2
dev: false
/body-parser@1.20.2: /body-parser@1.20.2:
resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
@@ -1041,6 +1067,14 @@ packages:
fill-range: 7.0.1 fill-range: 7.0.1
dev: true dev: true
/browser-or-node@2.1.1:
resolution: {integrity: sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==}
dev: false
/buffer-crc32@0.2.13:
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
dev: false
/buffer-equal-constant-time@1.0.1: /buffer-equal-constant-time@1.0.1:
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
dev: false dev: false
@@ -1250,6 +1284,11 @@ packages:
supports-color: 5.5.0 supports-color: 5.5.0
dev: true dev: true
/decode-uri-component@0.2.2:
resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==}
engines: {node: '>=0.10'}
dev: false
/decompress-response@6.0.0: /decompress-response@6.0.0:
resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -1710,6 +1749,13 @@ packages:
micromatch: 4.0.5 micromatch: 4.0.5
dev: true dev: true
/fast-xml-parser@4.3.6:
resolution: {integrity: sha512-M2SovcRxD4+vC493Uc2GZVcZaj66CCJhWurC4viynVSTvrpErCShNcDz1lAho6n9REQKvL/ll4A4/fw6Y9z8nw==}
hasBin: true
dependencies:
strnum: 1.0.5
dev: false
/fastq@1.17.1: /fastq@1.17.1:
resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
dependencies: dependencies:
@@ -1750,6 +1796,11 @@ packages:
to-regex-range: 5.0.1 to-regex-range: 5.0.1
dev: true dev: true
/filter-obj@1.1.0:
resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==}
engines: {node: '>=0.10.0'}
dev: false
/finalhandler@1.2.0: /finalhandler@1.2.0:
resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
@@ -1772,6 +1823,12 @@ packages:
semver-regex: 4.0.5 semver-regex: 4.0.5
dev: true dev: true
/for-each@0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
dependencies:
is-callable: 1.2.7
dev: false
/forwarded@0.2.0: /forwarded@0.2.0:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
@@ -1908,6 +1965,13 @@ packages:
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dev: false dev: false
/has-tostringtag@1.0.2:
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
engines: {node: '>= 0.4'}
dependencies:
has-symbols: 1.0.3
dev: false
/hasown@2.0.2: /hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -1980,6 +2044,19 @@ packages:
engines: {node: '>= 0.10'} engines: {node: '>= 0.10'}
dev: false dev: false
/ipaddr.js@2.2.0:
resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==}
engines: {node: '>= 10'}
dev: false
/is-arguments@1.1.1:
resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==}
engines: {node: '>= 0.4'}
dependencies:
call-bind: 1.0.7
has-tostringtag: 1.0.2
dev: false
/is-binary-path@2.1.0: /is-binary-path@2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -1987,11 +2064,23 @@ packages:
binary-extensions: 2.3.0 binary-extensions: 2.3.0
dev: true dev: true
/is-callable@1.2.7:
resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
engines: {node: '>= 0.4'}
dev: false
/is-extglob@2.1.1: /is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true
/is-generator-function@1.0.10:
resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==}
engines: {node: '>= 0.4'}
dependencies:
has-tostringtag: 1.0.2
dev: false
/is-glob@4.0.3: /is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -2023,6 +2112,13 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/is-typed-array@1.1.13:
resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==}
engines: {node: '>= 0.4'}
dependencies:
which-typed-array: 1.1.15
dev: false
/is-what@4.1.16: /is-what@4.1.16:
resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
engines: {node: '>=12.13'} engines: {node: '>=12.13'}
@@ -2045,6 +2141,10 @@ packages:
dreamopt: 0.8.0 dreamopt: 0.8.0
dev: true dev: true
/json-stream@1.0.0:
resolution: {integrity: sha512-H/ZGY0nIAg3QcOwE1QN/rK/Fa7gJn7Ii5obwp6zyPO4xiPNwpIMjqy2gwjBEGqzkF/vSWEIBQCBuN19hYiL6Qg==}
dev: false
/jsonwebtoken@9.0.2: /jsonwebtoken@9.0.2:
resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==}
engines: {node: '>=12', npm: '>=6'} engines: {node: '>=12', npm: '>=6'}
@@ -2252,6 +2352,26 @@ packages:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
dev: false dev: false
/minio@7.1.3:
resolution: {integrity: sha512-xPrLjWkTT5E7H7VnzOjF//xBp9I40jYB4aWhb2xTFopXXfw+Wo82DDWngdUju7Doy3Wk7R8C4LAgwhLHHnf0wA==}
engines: {node: ^16 || ^18 || >=20}
dependencies:
async: 3.2.5
block-stream2: 2.1.0
browser-or-node: 2.1.1
buffer-crc32: 0.2.13
fast-xml-parser: 4.3.6
ipaddr.js: 2.2.0
json-stream: 1.0.0
lodash: 4.17.21
mime-types: 2.1.35
query-string: 7.1.3
through2: 4.0.2
web-encoding: 1.1.5
xml: 1.0.1
xml2js: 0.5.0
dev: false
/mkdirp-classic@0.5.3: /mkdirp-classic@0.5.3:
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
dev: false dev: false
@@ -2459,6 +2579,11 @@ packages:
nice-napi: 1.0.2 nice-napi: 1.0.2
dev: true dev: true
/possible-typed-array-names@1.0.0:
resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==}
engines: {node: '>= 0.4'}
dev: false
/postcss@8.4.38: /postcss@8.4.38:
resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
@@ -2516,6 +2641,16 @@ packages:
side-channel: 1.0.6 side-channel: 1.0.6
dev: false dev: false
/query-string@7.1.3:
resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==}
engines: {node: '>=6'}
dependencies:
decode-uri-component: 0.2.2
filter-obj: 1.1.0
split-on-first: 1.1.0
strict-uri-encode: 2.0.0
dev: false
/queue-microtask@1.2.3: /queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
dev: true dev: true
@@ -2612,6 +2747,10 @@ packages:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
dev: false dev: false
/sax@1.3.0:
resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==}
dev: false
/semver-regex@4.0.5: /semver-regex@4.0.5:
resolution: {integrity: sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==} resolution: {integrity: sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@@ -2782,11 +2921,21 @@ packages:
engines: {node: '>= 8'} engines: {node: '>= 8'}
dev: true dev: true
/split-on-first@1.1.0:
resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==}
engines: {node: '>=6'}
dev: false
/statuses@2.0.1: /statuses@2.0.1:
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
dev: false dev: false
/strict-uri-encode@2.0.0:
resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==}
engines: {node: '>=4'}
dev: false
/string_decoder@1.3.0: /string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
dependencies: dependencies:
@@ -2812,6 +2961,10 @@ packages:
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dev: true dev: true
/strnum@1.0.5:
resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==}
dev: false
/strtok3@7.0.0: /strtok3@7.0.0:
resolution: {integrity: sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ==} resolution: {integrity: sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ==}
engines: {node: '>=14.16'} engines: {node: '>=14.16'}
@@ -2854,6 +3007,12 @@ packages:
readable-stream: 3.6.2 readable-stream: 3.6.2
dev: false dev: false
/through2@4.0.2:
resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==}
dependencies:
readable-stream: 3.6.2
dev: false
/timers-ext@0.1.7: /timers-ext@0.1.7:
resolution: {integrity: sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==} resolution: {integrity: sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==}
dependencies: dependencies:
@@ -3008,6 +3167,16 @@ packages:
/util-deprecate@1.0.2: /util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
/util@0.12.5:
resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==}
dependencies:
inherits: 2.0.4
is-arguments: 1.1.1
is-generator-function: 1.0.10
is-typed-array: 1.1.13
which-typed-array: 1.1.15
dev: false
/utils-merge@1.0.1: /utils-merge@1.0.1:
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
engines: {node: '>= 0.4.0'} engines: {node: '>= 0.4.0'}
@@ -3078,6 +3247,25 @@ packages:
fsevents: 2.3.3 fsevents: 2.3.3
dev: false dev: false
/web-encoding@1.1.5:
resolution: {integrity: sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==}
dependencies:
util: 0.12.5
optionalDependencies:
'@zxing/text-encoding': 0.9.0
dev: false
/which-typed-array@1.1.15:
resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==}
engines: {node: '>= 0.4'}
dependencies:
available-typed-arrays: 1.0.7
call-bind: 1.0.7
for-each: 0.3.3
gopd: 1.0.1
has-tostringtag: 1.0.2
dev: false
/which@1.3.1: /which@1.3.1:
resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
hasBin: true hasBin: true
@@ -3100,6 +3288,23 @@ packages:
/wrappy@1.0.2: /wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
/xml2js@0.5.0:
resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==}
engines: {node: '>=4.0.0'}
dependencies:
sax: 1.3.0
xmlbuilder: 11.0.1
dev: false
/xml@1.0.1:
resolution: {integrity: sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==}
dev: false
/xmlbuilder@11.0.1:
resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==}
engines: {node: '>=4.0'}
dev: false
/xss@1.0.15: /xss@1.0.15:
resolution: {integrity: sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==} resolution: {integrity: sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==}
engines: {node: '>= 0.10.0'} engines: {node: '>= 0.10.0'}

View File

@@ -4,4 +4,12 @@ export const Config = {
"T4kE6/tIqCVEZYg9lwsqeJjYfOoXTXSXDEMyParsJjj57CjSdkrfPOLWP74/9lJpcBA=", "T4kE6/tIqCVEZYg9lwsqeJjYfOoXTXSXDEMyParsJjj57CjSdkrfPOLWP74/9lJpcBA=",
token_duration: process.env.TOKEN_DURATION || "365d", token_duration: process.env.TOKEN_DURATION || "365d",
api_url: process.env.API_URL || "http://localhost:3000", api_url: process.env.API_URL || "http://localhost:3000",
bucketName: process.env.BUCKET_NAME || "sorvor",
minioEndpoint: process.env.MINIO_ENDPOINT || "localhost",
minioSSL: (process.env.MINIO_SSL && process.env.MINIO_SSL == "true") || false,
minioPort: parseInt(process.env.MINIO_PORT || "9000"),
minioAccessKey: process.env.MINIO_ACCESS_KEY || "minioadmin",
minioSecretKey:
process.env.MINIO_SECRET_KEY ||
"K7RSS3iy/191QBeYwLJALtxGfZIHJVdBigSMdBXjqNE=",
}; };

40
src/minio.ts Normal file
View File

@@ -0,0 +1,40 @@
import * as minio from "minio";
import { Config } from "./config";
export function createClient() {
const mc = new minio.Client({
endPoint: Config.minioEndpoint,
useSSL: Config.minioSSL,
port: Config.minioPort,
accessKey: Config.minioAccessKey,
secretKey: Config.minioSecretKey,
});
return mc;
}
export async function createUploadImageUrl(
mc: minio.Client,
objectName: string,
contentType: string
) {
let policy = mc.newPostPolicy();
policy.setKey(objectName);
policy.setBucket(Config.bucketName);
let expires = new Date();
expires.setSeconds(30 * 60);
policy.setExpires(expires);
policy.setContentType(contentType);
policy.setContentDisposition(`attachment; filename="${objectName}"`);
policy.setContentLengthRange(1, 3 * 1024 * 1024);
let rs = await mc.presignedPostPolicy(policy);
return rs;
}
export async function createBucket(mc: minio.Client) {
if (!(await mc.bucketExists(Config.bucketName))) {
await mc.makeBucket(Config.bucketName);
} else {
console.log("Bucket already exists");
}
}

View File

@@ -24,6 +24,7 @@ export const user = sqliteTable(
twitter: text("twitter"), twitter: text("twitter"),
tiktok: text("tiktok"), tiktok: text("tiktok"),
otherSocial: text("other_social"), otherSocial: text("other_social"),
image: text("image"),
email: text("email"), email: text("email"),
job: text("job").notNull(), job: text("job").notNull(),
education: text("education").notNull(), education: text("education").notNull(),
@@ -38,6 +39,7 @@ export const user = sqliteTable(
}, },
(t) => ({ (t) => ({
phone_idx: index("phone_idx").on(t.phone), phone_idx: index("phone_idx").on(t.phone),
image_idx: index("image_idx").on(t.image),
}) })
); );
@@ -127,10 +129,12 @@ export const provinceRelation = relations(province, ({ many }) => ({
zones: many(zone), zones: many(zone),
})); }));
//----------------PhoneToken //----------------ImageToUser
export const phoneToken = sqliteTable("phone_tokens", { export const imageToUser = sqliteTable("image_to_user", {
phone: text("phone").primaryKey(), userId: integer("user_id")
token: text("token").notNull(), .primaryKey()
.references(() => user.id),
imageName: text("image_name").notNull(),
createdOn: integer("created_on", { mode: "timestamp" }).default( createdOn: integer("created_on", { mode: "timestamp" }).default(
sql`CURRENT_TIMESTAMP` sql`CURRENT_TIMESTAMP`
), ),

View File

@@ -1,12 +1,13 @@
import { router, publicProcedure, protectedProcedure } from "./trpc"; import { router, publicProcedure, protectedProcedure } from "./trpc";
import { db } from "./db"; import { db } from "./db";
import { opinion, user, userOpinion } from "./schema"; import { imageToUser, opinion, user, userOpinion } from "./schema";
import { createInsertSchema } from "drizzle-zod"; import { createInsertSchema } from "drizzle-zod";
import { z } from "zod"; import { z } from "zod";
import { SQL, eq } from "drizzle-orm"; import { SQL, count, eq } from "drizzle-orm";
import { Config } from "./config"; import { Config } from "./config";
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import * as jwt from "jsonwebtoken"; import * as jwt from "jsonwebtoken";
import { createClient, createUploadImageUrl } from "./minio";
const userInsertSchema = createInsertSchema(user, { const userInsertSchema = createInsertSchema(user, {
cid: (schema) => cid: (schema) =>
@@ -33,6 +34,41 @@ type UserInsertSchema = z.infer<typeof userInsertSchema>;
type UserUpdateSchema = z.infer<typeof userUpdateSchema>; type UserUpdateSchema = z.infer<typeof userUpdateSchema>;
export const userRoute = router({ export const userRoute = router({
createUser: publicProcedure
.input(
userInsertSchema.omit({ id: true }).extend({
opinions: opinionInsertSchema,
})
)
.mutation(
async ({ input }) => await createUser({ ...input }, input.opinions)
),
// changeImage: protectedProcedure
updateUser: protectedProcedure
.input(userUpdateSchema)
.mutation(async ({ input, ctx }) => await updateUser(ctx.user.id, input)),
login: publicProcedure
.input(z.object({ cid: z.string(), phone: z.string() }))
.mutation(async ({ input }) => await login(input.cid, input.phone)),
changeOpinion: protectedProcedure
.input(opinionUpdateSchema)
.mutation(
async ({ input, ctx }) =>
await changeOpinion(input.opinionId, ctx.user.id, input.choice)
),
requestChangeImage: protectedProcedure
.input(z.object({ imageName: z.string(), contentType: z.string() }))
.mutation(
async ({ input, ctx }) =>
await requestChangeImage(
ctx.user.id,
input.imageName,
input.contentType
)
),
confirmChangeImage: protectedProcedure.mutation(
async ({ ctx }) => await confirmChangeImage(ctx.user.id, ctx.user.image)
),
getAllUser: publicProcedure getAllUser: publicProcedure
.input( .input(
z.object({ z.object({
@@ -53,27 +89,6 @@ export const userRoute = router({
input.zone input.zone
) )
), ),
createUser: publicProcedure
.input(
userInsertSchema.omit({ id: true }).extend({
opinions: opinionInsertSchema,
})
)
.mutation(
async ({ input }) => await createUser({ ...input }, input.opinions)
),
updateUser: protectedProcedure
.input(userUpdateSchema)
.mutation(async ({ input, ctx }) => await updateUser(ctx.user.id, input)),
login: publicProcedure
.input(z.object({ cid: z.string(), phone: z.string() }))
.mutation(async ({ input }) => await login(input.cid, input.phone)),
changeOpinion: protectedProcedure
.input(opinionUpdateSchema)
.mutation(
async ({ input, ctx }) =>
await changeOpinion(input.opinionId, ctx.user.id, input.choice)
),
}); });
async function getAllUser( async function getAllUser(
@@ -112,6 +127,7 @@ async function getAllUser(
phone: hidePhone(u.phone), phone: hidePhone(u.phone),
})); }));
} }
async function createUser( async function createUser(
newUser: UserInsertSchema, newUser: UserInsertSchema,
opinions: OpinionInsertSchema opinions: OpinionInsertSchema
@@ -154,12 +170,6 @@ async function login(cid: string, phone: string) {
} }
} }
function createJWT(phone: string) {
return jwt.sign({ phone: phone }, Config.jwt_secret, {
expiresIn: "365d",
});
}
async function changeOpinion( async function changeOpinion(
opinionId: number, opinionId: number,
userId: number, userId: number,
@@ -203,6 +213,99 @@ async function changeOpinion(
}); });
} }
} }
async function requestChangeImage(
userId: number,
imageName: string,
contentType: string
) {
const mc = createClient();
// Check if the image is valid
const allowedImageTypes = z.enum(["image/png", "image/jpeg", "image/webp"]);
if (!allowedImageTypes.safeParse(contentType).success) {
throw new TRPCError({
message: "Only PNG, JPEG, and WEBP images are allowed",
code: "BAD_REQUEST",
});
}
const allowedExtension = z.enum(["png", "jpeg", "jpg", "webp"]);
const extension = imageName.split(".").pop();
if (!allowedExtension.safeParse(extension).success) {
throw new TRPCError({
message: "only .png, .jpeg, .jpg, and .webp extensions are allowed",
code: "BAD_REQUEST",
});
}
// Create a unique image name
let tryCount = 0;
let objectName: string | null = null;
while (tryCount < 3) {
let imageName = `${generateRandomString()}.${extension}`;
let ok = await db
.select({ value: count(user.image) })
.from(user)
.where(eq(user.image, imageName))
.then((v) => v[0].value === 0);
if (ok) {
objectName = imageName;
break;
}
}
if (objectName === null) {
throw new TRPCError({
message: "Unable to create image request (conflicting name)",
code: "INTERNAL_SERVER_ERROR",
});
}
// Store a record in the database
await db
.insert(imageToUser)
.values({ userId, imageName: objectName })
.onConflictDoUpdate({
target: [imageToUser.userId],
set: { imageName: objectName },
});
return await createUploadImageUrl(mc, objectName, contentType);
}
async function confirmChangeImage(userId: number, oldImage: string | null) {
const mc = createClient();
let rs = await db
.select({ imageName: imageToUser.imageName })
.from(imageToUser)
.where(eq(imageToUser.userId, userId));
if (rs.length === 0) {
throw new TRPCError({
message: "No image request found",
code: "BAD_REQUEST",
});
}
let imageName = rs[0].imageName;
const isImageExist = await mc
.statObject(Config.bucketName, imageName)
.then(() => true)
.catch(() => false);
if (!isImageExist) {
throw new TRPCError({
message: "Image not found",
code: "BAD_REQUEST",
});
}
const promises: Promise<any>[] = [];
if (oldImage) {
promises.push(mc.removeObject(Config.bucketName, oldImage).catch(() => {}));
}
const updateUser = db
.update(user)
.set({ image: imageName })
.where(eq(user.id, userId));
const deleteRecord = db
.delete(imageToUser)
.where(eq(imageToUser.userId, userId));
await Promise.all([...promises, updateUser, deleteRecord]);
return { status: "success" };
}
function isValidThaiID(id: string) { function isValidThaiID(id: string) {
if (!/^\d{13}$/.test(id)) { if (!/^\d{13}$/.test(id)) {
return false; return false;
@@ -219,3 +322,13 @@ function hidePhone(phone: string | null) {
if (phone === null) return phone; if (phone === null) return phone;
return phone.slice(0, 2).concat("******").concat(phone.slice(-3)); return phone.slice(0, 2).concat("******").concat(phone.slice(-3));
} }
function createJWT(phone: string) {
return jwt.sign({ phone: phone }, Config.jwt_secret, {
expiresIn: "365d",
});
}
function generateRandomString() {
return Math.random().toString(36).substring(2, 15);
}