Skip to content

Commit 62b0cd8

Browse files
authored
Add YouTube OAuth2 flow (#26)
* WIP youtube oauth2 flow frontend * Some initial work to service YouTube auth status in API * Improve error handling * Fix scrollbars * Add Google OAuth2 settings * Rough first pass at youtube api client setup * WIP more progress * Polished youtube auth flow * Ignore secrets * Fix up secrets handling * WIP more progress * Polished youtube auth flow * Fix up secrets handling * Cleanup * Finishing touches * Remove settings.json by default * More canonical settings.json example * Remove unused default param value * Create server-ci.yml * Update and rename server-ci.yml to pr-ci.yml * Super-Linter test * Update pr-ci.yml * Update pr-ci.yml * Update pr-ci.yml * Update pr-ci.yml * linting * Add continue-on-error * Run lint:ci on client * Fix typo * Update eslint config for client * Fix eslint style issues for client * Dangling config rule fix * More eslint rule adjustments * Ignore eslint_report.json * Style * Finish cleaning up JsonStorageRepo * Polish up experience getting Google OAuth2 redirect uri * Remove obsolete steps --------- Co-authored-by: Evan Strat <[email protected]>
1 parent 91eda31 commit 62b0cd8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+2459
-615
lines changed

.eslint/.eslintrc.js

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module.exports = {
2+
root: true,
3+
env: {
4+
node: true,
5+
},
6+
extends: [
7+
"eslint:recommended",
8+
],
9+
rules: {
10+
"comma-dangle": ["error", "always-multiline"],
11+
"comma-style": ["error", "last"],
12+
"quotes": ["error", "double"],
13+
"semi": ["error", "always"],
14+
"max-len": [
15+
"error",
16+
{
17+
"code": 120
18+
}
19+
],
20+
},
21+
}

.github/workflows/pr-ci.yml

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
name: ci
2+
3+
on:
4+
pull_request:
5+
branches: [ "main" ]
6+
7+
jobs:
8+
server_ci:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v3
12+
with:
13+
fetch-depth: 0
14+
- name: Use Node.js 16.x
15+
uses: actions/setup-node@v3
16+
with:
17+
node-version: 16.x
18+
cache: 'yarn'
19+
cache-dependency-path: '**/yarn.lock'
20+
- name: yarn install
21+
uses: borales/actions-yarn@v4
22+
with:
23+
cmd: install --frozen-lockfile
24+
dir: server
25+
- name: lint
26+
uses: borales/actions-yarn@v4
27+
with:
28+
cmd: lint:ci
29+
dir: server
30+
continue-on-error: true
31+
- name: Annotate eslint results
32+
uses: ataylorme/eslint-annotate-action@v2
33+
with:
34+
repo-token: "${{ secrets.GITHUB_TOKEN }}"
35+
report-json: "server/eslint_report.json"
36+
check-name: "eslint / server"
37+
38+
client_ci:
39+
runs-on: ubuntu-latest
40+
steps:
41+
- uses: actions/checkout@v3
42+
- name: Use Node.js 16.x
43+
uses: actions/setup-node@v3
44+
with:
45+
node-version: 16.x
46+
cache: 'yarn'
47+
cache-dependency-path: '**/yarn.lock'
48+
- name: yarn install
49+
uses: borales/actions-yarn@v4
50+
with:
51+
cmd: install --frozen-lockfile
52+
dir: client
53+
- name: lint
54+
uses: borales/actions-yarn@v4
55+
with:
56+
cmd: lint:ci
57+
dir: client
58+
continue-on-error: true
59+
- name: Annotate eslint results
60+
uses: ataylorme/eslint-annotate-action@v2
61+
with:
62+
repo-token: "${{ secrets.GITHUB_TOKEN }}"
63+
report-json: "client/eslint_report.json"
64+
check-name: "eslint / client"
65+
66+
build_client:
67+
runs-on: ubuntu-latest
68+
steps:
69+
- uses: actions/checkout@v3
70+
- name: Use Node.js 16.x
71+
uses: actions/setup-node@v3
72+
with:
73+
node-version: 16.x
74+
cache: 'yarn'
75+
cache-dependency-path: '**/yarn.lock'
76+
- name: yarn install
77+
uses: borales/actions-yarn@v4
78+
with:
79+
cmd: install --frozen-lockfile
80+
dir: client
81+
- name: build
82+
uses: borales/actions-yarn@v4
83+
with:
84+
cmd: build
85+
dir: client

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -113,5 +113,8 @@ dist
113113
# TernJS port file
114114
.tern-port
115115

116+
# eslint
117+
eslint_report.json
118+
116119
# MacOS folder attributes file
117120
.DS_Store

.run/build.run.xml

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<component name="ProjectRunConfigurationManager">
2+
<configuration default="false" name="build" type="js.build_tools.npm" nameIsGenerated="true">
3+
<package-json value="$PROJECT_DIR$/client/package.json" />
4+
<command value="run" />
5+
<scripts>
6+
<script value="build" />
7+
</scripts>
8+
<node-interpreter value="project" />
9+
<envs />
10+
<method v="2" />
11+
</configuration>
12+
</component>

.run/client.run.xml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<component name="ProjectRunConfigurationManager">
2+
<configuration default="false" name="client" type="js.build_tools.npm">
3+
<package-json value="$PROJECT_DIR$/client/package.json" />
4+
<command value="run" />
5+
<scripts>
6+
<script value="dev" />
7+
</scripts>
8+
<node-interpreter value="project" />
9+
<package-manager value="yarn" />
10+
<envs />
11+
<method v="2" />
12+
</configuration>
13+
</component>

.run/lint_ci.run.xml

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<component name="ProjectRunConfigurationManager">
2+
<configuration default="false" name="lint:ci" type="js.build_tools.npm" nameIsGenerated="true">
3+
<package-json value="$PROJECT_DIR$/client/package.json" />
4+
<command value="run" />
5+
<scripts>
6+
<script value="lint:ci" />
7+
</scripts>
8+
<node-interpreter value="project" />
9+
<envs />
10+
<method v="2" />
11+
</configuration>
12+
</component>

.run/server.run.xml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<component name="ProjectRunConfigurationManager">
2+
<configuration default="false" name="server" type="js.build_tools.npm">
3+
<package-json value="$PROJECT_DIR$/server/package.json" />
4+
<command value="run" />
5+
<scripts>
6+
<script value="dev" />
7+
</scripts>
8+
<node-interpreter value="project" />
9+
<package-manager value="yarn" />
10+
<envs />
11+
<method v="2" />
12+
</configuration>
13+
</component>

client/.eslintrc.js

+33-5
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,39 @@ module.exports = {
44
node: true,
55
},
66
extends: [
7-
'plugin:vue/vue3-essential',
8-
'eslint:recommended',
9-
'@vue/eslint-config-typescript',
7+
"plugin:vue/vue3-essential",
8+
"plugin:vue/essential",
9+
"plugin:vue/vue3-strongly-recommended",
10+
"plugin:vue/strongly-recommended",
11+
"plugin:vue/vue3-recommended",
12+
"plugin:vue/recommended",
13+
"eslint:recommended",
14+
"@vue/eslint-config-typescript",
15+
"../.eslint/.eslintrc.js",
1016
],
1117
rules: {
12-
'vue/multi-word-component-names': 'off',
18+
"comma-dangle": ["error", "always-multiline"],
19+
"comma-style": ["error", "last"],
20+
"quotes": ["error", "double"],
21+
"vue/component-name-in-template-casing": ["error", "PascalCase"],
22+
23+
"vue/first-attribute-linebreak": ["error", {
24+
multiline: "beside",
25+
}],
26+
"vue/html-self-closing": ["error", {
27+
"html": {
28+
"void": "always",
29+
},
30+
}],
31+
"vue/max-len": ["error", {
32+
"code": 120,
33+
}],
34+
"vue/max-attributes-per-line": ["error", {
35+
singleline: 2,
36+
}],
37+
"vue/multi-word-component-names": "off",
38+
"vue/no-multiple-template-root": "off",
39+
"vue/singleline-html-element-content-newline": "off",
40+
"vue/v-slot-style": "off",
1341
},
14-
}
42+
};

client/README.md

+13
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@ npm run dev
2626
pnpm dev
2727
```
2828

29+
### Lint
30+
31+
```
32+
# lint
33+
yarn lint
34+
35+
# lint and fix fixable problems
36+
yarn lint:fix
37+
38+
# lint (without fixing problems) and output JSON report
39+
yarn lint:ci
40+
```
41+
2942
### Compiles and minifies for production
3043

3144
```

client/package.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
"dev": "vite",
66
"build": "vue-tsc --noEmit && vite build",
77
"preview": "vite preview",
8-
"lint": "eslint . --fix --ignore-path .gitignore"
8+
"lint": "eslint . --ignore-path .gitignore",
9+
"lint:fix": "eslint . --fix --ignore-path .gitignore",
10+
"lint:ci": "eslint . --max-warnings=0 --output-file eslint_report.json --format json --ignore-path .gitignore"
911
},
1012
"dependencies": {
1113
"@mdi/font": "7.0.96",
@@ -26,5 +28,6 @@
2628
"vite": "^3.0.9",
2729
"vite-plugin-vuetify": "^1.0.0-alpha.12",
2830
"vue-tsc": "^1.0.9"
29-
}
31+
},
32+
"license": "GPL-3.0"
3033
}

client/src/components/form/AutosavingTextInput.vue

+60-22
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,112 @@
11
<template>
2-
<v-text-field
3-
variant="underlined"
4-
:disabled="state === State.LOADING"
5-
v-model="inputValue"
6-
:label="label"
7-
persistent-hint
8-
:hint="error"
9-
:error="!!error"
10-
@blur="submit()"
2+
<VTextField v-model="inputValue"
3+
variant="underlined"
4+
:disabled="state === State.LOADING || !!disabled"
5+
:label="label"
6+
persistent-hint
7+
:hint="!!error ? error: (helpText || '')"
8+
:error="!!error"
9+
:type="calculatedInputType"
10+
@blur="submit()"
1111
>
12-
<template v-if="state === State.SUCCESS" v-slot:append>
13-
<v-icon color="success">mdi-check</v-icon>
12+
<template v-if="inputType === 'password'" v-slot:append-inner>
13+
<VIcon v-if="showPlainText" @click="togglePlaintext">mdi-eye-off</VIcon>
14+
<VIcon v-else @click="togglePlaintext">mdi-eye</VIcon>
1415
</template>
15-
<template v-else-if="state === State.LOADING" v-slot:append>
16-
<v-progress-circular indeterminate></v-progress-circular>
16+
<template v-if="state !== State.READY" v-slot:append>
17+
<VIcon v-if="state === State.ERROR"
18+
color="error"
19+
class="mr-1"
20+
>
21+
mdi-alert-circle-outline
22+
</VIcon>
23+
<VIcon v-if="state === State.SUCCESS" color="success">mdi-check</VIcon>
24+
<VProgressCircular v-if="state===State.LOADING" indeterminate />
1725
</template>
18-
<template v-else-if="state === State.ERROR" v-slot:append>
19-
<v-icon color="error" class="mr-1">mdi-alert-circle-outline</v-icon>
20-
</template>
21-
</v-text-field>
26+
</VTextField>
2227
</template>
2328

2429

2530
<script lang="ts" setup>
26-
import {ref} from "vue";
31+
import {computed, ref} from "vue";
32+
import {SettingType} from "@/types/ISettings";
2733
34+
/* eslint-disable */
2835
enum State {
2936
LOADING,
3037
READY,
3138
SUCCESS,
3239
ERROR,
3340
}
41+
/* eslint-enable */
3442
3543
interface IProps {
3644
onSubmit: Function;
3745
name: string;
3846
label: string;
3947
initialValue: string|undefined;
48+
inputType: "text"|"password";
49+
settingType: SettingType;
50+
helpText?: string;
51+
disabled?: boolean;
4052
}
4153
4254
const props = defineProps<IProps>();
55+
const emit = defineEmits(["savedValueUpdated"]);
4356
44-
const state = ref<State>(State.READY)
57+
const state = ref<State>(State.READY);
4558
const inputValue = ref(props.initialValue);
4659
const lastSubmittedValue = ref(props.initialValue);
4760
const error = ref("");
61+
const showPlainText = ref(props.inputType === "text");
4862
4963
async function submit() {
50-
if (inputValue.value === lastSubmittedValue.value) {
64+
// Do not submit:
65+
// - in error state
66+
// - same value as most recent saved value
67+
// - empty value
68+
if (inputValue.value === "") {
69+
state.value = State.ERROR;
70+
error.value = "This field is required";
71+
return;
72+
}
73+
74+
if (state.value !== State.ERROR && !error.value && inputValue.value === lastSubmittedValue.value) {
5175
return;
5276
}
5377
5478
state.value = State.LOADING;
5579
lastSubmittedValue.value = inputValue.value;
5680
57-
const returnValue = await props.onSubmit(props.name, inputValue.value);
81+
const returnValue = await props.onSubmit(props.name, inputValue.value, props.settingType);
5882
5983
if (typeof returnValue === "boolean" && returnValue) {
6084
state.value = State.SUCCESS;
85+
error.value = "";
86+
emit("savedValueUpdated");
6187
} else {
6288
state.value = State.ERROR;
6389
6490
if (typeof returnValue === "string") {
6591
error.value = returnValue;
6692
} else {
67-
error.value = "Save error"
93+
error.value = "Save error";
6894
}
6995
}
7096
}
7197
98+
const calculatedInputType = computed(() => {
99+
if (props.inputType === "password" && !showPlainText.value) {
100+
return "password";
101+
}
102+
103+
return "text";
104+
});
105+
106+
function togglePlaintext() {
107+
showPlainText.value = !showPlainText.value;
108+
}
109+
72110
</script>
73111

74112
<style scoped>

0 commit comments

Comments
 (0)