mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-09 23:48:36 +01:00
update
This commit is contained in:
8
broker/.gitignore
vendored
8
broker/.gitignore
vendored
@@ -1,8 +0,0 @@
|
|||||||
node_modules
|
|
||||||
static
|
|
||||||
ecosystem.config.cjs
|
|
||||||
dist
|
|
||||||
scripts/start_dev.js
|
|
||||||
package-lock.json
|
|
||||||
build_all.bat
|
|
||||||
tests
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
ARG NODE_VERSION=21
|
|
||||||
|
|
||||||
FROM node:${NODE_VERSION}-alpine as base
|
|
||||||
|
|
||||||
ENV NODE_ENV=development
|
|
||||||
|
|
||||||
# Build stage
|
|
||||||
|
|
||||||
FROM base as build
|
|
||||||
|
|
||||||
RUN npm install -g pnpm
|
|
||||||
|
|
||||||
COPY --link broker/package.json broker/pnpm-lock.yaml home/app/
|
|
||||||
|
|
||||||
COPY --link shared/package.json shared/pnpm-lock.yaml /home/shared/
|
|
||||||
|
|
||||||
WORKDIR /home/app
|
|
||||||
RUN pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
WORKDIR /home/shared
|
|
||||||
RUN pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
COPY --link ../broker /home/app
|
|
||||||
|
|
||||||
COPY --link ../shared /home/shared
|
|
||||||
|
|
||||||
WORKDIR /home/app
|
|
||||||
|
|
||||||
RUN pnpm run build_all
|
|
||||||
|
|
||||||
RUN pnpm prune
|
|
||||||
|
|
||||||
# Final stage
|
|
||||||
|
|
||||||
FROM base
|
|
||||||
|
|
||||||
COPY --from=build /home/app /home/app
|
|
||||||
|
|
||||||
WORKDIR /home/app
|
|
||||||
|
|
||||||
EXPOSE ${PORT}
|
|
||||||
|
|
||||||
CMD ["node", "dist/app/src/index.js"]
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
apps: [
|
|
||||||
{
|
|
||||||
name: 'QueueBroker',
|
|
||||||
port: '3999',
|
|
||||||
exec_mode: 'fork',
|
|
||||||
script: './dist/producer/src/index.js',
|
|
||||||
env: {
|
|
||||||
EMAIL_SERVICE: "",
|
|
||||||
BREVO_API_KEY: "",
|
|
||||||
PORT: "",
|
|
||||||
MONGO_CONNECTION_STRING: "",
|
|
||||||
REDIS_URL: "",
|
|
||||||
REDIS_USERNAME: "",
|
|
||||||
REDIS_PASSWORD: "",
|
|
||||||
STREAM_NAME: ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
/** @type {import('ts-jest').JestConfigWithTsJest} **/
|
|
||||||
module.exports = {
|
|
||||||
testEnvironment: "node",
|
|
||||||
transform: {
|
|
||||||
"^.+.tsx?$": ["ts-jest",{}],
|
|
||||||
},
|
|
||||||
moduleNameMapper: {
|
|
||||||
'@services/(.*)': '<rootDir>/../shared/services/$1',
|
|
||||||
'@data/(.*)': '<rootDir>/../shared/data/$1',
|
|
||||||
'@functions/(.*)': '<rootDir>/../shared/functions/$1',
|
|
||||||
'@schema/(.*)': '<rootDir>/../shared/schema/$1',
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
{
|
|
||||||
"dependencies": {
|
|
||||||
"@getbrevo/brevo": "^2.2.0",
|
|
||||||
"cors": "^2.8.5",
|
|
||||||
"express": "^4.19.2",
|
|
||||||
"mongoose": "^8.3.2",
|
|
||||||
"nodemailer": "^6.9.13",
|
|
||||||
"redis": "^4.6.14",
|
|
||||||
"ua-parser-js": "^1.0.37"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@jest/globals": "^29.7.0",
|
|
||||||
"@types/cors": "^2.8.17",
|
|
||||||
"@types/express": "^4.17.21",
|
|
||||||
"@types/jest": "^29.5.12",
|
|
||||||
"@types/node": "^20.12.13",
|
|
||||||
"@types/nodemailer": "^6.4.15",
|
|
||||||
"@types/ua-parser-js": "^0.7.39",
|
|
||||||
"glob": "^10.4.1",
|
|
||||||
"jest": "^29.7.0",
|
|
||||||
"node-ssh": "^13.2.0",
|
|
||||||
"ts-jest": "^29.2.5",
|
|
||||||
"ts-node": "^10.9.2",
|
|
||||||
"typescript": "^5.4.5"
|
|
||||||
},
|
|
||||||
"name": "litlyx-queue-broker",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"main": "dist/index.js",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "node scripts/start_dev.js",
|
|
||||||
"compile": "tsc",
|
|
||||||
"build": "ts-node scripts/build.ts",
|
|
||||||
"create_db": "cd scripts && ts-node create_database.ts",
|
|
||||||
"build_all": "npm run compile && npm run build && npm run create_db",
|
|
||||||
"docker-build": "docker build -t litlyx-broker -f Dockerfile ../",
|
|
||||||
"docker-inspect": "docker run -it litlyx-broker sh",
|
|
||||||
"test": "jest"
|
|
||||||
},
|
|
||||||
"keywords": [],
|
|
||||||
"author": "Emily",
|
|
||||||
"license": "MIT",
|
|
||||||
"description": "Queue broker for Litlyx - Saves events to database."
|
|
||||||
}
|
|
||||||
4685
broker/pnpm-lock.yaml
generated
4685
broker/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,253 +0,0 @@
|
|||||||
geoname_id,locale_code,continent_code,continent_name,country_iso_code,country_name,is_in_european_union
|
|
||||||
49518,en,AF,Africa,RW,Rwanda,0
|
|
||||||
51537,en,AF,Africa,SO,Somalia,0
|
|
||||||
69543,en,AS,Asia,YE,Yemen,0
|
|
||||||
99237,en,AS,Asia,IQ,Iraq,0
|
|
||||||
102358,en,AS,Asia,SA,"Saudi Arabia",0
|
|
||||||
130758,en,AS,Asia,IR,Iran,0
|
|
||||||
146669,en,EU,Europe,CY,Cyprus,1
|
|
||||||
149590,en,AF,Africa,TZ,Tanzania,0
|
|
||||||
163843,en,AS,Asia,SY,Syria,0
|
|
||||||
174982,en,AS,Asia,AM,Armenia,0
|
|
||||||
192950,en,AF,Africa,KE,Kenya,0
|
|
||||||
203312,en,AF,Africa,CD,"DR Congo",0
|
|
||||||
223816,en,AF,Africa,DJ,Djibouti,0
|
|
||||||
226074,en,AF,Africa,UG,Uganda,0
|
|
||||||
239880,en,AF,Africa,CF,"Central African Republic",0
|
|
||||||
241170,en,AF,Africa,SC,Seychelles,0
|
|
||||||
248816,en,AS,Asia,JO,Jordan,0
|
|
||||||
272103,en,AS,Asia,LB,Lebanon,0
|
|
||||||
285570,en,AS,Asia,KW,Kuwait,0
|
|
||||||
286963,en,AS,Asia,OM,Oman,0
|
|
||||||
289688,en,AS,Asia,QA,Qatar,0
|
|
||||||
290291,en,AS,Asia,BH,Bahrain,0
|
|
||||||
290557,en,AS,Asia,AE,"United Arab Emirates",0
|
|
||||||
294640,en,AS,Asia,IL,Israel,0
|
|
||||||
298795,en,AS,Asia,TR,Türkiye,0
|
|
||||||
337996,en,AF,Africa,ET,Ethiopia,0
|
|
||||||
338010,en,AF,Africa,ER,Eritrea,0
|
|
||||||
357994,en,AF,Africa,EG,Egypt,0
|
|
||||||
366755,en,AF,Africa,SD,Sudan,0
|
|
||||||
390903,en,EU,Europe,GR,Greece,1
|
|
||||||
433561,en,AF,Africa,BI,Burundi,0
|
|
||||||
453733,en,EU,Europe,EE,Estonia,1
|
|
||||||
458258,en,EU,Europe,LV,Latvia,1
|
|
||||||
587116,en,AS,Asia,AZ,Azerbaijan,0
|
|
||||||
597427,en,EU,Europe,LT,Lithuania,1
|
|
||||||
607072,en,EU,Europe,SJ,"Svalbard and Jan Mayen",0
|
|
||||||
614540,en,AS,Asia,GE,Georgia,0
|
|
||||||
617790,en,EU,Europe,MD,Moldova,0
|
|
||||||
630336,en,EU,Europe,BY,Belarus,0
|
|
||||||
660013,en,EU,Europe,FI,Finland,1
|
|
||||||
661882,en,EU,Europe,AX,"Åland Islands",1
|
|
||||||
690791,en,EU,Europe,UA,Ukraine,0
|
|
||||||
718075,en,EU,Europe,MK,"North Macedonia",0
|
|
||||||
719819,en,EU,Europe,HU,Hungary,1
|
|
||||||
732800,en,EU,Europe,BG,Bulgaria,1
|
|
||||||
783754,en,EU,Europe,AL,Albania,0
|
|
||||||
798544,en,EU,Europe,PL,Poland,1
|
|
||||||
798549,en,EU,Europe,RO,Romania,1
|
|
||||||
831053,en,EU,Europe,XK,Kosovo,0
|
|
||||||
878675,en,AF,Africa,ZW,Zimbabwe,0
|
|
||||||
895949,en,AF,Africa,ZM,Zambia,0
|
|
||||||
921929,en,AF,Africa,KM,Comoros,0
|
|
||||||
927384,en,AF,Africa,MW,Malawi,0
|
|
||||||
932692,en,AF,Africa,LS,Lesotho,0
|
|
||||||
933860,en,AF,Africa,BW,Botswana,0
|
|
||||||
934292,en,AF,Africa,MU,Mauritius,0
|
|
||||||
934841,en,AF,Africa,SZ,Eswatini,0
|
|
||||||
935317,en,AF,Africa,RE,Réunion,1
|
|
||||||
953987,en,AF,Africa,ZA,"South Africa",0
|
|
||||||
1024031,en,AF,Africa,YT,Mayotte,1
|
|
||||||
1036973,en,AF,Africa,MZ,Mozambique,0
|
|
||||||
1062947,en,AF,Africa,MG,Madagascar,0
|
|
||||||
1149361,en,AS,Asia,AF,Afghanistan,0
|
|
||||||
1168579,en,AS,Asia,PK,Pakistan,0
|
|
||||||
1210997,en,AS,Asia,BD,Bangladesh,0
|
|
||||||
1218197,en,AS,Asia,TM,Turkmenistan,0
|
|
||||||
1220409,en,AS,Asia,TJ,Tajikistan,0
|
|
||||||
1227603,en,AS,Asia,LK,"Sri Lanka",0
|
|
||||||
1252634,en,AS,Asia,BT,Bhutan,0
|
|
||||||
1269750,en,AS,Asia,IN,India,0
|
|
||||||
1282028,en,AS,Asia,MV,Maldives,0
|
|
||||||
1282588,en,AS,Asia,IO,"British Indian Ocean Territory",0
|
|
||||||
1282988,en,AS,Asia,NP,Nepal,0
|
|
||||||
1327865,en,AS,Asia,MM,Myanmar,0
|
|
||||||
1512440,en,AS,Asia,UZ,Uzbekistan,0
|
|
||||||
1522867,en,AS,Asia,KZ,Kazakhstan,0
|
|
||||||
1527747,en,AS,Asia,KG,Kyrgyzstan,0
|
|
||||||
1546748,en,AN,Antarctica,TF,"French Southern Territories",0
|
|
||||||
1547314,en,AN,Antarctica,HM,"Heard and McDonald Islands",0
|
|
||||||
1547376,en,AS,Asia,CC,"Cocos (Keeling) Islands",0
|
|
||||||
1559582,en,OC,Oceania,PW,Palau,0
|
|
||||||
1562822,en,AS,Asia,VN,Vietnam,0
|
|
||||||
1605651,en,AS,Asia,TH,Thailand,0
|
|
||||||
1643084,en,AS,Asia,ID,Indonesia,0
|
|
||||||
1655842,en,AS,Asia,LA,Laos,0
|
|
||||||
1668284,en,AS,Asia,TW,Taiwan,0
|
|
||||||
1694008,en,AS,Asia,PH,Philippines,0
|
|
||||||
1733045,en,AS,Asia,MY,Malaysia,0
|
|
||||||
1814991,en,AS,Asia,CN,China,0
|
|
||||||
1819730,en,AS,Asia,HK,"Hong Kong",0
|
|
||||||
1820814,en,AS,Asia,BN,Brunei,0
|
|
||||||
1821275,en,AS,Asia,MO,Macao,0
|
|
||||||
1831722,en,AS,Asia,KH,Cambodia,0
|
|
||||||
1835841,en,AS,Asia,KR,"South Korea",0
|
|
||||||
1861060,en,AS,Asia,JP,Japan,0
|
|
||||||
1873107,en,AS,Asia,KP,"North Korea",0
|
|
||||||
1880251,en,AS,Asia,SG,Singapore,0
|
|
||||||
1899402,en,OC,Oceania,CK,"Cook Islands",0
|
|
||||||
1966436,en,OC,Oceania,TL,Timor-Leste,0
|
|
||||||
2017370,en,EU,Europe,RU,Russia,0
|
|
||||||
2029969,en,AS,Asia,MN,Mongolia,0
|
|
||||||
2077456,en,OC,Oceania,AU,Australia,0
|
|
||||||
2078138,en,OC,Oceania,CX,"Christmas Island",0
|
|
||||||
2080185,en,OC,Oceania,MH,"Marshall Islands",0
|
|
||||||
2081918,en,OC,Oceania,FM,"Federated States of Micronesia",0
|
|
||||||
2088628,en,OC,Oceania,PG,"Papua New Guinea",0
|
|
||||||
2103350,en,OC,Oceania,SB,"Solomon Islands",0
|
|
||||||
2110297,en,OC,Oceania,TV,Tuvalu,0
|
|
||||||
2110425,en,OC,Oceania,NR,Nauru,0
|
|
||||||
2134431,en,OC,Oceania,VU,Vanuatu,0
|
|
||||||
2139685,en,OC,Oceania,NC,"New Caledonia",0
|
|
||||||
2155115,en,OC,Oceania,NF,"Norfolk Island",0
|
|
||||||
2186224,en,OC,Oceania,NZ,"New Zealand",0
|
|
||||||
2205218,en,OC,Oceania,FJ,Fiji,0
|
|
||||||
2215636,en,AF,Africa,LY,Libya,0
|
|
||||||
2233387,en,AF,Africa,CM,Cameroon,0
|
|
||||||
2245662,en,AF,Africa,SN,Senegal,0
|
|
||||||
2260494,en,AF,Africa,CG,"Congo Republic",0
|
|
||||||
2264397,en,EU,Europe,PT,Portugal,1
|
|
||||||
2275384,en,AF,Africa,LR,Liberia,0
|
|
||||||
2287781,en,AF,Africa,CI,"Ivory Coast",0
|
|
||||||
2300660,en,AF,Africa,GH,Ghana,0
|
|
||||||
2309096,en,AF,Africa,GQ,"Equatorial Guinea",0
|
|
||||||
2328926,en,AF,Africa,NG,Nigeria,0
|
|
||||||
2361809,en,AF,Africa,BF,"Burkina Faso",0
|
|
||||||
2363686,en,AF,Africa,TG,Togo,0
|
|
||||||
2372248,en,AF,Africa,GW,Guinea-Bissau,0
|
|
||||||
2378080,en,AF,Africa,MR,Mauritania,0
|
|
||||||
2395170,en,AF,Africa,BJ,Benin,0
|
|
||||||
2400553,en,AF,Africa,GA,Gabon,0
|
|
||||||
2403846,en,AF,Africa,SL,"Sierra Leone",0
|
|
||||||
2410758,en,AF,Africa,ST,"São Tomé and Príncipe",0
|
|
||||||
2411586,en,EU,Europe,GI,Gibraltar,0
|
|
||||||
2413451,en,AF,Africa,GM,Gambia,0
|
|
||||||
2420477,en,AF,Africa,GN,Guinea,0
|
|
||||||
2434508,en,AF,Africa,TD,Chad,0
|
|
||||||
2440476,en,AF,Africa,NE,Niger,0
|
|
||||||
2453866,en,AF,Africa,ML,Mali,0
|
|
||||||
2461445,en,AF,Africa,EH,"Western Sahara",0
|
|
||||||
2464461,en,AF,Africa,TN,Tunisia,0
|
|
||||||
2510769,en,EU,Europe,ES,Spain,1
|
|
||||||
2542007,en,AF,Africa,MA,Morocco,0
|
|
||||||
2562770,en,EU,Europe,MT,Malta,1
|
|
||||||
2589581,en,AF,Africa,DZ,Algeria,0
|
|
||||||
2622320,en,EU,Europe,FO,"Faroe Islands",0
|
|
||||||
2623032,en,EU,Europe,DK,Denmark,1
|
|
||||||
2629691,en,EU,Europe,IS,Iceland,0
|
|
||||||
2635167,en,EU,Europe,GB,"United Kingdom",0
|
|
||||||
2658434,en,EU,Europe,CH,Switzerland,0
|
|
||||||
2661886,en,EU,Europe,SE,Sweden,1
|
|
||||||
2750405,en,EU,Europe,NL,"The Netherlands",1
|
|
||||||
2782113,en,EU,Europe,AT,Austria,1
|
|
||||||
2802361,en,EU,Europe,BE,Belgium,1
|
|
||||||
2921044,en,EU,Europe,DE,Germany,1
|
|
||||||
2960313,en,EU,Europe,LU,Luxembourg,1
|
|
||||||
2963597,en,EU,Europe,IE,Ireland,1
|
|
||||||
2993457,en,EU,Europe,MC,Monaco,0
|
|
||||||
3017382,en,EU,Europe,FR,France,1
|
|
||||||
3041565,en,EU,Europe,AD,Andorra,0
|
|
||||||
3042058,en,EU,Europe,LI,Liechtenstein,0
|
|
||||||
3042142,en,EU,Europe,JE,Jersey,0
|
|
||||||
3042225,en,EU,Europe,IM,"Isle of Man",0
|
|
||||||
3042362,en,EU,Europe,GG,Guernsey,0
|
|
||||||
3057568,en,EU,Europe,SK,Slovakia,1
|
|
||||||
3077311,en,EU,Europe,CZ,Czechia,1
|
|
||||||
3144096,en,EU,Europe,NO,Norway,0
|
|
||||||
3164670,en,EU,Europe,VA,"Vatican City",0
|
|
||||||
3168068,en,EU,Europe,SM,"San Marino",0
|
|
||||||
3175395,en,EU,Europe,IT,Italy,1
|
|
||||||
3190538,en,EU,Europe,SI,Slovenia,1
|
|
||||||
3194884,en,EU,Europe,ME,Montenegro,0
|
|
||||||
3202326,en,EU,Europe,HR,Croatia,1
|
|
||||||
3277605,en,EU,Europe,BA,"Bosnia and Herzegovina",0
|
|
||||||
3351879,en,AF,Africa,AO,Angola,0
|
|
||||||
3355338,en,AF,Africa,NA,Namibia,0
|
|
||||||
3370751,en,AF,Africa,SH,"Saint Helena",0
|
|
||||||
3371123,en,AN,Antarctica,BV,"Bouvet Island",0
|
|
||||||
3374084,en,NA,"North America",BB,Barbados,0
|
|
||||||
3374766,en,AF,Africa,CV,"Cabo Verde",0
|
|
||||||
3378535,en,SA,"South America",GY,Guyana,0
|
|
||||||
3381670,en,SA,"South America",GF,"French Guiana",1
|
|
||||||
3382998,en,SA,"South America",SR,Suriname,0
|
|
||||||
3424932,en,NA,"North America",PM,"Saint Pierre and Miquelon",0
|
|
||||||
3425505,en,NA,"North America",GL,Greenland,0
|
|
||||||
3437598,en,SA,"South America",PY,Paraguay,0
|
|
||||||
3439705,en,SA,"South America",UY,Uruguay,0
|
|
||||||
3469034,en,SA,"South America",BR,Brazil,0
|
|
||||||
3474414,en,SA,"South America",FK,"Falkland Islands",0
|
|
||||||
3474415,en,AN,Antarctica,GS,"South Georgia and the South Sandwich Islands",0
|
|
||||||
3489940,en,NA,"North America",JM,Jamaica,0
|
|
||||||
3508796,en,NA,"North America",DO,"Dominican Republic",0
|
|
||||||
3562981,en,NA,"North America",CU,Cuba,0
|
|
||||||
3570311,en,NA,"North America",MQ,Martinique,1
|
|
||||||
3572887,en,NA,"North America",BS,Bahamas,0
|
|
||||||
3573345,en,NA,"North America",BM,Bermuda,0
|
|
||||||
3573511,en,NA,"North America",AI,Anguilla,0
|
|
||||||
3573591,en,NA,"North America",TT,"Trinidad and Tobago",0
|
|
||||||
3575174,en,NA,"North America",KN,"St Kitts and Nevis",0
|
|
||||||
3575830,en,NA,"North America",DM,Dominica,0
|
|
||||||
3576396,en,NA,"North America",AG,"Antigua and Barbuda",0
|
|
||||||
3576468,en,NA,"North America",LC,"Saint Lucia",0
|
|
||||||
3576916,en,NA,"North America",TC,"Turks and Caicos Islands",0
|
|
||||||
3577279,en,NA,"North America",AW,Aruba,0
|
|
||||||
3577718,en,NA,"North America",VG,"British Virgin Islands",0
|
|
||||||
3577815,en,NA,"North America",VC,"St Vincent and Grenadines",0
|
|
||||||
3578097,en,NA,"North America",MS,Montserrat,0
|
|
||||||
3578421,en,NA,"North America",MF,"Saint Martin",1
|
|
||||||
3578476,en,NA,"North America",BL,"Saint Barthélemy",0
|
|
||||||
3579143,en,NA,"North America",GP,Guadeloupe,1
|
|
||||||
3580239,en,NA,"North America",GD,Grenada,0
|
|
||||||
3580718,en,NA,"North America",KY,"Cayman Islands",0
|
|
||||||
3582678,en,NA,"North America",BZ,Belize,0
|
|
||||||
3585968,en,NA,"North America",SV,"El Salvador",0
|
|
||||||
3595528,en,NA,"North America",GT,Guatemala,0
|
|
||||||
3608932,en,NA,"North America",HN,Honduras,0
|
|
||||||
3617476,en,NA,"North America",NI,Nicaragua,0
|
|
||||||
3624060,en,NA,"North America",CR,"Costa Rica",0
|
|
||||||
3625428,en,SA,"South America",VE,Venezuela,0
|
|
||||||
3658394,en,SA,"South America",EC,Ecuador,0
|
|
||||||
3686110,en,SA,"South America",CO,Colombia,0
|
|
||||||
3703430,en,NA,"North America",PA,Panama,0
|
|
||||||
3723988,en,NA,"North America",HT,Haiti,0
|
|
||||||
3865483,en,SA,"South America",AR,Argentina,0
|
|
||||||
3895114,en,SA,"South America",CL,Chile,0
|
|
||||||
3923057,en,SA,"South America",BO,Bolivia,0
|
|
||||||
3932488,en,SA,"South America",PE,Peru,0
|
|
||||||
3996063,en,NA,"North America",MX,Mexico,0
|
|
||||||
4030656,en,OC,Oceania,PF,"French Polynesia",0
|
|
||||||
4030699,en,OC,Oceania,PN,"Pitcairn Islands",0
|
|
||||||
4030945,en,OC,Oceania,KI,Kiribati,0
|
|
||||||
4031074,en,OC,Oceania,TK,Tokelau,0
|
|
||||||
4032283,en,OC,Oceania,TO,Tonga,0
|
|
||||||
4034749,en,OC,Oceania,WF,"Wallis and Futuna",0
|
|
||||||
4034894,en,OC,Oceania,WS,Samoa,0
|
|
||||||
4036232,en,OC,Oceania,NU,Niue,0
|
|
||||||
4041468,en,OC,Oceania,MP,"Northern Mariana Islands",0
|
|
||||||
4043988,en,OC,Oceania,GU,Guam,0
|
|
||||||
4566966,en,NA,"North America",PR,"Puerto Rico",0
|
|
||||||
4796775,en,NA,"North America",VI,"U.S. Virgin Islands",0
|
|
||||||
5854968,en,OC,Oceania,UM,"U.S. Outlying Islands",0
|
|
||||||
5880801,en,OC,Oceania,AS,"American Samoa",0
|
|
||||||
6251999,en,NA,"North America",CA,Canada,0
|
|
||||||
6252001,en,NA,"North America",US,"United States",0
|
|
||||||
6254930,en,AS,Asia,PS,Palestine,0
|
|
||||||
6255147,en,AS,Asia,,,0
|
|
||||||
6255148,en,EU,Europe,,,0
|
|
||||||
6290252,en,EU,Europe,RS,Serbia,0
|
|
||||||
6697173,en,AN,Antarctica,AQ,Antarctica,0
|
|
||||||
7609695,en,NA,"North America",SX,"Sint Maarten",0
|
|
||||||
7626836,en,NA,"North America",CW,Curaçao,0
|
|
||||||
7626844,en,NA,"North America",BQ,"Bonaire, Sint Eustatius, and Saba",0
|
|
||||||
7909807,en,AF,Africa,SS,"South Sudan",0
|
|
||||||
|
@@ -1,17 +0,0 @@
|
|||||||
|
|
||||||
import fs from 'fs';
|
|
||||||
import { globSync } from 'glob';
|
|
||||||
const tsconfigContent = fs.readFileSync('tsconfig.json', 'utf8');
|
|
||||||
const tsconfigObject = JSON.parse(tsconfigContent);
|
|
||||||
const paths = tsconfigObject.compilerOptions.paths;
|
|
||||||
const filesList = globSync('dist/**/*.js');
|
|
||||||
filesList.forEach(file => {
|
|
||||||
let raw = fs.readFileSync(file, 'utf8');
|
|
||||||
for (const path in paths) {
|
|
||||||
const deep = (file.match(/\\|\//g) || []).length;
|
|
||||||
const pathText = path.replace('*', '');
|
|
||||||
const toReplaceText = new RegExp(`"${pathText}(.*?)"`, 'g');
|
|
||||||
raw = raw.replace(toReplaceText, `"${new Array(deep - 2).fill('../').join('')}${paths[path][0].replace('*', '')}${'$1'}"`);
|
|
||||||
}
|
|
||||||
fs.writeFileSync(file, raw);
|
|
||||||
});
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import fs from 'fs';
|
|
||||||
|
|
||||||
function createIpDatabase() {
|
|
||||||
const data = fs.readFileSync('GeoLite2-Country-Blocks-IPv4.csv', 'utf8');
|
|
||||||
const rows = data.split('\n');
|
|
||||||
rows.splice(0, 1);
|
|
||||||
rows.splice(-1);
|
|
||||||
const parsed: [number, string, number][] = [];
|
|
||||||
for (const row of rows) {
|
|
||||||
const lineData = row.trim().split(',');
|
|
||||||
parsed.push([
|
|
||||||
parseInt(lineData[0].split('.')[0]),
|
|
||||||
lineData[0],
|
|
||||||
parseInt(lineData[1] || '0')
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
fs.writeFileSync('../dist/ipv4-db.json', JSON.stringify(parsed));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function createCountryDatabase() {
|
|
||||||
const data = fs.readFileSync('GeoLite2-Country-Locations-en.csv', 'utf8');
|
|
||||||
const rows = data.split('\n');
|
|
||||||
rows.splice(0, 1);
|
|
||||||
rows.splice(-1);
|
|
||||||
const parsed: [number, string, string][] = [];
|
|
||||||
for (const row of rows) {
|
|
||||||
const lineData = row.trim().split(',');
|
|
||||||
parsed.push([parseInt(lineData[0]), lineData[2], lineData[4]]);
|
|
||||||
}
|
|
||||||
fs.writeFileSync('../dist/countries-db.json', JSON.stringify(parsed));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
createIpDatabase();
|
|
||||||
createCountryDatabase();
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
import { ProjectModel } from "@schema/ProjectSchema";
|
|
||||||
import { UserModel } from "@schema/UserSchema";
|
|
||||||
import { LimitNotifyModel } from "@schema/broker/LimitNotifySchema";
|
|
||||||
import EmailService from '@services/EmailService';
|
|
||||||
import { requireEnv } from "@utils/requireEnv";
|
|
||||||
import { TProjectLimit } from "@schema/ProjectsLimits";
|
|
||||||
|
|
||||||
if (process.env.EMAIL_SERVICE) {
|
|
||||||
EmailService.init(requireEnv('BREVO_API_KEY'));
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function checkLimitsForEmail(projectCounts: TProjectLimit) {
|
|
||||||
|
|
||||||
const project_id = projectCounts.project_id;
|
|
||||||
const hasNotifyEntry = await LimitNotifyModel.findOne({ project_id });
|
|
||||||
if (!hasNotifyEntry) {
|
|
||||||
await LimitNotifyModel.create({ project_id, limit1: false, limit2: false, limit3: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((projectCounts.visits + projectCounts.events) >= (projectCounts.limit)) {
|
|
||||||
|
|
||||||
const notify = await LimitNotifyModel.findOne({ project_id });
|
|
||||||
if (notify && notify.limit3 === true) return;
|
|
||||||
|
|
||||||
const project = await ProjectModel.findById(project_id);
|
|
||||||
if (!project) return;
|
|
||||||
|
|
||||||
const owner = await UserModel.findById(project.owner);
|
|
||||||
if (!owner) return;
|
|
||||||
|
|
||||||
if (process.env.EMAIL_SERVICE) await EmailService.sendLimitEmailMax(owner.email, project.name);
|
|
||||||
await LimitNotifyModel.updateOne({ project_id: projectCounts.project_id }, { limit1: true, limit2: true, limit3: true });
|
|
||||||
|
|
||||||
} else if ((projectCounts.visits + projectCounts.events) >= (projectCounts.limit * 0.9)) {
|
|
||||||
|
|
||||||
const notify = await LimitNotifyModel.findOne({ project_id });
|
|
||||||
if (notify && notify.limit2 === true) return;
|
|
||||||
|
|
||||||
const project = await ProjectModel.findById(project_id);
|
|
||||||
if (!project) return;
|
|
||||||
|
|
||||||
const owner = await UserModel.findById(project.owner);
|
|
||||||
if (!owner) return;
|
|
||||||
|
|
||||||
if (process.env.EMAIL_SERVICE) await EmailService.sendLimitEmail90(owner.email, project.name);
|
|
||||||
await LimitNotifyModel.updateOne({ project_id: projectCounts.project_id }, { limit1: true, limit2: true, limit3: false });
|
|
||||||
|
|
||||||
} else if ((projectCounts.visits + projectCounts.events) >= (projectCounts.limit * 0.5)) {
|
|
||||||
|
|
||||||
const notify = await LimitNotifyModel.findOne({ project_id });
|
|
||||||
if (notify && notify.limit1 === true) return;
|
|
||||||
|
|
||||||
const project = await ProjectModel.findById(project_id);
|
|
||||||
if (!project) return;
|
|
||||||
|
|
||||||
const owner = await UserModel.findById(project.owner);
|
|
||||||
if (!owner) return;
|
|
||||||
|
|
||||||
if (process.env.EMAIL_SERVICE) await EmailService.sendLimitEmail50(owner.email, project.name);
|
|
||||||
await LimitNotifyModel.updateOne({ project_id: projectCounts.project_id }, { limit1: true, limit2: false, limit3: false });
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
export function getDeviceFromScreenSize(width: number, height: number) {
|
|
||||||
const totalArea = width * height;
|
|
||||||
|
|
||||||
const mobileArea = 375 * 667;
|
|
||||||
const tabletMinArea = 768 * 1366
|
|
||||||
const tabletMaxArea = 1024 * 1366
|
|
||||||
|
|
||||||
const isMobile = totalArea <= mobileArea;
|
|
||||||
const isTablet = totalArea >= tabletMinArea && totalArea <= tabletMaxArea;
|
|
||||||
|
|
||||||
if (isMobile) return 'mobile';
|
|
||||||
if (isTablet) return 'tablet'
|
|
||||||
return 'desktop';
|
|
||||||
}
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
|
|
||||||
import { RedisStreamService } from '@services/RedisStreamService';
|
|
||||||
import { requireEnv } from '@utils/requireEnv';
|
|
||||||
import { EventModel } from '@schema/metrics/EventSchema';
|
|
||||||
import { SessionModel } from '@schema/metrics/SessionSchema';
|
|
||||||
import { ProjectModel } from '@schema/ProjectSchema';
|
|
||||||
import { ProjectLimitModel } from '@schema/ProjectsLimits';
|
|
||||||
import { ProjectCountModel } from '@schema/ProjectsCounts';
|
|
||||||
import { EVENT_LOG_LIMIT_PERCENT } from '@data/broker/Limits';
|
|
||||||
import { checkLimitsForEmail } from './Controller';
|
|
||||||
import { lookup } from './lookup';
|
|
||||||
import { UAParser } from 'ua-parser-js';
|
|
||||||
import { VisitModel } from '@schema/metrics/VisitSchema';
|
|
||||||
|
|
||||||
|
|
||||||
export async function startStreamLoop() {
|
|
||||||
|
|
||||||
await RedisStreamService.connect();
|
|
||||||
|
|
||||||
await RedisStreamService.startReadingLoop({
|
|
||||||
streamName: requireEnv('STREAM_NAME'),
|
|
||||||
delay: { base: 10, empty: 5000 },
|
|
||||||
readBlock: 2000,
|
|
||||||
consumer: 'consumer_' + process.env.NODE_APP_INSTANCE
|
|
||||||
}, processStreamEvent);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export async function processStreamEvent(data: Record<string, string>) {
|
|
||||||
try {
|
|
||||||
const eventType = data._type;
|
|
||||||
if (!eventType) return;
|
|
||||||
|
|
||||||
const { pid, sessionHash } = data;
|
|
||||||
|
|
||||||
const project = await ProjectModel.exists({ _id: pid });
|
|
||||||
if (!project) return;
|
|
||||||
|
|
||||||
|
|
||||||
if (eventType === 'event') return await process_event(data, sessionHash);
|
|
||||||
if (eventType === 'keep_alive') return await process_keep_alive(data, sessionHash);
|
|
||||||
if (eventType === 'visit') return await process_visit(data, sessionHash);
|
|
||||||
|
|
||||||
} catch (ex: any) {
|
|
||||||
console.error('ERROR PROCESSING STREAM EVENT', ex.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async function checkLimits(project_id: string) {
|
|
||||||
const projectLimits = await ProjectLimitModel.findOne({ project_id });
|
|
||||||
if (!projectLimits) return false;
|
|
||||||
const TOTAL_COUNT = projectLimits.events + projectLimits.visits;
|
|
||||||
const COUNT_LIMIT = projectLimits.limit;
|
|
||||||
if ((TOTAL_COUNT) > COUNT_LIMIT * EVENT_LOG_LIMIT_PERCENT) return false;
|
|
||||||
await checkLimitsForEmail(projectLimits);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function process_visit(data: Record<string, string>, sessionHash: string) {
|
|
||||||
|
|
||||||
const { pid, ip, website, page, referrer, userAgent, flowHash } = data;
|
|
||||||
|
|
||||||
const canLog = await checkLimits(pid);
|
|
||||||
if (!canLog) return;
|
|
||||||
|
|
||||||
let referrerParsed;
|
|
||||||
try {
|
|
||||||
referrerParsed = new URL(referrer);
|
|
||||||
} catch (ex) {
|
|
||||||
referrerParsed = { hostname: referrer };
|
|
||||||
}
|
|
||||||
|
|
||||||
const geoLocation = lookup(ip);
|
|
||||||
|
|
||||||
const userAgentParsed = UAParser(userAgent);
|
|
||||||
|
|
||||||
const device = userAgentParsed.device.type;
|
|
||||||
|
|
||||||
const visit = new VisitModel({
|
|
||||||
project_id: pid, website, page, referrer: referrerParsed.hostname,
|
|
||||||
browser: userAgentParsed.browser.name || 'NO_BROWSER',
|
|
||||||
os: userAgentParsed.os.name || 'NO_OS',
|
|
||||||
device: device ? device : (userAgentParsed.browser.name ? 'desktop' : undefined),
|
|
||||||
session: sessionHash,
|
|
||||||
flowHash,
|
|
||||||
continent: geoLocation[0],
|
|
||||||
country: geoLocation[1],
|
|
||||||
});
|
|
||||||
|
|
||||||
await visit.save();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
await ProjectCountModel.updateOne({ project_id: pid }, { $inc: { 'visits': 1 } }, { upsert: true });
|
|
||||||
await ProjectLimitModel.updateOne({ project_id: pid }, { $inc: { 'visits': 1 } });
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
async function process_keep_alive(data: Record<string, string>, sessionHash: string) {
|
|
||||||
|
|
||||||
const { pid, instant, flowHash } = data;
|
|
||||||
|
|
||||||
const canLog = await checkLimits(pid);
|
|
||||||
if (!canLog) return;
|
|
||||||
|
|
||||||
const existingSession = await SessionModel.findOne({ project_id: pid, session: sessionHash }, { _id: 1 });
|
|
||||||
if (!existingSession) {
|
|
||||||
await ProjectCountModel.updateOne({ project_id: pid }, { $inc: { 'sessions': 1 } }, { upsert: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (instant == "true") {
|
|
||||||
await SessionModel.updateOne({ project_id: pid, session: sessionHash, }, {
|
|
||||||
$inc: { duration: 0 },
|
|
||||||
flowHash,
|
|
||||||
updated_at: Date.now()
|
|
||||||
}, { upsert: true });
|
|
||||||
} else {
|
|
||||||
await SessionModel.updateOne({ project_id: pid, session: sessionHash, }, {
|
|
||||||
$inc: { duration: 1 },
|
|
||||||
flowHash,
|
|
||||||
updated_at: Date.now()
|
|
||||||
}, { upsert: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function process_event(data: Record<string, string>, sessionHash: string) {
|
|
||||||
|
|
||||||
const { name, metadata, pid, flowHash } = data;
|
|
||||||
|
|
||||||
const canLog = await checkLimits(pid);
|
|
||||||
if (!canLog) return;
|
|
||||||
|
|
||||||
let metadataObject;
|
|
||||||
try {
|
|
||||||
if (metadata) metadataObject = JSON.parse(metadata);
|
|
||||||
} catch (ex) {
|
|
||||||
metadataObject = { error: 'Error parsing metadata' }
|
|
||||||
}
|
|
||||||
|
|
||||||
const event = new EventModel({ project_id: pid, name, flowHash, metadata: metadataObject, session: sessionHash });
|
|
||||||
await event.save();
|
|
||||||
|
|
||||||
await ProjectCountModel.updateOne({ project_id: pid }, { $inc: { 'events': 1 } }, { upsert: true });
|
|
||||||
await ProjectLimitModel.updateOne({ project_id: pid }, { $inc: { 'events': 1 } });
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import express from 'express';
|
|
||||||
import cors from 'cors';
|
|
||||||
|
|
||||||
import { requireEnv } from '@utils/requireEnv';
|
|
||||||
import { connectDatabase } from '@services/DatabaseService';
|
|
||||||
import { startStreamLoop } from './StreamLoopController';
|
|
||||||
|
|
||||||
const app = express();
|
|
||||||
app.use(cors());
|
|
||||||
|
|
||||||
connectDatabase(requireEnv('MONGO_CONNECTION_STRING'));
|
|
||||||
|
|
||||||
import HealthRouter from './routes/HealthRouter';
|
|
||||||
app.use('/health', HealthRouter);
|
|
||||||
|
|
||||||
app.listen(requireEnv('PORT'), () => console.log(`Listening on port ${requireEnv('PORT')}`));
|
|
||||||
|
|
||||||
startStreamLoop();
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import fs from 'fs';
|
|
||||||
|
|
||||||
const ipsData = JSON.parse(fs.readFileSync('./dist/ipv4-db.json', 'utf8'));
|
|
||||||
const countriesData = JSON.parse(fs.readFileSync('./dist/countries-db.json', 'utf8'));
|
|
||||||
|
|
||||||
function inRange(ip: string, cidr: string) {
|
|
||||||
const [subnet, mask] = cidr.split('/');
|
|
||||||
const ipBytes = ip.split('.').map(Number);
|
|
||||||
const subnetBytes = subnet.split('.').map(Number);
|
|
||||||
|
|
||||||
const ipInt = (ipBytes[0] << 24) | (ipBytes[1] << 16) | (ipBytes[2] << 8) | ipBytes[3];
|
|
||||||
const subnetInt = (subnetBytes[0] << 24) | (subnetBytes[1] << 16) | (subnetBytes[2] << 8) | subnetBytes[3];
|
|
||||||
|
|
||||||
const maskInt = 0xffffffff << (32 - parseInt(mask));
|
|
||||||
|
|
||||||
return (ipInt & maskInt) === (subnetInt & maskInt);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCountryFromId(id: number) {
|
|
||||||
for (const country of countriesData) {
|
|
||||||
if (country[0] == id) {
|
|
||||||
return country;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function lookup(ip: string) {
|
|
||||||
try {
|
|
||||||
const startPiece = parseInt(ip.split('.')[0]);
|
|
||||||
for (const target of ipsData) {
|
|
||||||
const matchingStartPiece = target[0] == startPiece;
|
|
||||||
if (!matchingStartPiece) continue;
|
|
||||||
if (!inRange(ip, target[1])) continue;
|
|
||||||
const country = getCountryFromId(target[2]);
|
|
||||||
return [country[1], country[2]];
|
|
||||||
}
|
|
||||||
return ['??', '??'];
|
|
||||||
} catch (ex) {
|
|
||||||
console.error('ERROR DURING LOOKUP', ex);
|
|
||||||
return ['??', '??'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
|
|
||||||
import { Router } from "express";
|
|
||||||
|
|
||||||
const router = Router();
|
|
||||||
|
|
||||||
router.get('/', async (req, res) => {
|
|
||||||
try {
|
|
||||||
return res.json({ alive: true });
|
|
||||||
} catch (ex) {
|
|
||||||
console.error(ex);
|
|
||||||
return res.status(500).json({ error: 'ERROR' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { Request } from "express";
|
|
||||||
import crypto from 'crypto';
|
|
||||||
|
|
||||||
export function getIPFromRequest(req: Request) {
|
|
||||||
const ip = req.header('X-Real-IP') || req.header('X-Forwarded-For') || '0.0.0.0';
|
|
||||||
return ip;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function createSessionHash(website: string, ip: string, userAgent: string) {
|
|
||||||
const dailySalt = new Date().toLocaleDateString('it-IT');
|
|
||||||
const sessionClean = dailySalt + website + ip + userAgent;
|
|
||||||
const sessionHash = crypto.createHash('md5').update(sessionClean).digest("hex");
|
|
||||||
return sessionHash;
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"baseUrl": ".",
|
|
||||||
"module": "NodeNext",
|
|
||||||
"target": "ESNext",
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"outDir": "dist",
|
|
||||||
"types": [
|
|
||||||
"node",
|
|
||||||
"jest"
|
|
||||||
],
|
|
||||||
"paths": {
|
|
||||||
"@schema/*": [
|
|
||||||
"../shared/schema/*"
|
|
||||||
],
|
|
||||||
"@services/*": [
|
|
||||||
"../shared/services/*"
|
|
||||||
],
|
|
||||||
"@data/*": [
|
|
||||||
"../shared/data/*"
|
|
||||||
],
|
|
||||||
"@functions/*": [
|
|
||||||
"../shared/functions/*"
|
|
||||||
],
|
|
||||||
"@utils/*": [
|
|
||||||
"../shared/utils/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"src/**/*.ts",
|
|
||||||
"scripts/**/*.ts",
|
|
||||||
"tests/**/*.test.ts",
|
|
||||||
"tests/utils.ts"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -76,7 +76,6 @@ async function confirmSnapshot() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 justify-center flex w-full">
|
<div class="mt-4 justify-center flex w-full">
|
||||||
|
|
||||||
<UPopover class="w-full" :popper="{ placement: 'bottom' }">
|
<UPopover class="w-full" :popper="{ placement: 'bottom' }">
|
||||||
<UButton class="w-full" color="primary" variant="solid">
|
<UButton class="w-full" color="primary" variant="solid">
|
||||||
<div class="flex items-center justify-center w-full gap-2">
|
<div class="flex items-center justify-center w-full gap-2">
|
||||||
@@ -97,8 +96,6 @@ async function confirmSnapshot() {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</UPopover>
|
</UPopover>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grow"></div>
|
<div class="grow"></div>
|
||||||
|
|||||||
157
dashboard/components/integrations/SupabaseChartDialog.vue
Normal file
157
dashboard/components/integrations/SupabaseChartDialog.vue
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
import { sub, isSameDay, type Duration } from 'date-fns'
|
||||||
|
|
||||||
|
type ChartType = 'bar' | 'line';
|
||||||
|
const chartTypeOptions: { value: ChartType, label: string }[] = [
|
||||||
|
{ value: 'bar', label: 'Bar chart' },
|
||||||
|
{ value: 'line', label: 'Line chart' },
|
||||||
|
]
|
||||||
|
|
||||||
|
type yAxisMode = 'count';
|
||||||
|
const yAxisModeOptions: { value: yAxisMode, label: string }[] = [
|
||||||
|
{ value: 'count', label: 'Count fields' },
|
||||||
|
]
|
||||||
|
|
||||||
|
type Slice = 'day' | 'month';
|
||||||
|
const sliceOptions: Slice[] = ['day', 'month'];
|
||||||
|
|
||||||
|
const chartType = ref<ChartType>('line');
|
||||||
|
const tableName = ref<string>('');
|
||||||
|
const xAxis = ref<string>('');
|
||||||
|
const yAxisMode = ref<yAxisMode>('count');
|
||||||
|
const slice = ref<Slice>('day');
|
||||||
|
const visualizationName = ref<string>('');
|
||||||
|
|
||||||
|
|
||||||
|
const ranges = [
|
||||||
|
{ label: 'Last 7 days', duration: { days: 7 } },
|
||||||
|
{ label: 'Last 14 days', duration: { days: 14 } },
|
||||||
|
{ label: 'Last 30 days', duration: { days: 30 } },
|
||||||
|
{ label: 'Last 3 months', duration: { months: 3 } },
|
||||||
|
{ label: 'Last 6 months', duration: { months: 6 } },
|
||||||
|
{ label: 'Last year', duration: { years: 1 } }
|
||||||
|
]
|
||||||
|
const timeframe = ref<{ start: Date, end: Date }>({ start: sub(new Date(), { days: 14 }), end: new Date() })
|
||||||
|
|
||||||
|
function isRangeSelected(duration: Duration) {
|
||||||
|
return isSameDay(timeframe.value.start, sub(new Date(), duration)) && isSameDay(timeframe.value.end, new Date())
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectRange(duration: Duration) {
|
||||||
|
timeframe.value = { start: sub(new Date(), duration), end: new Date() }
|
||||||
|
}
|
||||||
|
|
||||||
|
const { createAlert } = useAlert();
|
||||||
|
const { closeDialog } = useCustomDialog();
|
||||||
|
const activeProjectId = useActiveProjectId();
|
||||||
|
|
||||||
|
const { integrationsCredentials,testConnection } = useSupabase();
|
||||||
|
|
||||||
|
async function generate() {
|
||||||
|
const credentials = integrationsCredentials.data.value;
|
||||||
|
if (!credentials?.supabase) return createAlert('Credentials not found', 'Please add supabase credentials on the integration page', 'far fa-error', 5000);
|
||||||
|
const connectionStatus = await testConnection();
|
||||||
|
if (!connectionStatus) return createAlert('Invalid supabase credentials', 'Please check your supabase credentials on the integration page', 'far fa-error', 5000);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const creation = await $fetch('/api/integrations/supabase/add', {
|
||||||
|
...signHeaders({
|
||||||
|
'x-pid': activeProjectId.data.value || '',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}),
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: visualizationName.value,
|
||||||
|
chart_type: chartType.value,
|
||||||
|
table_name: tableName.value,
|
||||||
|
xField: xAxis.value,
|
||||||
|
yMode: yAxisMode.value,
|
||||||
|
from: timeframe.value.start,
|
||||||
|
to: timeframe.value.end,
|
||||||
|
slice: slice.value
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
createAlert('Integration generated', 'Integration generated successfully', 'far fa-check-circle', 5000);
|
||||||
|
closeDialog();
|
||||||
|
} catch (ex: any) {
|
||||||
|
createAlert('Error generating integrations', ex.response._data.message.toString(), 'far fa-error', 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div>
|
||||||
|
<div> Visualization name </div>
|
||||||
|
<div>
|
||||||
|
<LyxUiInput class="w-full px-2 py-1" v-model="visualizationName"></LyxUiInput>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div> Chart type </div>
|
||||||
|
<USelect v-model="chartType" :options="chartTypeOptions" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div> Table name </div>
|
||||||
|
<div>
|
||||||
|
<LyxUiInput class="w-full px-2 py-1" v-model="tableName"></LyxUiInput>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div> X axis field </div>
|
||||||
|
<div>
|
||||||
|
<LyxUiInput class="w-full px-2 py-1" v-model="xAxis"></LyxUiInput>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div> Y axis mode </div>
|
||||||
|
<div>
|
||||||
|
<USelect v-model="yAxisMode" :options="yAxisModeOptions" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div> Timeframe </div>
|
||||||
|
<div>
|
||||||
|
<UPopover class="w-full" :popper="{ placement: 'bottom' }">
|
||||||
|
<UButton class="w-full" color="primary" variant="solid">
|
||||||
|
<div class="flex items-center justify-center w-full gap-2">
|
||||||
|
<i class="i-heroicons-calendar-days-20-solid"></i>
|
||||||
|
{{ timeframe.start.toLocaleDateString() }} - {{ timeframe.end.toLocaleDateString() }}
|
||||||
|
</div>
|
||||||
|
</UButton>
|
||||||
|
<template #panel="{ close }">
|
||||||
|
<div class="flex items-center sm:divide-x divide-gray-200 dark:divide-gray-800">
|
||||||
|
<div class="hidden sm:flex flex-col py-4">
|
||||||
|
<UButton v-for="(range, index) in ranges" :key="index" :label="range.label" color="gray"
|
||||||
|
variant="ghost" class="rounded-none px-6"
|
||||||
|
:class="[isRangeSelected(range.duration) ? 'bg-gray-100 dark:bg-gray-800' : 'hover:bg-gray-50 dark:hover:bg-gray-800/50']"
|
||||||
|
truncate @click="selectRange(range.duration)" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DatePicker v-model="timeframe" @close="close" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UPopover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div> View mode </div>
|
||||||
|
<div>
|
||||||
|
<USelect v-model="slice" :options="sliceOptions" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<LyxUiButton type="primary" @click="generate()">
|
||||||
|
Generate
|
||||||
|
</LyxUiButton>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
170
dashboard/components/integrations/SupabaseLineChart.vue
Normal file
170
dashboard/components/integrations/SupabaseLineChart.vue
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { TSupabaseIntegration } from '@schema/integrations/SupabaseIntegrationSchema';
|
||||||
|
import type { ChartData, ChartOptions } from 'chart.js';
|
||||||
|
import { useLineChart, LineChart } from 'vue-chart-3';
|
||||||
|
|
||||||
|
const props = defineProps<{ integration_id: string }>();
|
||||||
|
|
||||||
|
const activeProjectId = useActiveProjectId();
|
||||||
|
|
||||||
|
const supabaseData = ref<{ labels: string[], data: number[] }>();
|
||||||
|
const supabaseError = ref<string | undefined>(undefined);
|
||||||
|
const supabaseFetching = ref<boolean>(false);
|
||||||
|
|
||||||
|
const { getRemoteData } = useSupabase();
|
||||||
|
|
||||||
|
function createGradient() {
|
||||||
|
|
||||||
|
const c = document.createElement('canvas');
|
||||||
|
const ctx = c.getContext("2d");
|
||||||
|
let gradient: any = `#34B67C22`;
|
||||||
|
if (ctx) {
|
||||||
|
gradient = ctx.createLinearGradient(0, 25, 0, 300);
|
||||||
|
gradient.addColorStop(0, `#34B67C99`);
|
||||||
|
gradient.addColorStop(0.35, `#34B67C66`);
|
||||||
|
gradient.addColorStop(1, `#34B67C22`);
|
||||||
|
} else {
|
||||||
|
console.warn('Cannot get context for gradient');
|
||||||
|
}
|
||||||
|
|
||||||
|
chartData.value.datasets[0].backgroundColor = [gradient];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const chartOptions = ref<ChartOptions<'line'>>({
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
interaction: {
|
||||||
|
intersect: false,
|
||||||
|
mode: 'nearest',
|
||||||
|
axis: 'x',
|
||||||
|
includeInvisible: true
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
ticks: { display: true },
|
||||||
|
grid: {
|
||||||
|
display: true,
|
||||||
|
drawBorder: false,
|
||||||
|
color: '#CCCCCC22',
|
||||||
|
// borderDash: [5, 10]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
ticks: { display: true },
|
||||||
|
grid: {
|
||||||
|
display: true,
|
||||||
|
drawBorder: false,
|
||||||
|
color: '#CCCCCC22',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: { display: false },
|
||||||
|
title: { display: false },
|
||||||
|
tooltip: {
|
||||||
|
enabled: true,
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||||
|
titleFont: { size: 16, weight: 'bold' },
|
||||||
|
bodyFont: { size: 14 },
|
||||||
|
padding: 10,
|
||||||
|
cornerRadius: 4,
|
||||||
|
boxPadding: 10,
|
||||||
|
caretPadding: 20,
|
||||||
|
yAlign: 'bottom',
|
||||||
|
xAlign: 'center',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const chartData = ref<ChartData<'line'>>({
|
||||||
|
labels: [],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
data: [],
|
||||||
|
backgroundColor: ['#34B67C' + '77'],
|
||||||
|
borderColor: '#34B67C',
|
||||||
|
borderWidth: 4,
|
||||||
|
fill: true,
|
||||||
|
tension: 0.45,
|
||||||
|
pointRadius: 0,
|
||||||
|
pointHoverRadius: 10,
|
||||||
|
hoverBackgroundColor: '#34B67C',
|
||||||
|
hoverBorderColor: 'white',
|
||||||
|
hoverBorderWidth: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
|
||||||
|
supabaseFetching.value = true;
|
||||||
|
supabaseError.value = undefined;
|
||||||
|
|
||||||
|
const integrationData = await $fetch<TSupabaseIntegration>('/api/integrations/supabase/get', {
|
||||||
|
...signHeaders({
|
||||||
|
'x-pid': activeProjectId.data.value || '',
|
||||||
|
'x-integration': props.integration_id
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!integrationData) {
|
||||||
|
supabaseError.value = 'Cannot get integration data';
|
||||||
|
supabaseFetching.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const data = await getRemoteData(
|
||||||
|
integrationData.table_name,
|
||||||
|
integrationData.xField,
|
||||||
|
integrationData.yMode,
|
||||||
|
integrationData.from.toString(),
|
||||||
|
integrationData.to.toString(),
|
||||||
|
integrationData.slice,
|
||||||
|
);
|
||||||
|
if (data.error) {
|
||||||
|
supabaseError.value = data.error;
|
||||||
|
supabaseFetching.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
supabaseFetching.value = false;
|
||||||
|
supabaseData.value = data.result;
|
||||||
|
|
||||||
|
chartData.value.labels = data.result?.labels || [];
|
||||||
|
chartData.value.datasets[0].data = data.result?.data || [];
|
||||||
|
|
||||||
|
console.log(data.result);
|
||||||
|
createGradient();
|
||||||
|
} catch (ex: any) {
|
||||||
|
if (!ex.response._data) {
|
||||||
|
supabaseError.value = ex.message.toString();
|
||||||
|
supabaseFetching.value = false;
|
||||||
|
} else {
|
||||||
|
supabaseError.value = ex.response._data.message.toString();
|
||||||
|
supabaseFetching.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const { lineChartProps, lineChartRef } = useLineChart({ chartData: chartData, options: chartOptions });
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="!supabaseFetching">
|
||||||
|
<div v-if="!supabaseError">
|
||||||
|
<LineChart ref="lineChartRef" v-bind="lineChartProps"> </LineChart>
|
||||||
|
</div>
|
||||||
|
<div v-if="supabaseError"> {{ supabaseError }} </div>
|
||||||
|
</div>
|
||||||
|
<div v-if="supabaseFetching">
|
||||||
|
Getting remote data...
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
const ACCESS_TOKEN_STATE_KEY = 'access_token';
|
const ACCESS_TOKEN_STATE_KEY = 'access_token';
|
||||||
const ACCESS_TOKEN_COOKIE_KEY = 'access_token';
|
const ACCESS_TOKEN_COOKIE_KEY = 'access_token';
|
||||||
|
|
||||||
|
|
||||||
export function signHeaders(headers?: Record<string, string>) {
|
export function signHeaders(headers?: Record<string, string>) {
|
||||||
const { token } = useAccessToken()
|
const { token } = useAccessToken()
|
||||||
return { headers: { ...(headers || {}), 'Authorization': 'Bearer ' + token.value } }
|
return { headers: { ...(headers || {}), 'Authorization': 'Bearer ' + token.value } }
|
||||||
|
|||||||
125
dashboard/composables/useSupabase.ts
Normal file
125
dashboard/composables/useSupabase.ts
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
|
||||||
|
|
||||||
|
import type { TSupabaseIntegration } from "@schema/integrations/SupabaseIntegrationSchema";
|
||||||
|
import { createClient, SupabaseClient } from "@supabase/supabase-js";
|
||||||
|
|
||||||
|
import { format } from 'date-fns';
|
||||||
|
|
||||||
|
const activeProjectId = useActiveProjectId();
|
||||||
|
|
||||||
|
|
||||||
|
const computedHeaders = computed<Record<string, string>>(() => {
|
||||||
|
const signedHeaders = signHeaders();
|
||||||
|
return {
|
||||||
|
'x-pid': activeProjectId.data.value || '',
|
||||||
|
'Authorization': signedHeaders.headers.Authorization
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const integrationsCredentials = useFetch(`/api/integrations/credentials/get`, {
|
||||||
|
headers: computedHeaders,
|
||||||
|
onResponse: (e) => {
|
||||||
|
supabaseUrl.value = e.response._data.supabase.url || '';
|
||||||
|
supabaseAnonKey.value = e.response._data.supabase.anon_key || '';
|
||||||
|
supabaseServiceRoleKey.value = e.response._data.supabase.service_role_key || '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const supabaseUrl = ref<string>('');
|
||||||
|
const supabaseAnonKey = ref<string>('');
|
||||||
|
const supabaseServiceRoleKey = ref<string>('');
|
||||||
|
|
||||||
|
const supabaseIntegrations = useFetch<TSupabaseIntegration[]>('/api/integrations/supabase/list', { headers: computedHeaders })
|
||||||
|
|
||||||
|
|
||||||
|
const subabaseClientData: { client: SupabaseClient | undefined } = {
|
||||||
|
client: undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateIntegrationsCredentails(data: { supabase_url: string, supabase_anon_key: string, supabase_service_role_key: string }) {
|
||||||
|
try {
|
||||||
|
await $fetch(`/api/integrations/credentials/${activeProjectId.data.value}/update`, {
|
||||||
|
...signHeaders({ 'Content-Type': 'application/json' }),
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
supabase_url: data.supabase_url,
|
||||||
|
supabase_anon_key: data.supabase_anon_key,
|
||||||
|
supabase_service_role_key: data.supabase_service_role_key
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
integrationsCredentials.refresh();
|
||||||
|
return { ok: true, error: '' }
|
||||||
|
} catch (ex: any) {
|
||||||
|
return { ok: false, error: ex.message.toString() };
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSupabaseUrl(supabaseUrl: string) {
|
||||||
|
let result = supabaseUrl;
|
||||||
|
if (!result.includes('https://')) result = `https://${result}`;
|
||||||
|
if (!result.endsWith('.supabase.co')) result = `${result}.supabase.co`;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function testConnection() {
|
||||||
|
const url = createSupabaseUrl(supabaseUrl.value);
|
||||||
|
subabaseClientData.client = createClient(url, supabaseAnonKey.value);
|
||||||
|
const res = await subabaseClientData.client.from('_t_e_s_t_').select('*').limit(1);
|
||||||
|
if (res.error?.message.startsWith('TypeError')) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
type GroupBy = 'day' | 'month';
|
||||||
|
|
||||||
|
const groupByDate = (data: string[], groupBy: GroupBy) => {
|
||||||
|
return data.reduce((acc, item) => {
|
||||||
|
const date = new Date(item);
|
||||||
|
const dateKey = groupBy === 'day'
|
||||||
|
? format(date, 'yyyy-MM-dd') // Group by day
|
||||||
|
: format(date, 'yyyy-MM'); // Group by month
|
||||||
|
|
||||||
|
if (!acc[dateKey]) { acc[dateKey] = []; }
|
||||||
|
|
||||||
|
acc[dateKey].push(item);
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, string[]>);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getRemoteData(table: string, xField: string, yMode: string, from: string, to: string, slice: string) {
|
||||||
|
const url = createSupabaseUrl(supabaseUrl.value);
|
||||||
|
subabaseClientData.client = createClient(url, supabaseAnonKey.value);
|
||||||
|
const res = await subabaseClientData.client.from(table).select(xField)
|
||||||
|
.filter(xField, 'gte', from)
|
||||||
|
.filter(xField, 'lte', to);
|
||||||
|
|
||||||
|
if (res.error) return { error: res.error.message };
|
||||||
|
|
||||||
|
const grouped = groupByDate(res.data.map((e: any) => e.created_at) || [], slice as any);
|
||||||
|
|
||||||
|
const result: { labels: string[], data: number[] } = { labels: [], data: [] }
|
||||||
|
|
||||||
|
for (const key in grouped) {
|
||||||
|
result.labels.push(key);
|
||||||
|
result.data.push(grouped[key].length);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return { result };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSupabase() {
|
||||||
|
|
||||||
|
return {
|
||||||
|
getRemoteData,
|
||||||
|
testConnection,
|
||||||
|
supabaseIntegrations, integrationsCredentials,
|
||||||
|
supabaseUrl, supabaseAnonKey,
|
||||||
|
supabaseServiceRoleKey,
|
||||||
|
updateIntegrationsCredentails
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ const sections: Section[] = [
|
|||||||
{ label: 'Security', to: '/security', icon: 'fal fa-shield' },
|
{ label: 'Security', to: '/security', icon: 'fal fa-shield' },
|
||||||
{ label: 'Insights (soon)', to: '#', icon: 'fal fa-lightbulb', disabled: true },
|
{ label: 'Insights (soon)', to: '#', icon: 'fal fa-lightbulb', disabled: true },
|
||||||
{ label: 'Links (soon)', to: '#', icon: 'fal fa-globe-pointer', disabled: true },
|
{ label: 'Links (soon)', to: '#', icon: 'fal fa-globe-pointer', disabled: true },
|
||||||
{ label: 'Integrations (soon)', to: '#', icon: 'fal fa-cube', disabled: true },
|
{ label: 'Integrations (soon)', to: '/integrations', icon: 'fal fa-cube', disabled: true },
|
||||||
{ label: 'Settings', to: '/settings', icon: 'fal fa-gear' },
|
{ label: 'Settings', to: '/settings', icon: 'fal fa-gear' },
|
||||||
{
|
{
|
||||||
grow: true,
|
grow: true,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@getbrevo/brevo": "^2.2.0",
|
"@getbrevo/brevo": "^2.2.0",
|
||||||
"@nuxtjs/tailwindcss": "^6.12.0",
|
"@nuxtjs/tailwindcss": "^6.12.0",
|
||||||
|
"@supabase/supabase-js": "^2.45.4",
|
||||||
"chart.js": "^3.9.1",
|
"chart.js": "^3.9.1",
|
||||||
"chartjs-chart-funnel": "^4.2.1",
|
"chartjs-chart-funnel": "^4.2.1",
|
||||||
"chartjs-plugin-annotation": "^2.2.1",
|
"chartjs-plugin-annotation": "^2.2.1",
|
||||||
|
|||||||
105
dashboard/pages/integrations.vue
Normal file
105
dashboard/pages/integrations.vue
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import SupabaseChartDialog from '~/components/integrations/SupabaseChartDialog.vue';
|
||||||
|
|
||||||
|
definePageMeta({ layout: 'dashboard' });
|
||||||
|
const activeProjectId = useActiveProjectId();
|
||||||
|
|
||||||
|
|
||||||
|
const { createAlert } = useAlert();
|
||||||
|
|
||||||
|
const {
|
||||||
|
supabaseUrl, supabaseAnonKey, supabaseServiceRoleKey, integrationsCredentials,
|
||||||
|
supabaseIntegrations, updateIntegrationsCredentails
|
||||||
|
} = useSupabase()
|
||||||
|
|
||||||
|
async function updateCredentials() {
|
||||||
|
|
||||||
|
const res = await updateIntegrationsCredentails({
|
||||||
|
supabase_url: supabaseUrl.value,
|
||||||
|
supabase_anon_key: supabaseAnonKey.value,
|
||||||
|
supabase_service_role_key: supabaseServiceRoleKey.value
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok === true) {
|
||||||
|
integrationsCredentials.refresh();
|
||||||
|
createAlert('Credentials updated', 'Credentials updated successfully', 'far fa-error', 4000);
|
||||||
|
} else {
|
||||||
|
createAlert('Error updating credentials', res.error, 'far fa-error', 4000);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const { openDialogEx } = useCustomDialog()
|
||||||
|
|
||||||
|
function showChartDialog() {
|
||||||
|
openDialogEx(SupabaseChartDialog, {
|
||||||
|
closable: true,
|
||||||
|
width: '55vw',
|
||||||
|
height: '65vh'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<div class="home w-full h-full px-10 pt-6 overflow-y-auto">
|
||||||
|
|
||||||
|
<CardTitled title="Supabase integration" class="w-full">
|
||||||
|
<template #header>
|
||||||
|
<img class="h-10 w-10" :src="'supabase.svg'" alt="Supabase logo">
|
||||||
|
</template>
|
||||||
|
<div class="flex gap-6 flex-col w-full">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="text-lyx-text"> Supabase url </div>
|
||||||
|
<div class="text-lyx-text-dark"> Required to fetch data from supabase </div>
|
||||||
|
<LyxUiInput v-if="!integrationsCredentials.pending.value" class="w-full mt-2 px-4 py-1"
|
||||||
|
v-model="supabaseUrl" type="text"></LyxUiInput>
|
||||||
|
<div v-if="integrationsCredentials.pending.value"> Loading... </div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="text-lyx-text"> Supabase anon key </div>
|
||||||
|
<div class="text-lyx-text-dark"> Required to fetch data from supabase </div>
|
||||||
|
<LyxUiInput v-if="!integrationsCredentials.pending.value" class="w-full mt-2 px-4 py-1"
|
||||||
|
v-model="supabaseAnonKey" type="password"></LyxUiInput>
|
||||||
|
<div v-if="integrationsCredentials.pending.value"> Loading... </div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="text-lyx-text"> Supabase service role key </div>
|
||||||
|
<div class="text-lyx-text-dark"> Only used if you need to bypass RLS </div>
|
||||||
|
<LyxUiInput v-if="!integrationsCredentials.pending.value" class="w-full mt-2 px-4 py-1"
|
||||||
|
v-model="supabaseServiceRoleKey" type="password"></LyxUiInput>
|
||||||
|
<div v-if="integrationsCredentials.pending.value"> Loading... </div>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-3">
|
||||||
|
<LyxUiButton v-if="!integrationsCredentials.pending.value" @click="updateCredentials()"
|
||||||
|
type="primary"> Save
|
||||||
|
</LyxUiButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardTitled>
|
||||||
|
|
||||||
|
|
||||||
|
<LyxUiCard class="mt-6 w-full">
|
||||||
|
<div class="flex flex-col gap-8">
|
||||||
|
<div class="flex gap-2 items-center" v-for="supabaseIntegration of supabaseIntegrations.data.value">
|
||||||
|
<div> {{ supabaseIntegration.name }} </div>
|
||||||
|
<div> <i class="far fa-edit"></i> </div>
|
||||||
|
<div> <i class="far fa-trash"></i> </div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<LyxUiButton type="primary" @click="showChartDialog()"> Add supabase chart </LyxUiButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</LyxUiCard>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="mt-10">
|
||||||
|
<IntegrationsSupabaseLineChart integration_id="66f6c558d97e4abd408feee0"></IntegrationsSupabaseLineChart>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
86
dashboard/pnpm-lock.yaml
generated
86
dashboard/pnpm-lock.yaml
generated
@@ -14,6 +14,9 @@ importers:
|
|||||||
'@nuxtjs/tailwindcss':
|
'@nuxtjs/tailwindcss':
|
||||||
specifier: ^6.12.0
|
specifier: ^6.12.0
|
||||||
version: 6.12.0(rollup@4.18.0)
|
version: 6.12.0(rollup@4.18.0)
|
||||||
|
'@supabase/supabase-js':
|
||||||
|
specifier: ^2.45.4
|
||||||
|
version: 2.45.4
|
||||||
chart.js:
|
chart.js:
|
||||||
specifier: ^3.9.1
|
specifier: ^3.9.1
|
||||||
version: 3.9.1
|
version: 3.9.1
|
||||||
@@ -1138,6 +1141,28 @@ packages:
|
|||||||
resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==}
|
resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
'@supabase/auth-js@2.65.0':
|
||||||
|
resolution: {integrity: sha512-+wboHfZufAE2Y612OsKeVP4rVOeGZzzMLD/Ac3HrTQkkY4qXNjI6Af9gtmxwccE5nFvTiF114FEbIQ1hRq5uUw==}
|
||||||
|
|
||||||
|
'@supabase/functions-js@2.4.1':
|
||||||
|
resolution: {integrity: sha512-8sZ2ibwHlf+WkHDUZJUXqqmPvWQ3UHN0W30behOJngVh/qHHekhJLCFbh0AjkE9/FqqXtf9eoVvmYgfCLk5tNA==}
|
||||||
|
|
||||||
|
'@supabase/node-fetch@2.6.15':
|
||||||
|
resolution: {integrity: sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==}
|
||||||
|
engines: {node: 4.x || >=6.0.0}
|
||||||
|
|
||||||
|
'@supabase/postgrest-js@1.16.1':
|
||||||
|
resolution: {integrity: sha512-EOSEZFm5pPuCPGCmLF1VOCS78DfkSz600PBuvBND/IZmMciJ1pmsS3ss6TkB6UkuvTybYiBh7gKOYyxoEO3USA==}
|
||||||
|
|
||||||
|
'@supabase/realtime-js@2.10.2':
|
||||||
|
resolution: {integrity: sha512-qyCQaNg90HmJstsvr2aJNxK2zgoKh9ZZA8oqb7UT2LCh3mj9zpa3Iwu167AuyNxsxrUE8eEJ2yH6wLCij4EApA==}
|
||||||
|
|
||||||
|
'@supabase/storage-js@2.7.0':
|
||||||
|
resolution: {integrity: sha512-iZenEdO6Mx9iTR6T7wC7sk6KKsoDPLq8rdu5VRy7+JiT1i8fnqfcOr6mfF2Eaqky9VQzhP8zZKQYjzozB65Rig==}
|
||||||
|
|
||||||
|
'@supabase/supabase-js@2.45.4':
|
||||||
|
resolution: {integrity: sha512-E5p8/zOLaQ3a462MZnmnz03CrduA5ySH9hZyL03Y+QZLIOO4/Gs8Rdy4ZCKDHsN7x0xdanVEWWFN3pJFQr9/hg==}
|
||||||
|
|
||||||
'@swc/helpers@0.3.17':
|
'@swc/helpers@0.3.17':
|
||||||
resolution: {integrity: sha512-tb7Iu+oZ+zWJZ3HJqwx8oNwSDIU440hmVMDPhpACWQWnrZHK99Bxs70gT1L2dnr5Hg50ZRWEFkQCAnOVVV0z1Q==}
|
resolution: {integrity: sha512-tb7Iu+oZ+zWJZ3HJqwx8oNwSDIU440hmVMDPhpACWQWnrZHK99Bxs70gT1L2dnr5Hg50ZRWEFkQCAnOVVV0z1Q==}
|
||||||
|
|
||||||
@@ -1223,6 +1248,9 @@ packages:
|
|||||||
'@types/pdfkit@0.13.4':
|
'@types/pdfkit@0.13.4':
|
||||||
resolution: {integrity: sha512-ixGNDHYJCCKuamY305wbfYSphZ2WPe8FPkjn8oF4fHV+PgPV4V+hecPh2VOS2h4RNtpSB3zQcR4sCpNvvrEb1A==}
|
resolution: {integrity: sha512-ixGNDHYJCCKuamY305wbfYSphZ2WPe8FPkjn8oF4fHV+PgPV4V+hecPh2VOS2h4RNtpSB3zQcR4sCpNvvrEb1A==}
|
||||||
|
|
||||||
|
'@types/phoenix@1.6.5':
|
||||||
|
resolution: {integrity: sha512-xegpDuR+z0UqG9fwHqNoy3rI7JDlvaPh2TY47Fl80oq6g+hXT+c/LEuE43X48clZ6lOfANl5WrPur9fYO1RJ/w==}
|
||||||
|
|
||||||
'@types/qs@6.9.16':
|
'@types/qs@6.9.16':
|
||||||
resolution: {integrity: sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==}
|
resolution: {integrity: sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==}
|
||||||
|
|
||||||
@@ -1244,6 +1272,9 @@ packages:
|
|||||||
'@types/whatwg-url@11.0.5':
|
'@types/whatwg-url@11.0.5':
|
||||||
resolution: {integrity: sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==}
|
resolution: {integrity: sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==}
|
||||||
|
|
||||||
|
'@types/ws@8.5.12':
|
||||||
|
resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==}
|
||||||
|
|
||||||
'@ungap/structured-clone@1.2.0':
|
'@ungap/structured-clone@1.2.0':
|
||||||
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
|
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
|
||||||
|
|
||||||
@@ -1491,9 +1522,6 @@ packages:
|
|||||||
'@vue/shared@3.4.27':
|
'@vue/shared@3.4.27':
|
||||||
resolution: {integrity: sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==}
|
resolution: {integrity: sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==}
|
||||||
|
|
||||||
'@vue/shared@3.5.7':
|
|
||||||
resolution: {integrity: sha512-NBE1PBIvzIedxIc2RZiKXvGbJkrZ2/hLf3h8GlS4/sP9xcXEZMFWOazFkNd6aGeUCMaproe5MHVYB3/4AW9q9g==}
|
|
||||||
|
|
||||||
'@vue/shared@3.5.8':
|
'@vue/shared@3.5.8':
|
||||||
resolution: {integrity: sha512-mJleSWbAGySd2RJdX1RBtcrUBX6snyOc0qHpgk3lGi4l9/P/3ny3ELqFWqYdkXIwwNN/kdm8nD9ky8o6l/Lx2A==}
|
resolution: {integrity: sha512-mJleSWbAGySd2RJdX1RBtcrUBX6snyOc0qHpgk3lGi4l9/P/3ny3ELqFWqYdkXIwwNN/kdm8nD9ky8o6l/Lx2A==}
|
||||||
|
|
||||||
@@ -6632,6 +6660,48 @@ snapshots:
|
|||||||
|
|
||||||
'@sindresorhus/merge-streams@2.3.0': {}
|
'@sindresorhus/merge-streams@2.3.0': {}
|
||||||
|
|
||||||
|
'@supabase/auth-js@2.65.0':
|
||||||
|
dependencies:
|
||||||
|
'@supabase/node-fetch': 2.6.15
|
||||||
|
|
||||||
|
'@supabase/functions-js@2.4.1':
|
||||||
|
dependencies:
|
||||||
|
'@supabase/node-fetch': 2.6.15
|
||||||
|
|
||||||
|
'@supabase/node-fetch@2.6.15':
|
||||||
|
dependencies:
|
||||||
|
whatwg-url: 5.0.0
|
||||||
|
|
||||||
|
'@supabase/postgrest-js@1.16.1':
|
||||||
|
dependencies:
|
||||||
|
'@supabase/node-fetch': 2.6.15
|
||||||
|
|
||||||
|
'@supabase/realtime-js@2.10.2':
|
||||||
|
dependencies:
|
||||||
|
'@supabase/node-fetch': 2.6.15
|
||||||
|
'@types/phoenix': 1.6.5
|
||||||
|
'@types/ws': 8.5.12
|
||||||
|
ws: 8.17.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bufferutil
|
||||||
|
- utf-8-validate
|
||||||
|
|
||||||
|
'@supabase/storage-js@2.7.0':
|
||||||
|
dependencies:
|
||||||
|
'@supabase/node-fetch': 2.6.15
|
||||||
|
|
||||||
|
'@supabase/supabase-js@2.45.4':
|
||||||
|
dependencies:
|
||||||
|
'@supabase/auth-js': 2.65.0
|
||||||
|
'@supabase/functions-js': 2.4.1
|
||||||
|
'@supabase/node-fetch': 2.6.15
|
||||||
|
'@supabase/postgrest-js': 1.16.1
|
||||||
|
'@supabase/realtime-js': 2.10.2
|
||||||
|
'@supabase/storage-js': 2.7.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bufferutil
|
||||||
|
- utf-8-validate
|
||||||
|
|
||||||
'@swc/helpers@0.3.17':
|
'@swc/helpers@0.3.17':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
@@ -6719,6 +6789,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.12.12
|
'@types/node': 20.12.12
|
||||||
|
|
||||||
|
'@types/phoenix@1.6.5': {}
|
||||||
|
|
||||||
'@types/qs@6.9.16': {}
|
'@types/qs@6.9.16': {}
|
||||||
|
|
||||||
'@types/resize-observer-browser@0.1.11': {}
|
'@types/resize-observer-browser@0.1.11': {}
|
||||||
@@ -6735,6 +6807,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/webidl-conversions': 7.0.3
|
'@types/webidl-conversions': 7.0.3
|
||||||
|
|
||||||
|
'@types/ws@8.5.12':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 20.12.12
|
||||||
|
|
||||||
'@ungap/structured-clone@1.2.0': {}
|
'@ungap/structured-clone@1.2.0': {}
|
||||||
|
|
||||||
'@unhead/dom@1.9.11':
|
'@unhead/dom@1.9.11':
|
||||||
@@ -7152,7 +7228,7 @@ snapshots:
|
|||||||
'@volar/language-core': 1.11.1
|
'@volar/language-core': 1.11.1
|
||||||
'@volar/source-map': 1.11.1
|
'@volar/source-map': 1.11.1
|
||||||
'@vue/compiler-dom': 3.4.27
|
'@vue/compiler-dom': 3.4.27
|
||||||
'@vue/shared': 3.5.7
|
'@vue/shared': 3.5.8
|
||||||
computeds: 0.0.1
|
computeds: 0.0.1
|
||||||
minimatch: 9.0.4
|
minimatch: 9.0.4
|
||||||
muggle-string: 0.3.1
|
muggle-string: 0.3.1
|
||||||
@@ -7200,8 +7276,6 @@ snapshots:
|
|||||||
|
|
||||||
'@vue/shared@3.4.27': {}
|
'@vue/shared@3.4.27': {}
|
||||||
|
|
||||||
'@vue/shared@3.5.7': {}
|
|
||||||
|
|
||||||
'@vue/shared@3.5.8': {}
|
'@vue/shared@3.5.8': {}
|
||||||
|
|
||||||
'@vueuse/components@10.10.0(vue@3.4.27(typescript@5.4.2))':
|
'@vueuse/components@10.10.0(vue@3.4.27(typescript@5.4.2))':
|
||||||
|
|||||||
15
dashboard/public/supabase.svg
Normal file
15
dashboard/public/supabase.svg
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<svg width="98" height="100" viewBox="0 0 98 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M56.8944 98.338C54.3397 101.555 49.1599 99.7924 49.0983 95.6846L48.1982 35.6025H88.5973C95.9147 35.6025 99.9957 44.0542 95.4457 49.7849L56.8944 98.338Z" fill="url(#paint0_linear_99_24683)"/>
|
||||||
|
<path d="M56.8944 98.338C54.3397 101.555 49.1599 99.7924 49.0983 95.6846L48.1982 35.6025H88.5973C95.9147 35.6025 99.9957 44.0542 95.4457 49.7849L56.8944 98.338Z" fill="url(#paint1_linear_99_24683)" fill-opacity="0.2"/>
|
||||||
|
<path d="M40.464 1.66109C43.0187 -1.55638 48.1986 0.206562 48.2601 4.31445L48.6546 64.3964H8.76106C1.44348 64.3964 -2.63767 55.9448 1.91262 50.214L40.464 1.66109Z" fill="#3ECF8E"/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear_99_24683" x1="48.1982" y1="48.9242" x2="84.1036" y2="63.9829" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#249361"/>
|
||||||
|
<stop offset="1" stop-color="#3ECF8E"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint1_linear_99_24683" x1="32.2797" y1="27.1289" x2="48.6544" y2="57.9534" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop/>
|
||||||
|
<stop offset="1" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
23
dashboard/server/api/integrations/credentials/get.ts
Normal file
23
dashboard/server/api/integrations/credentials/get.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { getUserProjectFromId } from "~/server/LIVE_DEMO_DATA";
|
||||||
|
import { IntegrationsCredentialsModel } from '@schema/integrations/IntegrationsCredentialsSchema';
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
|
const project_id = getHeader(event, 'x-pid');
|
||||||
|
if (!project_id) return;
|
||||||
|
|
||||||
|
const user = getRequestUser(event);
|
||||||
|
const project = await getUserProjectFromId(project_id, user);
|
||||||
|
if (!project) return;
|
||||||
|
|
||||||
|
const credentials = await IntegrationsCredentialsModel.findOne({ project_id });
|
||||||
|
|
||||||
|
return {
|
||||||
|
supabase: {
|
||||||
|
anon_key: credentials?.supabase_anon_key || '',
|
||||||
|
service_role_key: credentials?.supabase_service_role_key || '',
|
||||||
|
url: credentials?.supabase_url || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
23
dashboard/server/api/integrations/credentials/update.post.ts
Normal file
23
dashboard/server/api/integrations/credentials/update.post.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { IntegrationsCredentialsModel } from "@schema/integrations/IntegrationsCredentialsSchema";
|
||||||
|
import { getUserProjectFromId } from "~/server/LIVE_DEMO_DATA";
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
|
const project_id = getHeader(event, 'x-pid');
|
||||||
|
if (!project_id) return;
|
||||||
|
|
||||||
|
const user = getRequestUser(event);
|
||||||
|
const project = await getUserProjectFromId(project_id, user);
|
||||||
|
if (!project) return;
|
||||||
|
|
||||||
|
const body = await readBody(event);
|
||||||
|
|
||||||
|
const res = await IntegrationsCredentialsModel.updateOne({ project_id }, {
|
||||||
|
supabase_anon_key: body.supabase_anon_key || '',
|
||||||
|
supabase_service_role_key: body.supabase_service_role_key || '',
|
||||||
|
supabase_url: body.supabase_url || '',
|
||||||
|
}, { upsert: true });
|
||||||
|
|
||||||
|
return { ok: res.acknowledged };
|
||||||
|
|
||||||
|
});
|
||||||
29
dashboard/server/api/integrations/supabase/add.post.ts
Normal file
29
dashboard/server/api/integrations/supabase/add.post.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { getUserProjectFromId } from "~/server/LIVE_DEMO_DATA";
|
||||||
|
import { SupabaseIntegrationModel } from "@schema/integrations/SupabaseIntegrationSchema";
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
|
const project_id = getHeader(event, 'x-pid');
|
||||||
|
if (!project_id) return;
|
||||||
|
|
||||||
|
const user = getRequestUser(event);
|
||||||
|
const project = await getUserProjectFromId(project_id, user);
|
||||||
|
if (!project) return;
|
||||||
|
|
||||||
|
const { chart_type, table_name, xField, yMode, from, to, slice, name } = await readBody(event);
|
||||||
|
|
||||||
|
if (!project.premium) {
|
||||||
|
const supabaseIntegrationsCount = await SupabaseIntegrationModel.countDocuments({ project_id });
|
||||||
|
if (supabaseIntegrationsCount > 0) return setResponseStatus(event, 400, 'LIMIT_REACHED');
|
||||||
|
}
|
||||||
|
|
||||||
|
await SupabaseIntegrationModel.create({
|
||||||
|
name,
|
||||||
|
project_id, chart_type,
|
||||||
|
table_name, xField, yMode,
|
||||||
|
from, to, slice,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { ok: true };
|
||||||
|
|
||||||
|
});
|
||||||
18
dashboard/server/api/integrations/supabase/get.ts
Normal file
18
dashboard/server/api/integrations/supabase/get.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { getUserProjectFromId } from "~/server/LIVE_DEMO_DATA";
|
||||||
|
import { SupabaseIntegrationModel } from '@schema/integrations/SupabaseIntegrationSchema';
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
|
const project_id = getHeader(event, 'x-pid');
|
||||||
|
if (!project_id) return;
|
||||||
|
|
||||||
|
const user = getRequestUser(event);
|
||||||
|
const project = await getUserProjectFromId(project_id, user);
|
||||||
|
if (!project) return;
|
||||||
|
|
||||||
|
const integration_id = getHeader(event, 'x-integration');
|
||||||
|
|
||||||
|
const integration = await SupabaseIntegrationModel.findOne({ _id: integration_id });
|
||||||
|
return integration;
|
||||||
|
|
||||||
|
});
|
||||||
16
dashboard/server/api/integrations/supabase/list.ts
Normal file
16
dashboard/server/api/integrations/supabase/list.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { getUserProjectFromId } from "~/server/LIVE_DEMO_DATA";
|
||||||
|
import { SupabaseIntegrationModel } from '@schema/integrations/SupabaseIntegrationSchema';
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
|
const project_id = getHeader(event, 'x-pid');
|
||||||
|
if (!project_id) return;
|
||||||
|
|
||||||
|
const user = getRequestUser(event);
|
||||||
|
const project = await getUserProjectFromId(project_id, user);
|
||||||
|
if (!project) return;
|
||||||
|
|
||||||
|
const integrations = await SupabaseIntegrationModel.find({ project_id });
|
||||||
|
return integrations;
|
||||||
|
|
||||||
|
});
|
||||||
@@ -32,7 +32,7 @@ services:
|
|||||||
|
|
||||||
|
|
||||||
broker:
|
broker:
|
||||||
image: litlyx-broker
|
image: litlyx-consumer
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- "3999:3999"
|
- "3999:3999"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
|
||||||
const props = defineProps<{ placeholder?: string, modelValue: string }>();
|
const props = defineProps<{ placeholder?: string, modelValue: string, type?: string }>();
|
||||||
|
|
||||||
const emits = defineEmits<{
|
const emits = defineEmits<{
|
||||||
(e: "update:modelValue", value: string): void
|
(e: "update:modelValue", value: string): void
|
||||||
@@ -18,8 +18,7 @@ const handleChange = (event: Event) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<input class="bg-lyx-widget-light text-lyx-text-dark poppins rounded-md outline outline-[1px] outline-lyx-widget-lighter" type="text"
|
<input
|
||||||
:placeholder="props.placeholder"
|
class="bg-lyx-widget-light text-lyx-text-dark poppins rounded-md outline outline-[1px] outline-lyx-widget-lighter"
|
||||||
:value="props.modelValue"
|
:type="props.type ?? 'text'" :placeholder="props.placeholder" :value="props.modelValue" @input="handleChange">
|
||||||
@input="handleChange">
|
|
||||||
</template>
|
</template>
|
||||||
18
shared/schema/integrations/IntegrationsCredentialsSchema.ts
Normal file
18
shared/schema/integrations/IntegrationsCredentialsSchema.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { model, Schema, Types } from 'mongoose';
|
||||||
|
|
||||||
|
export type TIntegrationsCredentials = {
|
||||||
|
_id: Schema.Types.ObjectId,
|
||||||
|
project_id: Schema.Types.ObjectId,
|
||||||
|
supabase_url: string,
|
||||||
|
supabase_anon_key: string,
|
||||||
|
supabase_service_role_key: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const IntegrationsCredentialsSchema = new Schema<TIntegrationsCredentials>({
|
||||||
|
project_id: { type: Types.ObjectId, index: 1 },
|
||||||
|
supabase_url: { type: String },
|
||||||
|
supabase_anon_key: { type: String },
|
||||||
|
supabase_service_role_key: { type: String },
|
||||||
|
});
|
||||||
|
|
||||||
|
export const IntegrationsCredentialsModel = model<TIntegrationsCredentials>('integrations_credentials', IntegrationsCredentialsSchema);
|
||||||
28
shared/schema/integrations/SupabaseIntegrationSchema.ts
Normal file
28
shared/schema/integrations/SupabaseIntegrationSchema.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { model, Schema, Types } from 'mongoose';
|
||||||
|
|
||||||
|
export type TSupabaseIntegration = {
|
||||||
|
_id: Schema.Types.ObjectId,
|
||||||
|
project_id: Schema.Types.ObjectId,
|
||||||
|
name: string,
|
||||||
|
chart_type: string,
|
||||||
|
table_name: string,
|
||||||
|
xField: string,
|
||||||
|
yMode: string,
|
||||||
|
from: Date,
|
||||||
|
to: Date,
|
||||||
|
slice: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const SupabaseIntegrationSchema = new Schema<TSupabaseIntegration>({
|
||||||
|
project_id: { type: Types.ObjectId, index: 1 },
|
||||||
|
name: { type: String, required: true },
|
||||||
|
chart_type: { type: String, required: true },
|
||||||
|
table_name: { type: String, required: true },
|
||||||
|
xField: { type: String, required: true },
|
||||||
|
yMode: { type: String, required: true },
|
||||||
|
from: { type: Date, required: true },
|
||||||
|
to: { type: Date, required: true },
|
||||||
|
slice: { type: String, required: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SupabaseIntegrationModel = model<TSupabaseIntegration>('supabase_integrations', SupabaseIntegrationSchema);
|
||||||
Reference in New Issue
Block a user