mirror of
https://github.com/Gimanh/taskview-community.git
synced 2026-09-12 05:49:01 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b3cc983965 | |||
| a14c332586 | |||
| aa91934f19 | |||
| aa8e6e2da6 | |||
| c4c9a16a09 | |||
| 293340c253 | |||
| 04bb545c25 | |||
| aa235c10ea | |||
| 45e4a11605 | |||
| 55b42dfecb | |||
| a620c39b50 | |||
| 8edb2d50d3 | |||
| 19093598a0 | |||
| 9091d576e1 | |||
| 7d9e4a811a | |||
| 1b03e61d2b | |||
| 0936f51a24 | |||
| c4c6d91456 | |||
| 1ba13eef45 | |||
| 8a67296673 | |||
| 8fe3353c5f | |||
| 61debcda11 | |||
| 615a47eafd | |||
| 4705055062 | |||
| 7426f72257 | |||
| 64ddf4acfa | |||
| d843008091 | |||
| 95cbd330b1 | |||
| cf7055ce60 |
@@ -28,11 +28,11 @@ jobs:
|
||||
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN_CLA }}
|
||||
with:
|
||||
path-to-signatures: 'signatures/cla.json'
|
||||
path-to-document: 'https://github.com/Gimanh/test-cla1/blob/main/CLA/CLA.md'
|
||||
path-to-document: 'https://github.com/Gimanh/taskview-community/blob/main/CLA/CLA.md'
|
||||
branch: 'cla-signatures'
|
||||
allowlist: bot*,dependabot[bot]
|
||||
custom-notsigned-prcomment: >
|
||||
Thank you for your contribution!
|
||||
Please read the CLA: $pathToCLADocument
|
||||
Please read the CLA: https://github.com/Gimanh/taskview-community/blob/main/CLA/CLA.md
|
||||
Then comment exactly:
|
||||
"I have read the CLA Document and I hereby sign the CLA"
|
||||
|
||||
+5
-1
@@ -35,4 +35,8 @@ tsconfig.app.tsbuildinfo
|
||||
customer-license
|
||||
!build-dockers.sh
|
||||
!build-docker-api.sh
|
||||
!build-docker-web.sh
|
||||
!build-docker-web.sh
|
||||
!entrypoint.sh
|
||||
!build-docker-migrations.sh
|
||||
*.private
|
||||
*.private*
|
||||
@@ -1,6 +1,6 @@
|
||||
# TaskView Source-Available License
|
||||
|
||||
Version 1.0 – Draft (tailored for TaskView)
|
||||
Version 1.0
|
||||
|
||||
---
|
||||
|
||||
@@ -113,9 +113,14 @@ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMA
|
||||
|
||||
For commercial licensing inquiries, contact:
|
||||
|
||||
**[licensing@taskview.tech](mailto:licensing@taskview.tech)**
|
||||
**[support@taskview.tech](mailto:support@taskview.tech)**
|
||||
|
||||
## 10. Termination
|
||||
|
||||
- Any violation of Sections 4 or 5 automatically terminates the rights granted under this License.
|
||||
- Rights may be reinstated if You cure the violation within 21 days and cease all prohibited use.
|
||||
|
||||
---
|
||||
|
||||
Copyright (c) 2026 Nikolai Giman
|
||||
All rights reserved.
|
||||
@@ -127,7 +127,7 @@ docker-compose up
|
||||
Make sure the image versions match the version defined in the root package.json.
|
||||
|
||||
## Roadmap
|
||||
- Migrate api-server to BunJS
|
||||
|
||||
- Plugin / extension system
|
||||
- Migrate to NuxtUI or similar ui library
|
||||
- Enterprise SSO and identity integrations
|
||||
@@ -137,4 +137,8 @@ Make sure the image versions match the version defined in the root package.json.
|
||||
|
||||
Note for contributors: contributions are accepted under the CLA (see CONTRIBUTING.md). The Project is distributed under the TaskView Source-Available License.
|
||||
|
||||
© TaskView
|
||||
---
|
||||
|
||||
TaskView is developed and maintained by Nikolai Giman.
|
||||
|
||||
Copyright © 2026 Nikolai Giman
|
||||
|
||||
+15
-10
@@ -5,14 +5,16 @@
|
||||
|
||||
VERSION=$1
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo "Usage: ./build-docker-api.sh <version>"
|
||||
echo "Example: ./build-docker-api.sh 1.17.0"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
node -v
|
||||
|
||||
# Build the server scripts for the docker container (obfuscated scripts)
|
||||
npm run vite-build-docker
|
||||
|
||||
# Create the taskview-server.js file for running the server
|
||||
# (taskview-server-api.jsc is bytecode file that will be created by bytenode when docker file is run)
|
||||
echo "const bytenode = require('bytenode'); require('./taskview-server-api.jsc');" > ./dist/taskview-server.js
|
||||
# Build the server scripts for the docker container
|
||||
npm run build:docker
|
||||
|
||||
# Copy the production package.json to the dist folder
|
||||
cp ./production.package.json ./dist/package.json
|
||||
@@ -20,14 +22,17 @@ cp ./production.package.json ./dist/package.json
|
||||
# Copy the production ecosystem.config.js to the dist folder
|
||||
cp ./production.ecosystem.config.js ./dist/ecosystem.config.js
|
||||
|
||||
# Build the migration scripts for the docker container (obfuscated scripts)
|
||||
npm run vite-build-migration
|
||||
mkdir ./dist-migration/taskview
|
||||
# Build the migration scripts for the docker container
|
||||
npm run build:migration
|
||||
mkdir -p ./dist-migration/taskview
|
||||
cp -R ./src/migrations/taskview/* ./dist-migration/taskview
|
||||
node ./commands/copy-migration-files.js
|
||||
|
||||
echo "Building docker image..."
|
||||
docker buildx build --platform=linux/amd64,linux/arm64 -t gimanhead/taskview-ce-api-server:$VERSION -t gimanhead/taskview-ce-api-server:latest . --load
|
||||
|
||||
cd postgresql
|
||||
bash ./build-docker-migrations.sh $VERSION
|
||||
cd ..
|
||||
cd ..
|
||||
|
||||
echo "Build complete! Image: gimanhead/taskview-ce-api-server:$VERSION"
|
||||
|
||||
@@ -14,7 +14,7 @@ services:
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
migration:
|
||||
image: gimanhead/taskview-ce-db-migration:1.18.0
|
||||
image: gimanhead/taskview-ce-db-migration:1.18.2
|
||||
restart: "no"
|
||||
depends_on:
|
||||
db:
|
||||
@@ -23,12 +23,12 @@ services:
|
||||
- ./.env.taskview
|
||||
|
||||
taskview-ce-webapp:
|
||||
image: gimanhead/taskview-ce-webapp:1.18.0
|
||||
image: gimanhead/taskview-ce-webapp:1.18.2
|
||||
restart: "no"
|
||||
ports:
|
||||
- "8888:80"
|
||||
taskview-api-server:
|
||||
image: gimanhead/taskview-ce-api-server:1.18.0
|
||||
image: gimanhead/taskview-ce-api-server:1.18.2
|
||||
restart: "no"
|
||||
ports:
|
||||
- "1725:1401"
|
||||
|
||||
+3
-10
@@ -1,10 +1,6 @@
|
||||
FROM node:20-bullseye-slim
|
||||
FROM node:24.12.0-alpine
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
curl \
|
||||
build-essential \
|
||||
qemu-user-static \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN apk add --no-cache curl
|
||||
|
||||
RUN npm install pm2 -g
|
||||
|
||||
@@ -12,10 +8,7 @@ WORKDIR /usr/src/app
|
||||
|
||||
COPY ./dist .
|
||||
|
||||
RUN npm install
|
||||
RUN npm run byte
|
||||
RUN rm ./taskview-server-api.js
|
||||
RUN rm ./taskview-server-docker.js
|
||||
RUN npm install --omit=dev
|
||||
|
||||
EXPOSE 1401
|
||||
|
||||
|
||||
+17
-12
@@ -2,14 +2,12 @@
|
||||
"name": "taskview-ce-api-server",
|
||||
"version": "1.17.0",
|
||||
"scripts": {
|
||||
"byte": "bytenode --compile ./dist/taskview-server-api.js",
|
||||
"vite-build": "vite build && javascript-obfuscator ./dist/taskview-server.js --output ./dist/taskview-server-api.js && npm run byte",
|
||||
"vite-build-docker": "vite build --config ./vite.config.docker.mts && javascript-obfuscator ./dist/taskview-server-docker.js --output ./dist/taskview-server-api.js",
|
||||
"vite-build-migration": "vite build --config ./vite.config-migration.mts",
|
||||
"start": "bun run --watch ./server.ts",
|
||||
"build": "bun build ./server.ts --outdir=dist --target=node",
|
||||
"build-all": "bun build ./server.ts --outdir=dist --target=node --minify && javascript-obfuscator ./dist/server.js --output ./dist/taskview.js",
|
||||
"start-prod": "NODE_ENV=production bun run --watch ./server.ts",
|
||||
"dev": "bun run --watch ./server.ts",
|
||||
"start": "NODE_ENV=production node ./dist/taskview-server.js",
|
||||
"build": "vite build",
|
||||
"build:docker": "vite build --config ./vite.config.docker.mts",
|
||||
"build:migration": "vite build --config ./vite.config-migration.mts",
|
||||
"build:all": "pnpm run build:docker && pnpm run build:migration",
|
||||
"test": "vitest",
|
||||
"type-check": "tsc --noEmit",
|
||||
"lint": "biome lint .",
|
||||
@@ -21,7 +19,7 @@
|
||||
"@rollup/plugin-commonjs": "^28.0.1",
|
||||
"@rollup/plugin-node-resolve": "^15.3.0",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/bun": "latest",
|
||||
"@types/cookie-parser": "^1.4.10",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/jsonwebtoken": "^9.0.7",
|
||||
@@ -29,10 +27,7 @@
|
||||
"@types/pg": "^8.15.5",
|
||||
"@types/semver": "^7.5.8",
|
||||
"aws-sdk": "^2.1691.0",
|
||||
"bytenode": "1.5.6",
|
||||
"javascript-obfuscator": "^4.1.1",
|
||||
"mock-aws-s3": "^4.0.2",
|
||||
"nexe": "^4.0.0-rc.6",
|
||||
"nock": "^13.5.5",
|
||||
"vite": "^5.4.9",
|
||||
"vite-plugin-node": "^4.0.0",
|
||||
@@ -45,10 +40,14 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/passport": "^1.0.17",
|
||||
"@types/passport-github2": "^1.2.9",
|
||||
"@types/passport-google-oauth20": "^2.0.17",
|
||||
"@vitejs/plugin-legacy": "^5.4.2",
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
"axios": "^1.7.7",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"drizzle-orm": "^0.44.4",
|
||||
@@ -56,6 +55,9 @@
|
||||
"express": "4.21.0",
|
||||
"helmet": "^7.1.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"passport": "^0.7.0",
|
||||
"passport-github2": "^0.1.12",
|
||||
"passport-google-oauth20": "^2.0.0",
|
||||
"pg": "^8.16.3",
|
||||
"pino": "^9.4.0",
|
||||
"rotating-file-stream": "^3.2.5",
|
||||
@@ -63,5 +65,8 @@
|
||||
"taskview-db-schemas": "workspace:^",
|
||||
"terser": "^5.36.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=24 <25"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
VERSION=$1
|
||||
|
||||
docker buildx build --platform=linux/amd64,linux/arm64 -t gimanhead/taskview-ce-db-migration:$VERSION -t gimanhead/taskview-ce-db-migration:latest . --load
|
||||
@@ -1,5 +1,5 @@
|
||||
FROM node:20-slim
|
||||
RUN apt-get update && apt-get install -y postgresql-client && apt-get clean
|
||||
FROM node:24.12.0-alpine
|
||||
RUN apk add --no-cache postgresql-client
|
||||
|
||||
# ENV DB_HOST=localhost
|
||||
# ENV DB_USER=tvdbuser
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/bin/sh
|
||||
# entrypoint.sh
|
||||
|
||||
echo "Waiting for the database service to be ready..."
|
||||
|
||||
# Wait for the database service to be ready
|
||||
until PGPASSWORD=$DB_PASSWORD psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -c '\q'; do
|
||||
>&2 echo "Postgres is unavailable - sleeping"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
>&2 echo "Postgres is up - executing command"
|
||||
|
||||
# Check if tables exist in the 'tasks' schema using SQL query.
|
||||
# 'count(*)' will return 0 if there are no tables.
|
||||
# We read this result and make a decision.
|
||||
TABLE_COUNT=$(PGPASSWORD=$DB_PASSWORD psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -t -c "SELECT count(*) FROM information_schema.tables WHERE table_schema = 'tasks';" | xargs)
|
||||
|
||||
if [ "$TABLE_COUNT" -eq 0 ]; then
|
||||
echo "No tables found in the 'tasks' schema. Running migration with --create."
|
||||
node /app-migration/taskview-db-migration.js --create
|
||||
else
|
||||
echo "Tables already exist in the 'tasks' schema ($TABLE_COUNT tables found). Running standard migration."
|
||||
node /app-migration/taskview-db-migration.js
|
||||
fi
|
||||
|
||||
# Check the exit code of the migration script
|
||||
if [ $? -ne 0 ]; then
|
||||
>&2 echo "Migration failed! Exiting with an error."
|
||||
exit 1
|
||||
else
|
||||
>&2 echo "Migration successful!"
|
||||
exit 0
|
||||
fi
|
||||
@@ -1,9 +1,8 @@
|
||||
{
|
||||
"name": "taskview-ce-api-server",
|
||||
"version": "1.17.0",
|
||||
"scripts": {
|
||||
"byte": "bytenode --compile ./taskview-server-api.js",
|
||||
"start": "pm2-runtime start ecosystem.config.js --env production"
|
||||
},
|
||||
"dependencies": {
|
||||
"bytenode": "1.5.6"
|
||||
}
|
||||
"dependencies": {}
|
||||
}
|
||||
+31
-3
@@ -4,8 +4,18 @@ import helmet from 'helmet';
|
||||
import { appUserMiddleware } from './middlewares/app-user-middleware';
|
||||
import errorHandler from './middlewares/error-handler';
|
||||
import routes from './routes';
|
||||
import passport, { initPassportLogin } from './tv-modules/auth/strategies/passport-login';
|
||||
import cookieParser from 'cookie-parser';
|
||||
|
||||
const VRS = '1.18.0';
|
||||
const allow = new Set([
|
||||
...(process.env.CORS_REMOVE_DEFAULT_ALLOWED_ORIGINS === 'true' ? [] : [
|
||||
// default allowed origins for official TaskView apps
|
||||
"https://app.taskview.tech",
|
||||
"https://taskview.handscream.com",
|
||||
"capacitor://taskview.handscream.com",
|
||||
"capacitor://app.taskview.tech",
|
||||
]),
|
||||
]);
|
||||
|
||||
export default class App {
|
||||
public app: express.Application;
|
||||
@@ -15,11 +25,20 @@ export default class App {
|
||||
this.app = express();
|
||||
this.port = port;
|
||||
|
||||
this.extendApp();
|
||||
|
||||
this.initializeMiddlewares();
|
||||
this.initializeRoutes();
|
||||
this.app.use(errorHandler);
|
||||
this.app.use(passport.initialize());
|
||||
initPassportLogin();
|
||||
|
||||
this.extendMiddlewares();
|
||||
}
|
||||
|
||||
protected extendApp(): void { }
|
||||
protected extendMiddlewares(): void { }
|
||||
|
||||
private initializeMiddlewares() {
|
||||
//add tvJson method, clien need response format like {response: data}
|
||||
this.app.use((_req: Request, res: Response, next) => {
|
||||
@@ -29,8 +48,18 @@ export default class App {
|
||||
next();
|
||||
});
|
||||
|
||||
this.app.use(cookieParser());
|
||||
this.app.use(appUserMiddleware);
|
||||
this.app.use(cors());
|
||||
|
||||
this.app.use(cors({
|
||||
credentials: true,
|
||||
origin(origin, cb) {
|
||||
if (!origin) return cb(null, true);
|
||||
if (allow.has(origin)) return cb(null, true);
|
||||
return cb(new Error(`CORS blocked origin: ${origin}`), false);
|
||||
},
|
||||
}));
|
||||
|
||||
this.app.use(helmet());
|
||||
this.app.use(express.json());
|
||||
this.app.use(express.urlencoded({ extended: true }));
|
||||
@@ -45,7 +74,6 @@ export default class App {
|
||||
public listen() {
|
||||
return this.app.listen(this.port, '0.0.0.0', () => {
|
||||
console.log(`Server is running on port ${this.port}`);
|
||||
console.log(`Server version is ${VRS}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,5 +282,17 @@
|
||||
"description": [
|
||||
"Release 1.16.0"
|
||||
]
|
||||
},
|
||||
"22": {
|
||||
"version": "1.18.2",
|
||||
"name": "Release 1.18.2",
|
||||
"releaseDate": "20260108",
|
||||
"scripts": [
|
||||
"/1.18.2/0.1.18.2.sql",
|
||||
"/1.18.2/all-triggers.sql"
|
||||
],
|
||||
"description": [
|
||||
"Release 1.18.2"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
--Delete orphaned records from history.tasks_tasks table
|
||||
delete from history.tasks_tasks h
|
||||
where not exists (
|
||||
select 1
|
||||
from tasks.tasks t
|
||||
where t.id = h.task_id
|
||||
);
|
||||
|
||||
--Delete orphaned records from history.tasks_goal_lists table
|
||||
delete from history.tasks_goal_lists h
|
||||
where not exists (
|
||||
select 1
|
||||
from tasks.goal_lists gl
|
||||
where gl.id = h.goal_list_id
|
||||
);
|
||||
|
||||
--Delete orphaned records from history.tasks_goals table
|
||||
delete from history.tasks_goals h
|
||||
where not exists (
|
||||
select 1
|
||||
from tasks.goals g
|
||||
where g.id = h.goal_id
|
||||
);
|
||||
|
||||
alter table history.tasks_tasks
|
||||
add constraint fk_history_tasks_tasks_task_id
|
||||
foreign key (task_id)
|
||||
references tasks.tasks (id) on delete cascade;
|
||||
|
||||
alter table history.tasks_goal_lists
|
||||
add constraint fk_history_tasks_goal_lists_goal_list_id
|
||||
foreign key (goal_list_id)
|
||||
references tasks.goal_lists (id) on delete cascade;
|
||||
|
||||
alter table history.tasks_goals
|
||||
add constraint fk_history_tasks_goals_goal_id
|
||||
foreign key (goal_id)
|
||||
references tasks.goals (id) on delete cascade;
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,557 @@
|
||||
--1.
|
||||
--Trigger set previous version
|
||||
create or replace function app.trigger_set_previous_version()
|
||||
returns trigger as
|
||||
$date_complete$
|
||||
begin
|
||||
new.prev_version = old.version;
|
||||
return new;
|
||||
end;
|
||||
$date_complete$
|
||||
language plpgsql;
|
||||
|
||||
drop trigger if exists trigger_set_previous_version on app.version;
|
||||
create trigger trigger_set_previous_version
|
||||
before insert
|
||||
on app.version
|
||||
for each row
|
||||
execute procedure app.trigger_set_previous_version();
|
||||
|
||||
--2.
|
||||
--Trigger for adding owner for taskList from goal
|
||||
create or replace function tasks.trigger_set_owner_for_component()
|
||||
returns trigger as
|
||||
$date_complete$
|
||||
begin
|
||||
new.owner = (select owner from tasks.goals where id = new.goal_id);
|
||||
return new;
|
||||
end;
|
||||
$date_complete$
|
||||
language plpgsql;
|
||||
|
||||
drop trigger if exists trigger_set_owner_for_component on tasks.goal_lists;
|
||||
create trigger trigger_set_owner_for_component
|
||||
before insert
|
||||
on tasks.goal_lists
|
||||
for each row
|
||||
execute procedure tasks.trigger_set_owner_for_component();
|
||||
|
||||
--3.
|
||||
--Trigger for updating date_complete for task
|
||||
create or replace function tasks.update_date_complete()
|
||||
returns trigger as
|
||||
$date_complete$
|
||||
begin
|
||||
if new.complete != old.complete
|
||||
then
|
||||
if new.complete = true
|
||||
then
|
||||
update tasks.tasks set date_complete = now() where id = old.id;
|
||||
else
|
||||
update tasks.tasks set date_complete = null where id = old.id;
|
||||
end if;
|
||||
end if;
|
||||
return new;
|
||||
end;
|
||||
$date_complete$
|
||||
language plpgsql;
|
||||
|
||||
drop trigger if exists tr_update_date_complete on tasks.tasks;
|
||||
create trigger tr_update_date_complete
|
||||
after update
|
||||
on tasks.tasks
|
||||
for each row
|
||||
execute procedure tasks.update_date_complete();
|
||||
|
||||
--4.
|
||||
-- Delete user from collaboration if not assigned to any goal
|
||||
create or replace function collaboration.delete_user_if_not_assigned_to_goal()
|
||||
returns trigger as $$
|
||||
declare
|
||||
count int;
|
||||
begin
|
||||
if not exists (
|
||||
select 1
|
||||
from collaboration.users_to_goals
|
||||
where user_id = old.user_id
|
||||
limit 1
|
||||
) then
|
||||
delete from collaboration.users where id = old.user_id;
|
||||
end if;
|
||||
|
||||
return old;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
drop trigger if exists trigger_delete_user_if_not_assigned_to_goal on collaboration.users_to_goals;
|
||||
create trigger trigger_delete_user_if_not_assigned_to_goal
|
||||
after delete
|
||||
on collaboration.users_to_goals
|
||||
for each row
|
||||
execute function collaboration.delete_user_if_not_assigned_to_goal();
|
||||
|
||||
--5.
|
||||
--Trigger for checking task graph relation goal to avoid connection between tasks from different goals
|
||||
create or replace function tasks.check_task_graph_relation_goal()
|
||||
returns trigger as $$
|
||||
declare
|
||||
from_goal int;
|
||||
to_goal int;
|
||||
begin
|
||||
select goal_id into from_goal from tasks.tasks where id = new.from_task_id;
|
||||
select goal_id into to_goal from tasks.tasks where id = new.to_task_id;
|
||||
|
||||
if from_goal is null or to_goal is null then
|
||||
raise exception 'Invalid task reference in relation';
|
||||
end if;
|
||||
|
||||
if from_goal <> to_goal then
|
||||
raise exception 'Relation goal_id must match both tasks'' goal_id';
|
||||
end if;
|
||||
|
||||
new.goal_id := from_goal;
|
||||
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
drop trigger if exists trigger_task_relation_goal on tasks.task_relations;
|
||||
create trigger trigger_task_relation_goal
|
||||
before insert or update on tasks.task_relations
|
||||
for each row execute function tasks.check_task_graph_relation_goal();
|
||||
|
||||
--6.
|
||||
--Trigger for logging changes in taskList to history table
|
||||
create or replace function tasks.log_changes_tasks_goal_lists()
|
||||
returns trigger as
|
||||
$body$
|
||||
begin
|
||||
if tg_op = 'DELETE' then
|
||||
insert into history.tasks_goal_lists (goal_list_id, edit_date, task, deleted) values (old.id, now(), to_jsonb(old), 1);
|
||||
return old;
|
||||
elseif tg_op = 'UPDATE' then
|
||||
insert into history.tasks_goal_lists (goal_list_id, edit_date, task, deleted)
|
||||
VALUES (old.id, new.date_creation, to_jsonb(old), 0);
|
||||
new.edit_date = now();
|
||||
return new;
|
||||
end if;
|
||||
end
|
||||
$body$
|
||||
language plpgsql;
|
||||
|
||||
drop trigger if exists trigger_log_changes_tasks_goal_lists on tasks.goal_lists;
|
||||
create trigger trigger_log_changes_tasks_goal_lists
|
||||
before update or delete
|
||||
on tasks.goal_lists
|
||||
for each row
|
||||
execute procedure tasks.log_changes_tasks_goal_lists();
|
||||
|
||||
--7.
|
||||
--Trigger for logging changes in goal to history table
|
||||
create or replace function tasks.log_changes_tasks_goals()
|
||||
returns trigger as
|
||||
$body$
|
||||
begin
|
||||
if tg_op = 'DELETE' then
|
||||
insert into history.tasks_goals (goal_id, edit_date, task, deleted) values (old.id, now(), to_jsonb(old), 1);
|
||||
return old;
|
||||
elseif tg_op = 'UPDATE' then
|
||||
insert into history.tasks_goals (goal_id, edit_date, task, deleted)
|
||||
VALUES (old.id, new.date_creation, to_jsonb(old), 0);
|
||||
new.edit_date = now();
|
||||
return new;
|
||||
end if;
|
||||
end
|
||||
$body$
|
||||
language plpgsql;
|
||||
|
||||
drop trigger if exists trigger_log_changes_tasks_goals on tasks.goals;
|
||||
create trigger trigger_log_changes_tasks_goals
|
||||
before update or delete
|
||||
on tasks.goals
|
||||
for each row
|
||||
execute procedure tasks.log_changes_tasks_goals();
|
||||
|
||||
--8.
|
||||
--Trigger for logging changes in task to history table
|
||||
create or replace function tasks.log_changes_tasks_tasks()
|
||||
returns trigger as
|
||||
$body$
|
||||
begin
|
||||
if tg_op = 'DELETE' then
|
||||
insert into history.tasks_tasks (task_id, edit_date, task, deleted) values (old.id, now(), to_jsonb(old), 1);
|
||||
return old;
|
||||
elseif tg_op = 'UPDATE' then
|
||||
insert into history.tasks_tasks (task_id, edit_date, task, deleted)
|
||||
VALUES (old.id, new.date_creation, to_jsonb(old), 0);
|
||||
new.edit_date = now();
|
||||
return new;
|
||||
end if;
|
||||
end
|
||||
$body$
|
||||
language plpgsql;
|
||||
|
||||
drop trigger if exists trigger_log_changes_tasks_tasks on tasks.tasks;
|
||||
create trigger trigger_log_changes_tasks_tasks
|
||||
before update or delete
|
||||
on tasks.tasks
|
||||
for each row
|
||||
execute procedure tasks.log_changes_tasks_tasks();
|
||||
|
||||
--9.
|
||||
--Trigger for setting goal_id default for task
|
||||
CREATE OR REPLACE FUNCTION tasks.set_goal_id_default_for_task()
|
||||
RETURNS TRIGGER AS
|
||||
$$
|
||||
DECLARE
|
||||
goal_id INT;
|
||||
BEGIN
|
||||
|
||||
SELECT gl.goal_id
|
||||
INTO goal_id
|
||||
FROM tasks.goal_lists gl
|
||||
WHERE gl.id = NEW.goal_list_id;
|
||||
|
||||
IF goal_id IS NOT NULL THEN
|
||||
NEW.goal_id := goal_id;
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
drop trigger if exists before_insert_set_goal_id_for_task on tasks.tasks;
|
||||
|
||||
CREATE TRIGGER before_insert_set_goal_id_for_task
|
||||
BEFORE INSERT OR UPDATE
|
||||
ON tasks.tasks
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION tasks.set_goal_id_default_for_task();
|
||||
|
||||
|
||||
--10.
|
||||
--Trigger for adding default roles and permissions for goal
|
||||
CREATE OR REPLACE FUNCTION tasks.add_roles_and_permissions()
|
||||
RETURNS TRIGGER AS
|
||||
$$
|
||||
DECLARE
|
||||
editor_role_id INTEGER;
|
||||
executor_role_id INTEGER;
|
||||
BEGIN
|
||||
-- 1. Create role "editor"
|
||||
INSERT INTO collaboration.roles (name, goal_id)
|
||||
VALUES ('editor', NEW.id)
|
||||
RETURNING id INTO editor_role_id;
|
||||
|
||||
-- 2. Create role "executor"
|
||||
INSERT INTO collaboration.roles (name, goal_id)
|
||||
VALUES ('executor', NEW.id)
|
||||
RETURNING id INTO executor_role_id;
|
||||
|
||||
-- 3. Add permissions for role "editor"
|
||||
INSERT INTO collaboration.permissions_to_role (role_id, permission_id)
|
||||
SELECT editor_role_id, id
|
||||
FROM tv_auth.permissions
|
||||
WHERE name IN (
|
||||
'goal_can_watch_content',
|
||||
'goal_can_edit',
|
||||
'goal_can_add_task_list',
|
||||
'goal_can_manage_users',
|
||||
'component_can_watch_content',
|
||||
'component_can_edit',
|
||||
'component_can_delete',
|
||||
'component_can_add_tasks',
|
||||
'task_can_edit_deadline',
|
||||
'task_can_watch_subtasks',
|
||||
'task_can_watch_note',
|
||||
'task_can_recovery_history',
|
||||
'task_can_watch_assigned_users',
|
||||
'task_can_edit_priority',
|
||||
'task_can_delete',
|
||||
'task_can_watch_details',
|
||||
'task_can_assign_users',
|
||||
'task_can_add_subtasks',
|
||||
'task_can_watch_tags',
|
||||
'task_can_watch_priority',
|
||||
'task_can_access_history',
|
||||
'task_can_edit_tags',
|
||||
'task_can_edit_description',
|
||||
'task_can_edit_status',
|
||||
'task_can_edit_note',
|
||||
'kanban_can_manage',
|
||||
'kanban_can_view',
|
||||
'graph_can_manage',
|
||||
'graph_can_view'
|
||||
);
|
||||
|
||||
-- 4. Add permissions for role "viewver"
|
||||
INSERT INTO collaboration.permissions_to_role (role_id, permission_id)
|
||||
SELECT executor_role_id, id
|
||||
FROM tv_auth.permissions
|
||||
WHERE name IN (
|
||||
'goal_can_watch_content',
|
||||
'component_can_watch_content',
|
||||
'component_can_add_tasks',
|
||||
'task_can_watch_subtasks',
|
||||
'task_can_watch_note',
|
||||
'task_can_watch_assigned_users',
|
||||
'task_can_watch_details',
|
||||
'task_can_add_subtasks',
|
||||
'task_can_watch_tags',
|
||||
'task_can_watch_priority'
|
||||
);
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
|
||||
drop trigger if exists add_roles_after_insert on tasks.goals;
|
||||
|
||||
CREATE TRIGGER add_roles_after_insert
|
||||
AFTER INSERT
|
||||
ON tasks.goals
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION tasks.add_roles_and_permissions();
|
||||
|
||||
|
||||
--11.
|
||||
--Trigger for adjusting start and end dates for task
|
||||
CREATE OR REPLACE FUNCTION tasks.adjust_start_and_end_dates()
|
||||
RETURNS TRIGGER AS
|
||||
$$
|
||||
DECLARE
|
||||
start_timestamp TIMESTAMPTZ;
|
||||
end_timestamp TIMESTAMPTZ;
|
||||
BEGIN
|
||||
-- If start_date is NULL, then start_time should be NULL
|
||||
IF NEW.start_date IS NULL THEN
|
||||
NEW.start_time := NULL;
|
||||
END IF;
|
||||
|
||||
-- If end_date is NULL, then end_time should be NULL
|
||||
IF NEW.end_date IS NULL THEN
|
||||
NEW.end_time := NULL;
|
||||
END IF;
|
||||
|
||||
-- If both dates are set
|
||||
IF NEW.start_date IS NOT NULL AND NEW.end_date IS NOT NULL THEN
|
||||
-- Adjust dates
|
||||
IF NEW.start_date > NEW.end_date THEN
|
||||
-- If start_date is greater than end_date, set end_date to start_date
|
||||
NEW.end_date := NEW.start_date;
|
||||
-- end_time remains unchanged
|
||||
ELSIF NEW.end_date < NEW.start_date THEN
|
||||
-- If end_date is less than start_date, set start_date to end_date
|
||||
NEW.start_date := NEW.end_date;
|
||||
-- start_time remains unchanged
|
||||
END IF;
|
||||
|
||||
-- Prepare timestamps for comparison
|
||||
start_timestamp := (NEW.start_date::text || ' ' || COALESCE(NEW.start_time::text, '00:00:00+00'))::timestamptz;
|
||||
end_timestamp := (NEW.end_date::text || ' ' || COALESCE(NEW.end_time::text, '00:00:00+00'))::timestamptz;
|
||||
|
||||
-- If start_timestamp is greater than end_timestamp, adjust end_date and end_time
|
||||
IF start_timestamp > end_timestamp THEN
|
||||
NEW.end_date := NEW.start_date;
|
||||
-- Assign end_time only if start_time is not NULL
|
||||
IF NEW.start_time IS NOT NULL AND NEW.end_time IS NOT NULL THEN
|
||||
NEW.end_time := NEW.start_time;
|
||||
END IF;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
|
||||
drop trigger if exists adjust_dates_and_times_trigger on tasks.tasks;
|
||||
CREATE TRIGGER adjust_dates_and_times_trigger
|
||||
BEFORE INSERT OR UPDATE
|
||||
ON tasks.tasks
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION tasks.adjust_start_and_end_dates();
|
||||
|
||||
--12.
|
||||
--Trigger for adding self/owner to collaboration table to be able to assign tasks to self
|
||||
create or replace function tasks.add_self_to_collaboration()
|
||||
returns trigger as $$
|
||||
DECLARE
|
||||
owner_email TEXT;
|
||||
BEGIN
|
||||
|
||||
select email into owner_email
|
||||
from tv_auth.users
|
||||
where id = NEW.owner;
|
||||
|
||||
if owner_email is not null then
|
||||
insert into collaboration.users (email) values (owner_email) ON CONFLICT (email) DO NOTHING;
|
||||
insert into collaboration.users_to_goals (goal_id, user_id) values (NEW.id, (select id from collaboration.users where email = owner_email));
|
||||
end if;
|
||||
|
||||
return NEW;
|
||||
END;
|
||||
$$ language plpgsql;
|
||||
|
||||
drop trigger if exists add_selt_to_collaboration_trg on tasks.goals;
|
||||
|
||||
create trigger add_selt_to_collaboration_trg
|
||||
after insert on tasks.goals
|
||||
for each row
|
||||
execute function tasks.add_self_to_collaboration();
|
||||
|
||||
--13.
|
||||
--Trigger for adding default kanban columns for new goal
|
||||
CREATE OR REPLACE FUNCTION tasks.kanban_add_default_columns()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
-- Add default columns for new goal
|
||||
INSERT INTO tasks.statuses (name, goal_id, view_order)
|
||||
VALUES
|
||||
('TODO', NEW.id, 1),
|
||||
('In Progress', NEW.id, 2),
|
||||
('Done', NEW.id, 3);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS kanban_add_default_columns_trg ON tasks.goals;
|
||||
|
||||
CREATE TRIGGER kanban_add_default_columns_trg
|
||||
AFTER INSERT ON tasks.goals
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION tasks.kanban_add_default_columns();
|
||||
|
||||
--14.
|
||||
--Trigger for validating the correct statusId for the inserted value. To avoid assigning a status that does not belong to the goal.
|
||||
CREATE OR REPLACE FUNCTION tasks.check_task_status_goal()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
-- Check if there is a record in tasks.statuses with the same goal_id
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM tasks.statuses s
|
||||
WHERE s.id = NEW.status_id AND s.goal_id = NEW.goal_id
|
||||
) THEN
|
||||
RAISE EXCEPTION 'Status ID % is not valid for goal ID %', NEW.status_id, NEW.goal_id;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
drop trigger if exists enforce_task_status_goal on tasks.tasks;
|
||||
|
||||
CREATE TRIGGER enforce_task_status_goal
|
||||
BEFORE INSERT OR UPDATE ON tasks.tasks
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.status_id IS NOT NULL)
|
||||
EXECUTE FUNCTION tasks.check_task_status_goal();
|
||||
|
||||
--15.
|
||||
--Trigger for setting default orders value for task
|
||||
CREATE OR REPLACE FUNCTION tasks.set_order_value()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF NEW.task_order IS NULL THEN
|
||||
NEW.task_order := NEW.id;
|
||||
END IF;
|
||||
IF NEW.kanban_order IS NULL THEN
|
||||
NEW.kanban_order := NEW.id;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
drop trigger if exists set_order_trigger on tasks.tasks;
|
||||
CREATE TRIGGER set_order_trigger
|
||||
BEFORE INSERT ON tasks.tasks
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION tasks.set_order_value();
|
||||
|
||||
--16.
|
||||
--Trigger for setting default view order for new status
|
||||
CREATE OR REPLACE FUNCTION tasks.status_set_default_view_order()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
new_view_order INT;
|
||||
BEGIN
|
||||
-- Determine the next view_order for the given goal_id
|
||||
SELECT COALESCE(MAX(view_order), 0) + 1 INTO new_view_order
|
||||
FROM tasks.statuses
|
||||
WHERE goal_id = NEW.goal_id;
|
||||
|
||||
-- Assign the calculated value to the view_order field
|
||||
NEW.view_order := new_view_order;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
drop trigger if exists set_default_status_view_order on tasks.statuses;
|
||||
|
||||
CREATE TRIGGER set_default_status_view_order
|
||||
BEFORE INSERT ON tasks.statuses
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION tasks.status_set_default_view_order();
|
||||
|
||||
--17.
|
||||
--Trigger for validating the correct user_id for the inserted value. To avoid assigning a user that does not belong to the goal.
|
||||
CREATE OR REPLACE FUNCTION tasks_auth.control_user_id_is_from_same_goal_as_task()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
user_exists BOOLEAN;
|
||||
BEGIN
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM tasks.tasks tt
|
||||
LEFT JOIN collaboration.users_to_goals utg ON utg.goal_id = tt.goal_id
|
||||
WHERE tt.id = NEW.task_id AND utg.user_id = NEW.collab_user_id
|
||||
) INTO user_exists;
|
||||
|
||||
IF NOT user_exists THEN
|
||||
RAISE EXCEPTION 'User % is not associated with the goal of task %', NEW.collab_user_id, NEW.task_id;
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
drop trigger if exists trigger_control_user_id_is_from_same_goal_as_task on tasks_auth.task_assignee;
|
||||
|
||||
CREATE TRIGGER trigger_control_user_id_is_from_same_goal_as_task
|
||||
BEFORE INSERT ON tasks_auth.task_assignee
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION tasks_auth.control_user_id_is_from_same_goal_as_task();
|
||||
|
||||
--18.
|
||||
--Trigger for adding owner for task, extend owner from goal or taskList
|
||||
|
||||
--delete old function with wrong name
|
||||
drop trigger if exists trigger_set_owner_for_task on tasks.tasks;
|
||||
drop function if exists tasks.trigger_set_owner_for_task();
|
||||
|
||||
CREATE OR REPLACE FUNCTION tasks.fn_set_owner_for_task()
|
||||
RETURNS TRIGGER AS
|
||||
$body$
|
||||
BEGIN
|
||||
NEW.owner := COALESCE(
|
||||
(SELECT owner FROM tasks.goal_lists WHERE id = NEW.goal_list_id),
|
||||
(SELECT owner FROM tasks.goals WHERE id = NEW.goal_id)
|
||||
);
|
||||
|
||||
IF NEW.owner IS NULL THEN
|
||||
RAISE EXCEPTION 'Can not insert task without owner';
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$body$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
drop trigger if exists trigger_set_owner_for_task on tasks.tasks;
|
||||
create trigger trigger_set_owner_for_task
|
||||
before insert
|
||||
on tasks.tasks
|
||||
for each row
|
||||
execute procedure tasks.fn_set_owner_for_task();
|
||||
|
||||
@@ -16,12 +16,15 @@ import {
|
||||
import { generateString, isEmail, time } from '../../utils/helpers';
|
||||
import EnEmailTemplate from './mail/confirm-email-en';
|
||||
import RuEmailTemplate from './mail/confirm-email-ru';
|
||||
import type { ExternalAuthUser } from './strategies/external-auth.types';
|
||||
|
||||
export default class AuthController {
|
||||
private readonly jwtAlg: Algorithm = process.env.JWT_ALG as Algorithm;
|
||||
private readonly jwtExp: string = process.env.ACCESS_LIFE_TIME!;
|
||||
private readonly jwtRefreshExp: string = process.env.REFRESH_LIFE_TIME!;
|
||||
|
||||
private readonly refreshTokenCookieName: string = 'taskview-refresh';
|
||||
|
||||
comparePasswords(pwd: string, hash: string): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
//Prev version was written in PHP need to replace
|
||||
@@ -91,6 +94,9 @@ export default class AuthController {
|
||||
return this.makeidLogin(16);
|
||||
}
|
||||
|
||||
generateLoginCode() {
|
||||
return `${this.makeidLogin(12)}:${Date.now()}`.toLocaleLowerCase();
|
||||
}
|
||||
/**
|
||||
* Register user by email and send login code to the email
|
||||
* @param req
|
||||
@@ -110,7 +116,7 @@ export default class AuthController {
|
||||
|
||||
const { email } = data.data;
|
||||
|
||||
const code = `${this.makeidLogin(12)}:${Date.now()}`.toLocaleLowerCase();
|
||||
const code = this.generateLoginCode();
|
||||
|
||||
$logger.info(data.data, `[AuthController:sendLoginCode] we got data for send login code`);
|
||||
|
||||
@@ -175,6 +181,86 @@ export default class AuthController {
|
||||
});
|
||||
}
|
||||
|
||||
loginByProvider = async (req: Request, res: Response) => {
|
||||
const user = req.user as ExternalAuthUser;
|
||||
|
||||
if (!user) {
|
||||
return res.status(400).send('User not found');
|
||||
}
|
||||
|
||||
let userData = await req.appUser.authManager.repository.getUserByLogin(
|
||||
user.email,
|
||||
isEmail(user.email)
|
||||
);
|
||||
|
||||
if (!userData) {
|
||||
const password = this.makeidLogin(7);
|
||||
const login = this.makeidLogin(7);
|
||||
|
||||
const id = await req.appUser.authManager.repository.registerUserInDb({
|
||||
login,
|
||||
email: user.email,
|
||||
password: hashSync(password, 10),
|
||||
block: 0,
|
||||
confirmEmailCode: '',
|
||||
});
|
||||
|
||||
if (!id) {
|
||||
$logger.error(`Can not register user ${user.email} & login ${login}`);
|
||||
return res.status(500).send(`Can not register user ${user.email} & login ${login}`);
|
||||
}
|
||||
|
||||
userData = await req.appUser.authManager.repository.getUserByLogin(
|
||||
user.email,
|
||||
isEmail(user.email)
|
||||
);
|
||||
}
|
||||
|
||||
if (!userData) {
|
||||
$logger.error(`Can not find user ${user.email} after registration`);
|
||||
return res.status(500).send(`Can not find user ${user.email} after registration`);
|
||||
}
|
||||
|
||||
const code = this.generateLoginCode();
|
||||
|
||||
const result = await req.appUser.authManager.repository.updateLoginCode(code, userData.email);
|
||||
|
||||
if (!result) {
|
||||
$logger.error(`Can not update login code for user ${userData.email}`);
|
||||
return res.status(500).send(`Can not update login code for user`);
|
||||
}
|
||||
|
||||
const authData = {
|
||||
code: code.split(':')[0],
|
||||
email: userData.email,
|
||||
};
|
||||
|
||||
const encodedAuthData = encodeURIComponent(JSON.stringify(authData));
|
||||
|
||||
try {
|
||||
const platformData = JSON.parse(req.query.state as string);
|
||||
|
||||
if (platformData.platform === "mobile") {
|
||||
return res.redirect(
|
||||
`taskview://login?tokens=${encodedAuthData}`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
$logger.info(`Can not parse platform data from state: ${req.query.state}`);
|
||||
}
|
||||
|
||||
return res.redirect(`${process.env.APP_URL}/login?tokens=${encodedAuthData}`);
|
||||
}
|
||||
|
||||
setRefreshToken = async (res: Response, refreshToken: string) => {
|
||||
res.cookie(this.refreshTokenCookieName, refreshToken, {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: "none",
|
||||
maxAge: 1000 * 60 * 60 * 24 * 30,
|
||||
});
|
||||
}
|
||||
|
||||
loginByCode = async (req: Request, res: Response) => {
|
||||
const schema = z.object({
|
||||
email: z.string().email().toLowerCase(),
|
||||
@@ -202,7 +288,12 @@ export default class AuthController {
|
||||
}
|
||||
|
||||
if (tokenFromDb[0] !== data.data.code) {
|
||||
return res.status(400).end();
|
||||
return res.status(400).send({ message: 'Invalid code' });
|
||||
}
|
||||
|
||||
if (tokenFromDb[1] && Date.now() - +tokenFromDb[1] > 60 * 1000) {
|
||||
await req.appUser.authManager.repository.updateLoginCode(null, userData.email);
|
||||
return res.status(400).send({ message: 'Code expired, get new code' });
|
||||
}
|
||||
|
||||
const tokenRowId = await req.appUser.authManager.jwtStorage.initTokenRecord(userData.id);
|
||||
@@ -225,11 +316,9 @@ export default class AuthController {
|
||||
$logger.error(`Can not update tokens in JWT Storage for user ${userData.id} and rowId ${tokenRowId}`);
|
||||
}
|
||||
|
||||
try {
|
||||
await req.appUser.authManager.repository.updateLoginCode(null, userData.email);
|
||||
} catch (_err: unknown) {
|
||||
$logger.error(`Can not update updateLoginCode after login`);
|
||||
}
|
||||
await req.appUser.authManager.repository.updateLoginCode(null, userData.email);
|
||||
|
||||
await this.setRefreshToken(res, tokens.refresh);
|
||||
|
||||
return res.json(tokens);
|
||||
};
|
||||
@@ -276,6 +365,8 @@ export default class AuthController {
|
||||
$logger.error(`Can not update tokens in JWT Storage for user ${userData.id} and rowId ${tokenRowId}`);
|
||||
}
|
||||
|
||||
await this.setRefreshToken(res, tokens.refresh);
|
||||
|
||||
return res.json(tokens);
|
||||
}
|
||||
|
||||
@@ -473,37 +564,46 @@ export default class AuthController {
|
||||
};
|
||||
|
||||
logout = async (req: Request, res: Response) => {
|
||||
if (!req.headers['authorization']) {
|
||||
return res.status(400).end();
|
||||
}
|
||||
this.setRefreshToken(res, '');
|
||||
|
||||
const result = req.headers['authorization'].match(/Bearer\s(\S+)/);
|
||||
const result = req.headers['authorization']?.match(/Bearer\s(\S+)/);
|
||||
|
||||
if (!result) {
|
||||
return res.status(400).end();
|
||||
return res.status(401).send({ message: 'Unauthorized' });
|
||||
}
|
||||
|
||||
const payload = decode(result['1']) as UserJwtPayload;
|
||||
if (!payload) {
|
||||
return res.status(400).end();
|
||||
const tokenId = req.appUser.getTokenId();
|
||||
|
||||
if (!tokenId) {
|
||||
return res.status(401).send({ message: 'Unauthorized' });
|
||||
}
|
||||
|
||||
const deleteResult = await req.appUser.authManager.jwtStorage.deleteTokens(payload.userData.id, result['1']);
|
||||
const deleteResult = await req.appUser.authManager.jwtStorage.deleteTokens(tokenId, result['1']);
|
||||
|
||||
if (!deleteResult) {
|
||||
return res.status(500).end();
|
||||
return res.status(500).send({ message: 'Failed to revoke token' });
|
||||
}
|
||||
|
||||
return res.send();
|
||||
return res.status(204).end();
|
||||
};
|
||||
|
||||
refreshTokens = async (req: Request, res: Response) => {
|
||||
const refreshData = RefreshTokenSchema.safeParse(req.body);
|
||||
if (!refreshData.success) {
|
||||
return res.status(400).end();
|
||||
let refreshToken = req.cookies[this.refreshTokenCookieName];
|
||||
|
||||
if (!refreshToken) {
|
||||
const refreshData = RefreshTokenSchema.safeParse(req.body);
|
||||
|
||||
if (!refreshData.success) {
|
||||
return res.status(400).send({ message: 'Invalid refresh token' });
|
||||
}
|
||||
|
||||
refreshToken = refreshData.data.refreshToken;
|
||||
$logger.info(`Refresh token found in body`);
|
||||
} else {
|
||||
$logger.info(`Refresh token found in cookies`);
|
||||
}
|
||||
|
||||
const payload = await AuthController.validateTokens(refreshData.data.refreshToken);
|
||||
const payload = await AuthController.validateTokens(refreshToken);
|
||||
|
||||
if (!payload) {
|
||||
return res.status(400).end();
|
||||
@@ -522,6 +622,8 @@ export default class AuthController {
|
||||
return res.status(500).end();
|
||||
}
|
||||
|
||||
await this.setRefreshToken(res, newTokens.refresh);
|
||||
|
||||
return res.json(newTokens);
|
||||
};
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ export default class AuthModel {
|
||||
return !!(data.rowCount && data.rowCount > 0);
|
||||
} catch (error: unknown) {
|
||||
$logger.error(error, 'Can not update login code');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Router } from 'express';
|
||||
import { Router, type NextFunction, type Request, type Response } from 'express';
|
||||
import type { Routable } from '../../types/routable.type';
|
||||
import AuthController from './AuthController';
|
||||
import { IsLoggedIn } from './middlewares/is-logged-in';
|
||||
|
||||
import passport from './strategies/passport-login';
|
||||
import { ExternalProviderScope } from './strategies/external-auth.types';
|
||||
export default class AuthRoutes implements Routable {
|
||||
private readonly router: ReturnType<typeof Router>;
|
||||
private readonly authController: AuthController;
|
||||
@@ -29,5 +30,23 @@ export default class AuthRoutes implements Routable {
|
||||
this.router.post('/refresh/token', this.authController.refreshTokens);
|
||||
this.router.post('/delete/account/code', [IsLoggedIn], this.authController.sendDeleteAccountCode);
|
||||
this.router.post('/delete/account', [IsLoggedIn], this.authController.deleteUserAccaunt);
|
||||
|
||||
this.router.get(
|
||||
'/provider/:providerName',
|
||||
(req: Request, res: Response, next: NextFunction) => passport.authenticate(req.params.providerName, {
|
||||
scope: ExternalProviderScope[req.params.providerName],
|
||||
session: false,
|
||||
state: JSON.stringify({
|
||||
platform: req.query.platform || '',
|
||||
})
|
||||
})(req, res, next)
|
||||
);
|
||||
this.router.get(
|
||||
'/provider/:providerName/callback',
|
||||
(req: Request, res: Response, next: NextFunction) => passport.authenticate(req.params.providerName, {
|
||||
scope: ExternalProviderScope[req.params.providerName], session: false
|
||||
})(req, res, next),
|
||||
this.authController.loginByProvider
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,11 @@ export default class JwtStorage {
|
||||
}
|
||||
return false;
|
||||
} catch (error: any) {
|
||||
$logger.error('Can not complete initTokenRecord', {
|
||||
$logger.error({
|
||||
userId,
|
||||
errorMessage: error.message,
|
||||
errorStack: error.stack,
|
||||
});
|
||||
}, 'Can not complete initTokenRecord');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ export default class JwtStorage {
|
||||
const res = await this.db.query(query, [accessToken, refreshToken, rowId]);
|
||||
return !!(res.rowCount && res.rowCount > 0);
|
||||
} catch (error: any) {
|
||||
$logger.error('Error updating tokens:', { errorMessage: error.message, errorStack: error.stack });
|
||||
$logger.error({ errorMessage: error.message, errorStack: error.stack }, 'Error updating tokens:');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -55,11 +55,11 @@ export default class JwtStorage {
|
||||
|
||||
return false;
|
||||
} catch (error: any) {
|
||||
$logger.error('Error fetching tokens', {
|
||||
$logger.error({
|
||||
rowId,
|
||||
errorMessage: error.message,
|
||||
errorStack: error.stack,
|
||||
});
|
||||
}, 'Error fetching tokens');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -67,8 +67,8 @@ export default class JwtStorage {
|
||||
async deleteTokens(userId: number, accessToken: string): Promise<boolean> {
|
||||
try {
|
||||
const query = 'DELETE FROM tv_auth.user_tokens WHERE user_id = $1 AND access_token = $2;';
|
||||
const deleteResul = await this.db.query(query, [userId, accessToken]);
|
||||
return !!(deleteResul.rowCount && deleteResul.rowCount > 0);
|
||||
await this.db.query(query, [userId, accessToken]);
|
||||
return true;
|
||||
} catch (_error: any) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
export type ExternalAuthUser = {
|
||||
email: string;
|
||||
provider: 'google' | 'github' | string;
|
||||
}
|
||||
|
||||
export const ExternalProviderScope: Record<ExternalAuthUser['provider'], string[]> = {
|
||||
google: ["email"],
|
||||
github: ["user:email"],
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import passport from "passport";
|
||||
import { Strategy as GitHubStrategy } from "passport-github2";
|
||||
import { $logger } from "../../../modules/logget";
|
||||
import type { ExternalAuthUser } from "./external-auth.types";
|
||||
import type { Profile } from "passport-github2";
|
||||
import type { VerifyCallback } from "passport-google-oauth20";
|
||||
|
||||
export function initGithubStrategy() {
|
||||
if (!process.env.GITHUB_CLIENT_ID || !process.env.GITHUB_CLIENT_SECRET || !process.env.GITHUB_CALLBACK_URL) {
|
||||
$logger.warn("GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, and GITHUB_CALLBACK_URL must be set");
|
||||
console.warn("GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, and GITHUB_CALLBACK_URL must be set");
|
||||
return;
|
||||
}
|
||||
|
||||
const options = {
|
||||
clientID: process.env.GITHUB_CLIENT_ID,
|
||||
clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
||||
callbackURL: process.env.GITHUB_CALLBACK_URL,
|
||||
scope: ["user:email"],
|
||||
}
|
||||
|
||||
passport.use(new GitHubStrategy(options, async (_accessToken: string, _refreshToken: string, profile: Profile, done: VerifyCallback) => {
|
||||
try {
|
||||
const email = profile.emails?.[0]?.value;
|
||||
if (!email) return done(null, false);
|
||||
|
||||
const user: ExternalAuthUser = {
|
||||
email,
|
||||
provider: "github",
|
||||
};
|
||||
|
||||
done(null, user);
|
||||
} catch (e) {
|
||||
done(e);
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import passport from "passport";
|
||||
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
|
||||
import { $logger } from "../../../modules/logget";
|
||||
import type { ExternalAuthUser } from "./external-auth.types";
|
||||
|
||||
export function initGoogleStrategy() {
|
||||
if (!process.env.GOOGLE_CLIENT_ID || !process.env.GOOGLE_CLIENT_SECRET || !process.env.GOOGLE_CALLBACK_URL) {
|
||||
$logger.warn("GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and GOOGLE_CALLBACK_URL must be set");
|
||||
console.warn("GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and GOOGLE_CALLBACK_URL must be set");
|
||||
return;
|
||||
}
|
||||
const options = {
|
||||
clientID: process.env.GOOGLE_CLIENT_ID,
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
||||
callbackURL: process.env.GOOGLE_CALLBACK_URL,
|
||||
}
|
||||
|
||||
passport.use(new GoogleStrategy(options, async (_accessToken, _refreshToken, profile, done) => {
|
||||
try {
|
||||
const email = profile.emails?.[0]?.value;
|
||||
if (!email || !profile._json.email_verified) return done(null, false);
|
||||
|
||||
const user: ExternalAuthUser = {
|
||||
email,
|
||||
provider: "google",
|
||||
};
|
||||
|
||||
done(null, user);
|
||||
} catch (e) {
|
||||
done(e);
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import passport from "passport";
|
||||
import { initGoogleStrategy } from "./google.strategy";
|
||||
import { initGithubStrategy } from "./github.strategy";
|
||||
|
||||
export function initPassportLogin() {
|
||||
initGoogleStrategy();
|
||||
initGithubStrategy();
|
||||
}
|
||||
|
||||
export default passport;
|
||||
|
||||
@@ -652,7 +652,7 @@ export class TasksRepository {
|
||||
}
|
||||
|
||||
const result = await callWithCatch(() =>
|
||||
this.db.dbDrizzle.select().from(TasksSchema).where(and(...conditions)).orderBy(asc(TasksSchema.kanbanOrder)).limit(10)
|
||||
this.db.dbDrizzle.select().from(TasksSchema).where(and(...conditions)).orderBy(asc(TasksSchema.kanbanOrder)).limit(20)
|
||||
);
|
||||
return result ?? [];
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export default defineConfig({
|
||||
ecma: 2015,
|
||||
compress: true,
|
||||
mangle: {
|
||||
properties: false,//couse error if true after build
|
||||
properties: false,//cause error if true after build
|
||||
},
|
||||
format: {
|
||||
comments: true,
|
||||
@@ -37,9 +37,9 @@ export default defineConfig({
|
||||
external: ['pg-native'],
|
||||
output: {
|
||||
format: 'umd',
|
||||
entryFileNames: 'taskview-server-docker.js',
|
||||
chunkFileNames: 'taskview-server-docker-[hash].js',
|
||||
assetFileNames: 'taskview-server-docker-[name]-[hash].[ext]',
|
||||
entryFileNames: 'taskview-server.js',
|
||||
chunkFileNames: 'taskview-server-[hash].js',
|
||||
assetFileNames: 'taskview-server-[name]-[hash].[ext]',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
+30
-4
@@ -1,19 +1,45 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script builds the docker images for the taskview-ce-monorepo
|
||||
# This script builds the docker images for TaskView Community Edition
|
||||
# - taskview-ce-api-server
|
||||
# - taskview-ce-db-migration
|
||||
# - taskview-ce-webapp
|
||||
|
||||
set -e
|
||||
|
||||
VERSION=$1
|
||||
|
||||
# If version not provided, read from package.json
|
||||
if [ -z "$VERSION" ]; then
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
fi
|
||||
|
||||
echo $VERSION
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||
nvm use 24
|
||||
node -v
|
||||
|
||||
echo "Building TaskView CE version: $VERSION"
|
||||
|
||||
# Build CE API
|
||||
echo "========================================="
|
||||
echo "Building CE API Server..."
|
||||
echo "========================================="
|
||||
cd api
|
||||
bash build-docker-api.sh $VERSION
|
||||
cd ..
|
||||
|
||||
|
||||
# Build CE Web
|
||||
echo "========================================="
|
||||
echo "Building CE Web App..."
|
||||
echo "========================================="
|
||||
cd web
|
||||
bash build-docker-web.sh $VERSION
|
||||
cd ..
|
||||
cd ..
|
||||
|
||||
echo "========================================="
|
||||
echo "Build complete!"
|
||||
echo "Images built:"
|
||||
echo " - gimanhead/taskview-ce-api-server:$VERSION"
|
||||
echo " - gimanhead/taskview-ce-webapp:$VERSION"
|
||||
echo "========================================="
|
||||
|
||||
+8
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "taskview-ce-monorepo",
|
||||
"version": "1.18.0",
|
||||
"version": "1.19.6",
|
||||
"private": true,
|
||||
"description": "TaskView CE monorepo containing web, API, and packages",
|
||||
"workspaces": [
|
||||
@@ -30,7 +30,13 @@
|
||||
"pnpm": ">=8.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/passport": "^1.0.17",
|
||||
"@types/passport-github2": "^1.2.9",
|
||||
"@types/passport-google-oauth20": "^2.0.17",
|
||||
"arktype": "2.1.20",
|
||||
"drizzle-arktype": "0.1.3"
|
||||
"drizzle-arktype": "0.1.3",
|
||||
"passport": "^0.7.0",
|
||||
"passport-github2": "^0.1.12",
|
||||
"passport-google-oauth20": "^2.0.0"
|
||||
}
|
||||
}
|
||||
Generated
+1852
-6069
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "taskview-ce-api",
|
||||
"name": "taskview-api",
|
||||
"private": false,
|
||||
"version": "1.17.0",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "taskview-ce-db-schemas",
|
||||
"name": "taskview-db-schemas",
|
||||
"version": "1.17.0",
|
||||
"type": "module",
|
||||
"main": "./dist/taskview-db-schemas.umd.js",
|
||||
|
||||
@@ -7,8 +7,8 @@ android {
|
||||
applicationId "com.handscreamgnl.taskview.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1150
|
||||
versionName "1.15.0"
|
||||
versionCode 1196
|
||||
versionName "1.19.6"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -10,6 +10,7 @@ android {
|
||||
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||||
dependencies {
|
||||
implementation project(':capacitor-app')
|
||||
implementation project(':capacitor-browser')
|
||||
implementation project(':capacitor-device')
|
||||
implementation project(':capacitor-preferences')
|
||||
implementation project(':capacitor-splash-screen')
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
"type": "SINGLE",
|
||||
"filters": [],
|
||||
"attributes": [],
|
||||
"versionCode": 114,
|
||||
"versionName": "1.14",
|
||||
"outputFile": "TaskView-1.14-debug.apk"
|
||||
"versionCode": 1150,
|
||||
"versionName": "1.15.0",
|
||||
"outputFile": "TaskView-1.15.0-debug.apk"
|
||||
}
|
||||
],
|
||||
"elementType": "File",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
@@ -6,6 +6,7 @@
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation"
|
||||
@@ -18,6 +19,12 @@
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="taskview" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
@@ -25,11 +32,12 @@
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
|
||||
<meta-data android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
<!-- Permissions -->
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
</manifest>
|
||||
</manifest>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<base-config cleartextTrafficPermitted="false">
|
||||
<trust-anchors>
|
||||
<certificates src="system" />
|
||||
<certificates src="user" />
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
</network-security-config>
|
||||
@@ -1,18 +1,21 @@
|
||||
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||
include ':capacitor-android'
|
||||
project(':capacitor-android').projectDir = new File('../../node_modules/.pnpm/@capacitor+android@7.4.4_@capacitor+core@7.4.4/node_modules/@capacitor/android/capacitor')
|
||||
project(':capacitor-android').projectDir = new File('../../../node_modules/.pnpm/@capacitor+android@7.4.5_@capacitor+core@7.4.5/node_modules/@capacitor/android/capacitor')
|
||||
|
||||
include ':capacitor-app'
|
||||
project(':capacitor-app').projectDir = new File('../../node_modules/.pnpm/@capacitor+app@7.0.1_@capacitor+core@7.4.4/node_modules/@capacitor/app/android')
|
||||
project(':capacitor-app').projectDir = new File('../../../node_modules/.pnpm/@capacitor+app@7.0.1_@capacitor+core@7.4.5/node_modules/@capacitor/app/android')
|
||||
|
||||
include ':capacitor-browser'
|
||||
project(':capacitor-browser').projectDir = new File('../../../node_modules/.pnpm/@capacitor+browser@7.0.3_@capacitor+core@7.4.5/node_modules/@capacitor/browser/android')
|
||||
|
||||
include ':capacitor-device'
|
||||
project(':capacitor-device').projectDir = new File('../../node_modules/.pnpm/@capacitor+device@7.0.1_@capacitor+core@7.4.4/node_modules/@capacitor/device/android')
|
||||
project(':capacitor-device').projectDir = new File('../../../node_modules/.pnpm/@capacitor+device@7.0.1_@capacitor+core@7.4.5/node_modules/@capacitor/device/android')
|
||||
|
||||
include ':capacitor-preferences'
|
||||
project(':capacitor-preferences').projectDir = new File('../../node_modules/.pnpm/@capacitor+preferences@7.0.1_@capacitor+core@7.4.4/node_modules/@capacitor/preferences/android')
|
||||
project(':capacitor-preferences').projectDir = new File('../../../node_modules/.pnpm/@capacitor+preferences@7.0.1_@capacitor+core@7.4.5/node_modules/@capacitor/preferences/android')
|
||||
|
||||
include ':capacitor-splash-screen'
|
||||
project(':capacitor-splash-screen').projectDir = new File('../../node_modules/.pnpm/@capacitor+splash-screen@7.0.3_@capacitor+core@7.4.4/node_modules/@capacitor/splash-screen/android')
|
||||
project(':capacitor-splash-screen').projectDir = new File('../../../node_modules/.pnpm/@capacitor+splash-screen@7.0.4_@capacitor+core@7.4.5/node_modules/@capacitor/splash-screen/android')
|
||||
|
||||
include ':capgo-capacitor-updater'
|
||||
project(':capgo-capacitor-updater').projectDir = new File('../../node_modules/.pnpm/@capgo+capacitor-updater@7.29.0_@capacitor+core@7.4.4/node_modules/@capgo/capacitor-updater/android')
|
||||
project(':capgo-capacitor-updater').projectDir = new File('../../../node_modules/.pnpm/@capgo+capacitor-updater@7.41.1_@capacitor+core@7.4.5/node_modules/@capgo/capacitor-updater/android')
|
||||
|
||||
+10
-9
@@ -12,18 +12,19 @@ const config: CapacitorConfig = {
|
||||
zoomEnabled: false,
|
||||
},
|
||||
server: {
|
||||
hostname: 'taskview.handscream.com',
|
||||
//androidScheme: 'https://',
|
||||
hostname: 'app.taskview.tech',
|
||||
},
|
||||
plugins: {
|
||||
"CapacitorUpdater": {
|
||||
"autoUpdate": false,
|
||||
}
|
||||
CapacitorUpdater: {
|
||||
autoUpdate: false,
|
||||
},
|
||||
CapacitorCookies: {
|
||||
enabled: true,
|
||||
},
|
||||
CapacitorHttp: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
// server: {
|
||||
// url: 'http://192.168.0.2:3000',
|
||||
// cleartext: true,
|
||||
// },
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -354,14 +354,14 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1.15.0;
|
||||
CURRENT_PROJECT_VERSION = 1.19.6;
|
||||
DEVELOPMENT_TEAM = H2W2SG48JT;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = TaskView;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 1.15.0;
|
||||
MARKETING_VERSION = 1.19.6;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.handscream.taskview.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -377,14 +377,14 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1.15.0;
|
||||
CURRENT_PROJECT_VERSION = 1.19.6;
|
||||
DEVELOPMENT_TEAM = H2W2SG48JT;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = TaskView;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 1.15.0;
|
||||
MARKETING_VERSION = 1.19.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.handscream.taskview.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict/>
|
||||
</plist>
|
||||
+9
-8
@@ -1,4 +1,4 @@
|
||||
require_relative '../../../node_modules/.pnpm/@capacitor+ios@7.4.4_@capacitor+core@7.4.4/node_modules/@capacitor/ios/scripts/pods_helpers'
|
||||
require_relative '../../../../node_modules/.pnpm/@capacitor+ios@7.4.5_@capacitor+core@7.4.5/node_modules/@capacitor/ios/scripts/pods_helpers'
|
||||
|
||||
platform :ios, '14.0'
|
||||
use_frameworks!
|
||||
@@ -9,13 +9,14 @@ use_frameworks!
|
||||
install! 'cocoapods', :disable_input_output_paths => true
|
||||
|
||||
def capacitor_pods
|
||||
pod 'Capacitor', :path => '../../../node_modules/.pnpm/@capacitor+ios@7.4.4_@capacitor+core@7.4.4/node_modules/@capacitor/ios'
|
||||
pod 'CapacitorCordova', :path => '../../../node_modules/.pnpm/@capacitor+ios@7.4.4_@capacitor+core@7.4.4/node_modules/@capacitor/ios'
|
||||
pod 'CapacitorApp', :path => '../../../node_modules/.pnpm/@capacitor+app@7.0.1_@capacitor+core@7.4.4/node_modules/@capacitor/app'
|
||||
pod 'CapacitorDevice', :path => '../../../node_modules/.pnpm/@capacitor+device@7.0.1_@capacitor+core@7.4.4/node_modules/@capacitor/device'
|
||||
pod 'CapacitorPreferences', :path => '../../../node_modules/.pnpm/@capacitor+preferences@7.0.1_@capacitor+core@7.4.4/node_modules/@capacitor/preferences'
|
||||
pod 'CapacitorSplashScreen', :path => '../../../node_modules/.pnpm/@capacitor+splash-screen@7.0.3_@capacitor+core@7.4.4/node_modules/@capacitor/splash-screen'
|
||||
pod 'CapgoCapacitorUpdater', :path => '../../../node_modules/.pnpm/@capgo+capacitor-updater@7.29.0_@capacitor+core@7.4.4/node_modules/@capgo/capacitor-updater'
|
||||
pod 'Capacitor', :path => '../../../../node_modules/.pnpm/@capacitor+ios@7.4.5_@capacitor+core@7.4.5/node_modules/@capacitor/ios'
|
||||
pod 'CapacitorCordova', :path => '../../../../node_modules/.pnpm/@capacitor+ios@7.4.5_@capacitor+core@7.4.5/node_modules/@capacitor/ios'
|
||||
pod 'CapacitorApp', :path => '../../../../node_modules/.pnpm/@capacitor+app@7.0.1_@capacitor+core@7.4.5/node_modules/@capacitor/app'
|
||||
pod 'CapacitorBrowser', :path => '../../../../node_modules/.pnpm/@capacitor+browser@7.0.3_@capacitor+core@7.4.5/node_modules/@capacitor/browser'
|
||||
pod 'CapacitorDevice', :path => '../../../../node_modules/.pnpm/@capacitor+device@7.0.1_@capacitor+core@7.4.5/node_modules/@capacitor/device'
|
||||
pod 'CapacitorPreferences', :path => '../../../../node_modules/.pnpm/@capacitor+preferences@7.0.1_@capacitor+core@7.4.5/node_modules/@capacitor/preferences'
|
||||
pod 'CapacitorSplashScreen', :path => '../../../../node_modules/.pnpm/@capacitor+splash-screen@7.0.4_@capacitor+core@7.4.5/node_modules/@capacitor/splash-screen'
|
||||
pod 'CapgoCapacitorUpdater', :path => '../../../../node_modules/.pnpm/@capgo+capacitor-updater@7.41.1_@capacitor+core@7.4.5/node_modules/@capgo/capacitor-updater'
|
||||
end
|
||||
|
||||
target 'TaskView' do
|
||||
|
||||
+33
-27
@@ -1,71 +1,77 @@
|
||||
PODS:
|
||||
- Alamofire (5.10.2)
|
||||
- BigInt (5.2.0)
|
||||
- Capacitor (7.4.4):
|
||||
- Capacitor (7.4.5):
|
||||
- CapacitorCordova
|
||||
- CapacitorApp (7.0.1):
|
||||
- Capacitor
|
||||
- CapacitorCordova (7.4.4)
|
||||
- CapacitorBrowser (7.0.3):
|
||||
- Capacitor
|
||||
- CapacitorCordova (7.4.5)
|
||||
- CapacitorDevice (7.0.1):
|
||||
- Capacitor
|
||||
- CapacitorPreferences (7.0.1):
|
||||
- Capacitor
|
||||
- CapacitorSplashScreen (7.0.3):
|
||||
- CapacitorSplashScreen (7.0.4):
|
||||
- Capacitor
|
||||
- CapgoCapacitorUpdater (7.29.0):
|
||||
- CapgoCapacitorUpdater (7.41.1):
|
||||
- Alamofire (= 5.10.2)
|
||||
- BigInt (= 5.2.0)
|
||||
- Capacitor
|
||||
- SSZipArchive (= 2.4.3)
|
||||
- Version (= 0.8.0)
|
||||
- SSZipArchive (2.4.3)
|
||||
- ZIPFoundation (~> 0.9)
|
||||
- Version (0.8.0)
|
||||
- ZIPFoundation (0.9.20)
|
||||
|
||||
DEPENDENCIES:
|
||||
- "Capacitor (from `../../../node_modules/.pnpm/@capacitor+ios@7.4.4_@capacitor+core@7.4.4/node_modules/@capacitor/ios`)"
|
||||
- "CapacitorApp (from `../../../node_modules/.pnpm/@capacitor+app@7.0.1_@capacitor+core@7.4.4/node_modules/@capacitor/app`)"
|
||||
- "CapacitorCordova (from `../../../node_modules/.pnpm/@capacitor+ios@7.4.4_@capacitor+core@7.4.4/node_modules/@capacitor/ios`)"
|
||||
- "CapacitorDevice (from `../../../node_modules/.pnpm/@capacitor+device@7.0.1_@capacitor+core@7.4.4/node_modules/@capacitor/device`)"
|
||||
- "CapacitorPreferences (from `../../../node_modules/.pnpm/@capacitor+preferences@7.0.1_@capacitor+core@7.4.4/node_modules/@capacitor/preferences`)"
|
||||
- "CapacitorSplashScreen (from `../../../node_modules/.pnpm/@capacitor+splash-screen@7.0.3_@capacitor+core@7.4.4/node_modules/@capacitor/splash-screen`)"
|
||||
- "CapgoCapacitorUpdater (from `../../../node_modules/.pnpm/@capgo+capacitor-updater@7.29.0_@capacitor+core@7.4.4/node_modules/@capgo/capacitor-updater`)"
|
||||
- "Capacitor (from `../../../../node_modules/.pnpm/@capacitor+ios@7.4.5_@capacitor+core@7.4.5/node_modules/@capacitor/ios`)"
|
||||
- "CapacitorApp (from `../../../../node_modules/.pnpm/@capacitor+app@7.0.1_@capacitor+core@7.4.5/node_modules/@capacitor/app`)"
|
||||
- "CapacitorBrowser (from `../../../../node_modules/.pnpm/@capacitor+browser@7.0.3_@capacitor+core@7.4.5/node_modules/@capacitor/browser`)"
|
||||
- "CapacitorCordova (from `../../../../node_modules/.pnpm/@capacitor+ios@7.4.5_@capacitor+core@7.4.5/node_modules/@capacitor/ios`)"
|
||||
- "CapacitorDevice (from `../../../../node_modules/.pnpm/@capacitor+device@7.0.1_@capacitor+core@7.4.5/node_modules/@capacitor/device`)"
|
||||
- "CapacitorPreferences (from `../../../../node_modules/.pnpm/@capacitor+preferences@7.0.1_@capacitor+core@7.4.5/node_modules/@capacitor/preferences`)"
|
||||
- "CapacitorSplashScreen (from `../../../../node_modules/.pnpm/@capacitor+splash-screen@7.0.4_@capacitor+core@7.4.5/node_modules/@capacitor/splash-screen`)"
|
||||
- "CapgoCapacitorUpdater (from `../../../../node_modules/.pnpm/@capgo+capacitor-updater@7.41.1_@capacitor+core@7.4.5/node_modules/@capgo/capacitor-updater`)"
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- Alamofire
|
||||
- BigInt
|
||||
- SSZipArchive
|
||||
- Version
|
||||
- ZIPFoundation
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
Capacitor:
|
||||
:path: "../../../node_modules/.pnpm/@capacitor+ios@7.4.4_@capacitor+core@7.4.4/node_modules/@capacitor/ios"
|
||||
:path: "../../../../node_modules/.pnpm/@capacitor+ios@7.4.5_@capacitor+core@7.4.5/node_modules/@capacitor/ios"
|
||||
CapacitorApp:
|
||||
:path: "../../../node_modules/.pnpm/@capacitor+app@7.0.1_@capacitor+core@7.4.4/node_modules/@capacitor/app"
|
||||
:path: "../../../../node_modules/.pnpm/@capacitor+app@7.0.1_@capacitor+core@7.4.5/node_modules/@capacitor/app"
|
||||
CapacitorBrowser:
|
||||
:path: "../../../../node_modules/.pnpm/@capacitor+browser@7.0.3_@capacitor+core@7.4.5/node_modules/@capacitor/browser"
|
||||
CapacitorCordova:
|
||||
:path: "../../../node_modules/.pnpm/@capacitor+ios@7.4.4_@capacitor+core@7.4.4/node_modules/@capacitor/ios"
|
||||
:path: "../../../../node_modules/.pnpm/@capacitor+ios@7.4.5_@capacitor+core@7.4.5/node_modules/@capacitor/ios"
|
||||
CapacitorDevice:
|
||||
:path: "../../../node_modules/.pnpm/@capacitor+device@7.0.1_@capacitor+core@7.4.4/node_modules/@capacitor/device"
|
||||
:path: "../../../../node_modules/.pnpm/@capacitor+device@7.0.1_@capacitor+core@7.4.5/node_modules/@capacitor/device"
|
||||
CapacitorPreferences:
|
||||
:path: "../../../node_modules/.pnpm/@capacitor+preferences@7.0.1_@capacitor+core@7.4.4/node_modules/@capacitor/preferences"
|
||||
:path: "../../../../node_modules/.pnpm/@capacitor+preferences@7.0.1_@capacitor+core@7.4.5/node_modules/@capacitor/preferences"
|
||||
CapacitorSplashScreen:
|
||||
:path: "../../../node_modules/.pnpm/@capacitor+splash-screen@7.0.3_@capacitor+core@7.4.4/node_modules/@capacitor/splash-screen"
|
||||
:path: "../../../../node_modules/.pnpm/@capacitor+splash-screen@7.0.4_@capacitor+core@7.4.5/node_modules/@capacitor/splash-screen"
|
||||
CapgoCapacitorUpdater:
|
||||
:path: "../../../node_modules/.pnpm/@capgo+capacitor-updater@7.29.0_@capacitor+core@7.4.4/node_modules/@capgo/capacitor-updater"
|
||||
:path: "../../../../node_modules/.pnpm/@capgo+capacitor-updater@7.41.1_@capacitor+core@7.4.5/node_modules/@capgo/capacitor-updater"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496
|
||||
BigInt: f668a80089607f521586bbe29513d708491ef2f7
|
||||
Capacitor: 09d9ff8e9618e8c4b3cab2bbee34a17215dd2fef
|
||||
Capacitor: a5bf59e09f9dd82694fdcca4d107b4d215ac470f
|
||||
CapacitorApp: d63334c052278caf5d81585d80b21905c6f93f39
|
||||
CapacitorCordova: bf648a636f3c153f652d312ae145fb508b6ffced
|
||||
CapacitorBrowser: 66aa8ff09cdca2a327ce464b113b470e6f667753
|
||||
CapacitorCordova: 31bbe4466000c6b86d9b7f1181ee286cff0205aa
|
||||
CapacitorDevice: fe3f190e1d718f4607bdc6b73993433d1c84f409
|
||||
CapacitorPreferences: cbf154e5e5519b7f5ab33817a334dda1e98387f9
|
||||
CapacitorSplashScreen: d06ae8804808e9f649a08e7bb7f283c77b688084
|
||||
CapgoCapacitorUpdater: b3a419b281c01eb9efc8969fe5183ea0da636d25
|
||||
SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef
|
||||
CapacitorSplashScreen: 03d04ffe57d5b619cfc162efc7415a965c92cc3f
|
||||
CapgoCapacitorUpdater: 4bc3381d413a3e36ecfee9a548b19cc3f4b2a9c8
|
||||
Version: de5907f2c5d0f3cf21708db7801d1d5401139486
|
||||
ZIPFoundation: dfd3d681c4053ff7e2f7350bc4e53b5dba3f5351
|
||||
|
||||
PODFILE CHECKSUM: 65fabdcdf017e31d75062b30247956106ea17004
|
||||
PODFILE CHECKSUM: d0ad3405654a881e2cb6b93743d30c0ace507415
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
+5
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "taskview-ce-webapp",
|
||||
"version": "1.17.0",
|
||||
"version": "1.19.6",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -20,6 +20,7 @@
|
||||
"dependencies": {
|
||||
"@capacitor/android": "^7.0.0",
|
||||
"@capacitor/app": "7.0.1",
|
||||
"@capacitor/browser": "^7.0.3",
|
||||
"@capacitor/core": "^7.0.0",
|
||||
"@capacitor/device": "7.0.1",
|
||||
"@capacitor/ios": "^7.0.0",
|
||||
@@ -91,5 +92,8 @@
|
||||
"vue-i18n": "11.1.10",
|
||||
"vue-tsc": "3.0.3",
|
||||
"vuetify": "3.7.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=24 <25"
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,11 @@
|
||||
key="3"
|
||||
@cancel-recovery="setRecoveryMode(false)"
|
||||
/>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<AuthByGoogle />
|
||||
<AuthByGithub />
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-divider />
|
||||
|
||||
@@ -47,7 +52,7 @@
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
<script async setup lang="ts">
|
||||
import { computed, reactive } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
@@ -56,6 +61,8 @@ import PasswordRecovery from '@/components/Authentication/PasswordRecovery';
|
||||
import RegistrationForm from '@/components/Authentication/RegistrationForm';
|
||||
import ResetPassword from '@/components/Authentication/ResetPassword';
|
||||
import LoginByCode from '../LoginByCode/LoginByCode.vue';
|
||||
import AuthByGoogle from '@/components/Authentication/AuthByGoogle.vue';
|
||||
import AuthByGithub from '@/components/Authentication/AuthByGithub.vue';
|
||||
|
||||
const data = reactive({
|
||||
recoveryModeActive: false,
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<ExternalAuthBaseBtn
|
||||
class="gsi-material-button"
|
||||
provider="github"
|
||||
>
|
||||
<template #icon>
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 98 96"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
|
||||
fill="#24292f"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
<span>Sign in with GitHub</span>
|
||||
</ExternalAuthBaseBtn>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import ExternalAuthBaseBtn from '@/components/Authentication/ExternalAuthBaseBtn.vue';
|
||||
</script>
|
||||
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<ExternalAuthBaseBtn
|
||||
class="gsi-material-button"
|
||||
provider="google"
|
||||
>
|
||||
<template #icon>
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 48 48"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
style="display: block;"
|
||||
>
|
||||
<path
|
||||
fill="#EA4335"
|
||||
d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"
|
||||
/>
|
||||
<path
|
||||
fill="#4285F4"
|
||||
d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"
|
||||
/>
|
||||
<path
|
||||
fill="#FBBC05"
|
||||
d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"
|
||||
/>
|
||||
<path
|
||||
fill="#34A853"
|
||||
d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
d="M0 0h48v48H0z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
<span>Sign in with Google</span>
|
||||
</ExternalAuthBaseBtn>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import ExternalAuthBaseBtn from '@/components/Authentication/ExternalAuthBaseBtn.vue';
|
||||
</script>
|
||||
@@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<button
|
||||
class="gsi-material-button"
|
||||
@click="login"
|
||||
>
|
||||
<div class="gsi-material-button-state" />
|
||||
<div class="gsi-material-button-content-wrapper">
|
||||
<div class="gsi-material-button-icon">
|
||||
<slot name="icon" />
|
||||
</div>
|
||||
<span class="gsi-material-button-contents">
|
||||
<slot />
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useAdditionalServer } from '@/composition/useAdditionalServer';
|
||||
import { Browser } from '@capacitor/browser';
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
|
||||
const props = defineProps<{
|
||||
provider: 'google' | 'github';
|
||||
}>();
|
||||
|
||||
const { mainServer } = await useAdditionalServer();
|
||||
|
||||
const login = async () => {
|
||||
const url = `${mainServer.value}/module/auth/provider/${props.provider}?platform=${
|
||||
Capacitor.isNativePlatform() ? "mobile" : "web"
|
||||
}`;
|
||||
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
await Browser.open({ url });
|
||||
} else {
|
||||
window.location.href = url;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.gsi-material-button {
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-webkit-appearance: none;
|
||||
background-color: WHITE;
|
||||
background-image: none;
|
||||
border: 1px solid #747775;
|
||||
-webkit-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
color: #1f1f1f;
|
||||
cursor: pointer;
|
||||
font-family: 'Roboto', arial, sans-serif;
|
||||
font-size: 14px;
|
||||
height: 40px;
|
||||
letter-spacing: 0.25px;
|
||||
outline: none;
|
||||
overflow: hidden;
|
||||
padding: 0 12px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
-webkit-transition: background-color .218s, border-color .218s, box-shadow .218s;
|
||||
transition: background-color .218s, border-color .218s, box-shadow .218s;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
width: auto;
|
||||
max-width: 400px;
|
||||
min-width: min-content;
|
||||
}
|
||||
|
||||
.gsi-material-button .gsi-material-button-icon {
|
||||
height: 20px;
|
||||
margin-right: 10px;
|
||||
min-width: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.gsi-material-button .gsi-material-button-content-wrapper {
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-flex-wrap: nowrap;
|
||||
flex-wrap: nowrap;
|
||||
height: 100%;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.gsi-material-button .gsi-material-button-contents {
|
||||
-webkit-flex-grow: 1;
|
||||
flex-grow: 1;
|
||||
font-family: 'Roboto', arial, sans-serif;
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.gsi-material-button .gsi-material-button-state {
|
||||
-webkit-transition: opacity .218s;
|
||||
transition: opacity .218s;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.gsi-material-button:disabled {
|
||||
cursor: default;
|
||||
background-color: #ffffff61;
|
||||
border-color: #1f1f1f1f;
|
||||
}
|
||||
|
||||
.gsi-material-button:disabled .gsi-material-button-contents {
|
||||
opacity: 38%;
|
||||
}
|
||||
|
||||
.gsi-material-button:disabled .gsi-material-button-icon {
|
||||
opacity: 38%;
|
||||
}
|
||||
|
||||
.gsi-material-button:not(:disabled):active .gsi-material-button-state,
|
||||
.gsi-material-button:not(:disabled):focus .gsi-material-button-state {
|
||||
background-color: #303030;
|
||||
opacity: 12%;
|
||||
}
|
||||
|
||||
.gsi-material-button:not(:disabled):hover {
|
||||
-webkit-box-shadow: 0 1px 2px 0 rgba(60, 64, 67, .30), 0 1px 3px 1px rgba(60, 64, 67, .15);
|
||||
box-shadow: 0 1px 2px 0 rgba(60, 64, 67, .30), 0 1px 3px 1px rgba(60, 64, 67, .15);
|
||||
}
|
||||
|
||||
.gsi-material-button:not(:disabled):hover .gsi-material-button-state {
|
||||
background-color: #303030;
|
||||
opacity: 8%;
|
||||
}
|
||||
</style>
|
||||
@@ -202,7 +202,7 @@ const loadMoreTasks = (columnId: number) => {
|
||||
console.log('loadMoreTasks', columnId);
|
||||
kanbanStore.fetchTasksForColumn(
|
||||
kanbanStore.goalId,
|
||||
columnId,
|
||||
columnId === DEFAULT_ID ? null : columnId,
|
||||
kanbanStore.tasksData[columnId]?.tasks[kanbanStore.tasksData[columnId]?.tasks.length - 1]?.kanbanOrder || null
|
||||
);
|
||||
canShowLoadMore.value[columnId] = false;
|
||||
|
||||
@@ -79,9 +79,11 @@ onMounted(async () => {
|
||||
});
|
||||
|
||||
async function logoutFromApp() {
|
||||
await logout();
|
||||
await router.push('/');
|
||||
router.go(0);
|
||||
const result = await logout();
|
||||
if (result) {
|
||||
await router.push('/');
|
||||
router.go(0);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkForUpdates() {
|
||||
|
||||
@@ -3,8 +3,14 @@ import $api from '@/helpers/axios';
|
||||
import { $ls } from '@/plugins/axios';
|
||||
|
||||
export async function logout() {
|
||||
await $api.post<LogoutResponse>('/module/auth/logout').catch((err) => {
|
||||
const result = await $api.post<LogoutResponse>('/module/auth/logout').catch((err) => {
|
||||
console.log(err, $api);
|
||||
});
|
||||
await $ls.invalidateTokens();
|
||||
|
||||
if (result) {
|
||||
await $ls.invalidateTokens();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ import axios from 'axios';
|
||||
import { useTaskViewMainUrl } from '@/composition/useTaskViewMainUrl';
|
||||
|
||||
const $api = axios.create({
|
||||
withCredentials: true,
|
||||
baseURL: useTaskViewMainUrl(),
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
export default $api;
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
import { AppCredentialsForm } from '@/components/Authentication/AppCredentialsForm';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
AppCredentialsForm,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
msg: 'login',
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -1,7 +1,77 @@
|
||||
<template>
|
||||
<div class="justify-center d-flex align-center start-page-height">
|
||||
<app-credentials-form />
|
||||
<Suspense>
|
||||
<AppCredentialsForm />
|
||||
</Suspense>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./LoginPage.ts" lang="ts" />
|
||||
<script setup lang="ts">
|
||||
import { AppCredentialsForm } from '@/components/Authentication/AppCredentialsForm';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { $ls } from '@/plugins/axios';
|
||||
import { onMounted } from 'vue';
|
||||
import type { LoginResponse } from '@/components/Authentication/LoginForm/Types';
|
||||
import { redirectToUser } from '@/helpers/app-helper';
|
||||
import { App } from "@capacitor/app";
|
||||
import { Browser } from '@capacitor/browser';
|
||||
import $api from '@/helpers/axios';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
type LoginTokens = {
|
||||
code: string;
|
||||
email: string;
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
try{
|
||||
const tokens = route.query.tokens as string;
|
||||
|
||||
if(!tokens){
|
||||
return;
|
||||
}
|
||||
|
||||
const result = JSON.parse(decodeURIComponent(tokens)) as LoginTokens;
|
||||
await loginByCode(result.code, result.email);
|
||||
}catch(error){
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
App.addListener("appUrlOpen", async ({ url }) => {
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (url.startsWith("taskview://login?tokens")) {
|
||||
const parsed = new URL(url);
|
||||
const tokens = parsed.searchParams.get("tokens");
|
||||
|
||||
if(!tokens){
|
||||
return;
|
||||
}
|
||||
|
||||
try{
|
||||
const result = JSON.parse(decodeURIComponent(tokens)) as LoginTokens;
|
||||
await loginByCode(result.code, result.email);
|
||||
}catch(error){
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
await Browser.close();
|
||||
});
|
||||
|
||||
const loginByCode = async (code: string, email: string) => {
|
||||
const result = await $api.post<LoginResponse>('/module/auth/login-by-code', { code, email });
|
||||
if (result && result.data.access) {
|
||||
$ls.setToken(result.data.access);
|
||||
$ls.setRefreshToken(result.data.refresh);
|
||||
await $ls.updateUserStoreByToken();
|
||||
await redirectToUser(router);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import LoginPage from '@/pages/LoginPage/LoginPage.vue';
|
||||
|
||||
export default LoginPage;
|
||||
@@ -67,7 +67,7 @@ const router = createRouter({
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: () => import('@/pages/LoginPage'),
|
||||
component: () => import('@/pages/LoginPage/LoginPage.vue'),
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -115,6 +115,10 @@ export const useTasksStore = defineStore('tasks', {
|
||||
if (mainTask) {
|
||||
mainTask.subtasks.push(task);
|
||||
}
|
||||
|
||||
if (this.selectedTask?.id === task.parentId) {
|
||||
this.selectedTask.subtasks.push(task);
|
||||
}
|
||||
} else {
|
||||
this.tasks.unshift(task);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user