Skip to content
Merged
Show file tree
Hide file tree
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
89 changes: 89 additions & 0 deletions api/migrations/42-add-last-active.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
ALTER TABLE ctfnote.profile
ADD COLUMN "lastactive" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP;

--- Security
DROP POLICY select_profile ON ctfnote.profile;
CREATE POLICY select_profile_admin ON ctfnote.profile
FOR SELECT TO user_admin
USING (TRUE);

CREATE POLICY select_profile ON ctfnote.profile
FOR SELECT TO user_guest
USING (id = ctfnote_private.user_id ());

CREATE OR REPLACE VIEW ctfnote.public_profile AS
SELECT
"ctfnote"."profile"."id" as id,
"ctfnote"."profile"."description",
"ctfnote"."profile"."color",
"ctfnote"."profile"."username",
"ctfnote_private"."user"."role",
CONCAT('profile-', "ctfnote"."profile"."id") as node_id
FROM ctfnote.profile
INNER JOIN "ctfnote_private"."user" ON "ctfnote_private"."user"."id" = "ctfnote"."profile"."id"
ORDER BY id;

GRANT SELECT on ctfnote.public_profile TO user_guest;

CREATE OR REPLACE FUNCTION ctfnote_private.notify_profile_edit ()
RETURNS TRIGGER
AS $$
BEGIN
CASE TG_OP
WHEN 'INSERT' THEN
PERFORM
ctfnote_private.notify ('created', 'profiles', (SELECT id FROM ctfnote.public_profile WHERE id = NEW.id)); RETURN NULL;
WHEN 'UPDATE' THEN
PERFORM
ctfnote_private.notify ('update', 'profiles', (SELECT id FROM ctfnote.public_profile WHERE id = NEW.id)); RETURN NULL;
WHEN 'DELETE' THEN
PERFORM
ctfnote_private.notify ('deleted', 'profiles', (SELECT id FROM ctfnote.public_profile WHERE id = OLD.id)); RETURN NULL;
END CASE;
END
$$ VOLATILE
LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION ctfnote_private.notify_role_edit ()
RETURNS TRIGGER
AS $$
BEGIN
CASE TG_OP
WHEN 'UPDATE' THEN
PERFORM
ctfnote_private.notify ('update', 'profiles', (SELECT id FROM ctfnote.public_profile WHERE id = NEW.id)); RETURN NULL;
END CASE;
END
$$ VOLATILE
LANGUAGE plpgsql;

/* UpdateLastActive */
CREATE FUNCTION ctfnote.update_last_active ()
RETURNS void
AS $$
BEGIN
UPDATE ctfnote.profile
SET lastactive = now()
WHERE id = ctfnote_private.user_id ();
END;
$$
LANGUAGE plpgsql VOLATILE
SECURITY DEFINER;
GRANT EXECUTE ON FUNCTION ctfnote.update_last_active () TO user_guest;


CREATE OR REPLACE FUNCTION ctfnote.me ()
RETURNS ctfnote.profile
AS $$
SELECT ctfnote.update_last_active();
SELECT
*
FROM
ctfnote.profile
WHERE
id = ctfnote_private.user_id ()
LIMIT 1;

$$
LANGUAGE SQL
STRICT STABLE;
2 changes: 2 additions & 0 deletions api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import importCtfPlugin from "./plugins/importCtf";
import uploadLogoPlugin from "./plugins/uploadLogo";
import uploadScalar from "./plugins/uploadScalar";
import ConnectionFilterPlugin from "postgraphile-plugin-connection-filter";
import ProfileSubscriptionPlugin from "./plugins/ProfileSubscriptionPlugin";

function getDbUrl(role: "user" | "admin") {
const login = config.db[role].login;
Expand Down Expand Up @@ -45,6 +46,7 @@ function createOptions() {
uploadLogoPlugin,
createTasKPlugin,
ConnectionFilterPlugin,
ProfileSubscriptionPlugin,
],
ownerConnectionString: getDbUrl("admin"),
enableQueryBatching: true,
Expand Down
38 changes: 38 additions & 0 deletions api/src/plugins/ProfileSubscriptionPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { makeExtendSchemaPlugin, gql } from "graphile-utils";
import { Context } from "./uploadLogo";

export default makeExtendSchemaPlugin((build) => {
const { pgSql: sql } = build;
return {
typeDefs: gql`
type PublicProfileSubscriptionPayload {
publicProfile: PublicProfile
event: String
}

extend type Subscription {
currentProfileUpdated: PublicProfileSubscriptionPayload
@pgSubscription(topic: "postgraphile:update:profiles")
currentProfileCreated: PublicProfileSubscriptionPayload
@pgSubscription(topic: "postgraphile:create:profiles")
currentProfileDeleted: PublicProfileSubscriptionPayload
@pgSubscription(topic: "postgraphile:delete:profiles")
}
`,
resolvers: {
PublicProfileSubscriptionPayload: {
publicProfile: async (event, _args, _context: Context, resolveInfo) => {
const rows = await resolveInfo.graphile.selectGraphQLResultFromTable(
sql.fragment`ctfnote.public_profile`,
(tableAlias, sqlBuilder) => {
sqlBuilder.where(
sql.fragment`${tableAlias}.id = ${sql.value(event.__node__[1])}`
);
}
);
return rows[0];
},
},
},
};
});
2 changes: 1 addition & 1 deletion api/src/plugins/uploadLogo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function isAdmin(pgRole: string): boolean {

const UPLOAD_DIR_NAME = "uploads";

interface Context {
export interface Context {
pgClient: Client;
pgRole: string;
jwtClaims: {
Expand Down
Loading