Skip to content

Commit

Permalink
Add: Add role intgration
Browse files Browse the repository at this point in the history
  • Loading branch information
sevenc-nanashi committed Jul 20, 2023
1 parent 1a13dd5 commit 565225d
Show file tree
Hide file tree
Showing 14 changed files with 661 additions and 24 deletions.
144 changes: 144 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,147 @@ data
.env
.env.prod
rails_master.key
# Created by https://www.toptal.com/developers/gitignore/api/node
# Edit at https://www.toptal.com/developers/gitignore?templates=node

### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

### Node Patch ###
# Serverless Webpack directories
.webpack/

# Optional stylelint cache

# SvelteKit build / generate output
.svelte-kit

# End of https://www.toptal.com/developers/gitignore/api/node
65 changes: 44 additions & 21 deletions backend/app/controllers/api/discord_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ def redirect_uri
# "HOST",
# (Rails.env.development? ? "http://" : "https://") + request.host_with_port
# ) + "/api/discord/callback"
"https://example.com/api/discord/callback"
"http://localhost:3100/api/discord/callback"
end
def scope
%w[identify guilds.join guilds]
%w[identify guilds role_connections.write]
end
def my_discord
require_login!
Expand All @@ -27,7 +27,7 @@ def my_discord
avatar: session.user.discord_avatar
}
end
def authorize
def link
state = SecureRandom.urlsafe_base64(32)
$redis.with do |conn|
conn.set(
Expand All @@ -53,26 +53,32 @@ def callback
return
end

data =
$redis
.with { |conn| conn.get("discord_auth_token/#{params[:state]}") }
&.then { |json| JSON.parse(json, symbolize_names: true) }
# data =
# $redis
# .with { |conn| conn.get("discord_auth_token/#{params[:state]}") }
# &.then { |json| JSON.parse(json, symbolize_names: true) }

unless data && data[:user_id] == session[:user_id]
render json: { error: "Invalid state" }, status: :bad_request
return
end
# unless data && data[:user_id] == session[:user_id]
# render json: { error: "Invalid state" }, status: :bad_request
# return
# end

payload = {
client_id: ENV["DISCORD_CLIENT_ID"],
client_secret: ENV["DISCORD_CLIENT_SECRET"],
grant_type: "authorization_code",
code: params[:code],
redirect_uri: redirect_uri,
scope: scope.join("+")
scope: scope.join(" ")
}

response = $discord.post("/oauth2/token", form: payload)
response =
begin
$discord.post("/oauth2/token", form: payload)
rescue StandardError
redirect_to "/discord/error?code=discord_error"
return
end

session_user = User.find_by(id: session[:user_id])
session_user.update!(
Expand All @@ -83,7 +89,13 @@ def callback

$redis.with { |conn| conn.del("discord_auth_token/#{params[:state]}") }

discord_user = session_user.discord.get("/users/@me")
discord_user =
begin
session_user.discord.get("/users/@me")
rescue StandardError
redirect_to "/discord/error?code=discord_error"
return
end
session_user.update!(
**if discord_user["discriminator"] == "0"
{
Expand Down Expand Up @@ -122,19 +134,30 @@ def callback
end
)

member =
begin
$discord.get(
"/guilds/#{ENV["DISCORD_GUILD_ID"]}/members/#{discord_user["id"]}"
)
rescue RuntimeError
redirect_to "/discord/error?code=notInGuild"
end

begin
$discord.get(
"/guilds/#{ENV["DISCORD_GUILD_ID"]}/members/#{discord_user["id"]}"
)
rescue RuntimeError
$discord.put(
"/guilds/#{ENV["DISCORD_GUILD_ID"]}/members/#{discord_user["id"]}",
session_user.discord.put(
"/users/@me/applications/#{ENV["DISCORD_CLIENT_ID"]}/role-connection",
json: {
access_token: response["access_token"]
"platform_name" => "Chart Cyanvas / Sonolus",
"platform_username" => session_user.to_s
}
)
rescue StandardError
redirect_to "/discord/error?code=discordError"
end

redirect_to "/charts/upload"
rescue StandardError => e
Rails.logger.error e
redirect_to "/discord/error?code=unknown"
end
end
21 changes: 21 additions & 0 deletions backend/app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,29 @@ def to_frontend
}
end

def to_s
"#{name}##{handle}"
end

def discord
return unless discord_token
refresh_discord_token if discord_expires_at < Time.now
@discord ||= DiscordRequest.new(bearer_token: discord_token)
end

def refresh_discord_token
payload = {
client_id: ENV["DISCORD_CLIENT_ID"],
client_secret: ENV["DISCORD_CLIENT_SECRET"],
grant_type: "refresh_token",
refresh_token: discord_refresh_token
}
response = $discord.post("/oauth2/token", form: payload)

update!(
discord_token: response["access_token"],
discord_refresh_token: response["refresh_token"],
discord_expires_at: Time.now + response["expires_in"].to_i.seconds
)
end
end
2 changes: 1 addition & 1 deletion backend/config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

Dotenv.load(
Dotenv.overload(
*(
%w[../.env ../.env.local ../../.env ../../.env.local].map do |f|
__dir__ + "/" + f
Expand Down
2 changes: 1 addition & 1 deletion backend/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
post "/admin/reconvert_sus", to: "api/admin#reconvert_sus"

get "/my/discord", to: "api/discord#my_discord"
get "/discord/authorize", to: "api/discord#authorize"
get "/discord/link", to: "api/discord#link"
get "/discord/callback", to: "api/discord#callback"
end

Expand Down
1 change: 1 addition & 0 deletions backend/lib/discord_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def request(method, path, **options)
end
end

Rails.logger.info("Discord: #{method.to_s.upcase} #{path}")
response =
HTTP.request(method, "https://discord.com/api/v10#{path}", **options)

Expand Down
2 changes: 1 addition & 1 deletion frontend/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ module.exports = {
"/users/[handle]": ["user"],
"/users/alts": ["myAlts"],

"/discord/callback": ["discordCallback"],
"/discord/error": ["discordError"],

"/admin": ["admin"],
},
Expand Down
8 changes: 8 additions & 0 deletions frontend/i18n/ja.yml
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,11 @@ myAlts:
errors:
tooShort: 名前が短すぎます。
tooLong: 名前が長すぎます。

discordError:
title: 連携エラー
description: Discordとの連携に失敗しました({{code}})。
error:
discordError: Discordとの通信に失敗しました
notInGuild: サーバーに参加していません
unknown: 不明なエラーが発生しました
32 changes: 32 additions & 0 deletions frontend/pages/discord/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { NextPage } from "next"

import Head from "next/head"
import useTranslation from "next-translate/useTranslation"

import { useEffect, useRef } from "react"

const DiscordError: NextPage = () => {
const { t } = useTranslation("discordError")
const { t: rootT } = useTranslation()

const code = useRef("")

useEffect(() => {
code.current =
new URLSearchParams(window.location.search).get("code") ?? "unknown"
}, [code])

return (
<div className="flex flex-col gap-2">
<Head>
<title>{t("title") + " | " + rootT("name")}</title>
</Head>
<div>
<h1 className="text-2xl font-bold mb-2">{t("title")}</h1>
<p>{t("description", { code: t(`error.${code.current}`) })}</p>
</div>
</div>
)
}

export default DiscordError
23 changes: 23 additions & 0 deletions tasks/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "tasks",
"version": "1.0.0",
"description": "",
"scripts": {
"registerRoleConnection": "ts-node ./src/registerRoleConnection.ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "MIT",
"devDependencies": {
"@tsconfig/node16": "16.1.0",
"discord-api-types": "0.37.48",
"ts-node": "10.9.1",
"typescript": "5.1.6"
},
"dependencies": {
"@discordjs/rest": "1.7.1",
"axios": "1.4.0",
"dotenv": "16.3.1"
}
}
Loading

0 comments on commit 565225d

Please sign in to comment.