Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat!: Auth user db sync #2666

Merged
merged 8 commits into from
Jan 2, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "api",
"version": "1.5.1",
"version": "1.6.0",
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
35 changes: 34 additions & 1 deletion packages/api/spec-export.json
Original file line number Diff line number Diff line change
@@ -130,6 +130,39 @@
]
}
},
"/auth_users/{auth_user_id}": {
"get": {
"operationId": "AuthUsersController_findOne",
"summary": "Get auth user profile",
"parameters": [
{
"name": "auth_user_id",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
},
{
"name": "x-deployment-db-name",
"in": "header",
"description": "Name of db for deployment to populate",
"schema": {
"type": "string",
"default": "plh"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"tags": [
"Auth Users"
]
}
},
"/contact_fields": {
"get": {
"operationId": "ContactFieldController_findAll",
@@ -339,7 +372,7 @@
"info": {
"title": "IDEMS Apps API",
"description": "App-Server Communication",
"version": "1.4.4",
"version": "1.6.0",
"contact": {}
},
"tags": [
1 change: 1 addition & 0 deletions packages/api/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -31,6 +31,7 @@ import { DeploymentModule } from "./modules";
}),
DefaultModule,
Endpoints.AppUsersModule,
Endpoints.AuthUsersModule,
Endpoints.ContactFieldsModule,
Endpoints.AppFeedbackModule,
Endpoints.AppNotificationInteractionModule,
9 changes: 5 additions & 4 deletions packages/api/src/db/index.ts
Original file line number Diff line number Diff line change
@@ -32,14 +32,15 @@ export class DBInstance {
database: this.dbName,
// disable verbose migration logs in test
logging: process.env.NODE_ENV === "test" ? false : true,
// Fix SSL issue
// Enable ssl mode when running on production
// https://dev.to/rodjosh/connectionerror-sequelizeconnectionerror-no-pghbaconf-entry-for-host-in-heroku-postgresql-using-sequelize-3icj
dialectOptions: {
ssl: {
// https://stackoverflow.com/a/61411969
dialectOptions: environment.production ? {
ssl: {
require: true,
rejectUnauthorized: false,
},
},
} : {},
});
await this.runMigrations(migrationClient);
await migrationClient.close();
13 changes: 13 additions & 0 deletions packages/api/src/db/migrations/008-auth-user-column.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { DataTypes, QueryInterface } from "sequelize";
import { UmzugOptions } from "umzug";

export const up = async (options: UmzugOptions<QueryInterface>) => {
const queryInterface = options.context as QueryInterface;
await queryInterface.addColumn("app_users", "auth_user_id", {
type: DataTypes.STRING,
});
};
export const down = async (options: UmzugOptions<QueryInterface>) => {
const queryInterface = options.context as QueryInterface;
await queryInterface.removeColumn("app_users", "auth_user_id");
};
3 changes: 3 additions & 0 deletions packages/api/src/endpoints/app_users/app_user.model.ts
Original file line number Diff line number Diff line change
@@ -13,6 +13,9 @@ export class AppUser extends Model<InferAttributes<AppUser>, InferCreationAttrib
@Column({ allowNull: false })
app_deployment_name: string;

@Column
auth_user_id: string;

@Column({ type: DataType.JSONB })
contact_fields: any;

24 changes: 24 additions & 0 deletions packages/api/src/endpoints/auth_users/auth_user.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Controller, Get, Param, } from "@nestjs/common";
import { ApiOperation, ApiParam, ApiTags } from "@nestjs/swagger";
import { DeploymentHeaders } from "src/modules/deployment.decorators";
import { DeploymentService } from "src/modules/deployment.service";
import { AppUser } from "../app_users/app_user.model";

@ApiTags("Auth Users")
@Controller("auth_users")
/**
* The auth_users endpoint are a thin wrapper around the app_users table to enable
* user lookup by auth_user_id
*/
export class AuthUsersController {
constructor(private readonly deploymentService: DeploymentService) {}

@Get(":auth_user_id")
@ApiParam({ name: "auth_user_id", type: String })
@ApiOperation({ summary: "Get auth user profile" })
@DeploymentHeaders()
findOne(@Param("auth_user_id") auth_user_id: string): Promise<AppUser[]> {
const model = this.deploymentService.model(AppUser);
return model.findAll({where:{auth_user_id}})
}
}
7 changes: 7 additions & 0 deletions packages/api/src/endpoints/auth_users/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { AuthUsersController } from './auth_user.controller';
import { Module } from '@nestjs/common';

@Module({
controllers: [AuthUsersController],
})
export class AuthUsersModule {}
1 change: 1 addition & 0 deletions packages/api/src/endpoints/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./app_users";
export * from './auth_users'
export * from "./contact_fields";
export * from "./tables";
export * from "./app_feedback";
2 changes: 1 addition & 1 deletion packages/data-models/fields.ts
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@
* They are used to store store computed or exported variables and are not user overridable
*/
enum PROTECTED_FIELDS {
APP_AUTH_USER = "app_auth_user",
APP_FIRST_LAUNCH = "app_first_launch",
APP_LANGUAGE = "app_language",
APP_SKIN = "app_skin",
@@ -14,6 +13,7 @@ enum PROTECTED_FIELDS {
APP_UPDATE_DOWNLOADED = "app_update_downloaded",
APP_USER_ID = "app_user_id",
APP_VERSION = "app_version",
AUTH_USER_ID = "auth_user_id",
CONTENT_VERSION = "content_version",
DEPLOYMENT_NAME = "deployment_name",
FEEDBACK_SELECTED_TEXT = "feedback_selected_text",
2 changes: 1 addition & 1 deletion packages/server/docker/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -46,7 +46,7 @@ services:
# context: ../../api
# dockerfile: Dockerfile
# target: prod-env
image: idems/apps-api:1.5.1
image: idems/apps-api:1.6.0
env_file:
- ../../api/.env
environment:
28 changes: 21 additions & 7 deletions src/app/shared/services/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ import { IAuthUser } from "./types";
import { filter, firstValueFrom, tap } from "rxjs";
import { TemplateService } from "../../components/template/services/template.service";
import { toObservable } from "@angular/core/rxjs-interop";
import { ServerService } from "../server/server.service";

@Injectable({
providedIn: "root",
@@ -22,14 +23,20 @@ export class AuthService extends AsyncServiceBase {
private localStorageService: LocalStorageService,
private deploymentService: DeploymentService,
private injector: Injector,
private templateService: TemplateService
private templateService: TemplateService,
private serverService: ServerService
) {
super("Auth");
this.provider = getAuthProvider(this.config.provider);
this.registerInitFunction(this.initialise);
effect(async () => {
const authUser = this.provider.authUser();
await this.checkProfileRestore(authUser);
this.addStorageEntry(authUser);
// perform immediate sync if user signed in to ensure data backed up
if (authUser) {
this.serverService.syncUserData();
}
});
}

@@ -46,6 +53,14 @@ export class AuthService extends AsyncServiceBase {
}
}

private async checkProfileRestore(authUser?: IAuthUser) {
if (!authUser) return;
const existingUser = this.localStorageService.getProtected("AUTH_USER_ID");
if (existingUser) return;
// no existingUser user, should check if authUser exists on server and prompt restore
// TODO - handle
}

private async enforceLogin(templateName: string) {
// If user already logged in simply return. If providers auto-login during then waiting to verify
// should be included during the provide init method
@@ -91,13 +106,12 @@ export class AuthService extends AsyncServiceBase {
});
}

/** Keep a subset of auth user info in contact fields for db lookup*/
private addStorageEntry(user?: IAuthUser) {
if (user) {
const { uid } = user;
this.localStorageService.setProtected("APP_AUTH_USER", JSON.stringify({ uid }));
/** Keep id of auth user info in contact fields for db lookup*/
private addStorageEntry(auth_user?: IAuthUser) {
if (auth_user) {
this.localStorageService.setProtected("AUTH_USER_ID", auth_user.uid);
} else {
this.localStorageService.removeProtected("APP_AUTH_USER");
this.localStorageService.removeProtected("AUTH_USER_ID");
}
}
}
4 changes: 4 additions & 0 deletions src/app/shared/services/server/server.service.ts
Original file line number Diff line number Diff line change
@@ -76,14 +76,18 @@ export class ServerService extends SyncServiceBase {
const timestamp = generateTimestamp();
contact_fields[getProtectedFieldName("SERVER_SYNC_LATEST")] = timestamp;

const auth_user_id = this.localStorageService.getProtected("AUTH_USER_ID") || null;

// TODO - get DTO from api (?)
const data = {
auth_user_id,
contact_fields,
app_version: _app_builder_version,
device_info: this.device_info,
app_deployment_name: name,
dynamic_data,
};

console.log("[SERVER] sync data", data);
return new Promise<string>((resolve, reject) => {
this.http
Loading