I'm using grails 3.3.9 with a sql server db. I'm using default scaffold-ed code for my domain class. When I hit the index page, I get the No Session found for current thread error. For using the default scaffold-ed code, I'm a bit stumped as to why this is happening. In Grails 2.x once I scaffold the app just works. Anyway here is my code and hopefully someone can shed some light on what I don't obviously know:
application.yml
hibernate:
cache:
queries: false
use_second_level_cache: false
use_query_cache: false
dataSource:
pooled: true
jmxExport: true
driverClassName: org.h2.Driver
username: sa
password: ''
environments:
development:
dataSource:
dbCreate: create-drop
url: jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
dataSources:
tst:
dbCreate: update
url: jdbc:sqlserver://TESTSERVER;databaseName=Technician;useNTLMv2=true
driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
dialect: org.hibernate.dialect.SQLServer2012Dialect
pooled: true
username: user
password: 'pass'
formatSql: true
logSql: true
test:
dataSource:
dbCreate: update
url: jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
production:
dataSource:
dbCreate: none
url: jdbc:h2:./prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
properties:
jmxEnabled: true
initialSize: 5
maxActive: 50
minIdle: 5
maxIdle: 25
maxWait: 10000
maxAge: 600000
timeBetweenEvictionRunsMillis: 5000
minEvictableIdleTimeMillis: 60000
validationQuery: SELECT 1
validationQueryTimeout: 3
validationInterval: 15000
testOnBorrow: true
testWhileIdle: true
testOnReturn: false
jdbcInterceptors: ConnectionState
defaultTransactionIsolation: 2 # TRANSACTION_READ_COMMITTED
build.gradle
buildscript {
repositories {
mavenLocal()
maven { url "https://repo.grails.org/grails/core" }
}
dependencies {
classpath "org.grails:grails-gradle-plugin:$grailsVersion"
classpath "org.grails.plugins:hibernate5:${gormVersion-".RELEASE"}"
classpath "com.bertramlabs.plugins:asset-pipeline-gradle:2.15.1"
}
}
version "0.1"
group "tstsupport"
apply plugin:"eclipse"
apply plugin:"idea"
apply plugin:"war"
apply plugin:"org.grails.grails-web"
apply plugin:"asset-pipeline"
apply plugin:"org.grails.grails-gsp"
repositories {
mavenLocal()
maven { url "https://repo.grails.org/grails/core" }
}
dependencies {
compile "org.springframework.boot:spring-boot-starter-logging"
compile "org.springframework.boot:spring-boot-autoconfigure"
compile "org.grails:grails-core"
compile "org.springframework.boot:spring-boot-starter-actuator"
compile "org.springframework.boot:spring-boot-starter-tomcat"
compile "org.grails:grails-web-boot"
compile "org.grails:grails-logging"
compile "org.grails:grails-plugin-rest"
compile "org.grails:grails-plugin-databinding"
compile "org.grails:grails-plugin-i18n"
compile "org.grails:grails-plugin-services"
compile "org.grails:grails-plugin-url-mappings"
compile "org.grails:grails-plugin-interceptors"
compile "org.grails.plugins:cache"
compile "org.grails.plugins:async"
compile "org.grails.plugins:scaffolding"
compile "org.grails.plugins:events"
compile "org.grails.plugins:hibernate5"
compile "org.hibernate:hibernate-core:5.1.16.Final"
compile "org.grails.plugins:gsp"
console "org.grails:grails-console"
profile "org.grails.profiles:web"
runtime "org.glassfish.web:el-impl:2.1.2-b03"
runtime "com.h2database:h2"
runtime "org.apache.tomcat:tomcat-jdbc"
runtime "com.bertramlabs.plugins:asset-pipeline-grails:2.15.1"
runtime "com.microsoft.sqlserver:mssql-jdbc:7.2.1.jre8"
testCompile "org.grails:grails-gorm-testing-support"
testCompile "org.grails.plugins:geb"
testCompile "org.grails:grails-web-testing-support"
testRuntime "org.seleniumhq.selenium:selenium-htmlunit-driver:2.47.1"
testRuntime "net.sourceforge.htmlunit:htmlunit:2.18"
}
bootRun {
jvmArgs('-Dspring.output.ansi.enabled=always')
addResources = true
String springProfilesActive = 'spring.profiles.active'
systemProperty springProfilesActive, System.getProperty(springProfilesActive)
}
assets {
minifyJs = true
minifyCss = true
}
Domain class: TST_Customer.groovy
package TSTSupport
class TST_Technician {
String techCode
String techName
boolean tec990Flag
boolean tecCCFlag
Date tecCreateDate
String tecCreatedBy
Date tecModifyDate
String tecModifiedBy
boolean tecActiveFlag
static constraints = {
techCode shared: "techCode"
techName blank: false, maxSize: 75
tecCreatedBy blank: false, maxSize: 35
tecModifyDate nullable: true
tecModifiedBy blank: false, maxSize: 35
}
static mapping = {
datasource "tst"
table name: "lkp_Technician", schema: "dbo", catalog: "Technician"
version false
id generator: 'assigned', name: 'techCode', type: 'string'
techCode column: '[TechnicianCode]'
techName column: '[TechnicianName]'
tec990Flag column: '[Tech990Flag]'
tecCCFlag column: '[TechCCFlag]'
tecCreateDate column: '[TechCreateDate]'
tecCreatedBy column: '[TechCreatedBy]'
tecModifyDate column: '[TechModifyDate]'
tecModifiedBy column: '[TechModifiedBy]'
tecActiveFlag column: '[TechActiveFlag]'
}
}
Controller
package TSTSupport
import grails.validation.ValidationException
import static org.springframework.http.HttpStatus.*
class TST_CustomerController {
TST_CustomerService TST_CustomerService
static allowedMethods = [save: "POST", update: "PUT", delete: "DELETE"]
def index(Integer max) {
params.max = Math.min(max ?: 10, 100)
respond TST_CustomerService.list(params), model:[TST_CustomerCount: TST_CustomerService.count()]
}
def show(Long id) {
respond TST_CustomerService.get(id)
}
def create() {
respond new TST_Customer(params)
}
def save(TST_Customer TST_Customer) {
if (TST_Customer == null) {
notFound()
return
}
try {
TST_CustomerService.save(TST_Customer)
} catch (ValidationException e) {
respond TST_Customer.errors, view:'create'
return
}
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.created.message', args: [message(code: 'TST_Customer.label', default: 'TST_Customer'), TST_Customer.id])
redirect TST_Customer
}
'*' { respond TST_Customer, [status: CREATED] }
}
}
def edit(Long id) {
respond TST_CustomerService.get(id)
}
def update(TST_Customer TST_Customer) {
if (TST_Customer == null) {
notFound()
return
}
try {
TST_CustomerService.save(TST_Customer)
} catch (ValidationException e) {
respond TST_Customer.errors, view:'edit'
return
}
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.updated.message', args: [message(code: 'TST_Customer.label', default: 'TST_Customer'), TST_Customer.id])
redirect TST_Customer
}
'*'{ respond TST_Customer, [status: OK] }
}
}
def delete(Long id) {
if (id == null) {
notFound()
return
}
TST_CustomerService.delete(id)
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.deleted.message', args: [message(code: 'TST_Customer.label', default: 'TST_Customer'), id])
redirect action:"index", method:"GET"
}
'*'{ render status: NO_CONTENT }
}
}
protected void notFound() {
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'TST_Customer.label', default: 'TST_Customer'), params.id])
redirect action: "index", method: "GET"
}
'*'{ render status: NOT_FOUND }
}
}
}
Service
package TSTSupport
import grails.gorm.services.Service
#Service(TST_Customer)
interface TST_CustomerService {
TST_Customer get(Serializable id)
List<TST_Customer> list(Map args)
Long count()
void delete(Serializable id)
TST_Customer save(TST_Customer TST_Customer)
}
I actually had to Google the answer (none of the suggestions given to me worked for me). Anyway, in the controller you prefix right above the classname
#Transactional("name of datasource")
Full example:
package TSTSupport
import grails.validation.ValidationException
import static org.springframework.http.HttpStatus.*
#Transactional("tst")
class TST_CustomerController {
TST_CustomerService TST_CustomerService
static allowedMethods = [save: "POST", update: "PUT", delete: "DELETE"]
def index(Integer max) {
params.max = Math.min(max ?: 10, 100)
respond TST_CustomerService.list(params), model:[TST_CustomerCount: TST_CustomerService.count()]
}
def show(Long id) {
respond TST_CustomerService.get(id)
}
def create() {
respond new TST_Customer(params)
}
def save(TST_Customer TST_Customer) {
if (TST_Customer == null) {
notFound()
return
}
try {
TST_CustomerService.save(TST_Customer)
} catch (ValidationException e) {
respond TST_Customer.errors, view:'create'
return
}
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.created.message', args: [message(code: 'TST_Customer.label', default: 'TST_Customer'), TST_Customer.id])
redirect TST_Customer
}
'*' { respond TST_Customer, [status: CREATED] }
}
}
def edit(Long id) {
respond TST_CustomerService.get(id)
}
def update(TST_Customer TST_Customer) {
if (TST_Customer == null) {
notFound()
return
}
try {
TST_CustomerService.save(TST_Customer)
} catch (ValidationException e) {
respond TST_Customer.errors, view:'edit'
return
}
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.updated.message', args: [message(code: 'TST_Customer.label', default: 'TST_Customer'), TST_Customer.id])
redirect TST_Customer
}
'*'{ respond TST_Customer, [status: OK] }
}
}
def delete(Long id) {
if (id == null) {
notFound()
return
}
TST_CustomerService.delete(id)
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.deleted.message', args: [message(code: 'TST_Customer.label', default: 'TST_Customer'), id])
redirect action:"index", method:"GET"
}
'*'{ render status: NO_CONTENT }
}
}
protected void notFound() {
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'TST_Customer.label', default: 'TST_Customer'), params.id])
redirect action: "index", method: "GET"
}
'*'{ render status: NOT_FOUND }
}
}
}
Related
I'm trying to connect to a Microsoft SQL DB having synchronize: true option within TypeORM but it seems it doesn't work for some reason and I can't figure out why.
main.module.ts
providers: [
{
provide: "OMS_DB",
useFactory: async () => {
const connection = new DatabaseConnection(
new DatabaseConfiguration("OMS_DATABASE"),
[OMSOrderDto, OMSOrderLinesDto],
false
);
if (await connection.connect()) {
return connection;
}
return null;
},
},
]
database.connection.ts
constructor(
private configuration: DatabaseConfiguration,
private entities?: any,
private synchronize = false
) {}
/**
* Creates the actual connection
*/
async connect(): Promise<boolean> {
const config: DataSourceOptions = {
name: this.configuration.name,
type: "mssql",
host: this.configuration.hostname || "localhost",
port: this.configuration.port || 1433,
username: this.configuration.username,
password: this.configuration.password,
database: this.configuration.database,
entities: this.entities,
synchronize: false,
//logging: "all",
extra: {
trustServerCertificate: true,
},
};
this.logger.debug(
`Connecting to MSSQL Database ${this.configuration.hostname} / ${this.configuration.database}`
);
try {
this.connection = new DataSource(config);
await this.connection.initialize();
this.logger.log(`Conected to DB: ${this.configuration.hostname} / ${this.configuration.database}!`);
return true;
} catch (ex) {
this.logger.error(`Failed to connect to the database: ${this.configuration.name}`);
this.logger.error(ex);
}
return false;
}
Throwing Error:
Could not drop constraint. See previous errors.
I don't see any other errors.
We have users complaining because they are redirected to the login page of the Identity Server while in the middle of their work (and thus losing their current work). We have endeavoured to configure a sliding expiration, so I'm not sure why this is happening.
I realise there is quite a bit of code in this post. But there are a lot of moving parts and I want to give as much information as possible.
This behavour is arratic and it is hard to report an exact reproducable event. In my testing, I've been ejected at random times and it is hard to understand whether it has any relationship to any of the cofigurations which I have set. In my mind, I should not be ejected at all, as a silent sign-in is always sent during the addAccessTokenExpiring event.
The setup that we have is:
an Idp (using IdentityServer 4)
A client app, implemented using Vue.js (using Typescript)
An API, written in ASP.NET Core 5
The config and auth service which we have written are:
auth.config.ts
import { Log, UserManagerSettings, WebStorageStateStore } from "oidc-client";
import AppConfig from "./invariable/app.config";
/* eslint-disable */
class AuthConfig {
public settings: UserManagerSettings;
private baseUrl: string;
constructor() {
this.baseUrl = AppConfig.RunTimeConfig.VUE_APP_APPURL || process.env.VUE_APP_APPURL;
this.settings = {
userStore: new WebStorageStateStore({ store: window.localStorage }),
authority: AppConfig.RunTimeConfig.VUE_APP_IDPURL || process.env.VUE_APP_IDPURL,
client_id: AppConfig.RunTimeConfig.VUE_APP_CLIENTID || process.env.VUE_APP_CLIENTID,
client_secret: AppConfig.RunTimeConfig.VUE_APP_CLIENTSECRET || process.env.VUE_APP_CLIENTSECRET,
redirect_uri: this.baseUrl + process.env.VUE_APP_AUTHCALLBACK,
automaticSilentRenew: false,
silent_redirect_uri: this.baseUrl + process.env.VUE_APP_SILENTREFRESH,
response_type: "code",
response_mode: "query",
scope: "our_scopes",
post_logout_redirect_uri: this.baseUrl + process.env.VUE_APP_SIGNOUT_CALLBACK,
filterProtocolClaims: true,
loadUserInfo: true,
revokeAccessTokenOnSignout: true,
staleStateAge: 300, // should match access_token lifetime.
};
}
}
/* eslint-enable */
const authConfig = new AuthConfig();
export default authConfig;
auth.service.ts
import { UserManagerSettings, User, UserManager } from "oidc-client";
import authConfig from "#/config/auth.config";
import axios, { AxiosResponse } from "axios";
import { Ajax } from "#/config/invariable/ajax";
import AccessClaim from "#/domain/general/accessclaim";
import _ from "lodash";
import store from "#/store";
import StoreNamespaces from "#/config/invariable/store.namespaces";
import Token from "#/store/token/token";
export class AuthService {
private userManager: UserManager;
private tokenStore: string;
constructor(private settings: UserManagerSettings) {
this.settings = settings;
this.userManager = new UserManager(this.settings);
this.tokenStore = StoreNamespaces.tokenModule;
}
public addEvents(): void {
this.userManager.events.addUserSignedOut(() => {
this.signInAgain();
});
this.userManager.events.addAccessTokenExpired(() => {
console.log("Token expired");
this.clearLocalState();
console.log("Stale state cleaned up");
});
this.userManager.events.addAccessTokenExpiring(() => {
console.log("Access token about to expire.");
this.signInAgain();
});
this.userManager.events.addSilentRenewError(() => {
// custom logic here
console.log("An error happened whilst silently renewing the token.");
});
}
public clearLocalState(): Promise<void> {
return this.userManager.clearStaleState();
}
public getUserOnLoad(): Promise<User> {
return this.userManager.getUser().then((user) => {
if (!_.isNil(user) && !user.expired) {
console.log("first load sign-in");
const decodedIdToken = user.profile;
if (!_.isNil(decodedIdToken.store) && !_.isArray(decodedIdToken.store)) decodedIdToken.store = [decodedIdToken.store];
if (!_.isNil(decodedIdToken.classification) && !_.isArray(decodedIdToken.classification)) decodedIdToken.classification = [decodedIdToken.classification];
if (!_.isNil(decodedIdToken.location) && !_.isArray(decodedIdToken.location)) decodedIdToken.location = [decodedIdToken.location];
if (!_.isArray(decodedIdToken.app)) decodedIdToken.app = [decodedIdToken.app];
const token = new Token();
token.accessToken = user.access_token;
token.idToken = user.id_token;
token.storeClaims = decodedIdToken.store || [];
token.userType = decodedIdToken.usertype;
token.isLoggedIn = user && !user.expired;
token.app = decodedIdToken.app;
token.userName = decodedIdToken.name ?? "Unknown User";
store.dispatch(`${this.tokenStore}/setToken`, token);
return user;
} else {
return this.signInAgain();
}
});
}
public async getUserIfLoggedIn(): Promise<User | null> {
const currentUser: User | null = await this.userManager.getUser();
const loggedIn = currentUser !== null && !currentUser.expired;
return loggedIn ? currentUser : null;
}
public async isLoggedIn(): Promise<boolean> {
const currentUser: User | null = await this.userManager.getUser();
return currentUser !== null && !currentUser.expired;
}
public login(): Promise<void> {
return this.userManager.signinRedirect();
}
public logout(): Promise<void> {
return this.userManager.signoutRedirect();
}
public getAccessToken(): Promise<string> {
return this.userManager.getUser().then((data: any) => {
return data.access_token;
});
}
public signInAgain(): Promise<User> {
return this.userManager
.signinSilent()
.then((user) => {
console.log("silent sign-in");
const decodedIdToken = user.profile;
if (!_.isNil(decodedIdToken.store) && !_.isArray(decodedIdToken.store)) decodedIdToken.store = [decodedIdToken.store];
if (!_.isNil(decodedIdToken.classification) && !_.isArray(decodedIdToken.classification)) decodedIdToken.classification = [decodedIdToken.classification];
if (!_.isNil(decodedIdToken.location) && !_.isArray(decodedIdToken.location)) decodedIdToken.location = [decodedIdToken.location];
if (!_.isArray(decodedIdToken.app)) decodedIdToken.app = [decodedIdToken.app];
const token = new Token();
token.accessToken = user.access_token;
token.idToken = user.id_token;
token.storeClaims = decodedIdToken.store || [];
token.userType = decodedIdToken.usertype;
token.isLoggedIn = user && !user.expired;
token.app = decodedIdToken.app;
token.userName = decodedIdToken.name ?? "Unknown User";
store.dispatch(`${this.tokenStore}/setToken`, token);
return user;
})
.catch((err) => {
console.log("silent error");
console.log(err);
this.login();
return err;
});
}
public getAccessClaims(userDetails: any): Promise<AxiosResponse<any>> {
return axios.post(`${Ajax.appApiBase}/PermittedUse/GetAccessesForUser`, userDetails).then((resp: AxiosResponse<any>) => {
return resp.data;
});
}
public getPermissions(userDetails: any, siteId: number | null): Promise<AxiosResponse<any>> {
return axios.get(`${Ajax.appApiBase}/PermittedUse/GetPermissions/${siteId ?? 0}`).then((resp: AxiosResponse<any>) => {
return resp.data;
});
}
public constructAccess(userType: string, claims: Array<AccessClaim>): Array<AccessClaim> {
switch (userType) {
case "storeadmin":
case "storeuser":
return _.filter(claims, (claim) => {
return claim.claim === "store";
});
case "warduser":
return _.filter(claims, (claim) => {
return claim.claim === "classification";
});
}
return Array<AccessClaim>();
}
public getBookableLocations(userType: string, claims: Array<AccessClaim>): Array<AccessClaim> {
switch (userType) {
case "storeadmin":
case "storeuser":
return _.filter(claims, (claim) => {
return claim.claim === "store";
});
case "warduser":
return _.filter(claims, (claim) => {
return claim.claim === "location";
});
}
return Array<AccessClaim>();
}
}
export const authService = new AuthService(authConfig.settings);
On the Idp, our client configuration is:
ClientName = IcClients.Names.ConsumablesApp,
ClientId = IcClients.ConsumablesApp,
RequireConsent = false,
AccessTokenLifetime = TokenConfig.AccessTokenLifetime, // 300 for test purposes
IdentityTokenLifetime = TokenConfig.IdentityTokenLifetime, // 300
AllowOfflineAccess = true,
RefreshTokenUsage = TokenUsage.ReUse,
RefreshTokenExpiration = TokenExpiration.Sliding,
UpdateAccessTokenClaimsOnRefresh = true,
RequireClientSecret = true,
AllowedGrantTypes = GrantTypes.Code,
RequirePkce = true,
AllowAccessTokensViaBrowser = true,
AlwaysIncludeUserClaimsInIdToken = true,
RedirectUris = new List<string>
{
"https://localhost:44336/authcallback.html",
"https://localhost:8090/authcallback.html",
"https://localhost:44336/silent-refresh.html",
"https://localhost:8090/silent-refresh.html"
},
PostLogoutRedirectUris = new List<string>
{
"https://localhost:44336/signout-callback-oidc.html",
"https://localhost:8090/signout-callback-oidc.html"
},
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.LocalApi.ScopeName,
IcAccessScopes.IcAccessClaimsScope,
IdentityResources.UserDetails,
IcAccessScopes.ConsumablesScope
},
ClientSecrets = { new Secret("oursecret".Sha256())}
At the Idp, we are using ASP.NET Core Identity:
services.AddIdentity<IdpUser, IdentityRole<int>>()
.AddEntityFrameworkStores<IdpDbContext>()
.AddDefaultTokenProviders();
services.ConfigureApplicationCookie(options =>
{
options.ExpireTimeSpan = cookieDuration; // set to 1 hour
options.SlidingExpiration = true;
});
My expectation is that the sliding window should be extended every 5 minutes, as the user should be signed in again silently before the token expires.
When monitoring the IDP in my dev environment, one thing which I did note is that the checksession call is only being made once, when the user logs in. The wiki says that the checksession call should happen every 2s (by default). I have not changed this default (not knowingly). I even expressly set the checkSessionInterval property to 2000 to ensure that it was set to 2s.
The other thing I want to set out is the silent refresh html file, as I realise the CSP stuff can play into this:
<head>
<title></title>
<meta http-equiv="Content-Security-Policy" content="frame-src 'self' <%= VUE_APP_IDPURL %>; script-src 'self' 'unsafe-inline' 'unsafe-eval';" />
</head>
<body>
<script src="./oidc-client.min.js"></script>
<script>
(function refresh() {
window.location.hash = decodeURIComponent(window.location.hash);
new Oidc.UserManager({
// eslint-disable-next-line #typescript-eslint/camelcase
response_mode: "query",
userStore: new Oidc.WebStorageStateStore({
store: window.localStorage,
}),
})
.signinSilentCallback()
.then(function() {
console.log("****************************************signinSilentCallback****************************************");
})
.catch(function(err) {
debug;
console.log(err);
});
})();
</script>
</body>
If anyone can shed any light on this, it would be much appreciated.
Some further information. As a test, I set the refrsh time for the token and the cookie lifetime for the identity cookie to both be 10 hours (36,000s).
I am still getting reports of Users being kicked out after 45 minutes.
There's 2 conclusions I came to in resolving this and I would hesitate to say that it is a proper resolution to our problem.
There was a mistake in my Login code. I was using the IdentityServer4 extensions HttpContext.SignInAsync(user, authProperties) to create the session cookie. This is not the way to do things, if using ASP.NET Identity. For 1 reason, it does not include the SecurityStamp claim in the cookie. In their own quickstart, they use the SignInManager to sign in and issue the cookie _signInManager.PasswordSignInAsync(idpUser, model.Password, model.RememberLogin, true).
I needed to turn off the SecurityStamp validation feature in ASP.NET Identity. We had adjudged that we can live without it. You would think there would be a config setting for this, but I was not able to find it. So, at this stage, I have subclassed the UserManager and overridden the SupportsUserSecurityStamp property as follows: public override bool SupportsUserSecurityStamp => false;. Theoretically, this feature will now be turned off. Happy to be corrected if that is not the case or if there is a better way of doing it. (Would love to hear from a member of the ASP.NET team on this).
That's it.
user = {
'userid':'111',
'mail':[{
'time':22222,
'info':'this is a info1',
'read':false,
},{
'time':33333,
'info':'this is a info2',
'read':false,
}]
}
then, I want to change one user's read flag to true, how can i do for it ?
here is my answer:
User.update({
'userid':userid,
'mails':{
'$elemMatch':{
'read':false
}
}
},{
'$set':{
'mail.$.read':false,
'newmail':0,
}
}, { multi: true }, function (err, data) {
if(err > 0){
console.log('ReadMail err', err);
}
});
here is a simple example: my model is User !! my url is the path to the DB
var newUser = new User();
newUser.name = request.body.name;
newUser.email = request.body.email;
newUser.pass = request.body.pass;
newUser.position = request.body.pos;
newUser.phone = request.body.phone;
mongoose.connection.openUri(url);
var myquery = { _id : request.params._id };
console.log("updating...")
User.findByIdAndUpdate(myquery,{$set: newUser},{new: true},function(err, result) {
if(err)
{
throw err;
}
response.json({code: 0 , result});
});
I have Angular 2 Search Pipe that filters against an array of Project[]. It works for every property except for one containing an array of strings.
Here is a sample of data model
[{
'Id': 2,
'Title': 'Abc',
'Amount': '200',
'Outcome': ['Outcome 2', 'Outcome 22', 'Outcome 222', 'Outcome 2222']
},
{
'Id': 3,
'Title': 'Abc',
'Amount': '300',
'Outcome': ['Outcome 3', 'Outcome 333', 'Outcome 3333', 'Outcome 33333']
}]
Here is the SearchPipe -
not searching against Outcome array
export class SearchPipe implements PipeTransform {
transform(value, args?): Project[] {
let searchText = new RegExp(args, 'ig');
if (value) {
return value.filter(project => {
if (project) {
return project.Title.search(searchText) !== -1
|| project.Focus.search(searchText) !== -1
|| project.Outcome.forEach(outcome => {
if (outcome.search(searchText) !== -1) {
return true;
}
else {
return false;
}
});
}
});
}
}
}
Any Help would be much appreciated - Thank you!
Your foreach is incorrect. It doesnt return true or false. You can change your pipe to something like this and search if it contains something in the string and then return a boolean accordingly.
Like so:
#Pipe({name: 'Search'})
export class Search implements PipeTransform {
transform(value, args?) {
let searchText = 'test';
if (value) {
return value.filter(project => {
if (project) {
return !project.Outcome.every(outcome => {
return (!outcome.includes(searchText))
});
}
});
}
}
}
Also check the plunker I used to see it working ( https://plnkr.co/edit/ntyDUEwe0HXwjeupqDUr?p=preview )
The problem lies within the forEach loop you do. Returning true or false doesn't do what you expect.
A solution would be to move this logic to a separate function:
export class SearchPipe implements PipeTransform {
transform(value, args?): Project[] {
let searchText = new RegExp(args, 'ig');
if (value) {
return value.filter(project => {
if (project) {
return project.Title.search(searchText) !== -1
|| project.Focus.search(searchText) !== -1
|| this._checkArray(project.Outcome, searchText);
}
});
}
}
_checkArray(arr, search) : boolean {
let found: boolean = false;
arr.forEach(outcome => {
if (outcome.search(search) !== -1) {
return true;
}
})
return found;
}
}
It's untested, and not pretty yet. But you get the general idea
When using the angularFire $authWithPassword method in my app I am receiving a Permission_Denied from firebase. I'm very new to the Firebase Security rules and the mistake lies somewhere in how I've set them up (as it works fine when read and write are true globally). What I'm trying to do is disallow users from deleting the major collections of my architecture but allow them to CRUD child items.
So I have gifts, events, persons, and users. And my rules look like this:
{
"rules": {
".read": true,
"events": {
"$id": {
".write": true
}
},
"gifts": {
"$id": {
".write": true
}
},
"person": {
".indexOn": "created_by",
"$id": {
".write": true
}
},
"user": {
"$id": {
".write": true
}
}
}
}
As I said, there are no issues when using the default rules of:
{
"rules": {
".read": true,
".write": true,
"person": {
".indexOn": "created_by"
}
}
}
What am I doing wrong?
EDIT:
It is not the $authWithPassword doing the write operation, it is $createUser where I create the user and in the .then I am doing a 'CreateProfile' function which uses ref.set to update properties to /users/uid.
$scope.createAccount = function(email, pass, name, confirm) {
loaderSvc.toggleOn('Creating account...');
$scope.err = null;
if( !pass ) {
$scope.err = 'Please enter a password';
}
else if ( !name ) {
$scope.err = 'Please enter a display name';
}
else if( pass !== confirm ) {
$scope.err = 'Passwords do not match';
}
else {
Auth.$createUser({email: email, password: pass})
.then(function () {
// authenticate so we have permission to write to Firebase
return Auth.$authWithPassword({email: email, password: pass}, {rememberMe: true});
})
.then(createProfile)
.then(redirect, showError);
}
function createProfile(user) {
var ref = Ref.child('users/' + user.uid), def = $q.defer();
ref.set({email: email, name: name}, function(err) {
$timeout(function() {
if( err ) {
def.reject(err);
}
else {
sendWelcomeEmail(email, name);
toastr.success('Account created');
Analytics.trackEvent('profile', 'account created', user.uid);
def.resolve(ref);
}
});
});
return def.promise;
}
};