From 627806bac8882624270c57eb5321e32fbd790b44 Mon Sep 17 00:00:00 2001 From: a-ill Date: Fri, 11 Aug 2023 12:25:48 +0300 Subject: [PATCH] Added admin panel --- Server/app/resources/admin/AdminController.jl | 145 +++++ .../resources/admin/views/admin_panel.jl.html | 1 + .../app/resources/groups/GroupsController.jl | 17 - Server/app/svelte/src/admin-panel.svelte | 140 +++++ .../{ => profile}/groups-add-component.svelte | 0 Server/db/init_db.jl | 25 +- Server/public/js/components/admin-panel.js | 1 + Server/public/js/components/index-2620635a.js | 527 ++++++++++++++++++ Server/public/js/components/index-adbb0a76.js | 53 ++ Server/routes.jl | 16 +- 10 files changed, 903 insertions(+), 22 deletions(-) create mode 100644 Server/app/resources/admin/AdminController.jl create mode 100644 Server/app/resources/admin/views/admin_panel.jl.html create mode 100644 Server/app/svelte/src/admin-panel.svelte rename Server/app/svelte/src/{ => profile}/groups-add-component.svelte (100%) create mode 100644 Server/public/js/components/admin-panel.js create mode 100644 Server/public/js/components/index-2620635a.js create mode 100644 Server/public/js/components/index-adbb0a76.js diff --git a/Server/app/resources/admin/AdminController.jl b/Server/app/resources/admin/AdminController.jl new file mode 100644 index 0000000..ffbbcd0 --- /dev/null +++ b/Server/app/resources/admin/AdminController.jl @@ -0,0 +1,145 @@ +module AdminController + +using Genie, Genie.Renderer, Genie.Renderer.Html, Genie.Requests, GenieAuthentication, DataFrames, GenieAuthorisation +using JSON3 +using SearchLight,SearchLightPostgreSQL, LibPQ, JSON3 +using Server.DatabaseSupport, Server.TemplateEditor, Server.Users +import Server.DatabaseSupport: select_from_table, insert_into_table, delete_from_table, exist_in_table + +controller = "admin" +dict_layouts = Dict( + :admin_panel => generate_layout_html("main",controller,"admin-panel"), +) + +#---Page info----------------------------------------------------- + +const admin_panel_info = Dict( + "en" => Dict( + :title => "LibSoc - Admin panel", + :description => "" + ), + "ru" => Dict( + :title => "", + :description => "" + ) +) + +function get_locale() + data = payload() + if :locale in keys(data) + return data[:locale] + else + return "en" + end +end + +#---Helpers----------------------------------------------------------- + + +function table_to_json(name,df) + ar = [] + for df_row in eachrow(df) + dict = Dict() + for id in names(df_row) + dict[id] = df_row[id] + end + push!(ar,dict) + end + open("public/assets/"*name*".json", "w") do io + JSON3.write(io, ar) + end +end + +function compile(name) + df = select_from_table([name => ["*"]]) + table_to_json(name,df) +end + +function move_requests(name) + df_requests = select_from_table(["$(name)_requests" => ["*"]], where_data=["verified" => true, "added" => false]) + df = select_from_table([name => ["*"]]) + latitudes = df.latitude + longitudes = df.longitude + for df_row in eachrow(df_requests) + ind_id_given = ismissing(df_row.id_given) ? nothing : findfirst(df_row.id_given.==df.id) + if (!isnothing(ind_id_given)) + id = df[ind_id_given,:id] + row_found = df[ind_id_given,Not(:id)] + dict = Dict(zip(names(row_found),values(row_found))) + dict["members"] += 1 + update_table(name,dict, where_data=["id" => id]) + else + id = df_row.id + dict_update = Dict("added" => true) + update_table("$(name)_requests",dict_update, where_data=["id" => id]) + + df_row_to_add = df_row[Not(:id_given)] + df_row_to_add = df_row_to_add[Not(:verified)] + df_row_to_add = df_row_to_add[Not(:added)] + df_row_to_add = df_row_to_add[Not(:id)] + dict = Dict(zip(names(df_row_to_add),values(df_row_to_add))) + dict["members"] = 1 + insert_into_table(name,dict) + end + end +end + +#---Functions--------------------------------------------------------- + +current_user() = findone(Users.User, id = get_authentication()) + +function admin_panel() + @info has_permission(current_user(), "verification") + @info current_user() + if has_permission(current_user(), "verification") + locale = get_locale() + html(:admin,:admin_panel, layout = dict_layouts[:admin_panel], context = @__MODULE__, + title = admin_panel_info[locale][:title], + description = admin_panel_info[locale][:description] + ) + end +end + +function verify() + if has_permission(current_user(), "verification") + data = copy(jsonpayload()) + user_id = data["user_id"] + update_table("users",Dict("verified" => true), where_data=["id" => user_id]) + return nothing + end +end + +function get_unverified_users() + if has_permission(current_user(), "verification") + users = select_from_table("users" => ["id","email"], where_data = ["verified" => false]) + data = [] + if size(users,1)!=0 + for x in eachrow(users) + dict = Dict("user_id" => x["id"],"email" => x["email"]) + push!(data, dict) + end + end + return JSON3.write(data) + end +end + +function add_verified_groups() + if has_permission(current_user(), "admin") + groups_create_requests_verified = select_from_table("groups_requests" => ["*"], where_data = ["group_id" => nothing, "status" => 1]) + if size(groups_create_requests_verified,1)!=0 + data = Dict(zip(names(groups_create_requests_verified),groups_create_requests_verified[end,:])) + user_id = data["user_id"] + delete!(data,"group_id") + delete!(data,"user_id") + delete!(data,"id") + delete!(data,"status") + group_id = insert_into_table("groups",data, "RETURNING id")[1,1] + dict_users_groups = Dict("user_id" => user_id, "group_id" => group_id) + insert_into_table("users_groups",dict_users_groups) + delete_from_table("groups_requests",["user_id" => user_id]) + end + compile("groups") + end +end + +end diff --git a/Server/app/resources/admin/views/admin_panel.jl.html b/Server/app/resources/admin/views/admin_panel.jl.html new file mode 100644 index 0000000..2ceb742 --- /dev/null +++ b/Server/app/resources/admin/views/admin_panel.jl.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Server/app/resources/groups/GroupsController.jl b/Server/app/resources/groups/GroupsController.jl index 4d55990..2c96835 100644 --- a/Server/app/resources/groups/GroupsController.jl +++ b/Server/app/resources/groups/GroupsController.jl @@ -245,23 +245,6 @@ function reject_request() return nothing end -function add_verified_groups() - groups_create_requests_verified = select_from_table("groups_requests" => ["*"], where_data = ["group_id" => nothing, "status" => 1]) - if size(groups_create_requests_verified,1)!=0 - data = Dict(zip(names(groups_create_requests_verified),groups_create_requests_verified[end,:])) - user_id = data["user_id"] - delete!(data,"group_id") - delete!(data,"user_id") - delete!(data,"id") - delete!(data,"status") - group_id = insert_into_table("groups",data, "RETURNING id")[1,1] - dict_users_groups = Dict("user_id" => user_id, "group_id" => group_id) - insert_into_table("users_groups",dict_users_groups) - delete_from_table("groups_requests",["user_id" => user_id]) - end - compile("groups") -end - function changeMemberCount() user_id = get_authentication() groups_ids = select_from_table("users_groups" => ["group_id"], where_data = ["user_id" => user_id])[:,1] diff --git a/Server/app/svelte/src/admin-panel.svelte b/Server/app/svelte/src/admin-panel.svelte new file mode 100644 index 0000000..23f8271 --- /dev/null +++ b/Server/app/svelte/src/admin-panel.svelte @@ -0,0 +1,140 @@ + + + + +{#key $loaded} + {#if $loaded==numLoaded} + +
+

User verification

+
+ {#key keyRequests} + {#each requests_verification as req,ind} +
+
+ {req.email} +
+ +
+
+
+ {/each} + {/key} + +
+
+
+ {/if} +{/key} + + + \ No newline at end of file diff --git a/Server/app/svelte/src/groups-add-component.svelte b/Server/app/svelte/src/profile/groups-add-component.svelte similarity index 100% rename from Server/app/svelte/src/groups-add-component.svelte rename to Server/app/svelte/src/profile/groups-add-component.svelte diff --git a/Server/db/init_db.jl b/Server/db/init_db.jl index ea6d224..c0e0e1e 100644 --- a/Server/db/init_db.jl +++ b/Server/db/init_db.jl @@ -25,6 +25,9 @@ SearchLight.Configuration.load() |> SearchLight.connect #SearchLight.Migration.all_up!!(context=Server) #SearchLight.Migration.status() + +#---Create tables---------------------------------------------------- + p = "db/migrations/" files = readdir(p) files = files[map(x -> x[end-1:end].=="jl", files)] @@ -38,4 +41,24 @@ for f in files m.up() catch end -end \ No newline at end of file +end + + +#---Initialize Genie Authorization---------------------------------------------------- + +using GenieAuthorisation +using GenieAuthorisation: findone_or_create, save!, findone + +# Create roles +for r in ["admin"] + findone_or_create(Role, name = r) |> save! +end + +# Create permissions +for p in ["verification"] + findone_or_create(Permission, name = p) |> save! +end + +assign_permission(findone(Role, name = "admin"), findone(Permission, name = "verification")) + +# assign_role(findone(User, email = "user@user"), findone(Role, name = "admin")) diff --git a/Server/public/js/components/admin-panel.js b/Server/public/js/components/admin-panel.js new file mode 100644 index 0000000..d057a8e --- /dev/null +++ b/Server/public/js/components/admin-panel.js @@ -0,0 +1 @@ +import{S as t,i as e,a as n,b as o,s,e as r,n as i,d as a,c,g as d,o as l,f as m,h as p,j as u,k as f,l as h,m as g,p as b,t as v}from"./index-0d9f0c09.js";import{w as x}from"./index-1c123138.js";import{getData as j,sendData as w}from"../../../../../../../../../js/libraries/serverTools.js";import"../../../../../../../../../js/components/select-component.js";import"../../../../../../../../../js/components/switch-component.js";import"../../../../../../../../../js/components/pane-aligner.js";function y(t,e,n){const o=t.slice();return o[13]=e[n],o[15]=n,o}function k(t){let e,n,r,i,c,d,l,m,b,v=t[1],x=C(t);return{c(){e=p("pane-aligner"),n=p("div"),r=p("h3"),r.textContent="User verification",i=u(),c=p("section"),x.c(),d=u(),l=p("button"),l.textContent="Add verified pins",f(l,"id","add-verified-button"),f(l,"class","default-button"),f(c,"class","entries-section"),f(n,"slot","main")},m(s,a){o(s,e,a),h(e,n),h(n,r),h(n,i),h(n,c),x.m(c,null),h(c,d),h(c,l),t[9](c),t[10](n),m||(b=g(l,"click",t[7]),m=!0)},p(t,e){2&e&&s(v,v=t[1])?(x.d(1),x=C(t),x.c(),x.m(c,d)):x.p(t,e)},d(n){n&&a(e),x.d(n),t[9](null),t[10](null),m=!1,b()}}}function N(t){let e,n,s,r,i,c,d,l,m,b,x=t[13].email+"";function j(){return t[8](t[15],t[13])}return{c(){e=p("div"),n=p("div"),s=p("span"),r=v(x),i=u(),c=p("div"),d=p("button"),d.textContent="Approve",l=u(),f(d,"class","default-button approve-button"),f(c,"class","request-button-wrapper"),f(n,"class","change-field-line")},m(t,a){o(t,e,a),h(e,n),h(n,s),h(s,r),h(n,i),h(n,c),h(c,d),h(e,l),m||(b=g(d,"click",j),m=!0)},p(e,n){t=e},d(t){t&&a(e),m=!1,b()}}}function C(t){let e,n=t[4],s=[];for(let e=0;en(3,o=t)));let a,p=0;function u(t,e){w("/xx/verify",{user_id:e}),r.splice(t,1),n(1,p+=1)}d("profile-component"),j("/xx/get-unverified-users",(function(t){let e=JSON.parse(t);r.push(...e),i.update((t=>t+1))})),l((()=>{}));return[s,p,a,o,r,i,u,function(){j("/xx/add-verified-groups",(()=>""))},(t,e)=>u(t,e.user_id),function(t){m[t?"unshift":"push"]((()=>{s=t,n(0,s)}))},function(t){m[t?"unshift":"push"]((()=>{a=t,n(2,a)}))}]}class R extends t{constructor(t){super(),this.shadowRoot.innerHTML="",e(this,{target:this.shadowRoot,props:n(this.attributes),customElement:!0},E,z,s,{},null),t&&t.target&&o(t.target,this,t.anchor)}}customElements.define("admin-panel",R);export{R as default}; diff --git a/Server/public/js/components/index-2620635a.js b/Server/public/js/components/index-2620635a.js new file mode 100644 index 0000000..a5b20b0 --- /dev/null +++ b/Server/public/js/components/index-2620635a.js @@ -0,0 +1,527 @@ + +(function(l, r) { if (!l || l.getElementById('livereloadscript')) return; r = l.createElement('script'); r.async = 1; r.src = '//' + (self.location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1'; r.id = 'livereloadscript'; l.getElementsByTagName('head')[0].appendChild(r) })(self.document); +function noop() { } +function add_location(element, file, line, column, char) { + element.__svelte_meta = { + loc: { file, line, column, char } + }; +} +function run(fn) { + return fn(); +} +function blank_object() { + return Object.create(null); +} +function run_all(fns) { + fns.forEach(run); +} +function is_function(thing) { + return typeof thing === 'function'; +} +function safe_not_equal(a, b) { + return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function'); +} +let src_url_equal_anchor; +function src_url_equal(element_src, url) { + if (!src_url_equal_anchor) { + src_url_equal_anchor = document.createElement('a'); + } + src_url_equal_anchor.href = url; + return element_src === src_url_equal_anchor.href; +} +function is_empty(obj) { + return Object.keys(obj).length === 0; +} +function validate_store(store, name) { + if (store != null && typeof store.subscribe !== 'function') { + throw new Error(`'${name}' is not a store with a 'subscribe' method`); + } +} +function subscribe(store, ...callbacks) { + if (store == null) { + return noop; + } + const unsub = store.subscribe(...callbacks); + return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub; +} +function component_subscribe(component, store, callback) { + component.$$.on_destroy.push(subscribe(store, callback)); +} +function append(target, node) { + target.appendChild(node); +} +function insert(target, node, anchor) { + target.insertBefore(node, anchor || null); +} +function detach(node) { + node.parentNode.removeChild(node); +} +function destroy_each(iterations, detaching) { + for (let i = 0; i < iterations.length; i += 1) { + if (iterations[i]) + iterations[i].d(detaching); + } +} +function element(name) { + return document.createElement(name); +} +function svg_element(name) { + return document.createElementNS('http://www.w3.org/2000/svg', name); +} +function text(data) { + return document.createTextNode(data); +} +function space() { + return text(' '); +} +function empty() { + return text(''); +} +function listen(node, event, handler, options) { + node.addEventListener(event, handler, options); + return () => node.removeEventListener(event, handler, options); +} +function attr(node, attribute, value) { + if (value == null) + node.removeAttribute(attribute); + else if (node.getAttribute(attribute) !== value) + node.setAttribute(attribute, value); +} +function set_custom_element_data(node, prop, value) { + if (prop in node) { + node[prop] = typeof node[prop] === 'boolean' && value === '' ? true : value; + } + else { + attr(node, prop, value); + } +} +function to_number(value) { + return value === '' ? null : +value; +} +function children(element) { + return Array.from(element.childNodes); +} +function set_input_value(input, value) { + input.value = value == null ? '' : value; +} +function set_style(node, key, value, important) { + if (value === null) { + node.style.removeProperty(key); + } + else { + node.style.setProperty(key, value, important ? 'important' : ''); + } +} +function custom_event(type, detail, { bubbles = false, cancelable = false } = {}) { + const e = document.createEvent('CustomEvent'); + e.initCustomEvent(type, bubbles, cancelable, detail); + return e; +} +class HtmlTag { + constructor(is_svg = false) { + this.is_svg = false; + this.is_svg = is_svg; + this.e = this.n = null; + } + c(html) { + this.h(html); + } + m(html, target, anchor = null) { + if (!this.e) { + if (this.is_svg) + this.e = svg_element(target.nodeName); + else + this.e = element(target.nodeName); + this.t = target; + this.c(html); + } + this.i(anchor); + } + h(html) { + this.e.innerHTML = html; + this.n = Array.from(this.e.childNodes); + } + i(anchor) { + for (let i = 0; i < this.n.length; i += 1) { + insert(this.t, this.n[i], anchor); + } + } + p(html) { + this.d(); + this.h(html); + this.i(this.a); + } + d() { + this.n.forEach(detach); + } +} +function attribute_to_object(attributes) { + const result = {}; + for (const attribute of attributes) { + result[attribute.name] = attribute.value; + } + return result; +} + +let current_component; +function set_current_component(component) { + current_component = component; +} +function get_current_component() { + if (!current_component) + throw new Error('Function called outside component initialization'); + return current_component; +} +/** + * The `onMount` function schedules a callback to run as soon as the component has been mounted to the DOM. + * It must be called during the component's initialisation (but doesn't need to live *inside* the component; + * it can be called from an external module). + * + * `onMount` does not run inside a [server-side component](/docs#run-time-server-side-component-api). + * + * https://svelte.dev/docs#run-time-svelte-onmount + */ +function onMount(fn) { + get_current_component().$$.on_mount.push(fn); +} +/** + * Schedules a callback to run immediately after the component has been updated. + * + * The first time the callback runs will be after the initial `onMount` + */ +function afterUpdate(fn) { + get_current_component().$$.after_update.push(fn); +} +/** + * Associates an arbitrary `context` object with the current component and the specified `key` + * and returns that object. The context is then available to children of the component + * (including slotted content) with `getContext`. + * + * Like lifecycle functions, this must be called during component initialisation. + * + * https://svelte.dev/docs#run-time-svelte-setcontext + */ +function setContext(key, context) { + get_current_component().$$.context.set(key, context); + return context; +} +/** + * Retrieves the context that belongs to the closest parent component with the specified `key`. + * Must be called during component initialisation. + * + * https://svelte.dev/docs#run-time-svelte-getcontext + */ +function getContext(key) { + return get_current_component().$$.context.get(key); +} + +const dirty_components = []; +const binding_callbacks = []; +const render_callbacks = []; +const flush_callbacks = []; +const resolved_promise = Promise.resolve(); +let update_scheduled = false; +function schedule_update() { + if (!update_scheduled) { + update_scheduled = true; + resolved_promise.then(flush); + } +} +function add_render_callback(fn) { + render_callbacks.push(fn); +} +// flush() calls callbacks in this order: +// 1. All beforeUpdate callbacks, in order: parents before children +// 2. All bind:this callbacks, in reverse order: children before parents. +// 3. All afterUpdate callbacks, in order: parents before children. EXCEPT +// for afterUpdates called during the initial onMount, which are called in +// reverse order: children before parents. +// Since callbacks might update component values, which could trigger another +// call to flush(), the following steps guard against this: +// 1. During beforeUpdate, any updated components will be added to the +// dirty_components array and will cause a reentrant call to flush(). Because +// the flush index is kept outside the function, the reentrant call will pick +// up where the earlier call left off and go through all dirty components. The +// current_component value is saved and restored so that the reentrant call will +// not interfere with the "parent" flush() call. +// 2. bind:this callbacks cannot trigger new flush() calls. +// 3. During afterUpdate, any updated components will NOT have their afterUpdate +// callback called a second time; the seen_callbacks set, outside the flush() +// function, guarantees this behavior. +const seen_callbacks = new Set(); +let flushidx = 0; // Do *not* move this inside the flush() function +function flush() { + const saved_component = current_component; + do { + // first, call beforeUpdate functions + // and update components + while (flushidx < dirty_components.length) { + const component = dirty_components[flushidx]; + flushidx++; + set_current_component(component); + update(component.$$); + } + set_current_component(null); + dirty_components.length = 0; + flushidx = 0; + while (binding_callbacks.length) + binding_callbacks.pop()(); + // then, once components are updated, call + // afterUpdate functions. This may cause + // subsequent updates... + for (let i = 0; i < render_callbacks.length; i += 1) { + const callback = render_callbacks[i]; + if (!seen_callbacks.has(callback)) { + // ...so guard against infinite loops + seen_callbacks.add(callback); + callback(); + } + } + render_callbacks.length = 0; + } while (dirty_components.length); + while (flush_callbacks.length) { + flush_callbacks.pop()(); + } + update_scheduled = false; + seen_callbacks.clear(); + set_current_component(saved_component); +} +function update($$) { + if ($$.fragment !== null) { + $$.update(); + run_all($$.before_update); + const dirty = $$.dirty; + $$.dirty = [-1]; + $$.fragment && $$.fragment.p($$.ctx, dirty); + $$.after_update.forEach(add_render_callback); + } +} +const outroing = new Set(); +function transition_in(block, local) { + if (block && block.i) { + outroing.delete(block); + block.i(local); + } +} + +const globals = (typeof window !== 'undefined' + ? window + : typeof globalThis !== 'undefined' + ? globalThis + : global); +function mount_component(component, target, anchor, customElement) { + const { fragment, after_update } = component.$$; + fragment && fragment.m(target, anchor); + if (!customElement) { + // onMount happens before the initial afterUpdate + add_render_callback(() => { + const new_on_destroy = component.$$.on_mount.map(run).filter(is_function); + // if the component was destroyed immediately + // it will update the `$$.on_destroy` reference to `null`. + // the destructured on_destroy may still reference to the old array + if (component.$$.on_destroy) { + component.$$.on_destroy.push(...new_on_destroy); + } + else { + // Edge case - component was destroyed immediately, + // most likely as a result of a binding initialising + run_all(new_on_destroy); + } + component.$$.on_mount = []; + }); + } + after_update.forEach(add_render_callback); +} +function destroy_component(component, detaching) { + const $$ = component.$$; + if ($$.fragment !== null) { + run_all($$.on_destroy); + $$.fragment && $$.fragment.d(detaching); + // TODO null out other refs, including component.$$ (but need to + // preserve final state?) + $$.on_destroy = $$.fragment = null; + $$.ctx = []; + } +} +function make_dirty(component, i) { + if (component.$$.dirty[0] === -1) { + dirty_components.push(component); + schedule_update(); + component.$$.dirty.fill(0); + } + component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31)); +} +function init(component, options, instance, create_fragment, not_equal, props, append_styles, dirty = [-1]) { + const parent_component = current_component; + set_current_component(component); + const $$ = component.$$ = { + fragment: null, + ctx: [], + // state + props, + update: noop, + not_equal, + bound: blank_object(), + // lifecycle + on_mount: [], + on_destroy: [], + on_disconnect: [], + before_update: [], + after_update: [], + context: new Map(options.context || (parent_component ? parent_component.$$.context : [])), + // everything else + callbacks: blank_object(), + dirty, + skip_bound: false, + root: options.target || parent_component.$$.root + }; + append_styles && append_styles($$.root); + let ready = false; + $$.ctx = instance + ? instance(component, options.props || {}, (i, ret, ...rest) => { + const value = rest.length ? rest[0] : ret; + if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) { + if (!$$.skip_bound && $$.bound[i]) + $$.bound[i](value); + if (ready) + make_dirty(component, i); + } + return ret; + }) + : []; + $$.update(); + ready = true; + run_all($$.before_update); + // `false` as a special case of no DOM component + $$.fragment = create_fragment ? create_fragment($$.ctx) : false; + if (options.target) { + if (options.hydrate) { + const nodes = children(options.target); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + $$.fragment && $$.fragment.l(nodes); + nodes.forEach(detach); + } + else { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + $$.fragment && $$.fragment.c(); + } + if (options.intro) + transition_in(component.$$.fragment); + mount_component(component, options.target, options.anchor, options.customElement); + flush(); + } + set_current_component(parent_component); +} +let SvelteElement; +if (typeof HTMLElement === 'function') { + SvelteElement = class extends HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + } + connectedCallback() { + const { on_mount } = this.$$; + this.$$.on_disconnect = on_mount.map(run).filter(is_function); + // @ts-ignore todo: improve typings + for (const key in this.$$.slotted) { + // @ts-ignore todo: improve typings + this.appendChild(this.$$.slotted[key]); + } + } + attributeChangedCallback(attr, _oldValue, newValue) { + this[attr] = newValue; + } + disconnectedCallback() { + run_all(this.$$.on_disconnect); + } + $destroy() { + destroy_component(this, 1); + this.$destroy = noop; + } + $on(type, callback) { + // TODO should this delegate to addEventListener? + if (!is_function(callback)) { + return noop; + } + const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = [])); + callbacks.push(callback); + return () => { + const index = callbacks.indexOf(callback); + if (index !== -1) + callbacks.splice(index, 1); + }; + } + $set($$props) { + if (this.$$set && !is_empty($$props)) { + this.$$.skip_bound = true; + this.$$set($$props); + this.$$.skip_bound = false; + } + } + }; +} + +function dispatch_dev(type, detail) { + document.dispatchEvent(custom_event(type, Object.assign({ version: '3.52.0' }, detail), { bubbles: true })); +} +function append_dev(target, node) { + dispatch_dev('SvelteDOMInsert', { target, node }); + append(target, node); +} +function insert_dev(target, node, anchor) { + dispatch_dev('SvelteDOMInsert', { target, node, anchor }); + insert(target, node, anchor); +} +function detach_dev(node) { + dispatch_dev('SvelteDOMRemove', { node }); + detach(node); +} +function listen_dev(node, event, handler, options, has_prevent_default, has_stop_propagation) { + const modifiers = options === true ? ['capture'] : options ? Array.from(Object.keys(options)) : []; + if (has_prevent_default) + modifiers.push('preventDefault'); + if (has_stop_propagation) + modifiers.push('stopPropagation'); + dispatch_dev('SvelteDOMAddEventListener', { node, event, handler, modifiers }); + const dispose = listen(node, event, handler, options); + return () => { + dispatch_dev('SvelteDOMRemoveEventListener', { node, event, handler, modifiers }); + dispose(); + }; +} +function attr_dev(node, attribute, value) { + attr(node, attribute, value); + if (value == null) + dispatch_dev('SvelteDOMRemoveAttribute', { node, attribute }); + else + dispatch_dev('SvelteDOMSetAttribute', { node, attribute, value }); +} +function prop_dev(node, property, value) { + node[property] = value; + dispatch_dev('SvelteDOMSetProperty', { node, property, value }); +} +function set_data_dev(text, data) { + data = '' + data; + if (text.wholeText === data) + return; + dispatch_dev('SvelteDOMSetData', { node: text, data }); + text.data = data; +} +function validate_each_argument(arg) { + if (typeof arg !== 'string' && !(arg && typeof arg === 'object' && 'length' in arg)) { + let msg = '{#each} only iterates over array-like objects.'; + if (typeof Symbol === 'function' && arg && Symbol.iterator in arg) { + msg += ' You can use a spread to convert this iterable into an array.'; + } + throw new Error(msg); + } +} +function validate_slots(name, slot, keys) { + for (const slot_key of Object.keys(slot)) { + if (!~keys.indexOf(slot_key)) { + console.warn(`<${name}> received an unexpected slot "${slot_key}".`); + } + } +} + +export { run_all as A, flush as B, is_function as C, src_url_equal as D, set_custom_element_data as E, prop_dev as F, set_style as G, svg_element as H, HtmlTag as I, afterUpdate as J, to_number as K, set_input_value as L, SvelteElement as S, attribute_to_object as a, insert_dev as b, validate_store as c, dispatch_dev as d, component_subscribe as e, globals as f, getContext as g, empty as h, init as i, detach_dev as j, binding_callbacks as k, validate_each_argument as l, space as m, noop as n, onMount as o, set_data_dev as p, element as q, add_location as r, safe_not_equal as s, text as t, attr_dev as u, validate_slots as v, append_dev as w, listen_dev as x, destroy_each as y, setContext as z }; diff --git a/Server/public/js/components/index-adbb0a76.js b/Server/public/js/components/index-adbb0a76.js new file mode 100644 index 0000000..4b11eb1 --- /dev/null +++ b/Server/public/js/components/index-adbb0a76.js @@ -0,0 +1,53 @@ + +(function(l, r) { if (!l || l.getElementById('livereloadscript')) return; r = l.createElement('script'); r.async = 1; r.src = '//' + (self.location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1'; r.id = 'livereloadscript'; l.getElementsByTagName('head')[0].appendChild(r) })(self.document); +import { n as noop, s as safe_not_equal } from './index-2620635a.js'; + +const subscriber_queue = []; +/** + * Create a `Writable` store that allows both updating and reading by subscription. + * @param {*=}value initial value + * @param {StartStopNotifier=}start start and stop notifications for subscriptions + */ +function writable(value, start = noop) { + let stop; + const subscribers = new Set(); + function set(new_value) { + if (safe_not_equal(value, new_value)) { + value = new_value; + if (stop) { // store is ready + const run_queue = !subscriber_queue.length; + for (const subscriber of subscribers) { + subscriber[1](); + subscriber_queue.push(subscriber, value); + } + if (run_queue) { + for (let i = 0; i < subscriber_queue.length; i += 2) { + subscriber_queue[i][0](subscriber_queue[i + 1]); + } + subscriber_queue.length = 0; + } + } + } + } + function update(fn) { + set(fn(value)); + } + function subscribe(run, invalidate = noop) { + const subscriber = [run, invalidate]; + subscribers.add(subscriber); + if (subscribers.size === 1) { + stop = start(set) || noop; + } + run(value); + return () => { + subscribers.delete(subscriber); + if (subscribers.size === 0) { + stop(); + stop = null; + } + }; + } + return { set, update, subscribe }; +} + +export { writable as w }; diff --git a/Server/routes.jl b/Server/routes.jl index cf1d251..ded2ad2 100644 --- a/Server/routes.jl +++ b/Server/routes.jl @@ -1,6 +1,6 @@ -using Genie.Router, Genie.Requests, Genie.Renderer.Json, JSON3, GenieAuthentication -using Server.GroupsController +using Genie.Router, Genie.Requests, Genie.Renderer.Json, JSON3, GenieAuthentication, GenieAuthorisation +using Server.GroupsController, Server.AdminController #---Basic----------------------------------------------------------- @@ -12,6 +12,16 @@ route("/:locale/join-us/*", BasicController.join_us, named = :join_us) route("/:locale/political-compass/*", BasicController.political_compass, named = :political_compass) +#---Admin panel------------------------------------------------------ + +route("/:locale/bread/*", AdminController.admin_panel, named = :admin_panel) + +route("/:locale/get-unverified-users/*", AdminController.get_unverified_users, named = :get_unverified_users) + +route("/:locale/verify/*", AdminController.verify, method = POST, named=:verify) + +route("/:locale/add-verified-groups/*", AdminController.add_verified_groups, named = :add_verified_groups) + #---Authentication and such------------------------------------------ route("/:locale/auth/*", AuthenticationController.auth, named = :auth) @@ -54,8 +64,6 @@ route("/:locale/group-reject-request/*", GroupsController.reject_request, method route("/:locale/group-change/*", GroupsController.change_group, method = POST, named = :group_change) -route("/:locale/add-verified-groups/*", GroupsController.add_verified_groups, named = :add_verified_groups) - #---Coops---------------------------------------------------------- route("/:locale/cooperatives/*", CooperativesController.cooperatives, named = :cooperatives)