In many occasions it is handy for administrators to run scripts directly. So, instead of having to create a custom operation script, then a custom operation, then granting permissions, refreshing the browser and running, there is a menu called Run script, which presents a text box where the script may be typed in or pasted, which can be executed directly. Of course, only the basic bindings are available.
The result of the script can be either a string, which is then displayed as plain text, or an object / map compatible with org.cyclos.model.system.scripts.ScriptResult. So, for example, to return an HTML text with a title, the script can return [title:"The result title", richText:"<b>Formatted</b> text"]. To show a notification, the the script can return [notification:"Notification text"]. The same prefixes available on notifications for custom operations are availeble on notifications: [INFO], [WARN] and [ERROR].
Here is an example of a script to remove all regular users (not administrators) and related data, as well as all system to system transactions. When running in a network, will remove data on that network. When running in global mode, will affect the entire system. Be advised that there will be no confirmation, and all users and all related data will be removed.
The script works by first recreating all database constraints with the option ON DELETE CASCADE. Then, all users are removed, which will cascade the removal to accounts, transfers, advertisements, records, messages, notifications, references and so on. Be careful when running in systems where specific users are used in the configuration, such as fees that are paid by a specific user, or payment types which are restricted to specific users. In such configurations, all such related data will be removed as well. Also, note that it may take a while to run, so, please, wait before the script completes.
AGAIN: be very careful when using this script! Only run it on test instances and always have a database backup before running it.
import java.sql.Connection import org.cyclos.impl.InvokerHandler import org.cyclos.impl.access.SessionData import org.cyclos.impl.search.SearchHandler import org.cyclos.impl.system.ScriptHelper import org.cyclos.impl.utils.cache.CacheHandler import org.cyclos.impl.utils.cache.CacheType import org.springframework.jdbc.core.ConnectionCallback import org.springframework.jdbc.core.JdbcTemplate import groovy.sql.GroovyRowResult import groovy.sql.Sql import groovy.transform.Field import groovy.transform.TypeChecked @Field SessionData sessionData = binding.sessionData @Field ScriptHelper scriptHelper = binding.scriptHelper @Field CacheHandler cacheHandler = binding.cacheHandler @Field SearchHandler searchHandler = binding.searchHandler @Field InvokerHandler invokerHandler = binding.invokerHandler @Field JdbcTemplate jdbc = binding.jdbcTemplate @Field Connection connection = jdbc.execute({ it } as ConnectionCallback) @Field Sql sql = new Sql(connection) @Field Long networkId = sessionData.network?.id @Field List<GroovyRowResult> constraints = sql.rows(""" SELECT tc.table_name, kcu.column_name, tc.constraint_name, ccu.table_name AS foreign_table_name, ccu.column_name AS foreign_column_name FROM information_schema.table_constraints AS tc JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name WHERE constraint_type = 'FOREIGN KEY' """.toString()) def recreateConstraints(boolean cascade) { constraints.forEach { r -> String drop = """alter table ${r.table_name} drop constraint ${r.constraint_name}""" String create = """alter table ${r.table_name} add constraint ${r.constraint_name} foreign key (${r.column_name}) references ${r.foreign_table_name} (${r.foreign_column_name})""" if (cascade) { create += " on delete cascade" } sql.execute(drop) sql.execute(create) } } @Field String accountsSql = """select a.id from accounts a inner join account_types at on a.account_type_id = at.id inner join currencies c on at.currency_id = c.id """ if (networkId != null) { accountsSql += " where c.network_id = ${networkId}" } @TypeChecked def execute() { // Recreate constraints with on delete cascade recreateConstraints(true) // First break the cycle on accounts -> account_rates, // otherwise system accounts with rates are deleted as well sql.executeUpdate("update accounts set account_rates_id = null" + " where id in (" + accountsSql + ")") // Remove banking data. This will include system accounts as well def tablesAndColumns = [ transfers: ['from_id', 'to_id'], transactions: ['from_id', 'to_id'], amount_reservations: ['account_id'], closed_account_balances: ['account_id'], account_balances: ['account_id'], dirty_account_balances: ['account_id'], account_rates: ['account_id']] tablesAndColumns.forEach { table, cols -> cols.forEach { col -> sql.executeUpdate("delete from " + table + " where " + col + " in (" + accountsSql + ")") } } // Remove users def removedUsers = sql.executeUpdate(""" delete from users where (subclass = 'OPERATOR' or user_group_id in ( select id from groups where subclass in ('MEMBER_GROUP', 'BROKER_GROUP'))) """ + ((networkId == null) ? "" : " and network_id = ${networkId}")) // Remove some other tables which would be just placeholders with empty data sql.executeUpdate("delete from bulk_actions" + (networkId == null ? '' : " where network_id = ${networkId}")) sql.executeUpdate("delete from mailing_lists" + (networkId == null ? '' : " where network_id = ${networkId}")) sql.executeUpdate("delete from account_fee_logs" + (networkId == null ? '' : """ where account_fee_id in ( select af.id from account_fees af inner join account_types at on af.account_type_id = at.id inner join currencies c on at.currency_id = c.id where c.network_id = ${networkId})""")) // Recreate constraints as initial recreateConstraints(false) return "Removed ${removedUsers} users" } def output = execute() // Clear all caches CacheType.all().each { cacheHandler.scheduleClear(it) } // Reindex all data searchHandler.reindex() // Execute the script return output